diff --git a/.gitignore b/.gitignore index a5436e938..39aecb98e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ android/app/build/ android/app/.cxx/ .vs/ CMakeSettings.json +CMakePresets.json diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index d175b2539..41adbb200 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -30,6 +30,7 @@ #include "platform/arcade.h" #include "platform/tx81z.h" #include "platform/ym2203.h" +#include "platform/ym2203ext.h" #include "platform/ym2608.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" @@ -240,6 +241,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_OPN: dispatch=new DivPlatformYM2203; break; + case DIV_SYSTEM_OPN_EXT: + dispatch=new DivPlatformYM2203Ext; + break; case DIV_SYSTEM_PC98: dispatch=new DivPlatformYM2608; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 0a6fc7e90..9fcb85008 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -885,6 +885,7 @@ bool DivEngine::addSystem(DivSystem which) { return true; } +// TODO: maybe issue with subsongs? bool DivEngine::removeSystem(int index, bool preserveOrder) { if (song.systemLen<=1) { lastError="cannot remove the last one"; diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index dbc5aa82b..94546ed1d 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -27,7 +27,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} } -#define CHIP_DIVIDER 8 +#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8) const char* regCheatSheetAY[]={ "FreqL_A", "0", @@ -589,6 +589,7 @@ void DivPlatformAY8910::poke(std::vector& wlist) { } void DivPlatformAY8910::setFlags(unsigned int flags) { + clockSel=(flags>>7)&1; switch (flags&15) { case 1: chipClock=COLOR_PAL*2.0/5.0; @@ -620,6 +621,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { case 10: chipClock=2097152; break; + case 11: + chipClock=COLOR_NTSC; + break; + case 12: + chipClock=3600000; + break; default: chipClock=COLOR_NTSC/2.0; break; @@ -632,7 +639,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { if (ay!=NULL) delete ay; switch ((flags>>4)&3) { case 1: - ay=new ym2149_device(rate); + ay=new ym2149_device(rate,clockSel); sunsoft=false; intellivision=false; break; @@ -654,7 +661,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { } ay->device_start(); - stereo=flags>>6; + stereo=(flags>>6)&1; } int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index b257e3bbc..f93d34bdc 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -70,7 +70,7 @@ class DivPlatformAY8910: public DivDispatch { int delay; bool extMode; - bool stereo, sunsoft, intellivision; + bool stereo, sunsoft, intellivision, clockSel; bool ioPortA, ioPortB; unsigned char portAVal, portBVal; diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 6fb340968..8ec2aea2b 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -27,7 +27,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite2(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_DIVIDER 4 +#define CHIP_DIVIDER (clockSel?8:4) const char* regCheatSheetAY8930[]={ "FreqL_A", "00", @@ -64,7 +64,7 @@ const char* regCheatSheetAY8930[]={ void DivPlatformAY8930::immWrite(unsigned char a, unsigned char v) { if ((int)bank!=(a>>4)) { bank=a>>4; - immWrite2(0x0d, 0xa0|(bank<<4)|ayEnvMode[0]); + immWrite2(0x0d, 0xa0|(bank<<4)|chan[0].envelope.mode); } if (a==0x0d) { immWrite2(0x0d,0xa0|(bank<<4)|(v&15)); @@ -258,8 +258,8 @@ void DivPlatformAY8930::tick(bool sysTick) { rWrite(0x16+i,chan[i].std.ex1.val); } if (chan[i].std.ex2.had) { - ayEnvMode[i]=chan[i].std.ex2.val; - rWrite(regMode[i],ayEnvMode[i]); + chan[i].envelope.mode=chan[i].std.ex2.val; + rWrite(regMode[i],chan[i].envelope.mode); } if (chan[i].std.ex3.had) { chan[i].autoEnvNum=chan[i].std.ex3.val; @@ -296,29 +296,29 @@ void DivPlatformAY8930::tick(bool sysTick) { if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { - ayEnvPeriod[i]=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + chan[i].envelope.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); } chan[i].freqChanged=false; } - if (ayEnvSlide[i]!=0) { - ayEnvSlideLow[i]+=ayEnvSlide[i]; - while (ayEnvSlideLow[i]>7) { - ayEnvSlideLow[i]-=8; - if (ayEnvPeriod[i]<0xffff) { - ayEnvPeriod[i]++; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + if (chan[i].envelope.slide!=0) { + chan[i].envelope.slideLow+=chan[i].envelope.slide; + while (chan[i].envelope.slideLow>7) { + chan[i].envelope.slideLow-=8; + if (chan[i].envelope.period<0xffff) { + chan[i].envelope.period++; + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); } } - while (ayEnvSlideLow[i]<-7) { - ayEnvSlideLow[i]+=8; - if (ayEnvPeriod[i]>0) { - ayEnvPeriod[i]--; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + while (chan[i].envelope.slideLow<-7) { + chan[i].envelope.slideLow+=8; + if (chan[i].envelope.period>0) { + chan[i].envelope.period--; + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); } } } @@ -435,8 +435,8 @@ int DivPlatformAY8930::dispatch(DivCommand c) { rWrite(0x06,c.value); break; case DIV_CMD_AY_ENVELOPE_SET: - ayEnvMode[c.chan]=c.value>>4; - rWrite(regMode[c.chan],ayEnvMode[c.chan]); + chan[c.chan].envelope.mode=c.value>>4; + rWrite(regMode[c.chan],chan[c.chan].envelope.mode); if (c.value&15) { chan[c.chan].psgMode|=4; } else { @@ -449,19 +449,19 @@ int DivPlatformAY8930::dispatch(DivCommand c) { } break; case DIV_CMD_AY_ENVELOPE_LOW: - ayEnvPeriod[c.chan]&=0xff00; - ayEnvPeriod[c.chan]|=c.value; - immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]); - immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8); + chan[c.chan].envelope.period&=0xff00; + chan[c.chan].envelope.period|=c.value; + immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period); + immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8); break; case DIV_CMD_AY_ENVELOPE_HIGH: - ayEnvPeriod[c.chan]&=0xff; - ayEnvPeriod[c.chan]|=c.value<<8; - immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]); - immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8); + chan[c.chan].envelope.period&=0xff; + chan[c.chan].envelope.period|=c.value<<8; + immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period); + immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8); break; case DIV_CMD_AY_ENVELOPE_SLIDE: - ayEnvSlide[c.chan]=c.value; + chan[c.chan].envelope.slide=c.value; break; case DIV_CMD_AY_NOISE_MASK_AND: ayNoiseAnd=c.value; @@ -526,9 +526,9 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) { void DivPlatformAY8930::forceIns() { for (int i=0; i<3; i++) { chan[i].insChanged=true; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); - immWrite(regMode[i],ayEnvMode[i]); + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); + immWrite(regMode[i],chan[i].envelope.mode); } } @@ -556,10 +556,10 @@ void DivPlatformAY8930::reset() { chan[i]=DivPlatformAY8930::Channel(); chan[i].std.setEngine(parent); chan[i].vol=31; - ayEnvPeriod[i]=0; - ayEnvMode[i]=0; - ayEnvSlide[i]=0; - ayEnvSlideLow[i]=0; + chan[i].envelope.period=0; + chan[i].envelope.mode=0; + chan[i].envelope.slide=0; + chan[i].envelope.slideLow=0; } if (dumpWrites) { addWrite(0xffffffff,0); @@ -610,6 +610,7 @@ void DivPlatformAY8930::poke(std::vector& wlist) { } void DivPlatformAY8930::setFlags(unsigned int flags) { + clockSel=(flags>>7)&1; switch (flags&15) { case 1: chipClock=COLOR_PAL*2.0/5.0; @@ -641,6 +642,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { case 10: chipClock=2097152; break; + case 11: + chipClock=COLOR_NTSC; + break; + case 12: + chipClock=3600000; + break; default: chipClock=COLOR_NTSC/2.0; break; @@ -650,7 +657,7 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { oscBuf[i]->rate=rate; } - stereo=flags>>6; + stereo=(flags>>6)&1; } int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -662,7 +669,7 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); - ay=new ay8930_device(rate); + ay=new ay8930_device(rate,clockSel); ay->device_start(); ayBufLen=65536; for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen]; diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 5f477e123..67420f48c 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -27,6 +27,17 @@ class DivPlatformAY8930: public DivDispatch { protected: struct Channel { + struct Envelope { + unsigned char mode; + unsigned short period; + short slideLow; + short slide; + Envelope(): + mode(0), + period(0), + slideLow(0), + slide(0) {} + } envelope; unsigned char freqH, freqL; int freq, baseFreq, note, pitch, pitch2; int ins; @@ -59,16 +70,12 @@ class DivPlatformAY8930: public DivDispatch { int delay; - bool extMode, stereo; + bool extMode, stereo, clockSel; bool ioPortA, ioPortB; unsigned char portAVal, portBVal; short oldWrites[32]; short pendingWrites[32]; - unsigned char ayEnvMode[3]; - unsigned short ayEnvPeriod[3]; - short ayEnvSlideLow[3]; - short ayEnvSlide[3]; short* ayBuf[3]; size_t ayBufLen; diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 4d19e7de5..3dddf82c5 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -1021,10 +1021,8 @@ void ay8910_device::ay8910_write_reg(int r, int v) m_tone[2].set_duty(m_regs[AY_CDUTY]); break; case AY_NOISEAND: - m_noise_and=m_regs[AY_NOISEAND]; - break; case AY_NOISEOR: - m_noise_or=m_regs[AY_NOISEOR]; + // No action required break; default: m_regs[r] = 0; // reserved, set as 0 @@ -1047,7 +1045,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) if (!m_ready) { for (int chan = 0; chan < m_streams; chan++) - memset(outputs[chan],0,outLen*sizeof(short)); + memset(outputs[chan],0,outLen*sizeof(short)); } /* The 8910 has three outputs, each output is the mix of one of the three */ @@ -1063,8 +1061,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) for (int chan = 0; chan < NUM_CHANNELS; chan++) { tone = &m_tone[chan]; - const int period = std::max(1,tone->period); - tone->count += is_expanded_mode() ? 16 : (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; + const int period = tone->period * (m_step_mul << 1); + tone->count += is_expanded_mode() ? 32 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 1 : 2); while (tone->count >= period) { tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f; @@ -1073,8 +1071,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) } } - m_count_noise++; - if (m_count_noise >= noise_period()) + 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. @@ -1082,39 +1080,27 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) m_count_noise = 0; m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1); - if (!m_prescale_noise || is_expanded_mode()) // AY8930 noise generator rate is twice compares as compatibility mode + if (is_expanded_mode()) // AY8930 noise generator rate is twice? compares as compatibility mode { - if (is_expanded_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. - unsigned int noiseValuePeriod = ((m_rng & 0xFF & m_noise_and) | m_noise_or); - - // Clock the noise value. - if (m_noise_value >= noiseValuePeriod) { - 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_latch ^= 1; - - // The 17-bit LFSR is updated, using an XOR across bits 0 and 2. - unsigned int feedback = (m_rng & 1) ^ ((m_rng >> 2) & 1); - m_rng >>= 1; - m_rng |= (feedback << 16); - } - m_noise_value++; - } else { - /* The Random Number Generator of the 8910 is a 17-bit shift */ - /* register. The input to the shift register is bit0 XOR bit3 */ - /* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */ - m_rng ^= (((m_rng & 1) ^ ((m_rng >> 3) & 1)) << 17); - m_rng >>= 1; - } + // 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; + + // 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(); } for (int chan = 0; chan < NUM_CHANNELS; chan++) @@ -1129,9 +1115,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) envelope = &m_envelope[chan]; if (envelope->holding == 0) { - const int period = envelope->period * m_step; - envelope->count++; - if (envelope->count >= period) + const int period = envelope->period * m_env_step_mul; + if ((++envelope->count) >= period) { envelope->count = 0; envelope->step--; @@ -1263,20 +1248,14 @@ void ay8910_device::device_start() } -void ay8910_device::ay8910_reset_ym(bool ay8930) +void ay8910_device::ay8910_reset_ym() { m_active = false; m_register_latch = 0; - if (ay8930) { - m_rng = 0x1ffff; - } else { - m_rng = 1; - } + m_rng = (m_feature & PSG_HAS_EXPANDED_MODE) ? 0x1ffff : 1; m_mode = 0; // ay-3-8910 compatible mode - m_noise_and = 0xff; - m_noise_or = 0; - m_noise_value = 0; - m_noise_latch = 0; + m_noise_value = 0; + m_noise_out = 0; for (int chan = 0; chan < NUM_CHANNELS; chan++) { m_tone[chan].reset(); @@ -1335,7 +1314,7 @@ void ay8910_device::ay8910_write_ym(int addr, unsigned char data) unsigned char ay8910_device::ay8910_read_ym() { - int r = m_register_latch + get_register_bank(); + unsigned char r = m_register_latch + get_register_bank(); if (!m_active) return 0xff; // high impedance @@ -1380,7 +1359,7 @@ unsigned char ay8910_device::ay8910_read_ym() void ay8910_device::device_reset() { - ay8910_reset_ym(chip_type == AY8930); + ay8910_reset_ym(); } /************************************* @@ -1452,28 +1431,27 @@ ay8910_device::ay8910_device(unsigned int clock) } ay8910_device::ay8910_device(device_type type, unsigned int clock, - psg_type_t psg_type, int streams, int ioports, int feature) + psg_type_t psg_type, int streams, int ioports, int feature, bool clk_sel) : chip_type(type), - m_type(psg_type), + m_type(psg_type), m_streams(streams), m_ready(0), m_active(false), m_register_latch(0), m_last_enable(0), m_prescale_noise(0), + m_noise_value(0), m_count_noise(0), m_rng(0), - m_noise_and(0), - m_noise_or(0), - m_noise_value(0), - m_noise_latch(0), + m_noise_out(0), m_mode(0), m_env_step_mask((!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 0x0f : 0x1f), - m_step( (feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY) ? 2 : 1), + m_step_mul( ((feature & PSG_HAS_INTERNAL_DIVIDER) || ((feature & PSG_PIN26_IS_CLKSEL) && clk_sel)) ? 2 : 1), + m_env_step_mul( ((feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY)) ? (m_step_mul << 1) : m_step_mul), m_zero_is_off( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 1 : 0), m_par( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param), m_par_env( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param_env), - m_flags(AY8910_LEGACY_OUTPUT), + m_flags(AY8910_LEGACY_OUTPUT | (((feature & PSG_PIN26_IS_CLKSEL) && clk_sel) ? YM2149_PIN26_LOW : 0)), m_feature(feature) { memset(&m_regs,0,sizeof(m_regs)); @@ -1485,16 +1463,16 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock, m_res_load[0] = m_res_load[1] = m_res_load[2] = 1000; //Default values for resistor loads // TODO : measure ay8930 volume parameters (PSG_TYPE_YM for temporary 5 bit handling) - set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type); + set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type, clk_sel); } -void ay8910_device::set_type(psg_type_t psg_type) +void ay8910_device::set_type(psg_type_t psg_type, bool clk_sel) { m_type = psg_type; if (psg_type == PSG_TYPE_AY) { m_env_step_mask = 0x0f; - m_step = 2; + m_env_step_mul = is_clock_divided() ? 4 : 2; m_zero_is_off = 1; m_par = &ay8910_param; m_par_env = &ay8910_param; @@ -1502,11 +1480,15 @@ void ay8910_device::set_type(psg_type_t psg_type) else { m_env_step_mask = 0x1f; - m_step = (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; + m_env_step_mul = is_clock_divided() ? 2 : 1; m_zero_is_off = 0; m_par = &ym2149_param; m_par_env = &ym2149_param_env; } + if (m_feature & PSG_HAS_EXPANDED_MODE) + m_env_step_mul <<= 1; + + set_clock_sel(clk_sel); } @@ -1535,24 +1517,24 @@ ay8914_device::ay8914_device(unsigned int clock) -ay8930_device::ay8930_device(unsigned int clock) - : ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE) +ay8930_device::ay8930_device(unsigned int clock, bool clk_sel) + : ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE, clk_sel) { } -ym2149_device::ym2149_device(unsigned int clock) - : ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL) +ym2149_device::ym2149_device(unsigned int clock, bool clk_sel) + : ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel) { } -ym3439_device::ym3439_device(unsigned int clock) - : ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL) +ym3439_device::ym3439_device(unsigned int clock, bool clk_sel) + : ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel) { } diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h index c9de6da0a..314383f57 100644 --- a/src/engine/platform/sound/ay8910.h +++ b/src/engine/platform/sound/ay8910.h @@ -3,6 +3,10 @@ #ifndef MAME_SOUND_AY8910_H #define MAME_SOUND_AY8910_H +#pragma once + +#include + #define ALL_8910_CHANNELS -1 /* Internal resistance at Volume level 7. */ @@ -89,7 +93,8 @@ public: // configuration helpers void set_flags(int flags) { m_flags = flags; } - void set_psg_type(psg_type_t psg_type) { set_type(psg_type); } + void set_psg_type(psg_type_t psg_type) { set_type(psg_type, m_flags & YM2149_PIN26_LOW); } + void set_psg_type(psg_type_t psg_type, bool clk_sel) { set_type(psg_type, clk_sel); } void set_resistors_load(int res_load0, int res_load1, int res_load2) { m_res_load[0] = res_load0; m_res_load[1] = res_load1; m_res_load[2] = res_load2; } unsigned char data_r() { return ay8910_read_ym(); } @@ -97,7 +102,24 @@ public: void data_w(unsigned char data); // /RES - void reset_w(unsigned char data = 0) { ay8910_reset_ym(chip_type == AY8930); } + void reset_w(unsigned char data = 0) { ay8910_reset_ym(); } + + // Clock select pin + void set_clock_sel(bool clk_sel) + { + if (m_feature & PSG_PIN26_IS_CLKSEL) + { + if (clk_sel) + m_flags |= YM2149_PIN26_LOW; + else + m_flags &= ~YM2149_PIN26_LOW; + + m_step_mul = is_clock_divided() ? 2 : 1; + m_env_step_mul = (!(m_feature & PSG_HAS_EXPANDED_MODE)) && (m_type == PSG_TYPE_AY) ? (m_step_mul << 1) : m_step_mul; + if (m_feature & PSG_HAS_EXPANDED_MODE) + m_env_step_mul <<= 1; + } + } // use this when BC1 == A0; here, BC1=0 selects 'data' and BC1=1 selects 'latch address' void data_address_w(int offset, unsigned char data) { ay8910_write_ym(~offset & 1, data); } // note that directly connecting BC1 to A0 puts data on 0 and address on 1 @@ -127,7 +149,7 @@ public: // internal interface for PSG component of YM device // FIXME: these should be private, but vector06 accesses them directly - ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT); + ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT, bool clk_sel = false); // device-level overrides void device_start(); @@ -138,7 +160,7 @@ public: void ay8910_write_ym(int addr, unsigned char data); unsigned char ay8910_read_ym(); - void ay8910_reset_ym(bool ay8930); + void ay8910_reset_ym(); private: static constexpr int NUM_CHANNELS = 3; @@ -189,7 +211,7 @@ private: void reset() { - period = 0; + period = 1; volume = 0; duty = 0; count = 0; @@ -199,7 +221,7 @@ private: void set_period(unsigned char fine, unsigned char coarse) { - period = fine | (coarse << 8); + period = std::max(1, fine | (coarse << 8)); } void set_volume(unsigned char val) @@ -223,7 +245,7 @@ private: void reset() { - period = 0; + period = 1; count = 0; step = 0; volume = 0; @@ -235,7 +257,7 @@ private: void set_period(unsigned char fine, unsigned char coarse) { - period = fine | (coarse << 8); + period = std::max(1, fine | (coarse << 8)); } void set_shape(unsigned char shape, unsigned char mask) @@ -258,6 +280,18 @@ private: } }; + inline void noise_rng_tick() + { + // The Random Number Generator of the 8910 is a 17-bit shift + // register. The input to the shift register is bit0 XOR bit3 + // (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. + + if (m_feature & PSG_HAS_EXPANDED_MODE) // AY8930 LFSR algorithm is slightly different, verified from manual + m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 2)) << 16); + else + m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 3)) << 16); + } + // inlines inline bool tone_enable(int chan) { return BIT(m_regs[AY_ENABLE], chan); } inline unsigned char tone_volume(tone_t *tone) { return tone->volume & (is_expanded_mode() ? 0x1f : 0x0f); } @@ -266,14 +300,19 @@ private: inline unsigned char get_envelope_chan(int chan) { return is_expanded_mode() ? chan : 0; } inline bool noise_enable(int chan) { return BIT(m_regs[AY_ENABLE], 3 + chan); } - inline unsigned char noise_period() { return is_expanded_mode() ? m_regs[AY_NOISEPER] & 0xff : m_regs[AY_NOISEPER] & 0x1f; } - inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_latch & 1 : m_rng & 1; } + inline unsigned char noise_period() { return std::max(1, is_expanded_mode() ? (m_regs[AY_NOISEPER] & 0xff) : (m_regs[AY_NOISEPER] & 0x1f)); } + inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_out & 1 : m_rng & 1; } inline bool is_expanded_mode() { return ((m_feature & PSG_HAS_EXPANDED_MODE) && ((m_mode & 0xe) == 0xa)); } inline unsigned char get_register_bank() { return is_expanded_mode() ? (m_mode & 0x1) << 4 : 0; } + inline unsigned char noise_and() { return m_regs[AY_NOISEAND] & 0xff; } + inline unsigned char noise_or() { return m_regs[AY_NOISEOR] & 0xff; } + + inline bool is_clock_divided() { return ((m_feature & PSG_HAS_INTERNAL_DIVIDER) || ((m_feature & PSG_PIN26_IS_CLKSEL) && (m_flags & YM2149_PIN26_LOW))); } + // internal helpers - void set_type(psg_type_t psg_type); + void set_type(psg_type_t psg_type, bool clk_sel); inline float mix_3D(); void ay8910_write_reg(int r, int v); void build_mixer_table(); @@ -284,22 +323,21 @@ private: int m_ready; //sound_stream *m_channel; bool m_active; - int m_register_latch; + unsigned char m_register_latch; unsigned char m_regs[16 * 2]; int m_last_enable; tone_t m_tone[NUM_CHANNELS]; envelope_t m_envelope[NUM_CHANNELS]; unsigned char m_prescale_noise; - int m_count_noise; - int m_rng; - unsigned int m_noise_and; - unsigned int m_noise_or; - unsigned int m_noise_value; - unsigned int m_noise_latch; + signed short m_noise_value; + signed short m_count_noise; + unsigned int m_rng; + unsigned char m_noise_out; unsigned char m_mode; unsigned char m_env_step_mask; /* init parameters ... */ - int m_step; + int m_step_mul; + int m_env_step_mul; int m_zero_is_off; unsigned char m_vol_enabled[NUM_CHANNELS]; const ay_ym_param *m_par; @@ -337,19 +375,19 @@ public: class ay8930_device : public ay8910_device { public: - ay8930_device(unsigned int clock); + ay8930_device(unsigned int clock, bool clk_sel = false); }; class ym2149_device : public ay8910_device { public: - ym2149_device(unsigned int clock); + ym2149_device(unsigned int clock, bool clk_sel = false); }; class ym3439_device : public ay8910_device { public: - ym3439_device(unsigned int clock); + ym3439_device(unsigned int clock, bool clk_sel = false); }; class ymz284_device : public ay8910_device diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 7948d51e7..9ca77e9fc 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -463,27 +463,17 @@ void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) { } void DivPlatformYM2203Ext::forceIns() { - for (int i=0; i<6; i++) { + for (int i=0; i<3; i++) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (i==2) { // extended channel - if (isOpMuted[j]) { - rWrite(baseAddr+0x40,127); - } else if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[j].vol&0x7f))/127)); - } else { - rWrite(baseAddr+0x40,op.tl); - } + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - if (isMuted[i]) { - rWrite(baseAddr+ADDR_TL,127); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); } else { - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); - } else { - rWrite(baseAddr+ADDR_TL,op.tl); - } + rWrite(baseAddr+ADDR_TL,op.tl); } } rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); @@ -499,9 +489,10 @@ void DivPlatformYM2203Ext::forceIns() { chan[i].freqChanged=true; } } - for (int i=6; i<16; i++) { + for (int i=3; i<6; i++) { chan[i].insChanged=true; } + ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 236e6a087..b7e18e69d 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -1405,7 +1405,7 @@ const void* DivPlatformYM2608::getSampleMem(int index) { } size_t DivPlatformYM2608::getSampleMemCapacity(int index) { - return index == 0 ? 2097152 : 0; + return index == 0 ? 262144 : 0; } size_t DivPlatformYM2608::getSampleMemUsage(int index) { diff --git a/src/engine/platform/ym2608Interface.cpp b/src/engine/platform/ym2608Interface.cpp index 62cd8899f..242721f09 100644 --- a/src/engine/platform/ym2608Interface.cpp +++ b/src/engine/platform/ym2608Interface.cpp @@ -31,7 +31,7 @@ uint8_t DivYM2608Interface::ymfm_external_read(ymfm::access_class type, uint32_t if (adpcmBMem==NULL) { return 0; } - return adpcmBMem[address&0xffffff]; + return adpcmBMem[address&0x3ffff]; default: return 0; } diff --git a/src/engine/song.h b/src/engine/song.h index 219225e8f..60a0e56d9 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -262,13 +262,19 @@ struct DivSong { // - 8: 0.83MHz (Sunsoft 5B on PAL) // - 9: 1.10MHz (Gamate/VIC-20 PAL) // - 10: 2.097152MHz (Game Boy) + // - 11: 3.58MHz (Darky) + // - 12: 3.6MHz (Darky) // - bit 4-5: chip type (ignored on AY8930) // - 0: AY-3-8910 or similar // - 1: YM2149 // - 2: Sunsoft 5B - // - bit 6: stereo + // - 3: AY-3-8914 + // - bit 6: stereo (ignored on Sunsoft 5B) // - 0: mono // - 1: stereo ABC + // - bit 7: clock divider pin (YM2149, AY8930) + // - 0: high (disable divider) + // - 1: low (internally divided to half) // - SAA1099: // - bit 0-1: clock rate // - 0: 8MHz (SAM Coupé, Game Blaster) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 53eddc1b2..2916c1034 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -906,11 +906,41 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { } break; case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: + case DIV_SYSTEM_AY8930: { if (!hasAY) { + bool hasClockDivider=false; // Configurable clock divider + bool hasStereo=true; // Stereo hasAY=disCont[i].dispatch->chipClock; - ayConfig=(song.system[i]==DIV_SYSTEM_AY8930)?3:0; ayFlags=1; + if (song.system[i]==DIV_SYSTEM_AY8930) { // AY8930 + ayConfig=0x03; + hasClockDivider=true; + } else { + switch ((song.systemFlags[i]>>4)&3) { + default: + case 0: // AY8910 + ayConfig=0x00; + break; + case 1: // YM2149 + ayConfig=0x10; + hasClockDivider=true; + break; + case 2: // Sunsoft 5B + ayConfig=0x10; + ayFlags|=0x12; // Clock internally divided, Single sound output + hasStereo=false; // due to above, can't be per-channel stereo configurable + break; + case 3: // AY8914 + ayConfig=0x04; + break; + } + } + if (hasClockDivider && ((song.systemFlags[i]>>7)&1)) { + ayFlags|=0x10; + } + if (hasStereo && ((song.systemFlags[i]>>6)&1)) { + ayFlags|=0x80; + } willExport[i]=true; } else if (!(hasAY&0x40000000)) { isSecond[i]=true; @@ -919,6 +949,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + } case DIV_SYSTEM_SAA1099: if (!hasSAA) { hasSAA=disCont[i].dispatch->chipClock; diff --git a/src/gui/about.cpp b/src/gui/about.cpp index ce543f5f4..76fc80ad2 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -91,7 +91,7 @@ const char* aboutLine[]={ "ymfm by Aaron Giles", "MAME SN76496 by Nicola Salmoria", "MAME AY-3-8910 by Couriersud", - "with AY8930 fixes by Eulous", + "with AY8930 fixes by Eulous, cam900 and Grauw", "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ec38c86c2..55eab2af5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2794,6 +2794,7 @@ bool FurnaceGUI::loop() { } if (ImGui::BeginMenu("window")) { if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen; + if (ImGui::MenuItem("subsongs",BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS),subSongsOpen)) subSongsOpen=!subSongsOpen; if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen; if (ImGui::MenuItem("samples",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST),sampleListOpen)) sampleListOpen=!sampleListOpen; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 56ac54a69..d04d329d7 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -461,7 +461,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "NES with Sunsoft 5B", { DIV_SYSTEM_NES, 64, 0, 0, - DIV_SYSTEM_AY8910, 64, 0, 38, + DIV_SYSTEM_AY8910, 64, 0, 32, 0 } )); @@ -643,6 +643,15 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + Darky", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_AY8930, 64, 0, 139, // 3.58MHz + DIV_SYSTEM_AY8930, 64, 0, 139, // 3.58MHz or 3.6MHz selectable via register + // per-channel mixer (soft panning, post processing) isn't emulated at all + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "MSX + SCC", { DIV_SYSTEM_AY8910, 64, 0, 16, diff --git a/src/gui/subSongs.cpp b/src/gui/subSongs.cpp index 21b15001f..192cdb9d3 100644 --- a/src/gui/subSongs.cpp +++ b/src/gui/subSongs.cpp @@ -9,7 +9,7 @@ void FurnaceGUI::drawSubSongs() { ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } - if (!oscOpen) return; + if (!subSongsOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Subsongs",&subSongsOpen,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { char id[1024]; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 9ac2e4bd9..e70348454 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -200,7 +200,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool copyOfFlags=(flags&(~15))|5; } - if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) { + if (ImGui::RadioButton("0.89MHz (Pre-divided Sunsoft 5B)",(flags&15)==6)) { copyOfFlags=(flags&(~15))|6; } @@ -208,7 +208,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool copyOfFlags=(flags&(~15))|7; } - if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) { + if (ImGui::RadioButton("0.83MHz (Pre-divided Sunsoft 5B on PAL)",(flags&15)==8)) { copyOfFlags=(flags&(~15))|8; } @@ -219,6 +219,14 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { copyOfFlags=(flags&(~15))|10; + } + if (ImGui::RadioButton("3.58MHz (Darky)",(flags&15)==11)) { + copyOfFlags=(flags&(~15))|11; + + } + if (ImGui::RadioButton("3.6MHz (Darky)",(flags&15)==12)) { + copyOfFlags=(flags&(~15))|12; + } if (type==DIV_SYSTEM_AY8910) { ImGui::Text("Chip type:"); @@ -240,10 +248,17 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } } bool stereo=flags&0x40; - ImGui::BeginDisabled((flags&0x30)==32); + ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)==32)); if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { copyOfFlags=(flags&(~0x40))|(stereo?0x40:0); + } + ImGui::EndDisabled(); + bool clockSel=flags&0x80; + ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)!=16)); + if (ImGui::Checkbox("Half Clock divider##_AY_CLKSEL",&clockSel)) { + copyOfFlags=(flags&(~0x80))|(clockSel?0x80:0); + } ImGui::EndDisabled(); break;