// license:GPL-2.0+ // copyright-holders:Peter Trauner /****************************************************************************** PeT mess@utanet.at 2000,2001 ******************************************************************************/ #include "emu.h" #include "audio/lynx.h" /* 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", "Mikey") DEFINE_DEVICE_TYPE(LYNX2_SND, lynx2_sound_device, "lynx2_sound", "Mikey (Lynx II)") //************************************************************************** // 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(512); m_shift_xor = make_unique_clear(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 = machine().sound().stream_alloc(*this, 0, 1, machine().sample_rate()); m_usec_per_sample = 1000000 / machine().sample_rate(); m_timer_delegate.resolve(); init(); register_save(); } void lynx2_sound_device::device_start() { m_mixer_channel = machine().sound().stream_alloc(*this, 0, 2, machine().sample_rate()); m_usec_per_sample = 1000000 / machine().sample_rate(); 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); } } 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.control1 & 8 && (channel->reg.control1 & 7) != 7) 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) { int16_t 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) uint8_t xor_out = 0; for (int bit = 0; bit < 12; bit++) { if ((channel->mask >> bit) & 1) xor_out ^= (channel->shifter >> bit) & 1; } channel->shifter = ((channel->shifter << 1) & 0xffe) | (xor_out ^ 1); // output of xor is inverted if (channel->reg.control1 & 0x20) // 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 if (out_temp > 127) out_temp = 127; if (out_temp < -128) out_temp = -128; channel->reg.output = (int16_t)out_temp; } 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.control1 & 8) // count enable { channel->ticks += m_usec_per_sample; if ((channel->reg.control1 & 7) == 7) // link { if (channel->count < 0) // counter finished { //channel->count+=channel->reg.counter; // reload (wrong?) if (channel->reg.control1 & 0x10) channel->count = channel->reg.bakup; shift(chan_nr); } } else { int t = 1 << (channel->reg.control1 & 7); // 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.control1 & 0x10) channel->count = channel->reg.bakup; else break; } } } if (!(channel->reg.control1 & 0x20)) // normal mode { channel->reg.output = (channel->shifter & 1) ? channel->reg.volume : -channel->reg.volume; } } else { channel->ticks = 0; channel->count = 0; } } READ8_MEMBER(lynx_sound_device::read) { uint8_t 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; } WRITE8_MEMBER(lynx_sound_device::write) { //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, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { int v; stream_sample_t *buffer = outputs[0]; for (int i = 0; i < samples; i++, buffer++) { *buffer = 0; for (int channel = 0; channel < LYNX_AUDIO_CHANNELS; channel++) { execute(channel); v = m_audio[channel].reg.output; *buffer += v * 15; // where does the *15 come from? } } } //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- void lynx2_sound_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { stream_sample_t *left=outputs[0], *right=outputs[1]; int v; for (int i = 0; i < samples; i++, left++, right++) { *left = 0; *right= 0; for (int channel = 0; channel < LYNX_AUDIO_CHANNELS; channel++) { execute(channel); v = m_audio[channel].reg.output; if (!(m_master_enable & (0x10 << channel))) { if (m_attenuation_enable & (0x10 << channel)) *left += v * (m_audio[channel].attenuation >> 4); else *left += v * 15; } if (!(m_master_enable & (1 << channel))) { if (m_attenuation_enable & (1 << channel)) *right += v * (m_audio[channel].attenuation & 0xf); else *right += v * 15; } } } }