diff options
Diffstat (limited to 'src/devices/sound/lynx.cpp')
-rw-r--r-- | src/devices/sound/lynx.cpp | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/src/devices/sound/lynx.cpp b/src/devices/sound/lynx.cpp new file mode 100644 index 00000000000..d546e4ce00c --- /dev/null +++ b/src/devices/sound/lynx.cpp @@ -0,0 +1,532 @@ +// license:GPL-2.0+ +// copyright-holders:Peter Trauner +/****************************************************************************** + + Atari Lynx sound hardware, Part of Mikey (Hayato in Lynx II) + PeT mess@utanet.at 2000,2001 + +******************************************************************************/ + +#include "emu.h" +#include "lynx.h" +#include <algorithm> + + +/* accordingly to atari's reference manual + there were no stereo lynx produced (the manual knows only production until mid 1991) + the howard/developement board might have stereo support + the revised lynx 2 hardware might have stereo support at least at the stereo jacks + + some games support stereo +*/ + + +/* +AUDIO_A EQU $FD20 +AUDIO_B EQU $FD28 +AUDIO_C EQU $FD30 +AUDIO_D EQU $FD38 + +VOLUME_CNTRL EQU 0 +FEEDBACK_ENABLE EQU 1 ; enables 11/10/5..0 +OUTPUT_VALUE EQU 2 +SHIFTER_L EQU 3 +AUD_BAKUP EQU 4 +AUD_CNTRL1 EQU 5 +AUD_COUNT EQU 6 +AUD_CNTRL2 EQU 7 + +; AUD_CNTRL1 +FEEDBACK_7 EQU %10000000 +AUD_RESETDONE EQU %01000000 +AUD_INTEGRATE EQU %00100000 +AUD_RELOAD EQU %00010000 +AUD_CNTEN EQU %00001000 +AUD_LINK EQU %00000111 +; link timers (0->2->4 / 1->3->5->7->Aud0->Aud1->Aud2->Aud3->1 +AUD_64us EQU %00000110 +AUD_32us EQU %00000101 +AUD_16us EQU %00000100 +AUD_8us EQU %00000011 +AUD_4us EQU %00000010 +AUD_2us EQU %00000001 +AUD_1us EQU %00000000 + +; AUD_CNTRL2 (read only) +; B7..B4 ; shifter bits 11..8 +; B3 ; who knows +; B2 ; last clock state (0->1 causes count) +; B1 ; borrow in (1 causes count) +; B0 ; borrow out (count EQU 0 and borrow in) + +ATTEN_A EQU $FD40 +ATTEN_B EQU $FD41 +ATTEN_C EQU $FD42 +ATTEN_D EQU $FD43 +; B7..B4 attenuation left ear (0 silent ..15/16 volume) +; B3..B0 " right ear + +MPAN EQU $FD44 +; B7..B4 left ear +; B3..B0 right ear +; B7/B3 EQU Audio D +; a 1 enables attenuation for channel and side + + +MSTEREO EQU $FD50 ; a 1 disables audio connection +AUD_D_LEFT EQU %10000000 +AUD_C_LEFT EQU %01000000 +AUD_B_LEFT EQU %00100000 +AUD_A_LEFT EQU %00010000 +AUD_D_RIGHT EQU %00001000 +AUD_C_RIGHT EQU %00000100 +AUD_B_RIGHT EQU %00000010 +AUD_A_RIGHT EQU %00000001 + + */ + +#define LYNX_AUDIO_CHANNELS 4 + + +// device type definition +DEFINE_DEVICE_TYPE(LYNX_SND, lynx_sound_device, "lynx_sound", "Atari Mikey (Sound)") +DEFINE_DEVICE_TYPE(LYNX2_SND, lynx2_sound_device, "lynx2_sound", "Atari Hayato (Sound)") + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +//------------------------------------------------- +// lynx_sound_device - constructor +//------------------------------------------------- + +lynx_sound_device::lynx_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : lynx_sound_device(mconfig, LYNX_SND, tag, owner, clock) +{ +} + +lynx_sound_device::lynx_sound_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, type, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , m_timer_delegate(*this) +{ +} + + +lynx2_sound_device::lynx2_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : lynx_sound_device(mconfig, LYNX2_SND, tag, owner, clock) +{ +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void lynx_sound_device::register_save() +{ + save_item(NAME(m_attenuation_enable)); + save_item(NAME(m_master_enable)); + for (int chan = 0; chan < LYNX_AUDIO_CHANNELS; chan++) + { + save_item(NAME(m_audio[chan].reg.volume), chan); + save_item(NAME(m_audio[chan].reg.feedback), chan); + save_item(NAME(m_audio[chan].reg.output), chan); + save_item(NAME(m_audio[chan].reg.shifter), chan); + save_item(NAME(m_audio[chan].reg.bakup), chan); + save_item(NAME(m_audio[chan].reg.control1), chan); + save_item(NAME(m_audio[chan].reg.counter), chan); + save_item(NAME(m_audio[chan].reg.control2), chan); + save_item(NAME(m_audio[chan].attenuation), chan); + save_item(NAME(m_audio[chan].mask), chan); + save_item(NAME(m_audio[chan].shifter), chan); + save_item(NAME(m_audio[chan].ticks), chan); + save_item(NAME(m_audio[chan].count), chan); + } +} + +void lynx_sound_device::init() +{ + m_shift_mask = make_unique_clear<int[]>(512); + m_shift_xor = make_unique_clear<int[]>(4096); + + for (int i = 0; i < 512; i++) + { + m_shift_mask[i] = 0; + if (i & 1) m_shift_mask[i] |= 1; + if (i & 2) m_shift_mask[i] |= 2; + if (i & 4) m_shift_mask[i] |= 4; + if (i & 8) m_shift_mask[i] |= 8; + if (i & 0x10) m_shift_mask[i] |= 0x10; + if (i & 0x20) m_shift_mask[i] |= 0x20; + if (i & 0x40) m_shift_mask[i] |= 0x400; + if (i & 0x80) m_shift_mask[i] |= 0x800; + if (i & 0x100) m_shift_mask[i] |= 0x80; + } + + for (int i = 0; i < 4096; i++) + { + m_shift_xor[i] = 1; + for (int j = 4096/2; j > 0; j >>= 1) + { + if (i & j) + m_shift_xor[i] ^= 1; + } + } +} + +void lynx_sound_device::device_start() +{ + m_mixer_channel = stream_alloc(0, 1, clock() / 16); + m_timer_delegate.resolve(); + init(); + register_save(); +} + + +void lynx2_sound_device::device_start() +{ + m_mixer_channel = stream_alloc(0, 2, clock() / 16); + m_timer_delegate.resolve(); + init(); + register_save(); +} + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void lynx_sound_device::device_reset() +{ + for (auto & elem : m_audio) + { + reset_channel(&elem); + } +} + +//------------------------------------------------- +// device_clock_changed - called if the clock +// changes +//------------------------------------------------- + +void lynx_sound_device::device_clock_changed() +{ + m_mixer_channel->set_sample_rate(clock() / 16); +} + +void lynx_sound_device::reset_channel(LYNX_AUDIO *channel) +{ + channel->reg.volume = 0; + channel->reg.feedback = 0; + channel->reg.output = 0; + channel->reg.shifter = 0; + channel->reg.bakup = 0; + channel->reg.control1 = 0; + channel->reg.counter = 0; + channel->reg.control2 = 0; + + channel->attenuation = 0; + channel->mask = 0; + channel->shifter = 0; + channel->ticks = 0; + channel->count = 0; +} + +void lynx_sound_device::count_down(int nr) +{ + LYNX_AUDIO *channel = &m_audio[nr]; + if ((channel->reg.count_en()) && (!(channel->reg.linked()))) + return; + if (nr == 0) + m_mixer_channel->update(); + //if ((channel->reg.control1 & 0x0f) == 0x0f) //count down if linking enabled and count enabled + channel->count--; +} + +void lynx_sound_device::shift(int chan_nr) +{ + s16 out_temp; + LYNX_AUDIO *channel; + + assert(chan_nr < 4); + + channel = &m_audio[chan_nr]; + //channel->shifter = ((channel->shifter << 1) & 0xffe) | (m_shift_xor[channel->shifter & channel->mask] & 1); + + // alternative method (functionally the same as above) + u8 xor_out = 1; // output of xor is inverted + for (int bit = 0; bit < 12; bit++) + { + if (BIT(channel->mask, bit)) + xor_out ^= BIT(channel->shifter, bit); + } + channel->shifter = ((channel->shifter << 1) & 0xffe) | xor_out; + + + if (channel->reg.integrate_mode()) // integrate mode enabled + { + if (channel->shifter & 1) + out_temp = channel->reg.output + channel->reg.volume; + else + out_temp = channel->reg.output - channel->reg.volume; + + // clipping + channel->reg.output = std::clamp<s16>(out_temp, -128, 127); + } + + switch (chan_nr) + { + case 0: count_down(1); break; + case 1: count_down(2); break; + case 2: count_down(3); break; + case 3: + if (!m_timer_delegate.isnull()) + m_timer_delegate(); + break; + } +} + +void lynx_sound_device::execute(int chan_nr) +{ + LYNX_AUDIO *channel; + + assert(chan_nr < 4); + + channel = &m_audio[chan_nr]; + + if (channel->reg.count_en()) // count enable + { + channel->ticks++; + if (channel->reg.linked()) // link + { + if (channel->count < 0) // counter finished + { + //channel->count+=channel->reg.counter; // reload (wrong?) + if (channel->reg.reload_en()) + channel->count = channel->reg.bakup; + shift(chan_nr); + } + } + else + { + const int t = 1 << (channel->reg.timer_clock()); // microseconds per count + for (;;) + { + for (; (channel->ticks >= t) && (channel->count >= 0); channel->ticks -= t) // at least one sampled worth of time left, timer not expired + channel->count--; + + if (channel->ticks < t) + break; + + if (channel->count < 0) + { + shift(chan_nr); + if (channel->reg.reload_en()) + channel->count = channel->reg.bakup; + else + break; + } + } + } + + if (!(channel->reg.integrate_mode())) // normal mode + channel->reg.output = BIT(channel->shifter, 0) ? channel->reg.volume : -channel->reg.volume; + + } + else + { + channel->ticks = 0; + channel->count = 0; + } +} + +u8 lynx_sound_device::read(offs_t offset) +{ + u8 value = 0; + LYNX_AUDIO *channel = &m_audio[(offset >> 3) & 3]; + + m_mixer_channel->update(); + + if (offset < 0x40) + { + switch (offset & 7) + { + case 0: + value = channel->reg.volume; + break; + case 1: + value = channel->reg.feedback; + break; + case 2: + value = channel->reg.output; + break; + case 3: + // current shifter state (lower 8 bits) + value = channel->shifter & 0xff; + break; + case 4: + value = channel->reg.bakup; + break; + case 5: + value = channel->reg.control1; + break; + case 6: + //current timer value + if (channel->count >= 0) + value = channel->count; + else + value = 0; + break; + case 7: + // current shifter state (upper 4 bits), status bits + value = (channel->shifter >> 4) & 0xf0; + value |= channel->reg.control2 & 0x0f; + break; + } + } + else + { + switch (offset) // Lynx II stereo control registers + { + case 0x40: case 0x41: case 0x42: case 0x43: + value = m_audio[offset & 3].attenuation; + break; + case 0x44: + value = m_attenuation_enable; + break; + case 0x50: + value = m_master_enable; + break; + } + + } + return value; +} + +void lynx_sound_device::write(offs_t offset, u8 data) +{ + //logerror("audio write %.2x %.2x\n", offset, data); + LYNX_AUDIO *channel = &m_audio[(offset >> 3) & 3]; + + m_mixer_channel->update(); + + if (offset < 0x40) + { + switch (offset & 0x07) + { + // Volume control (signed) + case 0: + channel->reg.volume = data; + //logerror("write to volume %d\n", data); + break; + // Shift register feedback enable bits 0-5, 11,10 + case 1: + channel->reg.feedback = data; + channel->mask &= 0x80; + channel->mask |= (data & 0x3f) | ((data & 0xc0) << 4); + break; + // Output value + case 2: + channel->reg.output = data; + //logerror("write to output %d\n", data); + break; + // Lower 8 bits of shift register + case 3: + channel->shifter &= 0xf00; + channel->shifter |= data; + break; + // Audio timer backup value + case 4: + channel->reg.bakup = data; + break; + // Audio control bits + case 5: + channel->mask &= ~0x80; + channel->mask |= (data & 0x80); + channel->reg.control1 = data; + break; + // Current count + case 6: + channel->count = data; + break; + // Upper 4 bits of shift register and audio status bits + case 7: + channel->shifter &= 0xff; + channel->shifter |= (data & 0xf0) << 4; + channel->reg.control2 = data; + break; + } + } + else + { + switch (offset) // Stereo Registers + { + case 0x40: case 0x41: case 0x42: case 0x43: + m_audio[offset & 3].attenuation = data; + break; + case 0x44: + m_attenuation_enable = data; + break; + case 0x50: + m_master_enable = data; + break; + } + } +} + + +//------------------------------------------------- +// sound_stream_update - handle a stream update +//------------------------------------------------- + +void lynx_sound_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +{ + auto &buffer = outputs[0]; + + for (int i = 0; i < buffer.samples(); i++) + { + s32 result = 0; + for (int channel = 0; channel < LYNX_AUDIO_CHANNELS; channel++) + { + execute(channel); + result += m_audio[channel].reg.output * 15; // where does the *15 come from? + } + buffer.put_int(i, result, 32768); + } +} + +//------------------------------------------------- +// sound_stream_update - handle a stream update +//------------------------------------------------- + +void lynx2_sound_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +{ + auto &left = outputs[0]; + auto &right = outputs[1]; + + for (int i = 0; i < left.samples(); i++) + { + s32 lsum = 0; + s32 rsum = 0; + for (int channel = 0; channel < LYNX_AUDIO_CHANNELS; channel++) + { + execute(channel); + int v = m_audio[channel].reg.output; + if (!(m_master_enable & (0x10 << channel))) + { + if (m_attenuation_enable & (0x10 << channel)) + lsum += v * (m_audio[channel].attenuation >> 4); + else + lsum += v * 15; + } + if (!(m_master_enable & (1 << channel))) + { + if (m_attenuation_enable & (1 << channel)) + rsum += v * (m_audio[channel].attenuation & 0xf); + else + rsum += v * 15; + } + } + left.put_int(i, lsum, 32768); + right.put_int(i, rsum, 32768); + } +} |