From 9681f25e54d12a13eca2c41a7dcc7ea467bb15a7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 7 Mar 2025 20:19:24 -0500 Subject: [PATCH] AY: this sucks, part 1 --- src/engine/platform/ay.cpp | 89 +++++--- src/engine/platform/ay.h | 5 +- src/engine/platform/sound/ay8910.cpp | 290 +++++++++++++-------------- src/engine/platform/sound/ay8910.h | 2 +- 4 files changed, 215 insertions(+), 171 deletions(-) diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 813a8e159..e751d962f 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -248,40 +248,74 @@ void DivPlatformAY8910::checkWrites() { } } -void DivPlatformAY8910::acquire_mame(short** buf, size_t len) { +void DivPlatformAY8910::acquire_mame(blip_buffer_t** bb, size_t len) { thread_local short ayBuf[3]; for (int i=0; i<3; i++) { oscBuf[i]->begin(len); } - if (sunsoft) { - for (size_t i=0; im_tone[j].period)*(ay->m_step_mul<<1); + const int remain=period-ay->m_tone[j].count; + if (remainsound_stream_update(ayBuf,1); - buf[0][i]=ayBuf[0]; - buf[1][i]=buf[0][i]; + // envelope + if (j<1) { + if (ay->m_envelope[j].holding==0) { + const int periodEnv=MAX(1,ay->m_envelope[j].period)*ay->m_env_step_mul; + const int remainEnv=periodEnv-ay->m_envelope[j].count; + if (remainEnvnoise_period())*ay->m_step_mul; + const int noiseRemain=noisePeriod-ay->m_count_noise; + if (noiseRemainsound_stream_update(ayBuf,advance); + i+=advance-1; + + if (sunsoft) { + if (lastOut[0]!=ayBuf[0]) { + blip_add_delta(bb[0],i,ayBuf[0]-lastOut[0]); + blip_add_delta(bb[1],i,ayBuf[0]-lastOut[0]); + lastOut[0]=ayBuf[0]; + } oscBuf[0]->putSample(i,CLAMP(sunsoftVolTable[31-(ay->lastIndx&31)]<<3,-32768,32767)); oscBuf[1]->putSample(i,CLAMP(sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<3,-32768,32767)); oscBuf[2]->putSample(i,CLAMP(sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<3,-32768,32767)); - } - } else { - for (size_t i=0; isound_stream_update(ayBuf,1); + } else { if (stereo) { - buf[0][i]=ayBuf[0]+ayBuf[1]+((ayBuf[2]*stereoSep)>>8); - buf[1][i]=((ayBuf[0]*stereoSep)>>8)+ayBuf[1]+ayBuf[2]; + int out0=ayBuf[0]+ayBuf[1]+((ayBuf[2]*stereoSep)>>8); + int out1=((ayBuf[0]*stereoSep)>>8)+ayBuf[1]+ayBuf[2]; + if (lastOut[0]!=out0) { + blip_add_delta(bb[0],i,out0-lastOut[0]); + lastOut[0]=out0; + } + if (lastOut[1]!=out1) { + blip_add_delta(bb[1],i,out1-lastOut[1]); + lastOut[1]=out1; + } } else { - buf[0][i]=ayBuf[0]+ayBuf[1]+ayBuf[2]; - buf[1][i]=buf[0][i]; + int out=ayBuf[0]+ayBuf[1]+ayBuf[2]; + if (lastOut[0]!=out) { + blip_add_delta(bb[0],i,out-lastOut[0]); + blip_add_delta(bb[1],i,out-lastOut[0]); + lastOut[0]=out; + } } oscBuf[0]->putSample(i,ayBuf[0]<<2); @@ -330,11 +364,14 @@ void DivPlatformAY8910::acquire_atomic(short** buf, size_t len) { } } +void DivPlatformAY8910::acquireDirect(blip_buffer_t** bb, size_t len) { + if (selCore && !intellivision) return; + acquire_mame(bb,len); +} + void DivPlatformAY8910::acquire(short** buf, size_t len) { if (selCore && !intellivision) { acquire_atomic(buf,len); - } else { - acquire_mame(buf,len); } } @@ -1011,6 +1048,8 @@ void DivPlatformAY8910::reset() { ayEnvSlideLow=0; delay=0; + lastOut[0]=0; + lastOut[1]=0; ioPortA=false; ioPortB=false; @@ -1026,6 +1065,10 @@ bool DivPlatformAY8910::keyOffAffectsArp(int ch) { return true; } +bool DivPlatformAY8910::hasAcquireDirect() { + return (!selCore || intellivision); +} + bool DivPlatformAY8910::getLegacyAlwaysSetVolume() { return false; } diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index fa3a4a522..227a989bd 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -128,6 +128,7 @@ class DivPlatformAY8910: public DivDispatch { ssg_t ay_atomic; int delay; + int lastOut[2]; bool extMode; unsigned int extClock; @@ -149,7 +150,7 @@ class DivPlatformAY8910: public DivDispatch { void checkWrites(); void updateOutSel(bool immediate=false); - void acquire_mame(short** buf, size_t len); + void acquire_mame(blip_buffer_t** bb, size_t len); void acquire_atomic(short** buf, size_t len); friend void putDispatchChip(void*,int); @@ -160,6 +161,7 @@ class DivPlatformAY8910: public DivDispatch { void runTFX(int runRate=0); void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8); void acquire(short** buf, size_t len); + void acquireDirect(blip_buffer_t** bb, size_t len); void fillStream(std::vector& stream, int sRate, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); @@ -177,6 +179,7 @@ class DivPlatformAY8910: public DivDispatch { void setFlags(const DivConfig& flags); int getOutputCount(); bool keyOffAffectsArp(int ch); + bool hasAcquireDirect(); DivMacroInt* getChanMacroInt(int ch); DivSamplePos getSamplePos(int ch); bool getLegacyAlwaysSetVolume(); diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 5a3309929..85171b148 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -1035,167 +1035,165 @@ void ay8910_device::ay8910_write_reg(int r, int v) // sound_stream_update - handle a stream update //------------------------------------------------- -void ay8910_device::sound_stream_update(short* outputs, int outLen) +void ay8910_device::sound_stream_update(short* outputs, int advance) { - tone_t *tone; - envelope_t *envelope; + tone_t *tone; + envelope_t *envelope; - int samples = outLen; + /* hack to prevent us from hanging when starting filtered outputs */ + if (!m_ready) + { + for (int chan = 0; chan < m_streams; chan++) + outputs[chan]=0; + } - /* hack to prevent us from hanging when starting filtered outputs */ - if (!m_ready) - { - for (int chan = 0; chan < m_streams; chan++) - outputs[chan]=0; - } + /* The 8910 has three outputs, each output is the mix of one of the three */ + /* tone generators and of the (single) noise generator. The two are mixed */ + /* BEFORE going into the DAC. The formula to mix each channel is: */ + /* (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). */ + /* Note that this means that if both tone and noise are disabled, the output */ + /* is 1, not 0, and can be modulated changing the volume. */ - /* The 8910 has three outputs, each output is the mix of one of the three */ - /* tone generators and of the (single) noise generator. The two are mixed */ - /* BEFORE going into the DAC. The formula to mix each channel is: */ - /* (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). */ - /* Note that this means that if both tone and noise are disabled, the output */ - /* is 1, not 0, and can be modulated changing the volume. */ + /* loop? kill the loop and optimize! */ + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + tone = &m_tone[chan]; + const int period = std::max(1, tone->period) * (m_step_mul << 1); + tone->count += advance << (is_expanded_mode() ? 5 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 0 : 1)); + if (tone->count>=period) { + tone->duty_cycle = (tone->duty_cycle - (tone->count/period)) & 0x1f; + tone->output = is_expanded_mode() ? BIT(duty_cycle[tone_duty(tone)], tone->duty_cycle) : BIT(tone->duty_cycle, 0); + tone->count = tone->count % period; + } + } - /* buffering loop */ - for (int sampindex = 0; sampindex < samples; sampindex++) - { - for (int chan = 0; chan < NUM_CHANNELS; chan++) - { - tone = &m_tone[chan]; - const int period = std::max(1, tone->period) * (m_step_mul << 1); - tone->count += is_expanded_mode() ? 32 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 1 : 2); - if (tone->count>=period) { - tone->duty_cycle = (tone->duty_cycle - (tone->count/period)) & 0x1f; - tone->output = is_expanded_mode() ? BIT(duty_cycle[tone_duty(tone)], tone->duty_cycle) : BIT(tone->duty_cycle, 0); - tone->count = tone->count % period; - } - } + const int period_noise = (int)(noise_period()) * m_step_mul; + m_count_noise+=advance; + while (m_count_noise >= period_noise) + { + /* toggle the prescaler output. Noise is no different to + * channels. + */ + m_count_noise -= period_noise; + m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1); - const int period_noise = (int)(noise_period()) * m_step_mul; - if ((++m_count_noise) >= period_noise) - { - /* toggle the prescaler output. Noise is no different to - * channels. - */ - m_count_noise = 0; - m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1); + if (is_expanded_mode()) // AY8930 noise generator rate is twice? compares as compatibility mode + { + // This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR. + // Using AND/OR gates, specific periods can be "filtered" out. + // A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation. - if (is_expanded_mode()) // AY8930 noise generator rate is twice? compares as compatibility mode - { - // This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR. - // Using AND/OR gates, specific periods can be "filtered" out. - // A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation. + // The period of the noise is determined by this value. + // The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask. + if ((++m_noise_value) >= (((unsigned char)(m_rng) & noise_and()) | noise_or())) // Clock the noise value. + { + m_noise_value = 0; - // The period of the noise is determined by this value. - // The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask. - if ((++m_noise_value) >= (((unsigned char)(m_rng) & noise_and()) | noise_or())) // Clock the noise value. - { - m_noise_value = 0; + // When everything is finally said and done, a 1bit latch is flipped. + // This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel. + m_noise_out ^= 1; - // When everything is finally said and done, a 1bit latch is flipped. - // This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel. - m_noise_out ^= 1; + noise_rng_tick(); + } + } + else if (!m_prescale_noise) + noise_rng_tick(); + } - noise_rng_tick(); - } - } - else if (!m_prescale_noise) - noise_rng_tick(); - } + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + tone = &m_tone[chan]; + m_vol_enabled[chan] = (tone->output | (unsigned char)tone_enable(chan)) & (noise_output() | (unsigned char)noise_enable(chan)); + } - for (int chan = 0; chan < NUM_CHANNELS; chan++) - { - tone = &m_tone[chan]; - m_vol_enabled[chan] = (tone->output | (unsigned char)tone_enable(chan)) & (noise_output() | (unsigned char)noise_enable(chan)); - } + /* update envelope */ + // who cares about env 1/2 on 8910 + for (int chan = 0; chan < (is_expanded_mode() ? NUM_CHANNELS : 1); chan++) + { + envelope = &m_envelope[chan]; + if (envelope->holding == 0) + { + const int period = std::max(1, envelope->period) * m_env_step_mul; + envelope->count += advance; + if (envelope->count >= period) + { + envelope->count %= period; + envelope->step--; - /* update envelope */ - for (int chan = 0; chan < NUM_CHANNELS; chan++) - { - envelope = &m_envelope[chan]; - if (envelope->holding == 0) - { - const int period = std::max(1, envelope->period) * m_env_step_mul; - if ((++envelope->count) >= period) - { - envelope->count = 0; - envelope->step--; + /* check envelope current position */ + if (envelope->step < 0) + { + if (envelope->hold) + { + if (envelope->alternate) + envelope->attack ^= m_env_step_mask; + envelope->holding = 1; + envelope->step = 0; + } + else + { + /* if CountEnv has looped an odd number of times (usually 1), */ + /* invert the output. */ + if (envelope->alternate && (envelope->step & (m_env_step_mask + 1))) + envelope->attack ^= m_env_step_mask; - /* check envelope current position */ - if (envelope->step < 0) - { - if (envelope->hold) - { - if (envelope->alternate) - envelope->attack ^= m_env_step_mask; - envelope->holding = 1; - envelope->step = 0; - } - else - { - /* if CountEnv has looped an odd number of times (usually 1), */ - /* invert the output. */ - if (envelope->alternate && (envelope->step & (m_env_step_mask + 1))) - envelope->attack ^= m_env_step_mask; + envelope->step &= m_env_step_mask; + } + } - envelope->step &= m_env_step_mask; - } - } + } + } + envelope->volume = (envelope->step ^ envelope->attack); + } - } - } - envelope->volume = (envelope->step ^ envelope->attack); - } - - if (m_streams == 3) - { - for (int chan = 0; chan < NUM_CHANNELS; chan++) - { - tone = &m_tone[chan]; - if (tone_envelope(tone) != 0) - { - envelope = &m_envelope[get_envelope_chan(chan)]; - unsigned int env_volume = envelope->volume; - if (m_feature & PSG_HAS_EXPANDED_MODE) - { - if (!is_expanded_mode()) - { - env_volume >>= 1; - if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field - outputs[chan]=m_vol_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; - else - outputs[chan]=m_vol_table[chan][m_vol_enabled[chan] ? env_volume : 0]; - } - else - { - if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field - outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; - else - outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume : 0]; - } - } - else - { - if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field - outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; - else - outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume : 0]; - } - } - else - { - if (is_expanded_mode()) - outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? tone_volume(tone) : 0]; - else - outputs[chan]=m_vol_table[chan][m_vol_enabled[chan] ? tone_volume(tone) : 0]; - } - } - } - else - { - outputs[0]=mix_3D(); - } - } + if (m_streams == 3) + { + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + tone = &m_tone[chan]; + if (tone_envelope(tone) != 0) + { + envelope = &m_envelope[get_envelope_chan(chan)]; + unsigned int env_volume = envelope->volume; + if (m_feature & PSG_HAS_EXPANDED_MODE) + { + if (!is_expanded_mode()) + { + env_volume >>= 1; + if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field + outputs[chan]=m_vol_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; + else + outputs[chan]=m_vol_table[chan][m_vol_enabled[chan] ? env_volume : 0]; + } + else + { + if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field + outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; + else + outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume : 0]; + } + } + else + { + if (m_feature & PSG_EXTENDED_ENVELOPE) // AY8914 Has a two bit tone_envelope field + outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume >> (3-tone_envelope(tone)) : 0]; + else + outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? env_volume : 0]; + } + } + else + { + if (is_expanded_mode()) + outputs[chan]=m_env_table[chan][m_vol_enabled[chan] ? tone_volume(tone) : 0]; + else + outputs[chan]=m_vol_table[chan][m_vol_enabled[chan] ? tone_volume(tone) : 0]; + } + } + } + else + { + outputs[0]=mix_3D(); + } } void ay8910_device::build_mixer_table() diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h index d56eb6feb..c6d4beb7c 100644 --- a/src/engine/platform/sound/ay8910.h +++ b/src/engine/platform/sound/ay8910.h @@ -164,7 +164,7 @@ public: unsigned char ay8910_read_ym(); void ay8910_reset_ym(); -private: +public: static constexpr int NUM_CHANNELS = 3; device_type chip_type;