From 4f1ebc3b6e32a574c7fe88be93618221f756a97c Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 4 May 2022 23:04:20 +0900 Subject: [PATCH 01/70] Some AY-3-8910, AY8930 enhancements Fix AY PSG clock divider function when exists, Update emulation core (MAME AY PSG and compatibles - includes noise routine updates) Fix configuration flag, Add MSX Darky sound expander to preset Clock divider isn't affected to expanded mode's tone and noise generator? --- src/engine/platform/ay.cpp | 12 +++- src/engine/platform/ay.h | 2 +- src/engine/platform/ay8930.cpp | 90 +++++++++++++----------- src/engine/platform/ay8930.h | 17 +++-- src/engine/platform/sound/ay8910.cpp | 100 ++++++++++----------------- src/engine/platform/sound/ay8910.h | 53 +++++++++++--- src/gui/presets.cpp | 9 +++ src/gui/sysConf.cpp | 17 ++++- 8 files changed, 178 insertions(+), 122 deletions(-) diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 37108452e..702cb00ed 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; @@ -653,8 +660,9 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { break; } ay->device_start(); + ay->set_clock_sel(clockSel); - 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 3fec732b5..41c54c572 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 8 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); @@ -641,16 +641,23 @@ 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; } - rate=chipClock/4; + rate=chipClock/8; for (int i=0; i<3; i++) { oscBuf[i]->rate=rate; } - stereo=flags>>6; + stereo=(flags>>6)&1; + clockSel=(flags>>7)&1; } int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -664,6 +671,7 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in setFlags(flags); ay=new ay8930_device(rate); ay->device_start(); + ay->set_clock_sel(clockSel); ayBufLen=65536; for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen]; reset(); 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..bfd1b450f 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 = std::max(1,tone->period) << 1; + tone->count += is_expanded_mode() ? 32 : (is_clock_divided() ? 1 : 2); while (tone->count >= period) { tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f; @@ -1073,48 +1071,35 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) } } - m_count_noise++; - if (m_count_noise >= noise_period()) + if ((++m_count_noise) >= noise_period()) { /* 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); + m_prescale_noise = (m_prescale_noise + 1) & (is_clock_divided() ? 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++) @@ -1130,8 +1115,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) if (envelope->holding == 0) { const int period = envelope->period * m_step; - envelope->count++; - if (envelope->count >= period) + if ((++envelope->count) >= period) { envelope->count = 0; envelope->step--; @@ -1263,20 +1247,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 +1313,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 +1358,7 @@ unsigned char ay8910_device::ay8910_read_ym() void ay8910_device::device_reset() { - ay8910_reset_ym(chip_type == AY8930); + ay8910_reset_ym(); } /************************************* @@ -1454,22 +1432,20 @@ 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) : 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( (feature & PSG_HAS_INTERNAL_DIVIDER) || (psg_type == PSG_TYPE_AY) ? 2 : 1), 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), @@ -1494,7 +1470,7 @@ void ay8910_device::set_type(psg_type_t psg_type) if (psg_type == PSG_TYPE_AY) { m_env_step_mask = 0x0f; - m_step = 2; + m_step = is_clock_divided() ? 4 : 2; m_zero_is_off = 1; m_par = &ay8910_param; m_par_env = &ay8910_param; @@ -1502,7 +1478,7 @@ 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_step = is_clock_divided() ? 2 : 1; m_zero_is_off = 0; m_par = &ym2149_param; m_par_env = &ym2149_param_env; diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h index c9de6da0a..952d94154 100644 --- a/src/engine/platform/sound/ay8910.h +++ b/src/engine/platform/sound/ay8910.h @@ -97,7 +97,25 @@ 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; + set_type(m_type); + } + else + { + m_flags &= ~YM2149_PIN26_LOW; + set_type(m_type); + } + } + } // 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 @@ -138,7 +156,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; @@ -258,6 +276,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); } @@ -267,11 +297,16 @@ private: 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_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); inline float mix_3D(); @@ -284,18 +319,16 @@ 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 ... */ diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 3dd24e0af..156a67cd7 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -425,6 +425,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, 140, // 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( "ZX Spectrum (48K)", { DIV_SYSTEM_AY8910, 64, 0, 2, diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 8005ee0d3..199c6217e 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -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; From 7b91045bac868b3482692dd905c4bc82731dec22 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 5 May 2022 13:38:10 +0900 Subject: [PATCH 02/70] Add documents Important: it will break previously baked modules with 5B and AY8930 --- src/engine/engine.h | 4 ++-- src/engine/song.h | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index d15838942..169a3f02a 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev91" -#define DIV_ENGINE_VERSION 91 +#define DIV_VERSION "devff" // it breaks compatiblity +#define DIV_ENGINE_VERSION 0xff // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/song.h b/src/engine/song.h index c0852ea0e..39d388699 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -215,13 +215,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) From 96f303ac9f9de3aeb93239e89a998d62c59602c0 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 5 May 2022 15:43:59 +0900 Subject: [PATCH 03/70] Fix configuration description --- src/gui/presets.cpp | 2 +- src/gui/sysConf.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 156a67cd7..60ca94480 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -257,7 +257,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 } )); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 199c6217e..b38a1366f 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; } From 3c7ce65db7a351ced216066381ccd0bd4dee82a2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 6 May 2022 19:18:55 +0900 Subject: [PATCH 04/70] it isn't necesaary --- src/engine/engine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 11be4e96e..e9a5db2e0 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,7 +45,7 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev93" +#define DIV_VERSION "dev93" #define DIV_ENGINE_VERSION 93 // for imports From 0b0a6dbf282023df937fdd668e65e9d20ed85716 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 7 May 2022 23:00:22 +0900 Subject: [PATCH 05/70] Fix divider again, split tone and envelope clock divider --- src/engine/platform/ay8930.cpp | 4 ++-- src/engine/platform/sound/ay8910.cpp | 20 ++++++++++++-------- src/engine/platform/sound/ay8910.h | 14 +++++++------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 41c54c572..12259689a 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 8 +#define CHIP_DIVIDER (clockSel?8:4) const char* regCheatSheetAY8930[]={ "FreqL_A", "00", @@ -651,7 +651,7 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { chipClock=COLOR_NTSC/2.0; break; } - rate=chipClock/8; + rate=chipClock/4; for (int i=0; i<3; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index bfd1b450f..b620eaf77 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -1061,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) << 1; - tone->count += is_expanded_mode() ? 32 : (is_clock_divided() ? 1 : 2); + 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); while (tone->count >= period) { tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f; @@ -1071,13 +1071,14 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) } } - if ((++m_count_noise) >= noise_period()) + const int period_noise = 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) & (is_clock_divided() ? 3 : 1); + 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 { @@ -1114,7 +1115,7 @@ 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; + const int period = envelope->period * m_env_step_mul; if ((++envelope->count) >= period) { envelope->count = 0; @@ -1445,7 +1446,8 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock, 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_INTERNAL_DIVIDER) || (psg_type == PSG_TYPE_AY) ? 2 : 1), + m_step_mul( (feature & PSG_HAS_INTERNAL_DIVIDER) ? 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), @@ -1470,7 +1472,7 @@ void ay8910_device::set_type(psg_type_t psg_type) if (psg_type == PSG_TYPE_AY) { m_env_step_mask = 0x0f; - m_step = is_clock_divided() ? 4 : 2; + m_env_step_mul = is_clock_divided() ? 4 : 2; m_zero_is_off = 1; m_par = &ay8910_param; m_par_env = &ay8910_param; @@ -1478,11 +1480,13 @@ void ay8910_device::set_type(psg_type_t psg_type) else { m_env_step_mask = 0x1f; - m_step = is_clock_divided() ? 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; } diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h index 952d94154..b71d9ad91 100644 --- a/src/engine/platform/sound/ay8910.h +++ b/src/engine/platform/sound/ay8910.h @@ -105,15 +105,14 @@ public: if (m_feature & PSG_PIN26_IS_CLKSEL) { if (clk_sel) - { m_flags |= YM2149_PIN26_LOW; - set_type(m_type); - } else - { m_flags &= ~YM2149_PIN26_LOW; - set_type(m_type); - } + + 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; } } @@ -332,7 +331,8 @@ private: 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; From a8258d9a1ad667b48cd11691bc0c1f479810d6a5 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 10 May 2022 13:18:02 +0900 Subject: [PATCH 06/70] Divider related emulation core update, Fix init and period limitation --- .gitignore | 1 + src/engine/platform/ay.cpp | 3 +-- src/engine/platform/ay8930.cpp | 5 ++--- src/engine/platform/sound/ay8910.cpp | 30 +++++++++++++++------------- src/engine/platform/sound/ay8910.h | 27 +++++++++++++++---------- 5 files changed, 36 insertions(+), 30 deletions(-) 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/platform/ay.cpp b/src/engine/platform/ay.cpp index 702cb00ed..cb309d99d 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -639,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; @@ -660,7 +660,6 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { break; } ay->device_start(); - ay->set_clock_sel(clockSel); stereo=(flags>>6)&1; } diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 12259689a..c8f46dd24 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -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; @@ -657,7 +658,6 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { } stereo=(flags>>6)&1; - clockSel=(flags>>7)&1; } int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -669,9 +669,8 @@ 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(); - ay->set_clock_sel(clockSel); ayBufLen=65536; for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen]; reset(); diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index b620eaf77..3dddf82c5 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -1061,7 +1061,7 @@ 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) * (m_step_mul << 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) { @@ -1071,7 +1071,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) } } - const int period_noise = noise_period() * m_step_mul; + 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 @@ -1431,7 +1431,7 @@ 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_streams(streams), @@ -1446,12 +1446,12 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock, 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_mul( (feature & PSG_HAS_INTERNAL_DIVIDER) ? 2 : 1), - m_env_step_mul( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? (m_step_mul << 1) : m_step_mul), + 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)); @@ -1463,10 +1463,10 @@ 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) @@ -1487,6 +1487,8 @@ void ay8910_device::set_type(psg_type_t psg_type) } if (m_feature & PSG_HAS_EXPANDED_MODE) m_env_step_mul <<= 1; + + set_clock_sel(clk_sel); } @@ -1515,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 b71d9ad91..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(); } @@ -144,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(); @@ -206,7 +211,7 @@ private: void reset() { - period = 0; + period = 1; volume = 0; duty = 0; count = 0; @@ -216,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) @@ -240,7 +245,7 @@ private: void reset() { - period = 0; + period = 1; count = 0; step = 0; volume = 0; @@ -252,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) @@ -295,7 +300,7 @@ 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_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)); } @@ -307,7 +312,7 @@ private: 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(); @@ -370,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 From 0c0a97c21b9d641a6b0c012e4b1d32468c81f3e6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 01:13:21 -0500 Subject: [PATCH 07/70] SCC: address review comments --- src/engine/platform/scc.cpp | 12 ++++++------ src/engine/song.h | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 87a42afee..125425a54 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -23,7 +23,7 @@ #define CHIP_DIVIDER 32 -#define rWrite(a,v) {if(!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }} +#define rWrite(a,v) {if (!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if (dumpWrites) addWrite(a,v); }} const char* regCheatSheetSCC[]={ "Ch1_Wave", "00", @@ -153,11 +153,11 @@ void DivPlatformSCC::tick(bool sysTick) { } } if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2; - if (chan[i].freq<1) chan[i].freq=1; - if (chan[i].freq>4096) chan[i].freq=4096; - rWrite(regBase+0+i*2,(chan[i].freq-1)&0xff); - rWrite(regBase+1+i*2,(chan[i].freq-1)>>8); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>4095) chan[i].freq=4095; + rWrite(regBase+0+i*2,chan[i].freq&0xff); + rWrite(regBase+1+i*2,chan[i].freq>>8); chan[i].freqChanged=false; } } diff --git a/src/engine/song.h b/src/engine/song.h index 4c88794d9..d8a37cbb6 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -376,7 +376,6 @@ struct DivSong { */ void clearSamples(); - /** * unloads the song, freeing all memory associated with it. * use before destroying the object. From 35ff5430d30e85bdfdbe032e495645c667877c67 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 02:20:14 -0500 Subject: [PATCH 08/70] SCC: VGM export, forceIns bug fix and 10xx --- src/engine/platform/scc.cpp | 14 +++++++---- src/engine/sysDef.cpp | 12 ++++++---- src/engine/vgmOps.cpp | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 125425a54..96ec2dfd2 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -21,7 +21,7 @@ #include "../engine.h" #include -#define CHIP_DIVIDER 32 +#define CHIP_DIVIDER 16 #define rWrite(a,v) {if (!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if (dumpWrites) addWrite(a,v); }} @@ -281,7 +281,9 @@ void DivPlatformSCC::forceIns() { for (int i=0; i<5; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; - updateWave(i); + if (chan[i].active) { + updateWave(i); + } } } @@ -326,7 +328,9 @@ void DivPlatformSCC::notifyWaveChange(int wave) { for (int i=0; i<5; i++) { if (chan[i].wave==wave) { chan[i].ws.changeWave1(chan[i].wave); - updateWave(i); + if (chan[i].active) { + updateWave(i); + } } } } @@ -358,8 +362,8 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=COLOR_NTSC; - rate=chipClock/16; + chipClock=COLOR_NTSC/2.0; + rate=chipClock/8; for (int i=0; i<5; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index c56648406..436ca6891 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1459,11 +1459,13 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SCC]=new DivSysDef( - "Konami SCC", NULL, 0xa1, 0, 5, false, true, 0, false, + "Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, + {}, + waveOnlyEffectHandler ); auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { @@ -1740,11 +1742,13 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( - "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0, false, + "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, + {}, + waveOnlyEffectHandler ); sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef( diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 53800a525..f402322bd 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -598,6 +598,36 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_SCC: + if (write.addr<0x80) { + w->writeC(0xd2); + w->writeC(0); + w->writeC(baseAddr2|(write.addr&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr<0x8a) { + w->writeC(0xd2); + w->writeC(1); + w->writeC(baseAddr2|((write.addr-0x80)&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr<0x8f) { + w->writeC(0xd2); + w->writeC(2); + w->writeC(baseAddr2|((write.addr-0x8a)&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr<0x90) { + w->writeC(0xd2); + w->writeC(3); + w->writeC(baseAddr2|((write.addr-0x8f)&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr>=0xe0) { + w->writeC(0xd2); + w->writeC(5); + w->writeC(baseAddr2|((write.addr-0xe0)&0x7f)); + w->writeC(write.val&0xff); + } else { + logW("SCC: writing to unmapped address %.2x!",write.addr); + } + break; default: logW("write not handled!"); break; @@ -988,6 +1018,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + case DIV_SYSTEM_SCC: + case DIV_SYSTEM_SCC_PLUS: + if (!hasK051649) { + hasK051649=disCont[i].dispatch->chipClock; + if (song.system[i]==DIV_SYSTEM_SCC_PLUS) { + hasK051649|=0x80000000; + } + willExport[i]=true; + } else if (!(hasK051649&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasK051649|=0x40000000; + if (song.system[i]==DIV_SYSTEM_SCC_PLUS) { + hasK051649|=0x80000000; + } + howManyChips++; + } + break; default: break; } From 955cd48fcfe0a88a2f2e0fe830c8eb4b2170b895 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 02:32:08 -0500 Subject: [PATCH 09/70] SCC: SCC+ and dual-SCC VGM export --- src/engine/vgmOps.cpp | 55 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index f402322bd..0a995e521 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -601,33 +601,68 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_SCC: if (write.addr<0x80) { w->writeC(0xd2); - w->writeC(0); - w->writeC(baseAddr2|(write.addr&0x7f)); + w->writeC(baseAddr2|0); + w->writeC(write.addr&0x7f); w->writeC(write.val&0xff); } else if (write.addr<0x8a) { w->writeC(0xd2); - w->writeC(1); - w->writeC(baseAddr2|((write.addr-0x80)&0x7f)); + w->writeC(baseAddr2|1); + w->writeC((write.addr-0x80)&0x7f); w->writeC(write.val&0xff); } else if (write.addr<0x8f) { w->writeC(0xd2); - w->writeC(2); - w->writeC(baseAddr2|((write.addr-0x8a)&0x7f)); + w->writeC(baseAddr2|2); + w->writeC((write.addr-0x8a)&0x7f); w->writeC(write.val&0xff); } else if (write.addr<0x90) { w->writeC(0xd2); - w->writeC(3); - w->writeC(baseAddr2|((write.addr-0x8f)&0x7f)); + w->writeC(baseAddr2|3); + w->writeC((write.addr-0x8f)&0x7f); w->writeC(write.val&0xff); } else if (write.addr>=0xe0) { w->writeC(0xd2); - w->writeC(5); - w->writeC(baseAddr2|((write.addr-0xe0)&0x7f)); + w->writeC(baseAddr2|5); + w->writeC((write.addr-0xe0)&0x7f); w->writeC(write.val&0xff); } else { logW("SCC: writing to unmapped address %.2x!",write.addr); } break; + case DIV_SYSTEM_SCC_PLUS: + if (write.addr<0x80) { + w->writeC(0xd2); + w->writeC(baseAddr2|0); + w->writeC(write.addr&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr<0xa0) { + w->writeC(0xd2); + w->writeC(baseAddr2|4); + w->writeC(write.addr); + w->writeC(write.val&0xff); + } else if (write.addr<0xaa) { + w->writeC(0xd2); + w->writeC(baseAddr2|1); + w->writeC((write.addr-0xa0)&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr<0xaf) { + w->writeC(0xd2); + w->writeC(baseAddr2|2); + w->writeC((write.addr-0xaa)&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr<0xb0) { + w->writeC(0xd2); + w->writeC(baseAddr2|3); + w->writeC((write.addr-0xaf)&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr>=0xe0) { + w->writeC(0xd2); + w->writeC(baseAddr2|5); + w->writeC((write.addr-0xe0)&0x7f); + w->writeC(write.val&0xff); + } else { + logW("SCC+: writing to unmapped address %.2x!",write.addr); + } + break; default: logW("write not handled!"); break; From 431b6b595765d7693b8aee2fac38ea26a6908338 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 02:42:05 -0500 Subject: [PATCH 10/70] fix 09xx, 0Bxx, 0Dxx and 0Fxx being ignored on ED --- src/engine/playback.cpp | 79 +++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index feec7d961..6a27dc65e 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -268,26 +268,53 @@ void DivEngine::processRow(int i, bool afterDelay) { int whatRow=afterDelay?chan[i].delayRow:curRow; DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false); // pre effects - if (!afterDelay) for (int j=0; jdata[whatRow][4+(j<<1)]; - short effectVal=pat->data[whatRow][5+(j<<1)]; + if (!afterDelay) { + bool returnAfterPre=false; + for (int j=0; jdata[whatRow][4+(j<<1)]; + short effectVal=pat->data[whatRow][5+(j<<1)]; - if (effectVal==-1) effectVal=0; - if (effect==0xed && effectVal!=0) { - if (effectVal<=nextSpeed) { - chan[i].rowDelay=effectVal+1; - chan[i].delayOrder=whatOrder; - chan[i].delayRow=whatRow; - if (effectVal==nextSpeed) { - //if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; - } else { - chan[i].delayLocked=false; - } - return; - } else { - chan[i].delayLocked=false; + if (effectVal==-1) effectVal=0; + + switch (effect) { + case 0x09: // speed 1 + if (effectVal>0) speed1=effectVal; + break; + case 0x0f: // speed 2 + if (effectVal>0) speed2=effectVal; + break; + case 0x0b: // change order + if (changeOrd==-1) { + changeOrd=effectVal; + changePos=0; + } + break; + case 0x0d: // next order + if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + break; + case 0xed: // delay + if (effectVal!=0) { + if (effectVal<=nextSpeed) { + chan[i].rowDelay=effectVal+1; + chan[i].delayOrder=whatOrder; + chan[i].delayRow=whatRow; + if (effectVal==nextSpeed) { + //if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; + } else { + chan[i].delayLocked=false; + } + returnAfterPre=true; + } else { + chan[i].delayLocked=false; + } + } + break; } } + if (returnAfterPre) return; } if (chan[i].delayLocked) return; @@ -386,24 +413,6 @@ void DivEngine::processRow(int i, bool afterDelay) { // per-system effect if (!perSystemEffect(i,effect,effectVal)) switch (effect) { - case 0x09: // speed 1 - if (effectVal>0) speed1=effectVal; - break; - case 0x0f: // speed 2 - if (effectVal>0) speed2=effectVal; - break; - case 0x0b: // change order - if (changeOrd==-1) { - changeOrd=effectVal; - changePos=0; - } - break; - case 0x0d: // next order - if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { - changeOrd=-2; - changePos=effectVal; - } - break; case 0x08: // panning (split 4-bit) chan[i].panL=(effectVal>>4)|(effectVal&0xf0); chan[i].panR=(effectVal&15)|((effectVal&15)<<4); From e48faa4114c61f4272279c6e50dc426ea2350210 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 11 May 2022 15:22:17 +0700 Subject: [PATCH 11/70] VERA: halve output volume to match other PSGs In terms of maximum single-channel output. X16's mixing is kept by halving YM2151 part. --- src/engine/platform/vera.cpp | 2 +- src/gui/presets.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 58aa55b92..65a7452ee 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -414,7 +414,7 @@ void DivPlatformVERA::muteChannel(int ch, bool mute) { } float DivPlatformVERA::getPostAmp() { - return 8.0f; + return 4.0f; } bool DivPlatformVERA::isStereo() { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 28a57f861..efb73ec51 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -599,7 +599,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "Commander X16", { DIV_SYSTEM_VERA, 64, 0, 0, - DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_YM2151, 32, 0, 0, 0 } )); From ab969c1da00f89fa28cba7a39e9af9db186745e9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 03:29:03 -0500 Subject: [PATCH 12/70] add OPN chip pretty much copy-paste from YM2610B code next up: even more blatant copy-paste in 2608 --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/ym2203.cpp | 1132 ++++++++++++++++++++++++++++ src/engine/platform/ym2203.h | 135 ++++ src/engine/platform/ym2203shared.h | 45 ++ src/engine/sysDef.cpp | 5 +- src/gui/guiConst.cpp | 2 + src/gui/presets.cpp | 6 + 8 files changed, 1329 insertions(+), 1 deletion(-) create mode 100644 src/engine/platform/ym2203.cpp create mode 100644 src/engine/platform/ym2203.h create mode 100644 src/engine/platform/ym2203shared.h diff --git a/CMakeLists.txt b/CMakeLists.txt index db0a51a5c..b415fb99d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -345,6 +345,7 @@ src/engine/platform/nes.cpp src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/tx81z.cpp +src/engine/platform/ym2203.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610b.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index c04276ea7..7595d14bb 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -29,6 +29,7 @@ #include "platform/c64.h" #include "platform/arcade.h" #include "platform/tx81z.h" +#include "platform/ym2203.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ym2610b.h" @@ -234,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_TIA: dispatch=new DivPlatformTIA; break; + case DIV_SYSTEM_OPN: + dispatch=new DivPlatformYM2203; + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp new file mode 100644 index 000000000..e18d76d2a --- /dev/null +++ b/src/engine/platform/ym2203.cpp @@ -0,0 +1,1132 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ym2203.h" +#include "sound/ymfm/ymfm.h" +#include "../engine.h" +#include +#include + +#include "ym2203shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 +}; + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetYM2203[]={ + // SSG + "SSG_FreqL_A", "000", + "SSG_FreqH_A", "001", + "SSG_FreqL_B", "002", + "SSG_FreqH_B", "003", + "SSG_FreqL_C", "004", + "SSG_FreqH_C", "005", + "SSG_FreqNoise", "006", + "SSG_Enable", "007", + "SSG_Volume_A", "008", + "SSG_Volume_B", "009", + "SSG_Volume_C", "00A", + "SSG_FreqL_Env", "00B", + "SSG_FreqH_Env", "00C", + "SSG_Control_Env", "00D", + // FM (Common) + "FM_Test", "021", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-3) + "FM1_Op1_DT_MULT", "030", + "FM2_Op1_DT_MULT", "031", + "FM3_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "034", + "FM2_Op2_DT_MULT", "035", + "FM3_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "038", + "FM2_Op3_DT_MULT", "039", + "FM3_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03C", + "FM2_Op4_DT_MULT", "03D", + "FM3_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "040", + "FM2_Op1_TL", "041", + "FM3_Op1_TL", "042", + "FM1_Op2_TL", "044", + "FM2_Op2_TL", "045", + "FM3_Op2_TL", "046", + "FM1_Op3_TL", "048", + "FM2_Op3_TL", "049", + "FM3_Op3_TL", "04A", + "FM1_Op4_TL", "04C", + "FM2_Op4_TL", "04D", + "FM3_Op4_TL", "04E", + "FM1_Op1_KS_AR", "050", + "FM2_Op1_KS_AR", "051", + "FM3_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "054", + "FM2_Op2_KS_AR", "055", + "FM3_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "058", + "FM2_Op3_KS_AR", "059", + "FM3_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05C", + "FM2_Op4_KS_AR", "05D", + "FM3_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "060", + "FM2_Op1_AM_DR", "061", + "FM3_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "064", + "FM2_Op2_AM_DR", "065", + "FM3_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "068", + "FM2_Op3_AM_DR", "069", + "FM3_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06C", + "FM2_Op4_AM_DR", "06D", + "FM3_Op4_AM_DR", "06E", + "FM1_Op1_SR", "070", + "FM2_Op1_SR", "071", + "FM3_Op1_SR", "072", + "FM1_Op2_SR", "074", + "FM2_Op2_SR", "075", + "FM3_Op2_SR", "076", + "FM1_Op3_SR", "078", + "FM2_Op3_SR", "079", + "FM3_Op3_SR", "07A", + "FM1_Op4_SR", "07C", + "FM2_Op4_SR", "07D", + "FM3_Op4_SR", "07E", + "FM1_Op1_SL_RR", "080", + "FM2_Op1_SL_RR", "081", + "FM3_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "084", + "FM2_Op2_SL_RR", "085", + "FM3_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "088", + "FM2_Op3_SL_RR", "089", + "FM3_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08C", + "FM2_Op4_SL_RR", "08D", + "FM3_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "090", + "FM2_Op1_SSG_EG", "091", + "FM3_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "094", + "FM2_Op2_SSG_EG", "095", + "FM3_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "098", + "FM2_Op3_SSG_EG", "099", + "FM3_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09C", + "FM2_Op4_SSG_EG", "09D", + "FM3_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A0", + "FM2_FNum1", "0A1", + "FM3_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A4", + "FM2_FNum2", "0A5", + "FM3_(Op1)FNum2", "0A6", + "FM3_Op2_FNum1", "0A8", + "FM3_Op3_FNum1", "0A9", + "FM3_Op4_FNum1", "0AA", + "FM3_Op2_FNum2", "0AC", + "FM3_Op3_FNum2", "0AD", + "FM3_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B0", + "FM2_FB_ALG", "0B1", + "FM3_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B4", + "FM2_Pan_LFO", "0B5", + "FM3_Pan_LFO", "0B6", + NULL +}; + +const char** DivPlatformYM2203::getRegisterSheet() { + return regCheatSheetYM2203; +} + +const char* DivPlatformYM2203::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x20: + return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; + break; + case 0x21: + return "21xx: Set SSG noise frequency (0 to 1F)"; + break; + case 0x22: + return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; + break; + case 0x23: + return "23xx: Set SSG envelope period low byte"; + break; + case 0x24: + return "24xx: Set SSG envelope period high byte"; + break; + case 0x25: + return "25xx: SSG envelope slide up"; + break; + case 0x26: + return "26xx: SSG envelope slide down"; + break; + case 0x29: + return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; + break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; + } + return NULL; +} + +double DivPlatformYM2203::NOTE_OPN(int ch, int note) { + // FM + return NOTE_FNUM_BLOCK(note,11); +} + +void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os; + + /*ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + fmChan[i]=fme->debug_channel(i); + adpcmAChan[i]=aae->debug_channel(i); + }*/ + + for (size_t h=start; hwrite(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + delay=16; + } + } + + fm->generate(&fmout); + + os=fmout.data[0]+((fmout.data[1]+fmout.data[2]+fmout.data[3])>>1); + if (os<-32768) os=-32768; + if (os>32767) os=32767; + + bufL[h]=os; + + /* + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=6; i<9; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + } + + for (int i=9; i<15; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + } + + oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + */ + } +} + +void DivPlatformYM2203::tick(bool sysTick) { + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + + // FM + for (int i=0; i<3; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + 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); + } + } + } + + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); + } else { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,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); + } + } + } + } + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + 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); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } + immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } + chan[i].keyOff=false; + } + } + + for (int i=16; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<3; i++) { + if (i==2 && extMode) continue; + if (chan[i].freqChanged) { + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + } + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } +} + +int DivPlatformYM2203::dispatch(DivCommand c) { + if (c.chan>2) { + c.chan-=3; + return ay->dispatch(c); + } + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroInit(ins); + if (c.chan<3) { + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + } + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + if (c.value==0 && c.value2==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); + } + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (c.chan>2 || parent->song.linearPitch==2) { // PSG + int destFreq=NOTE_OPN(c.chan,c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); + int newFreq; + bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value; + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value; + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + // check for octave boundary + // what the heck! + if (!chan[c.chan].portaPause) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_OPN(c.chan,c.value); + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_FB: { + if (c.chan>2) break; + chan[c.chan].state.fb=c.value&7; + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + if (c.chan>2) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + if (c.chan>2) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.chan>2) break; + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan>2) return 15; + return 127; + break; + case DIV_CMD_PRE_PORTA: + if (c.chan>2) { + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformYM2203::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>2) { // PSG + ay->muteChannel(ch-3,mute); + return; + } + // FM + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); +} + +void DivPlatformYM2203::forceIns() { + 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 (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_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + for (int i=3; i<6; i++) { + chan[i].insChanged=true; + } + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); +} + +void* DivPlatformYM2203::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2203::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformYM2203::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2203::getRegisterPoolSize() { + return 512; +} + +void DivPlatformYM2203::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformYM2203::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformYM2203::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + fm->reset(); + for (int i=0; i<6; i++) { + chan[i]=DivPlatformYM2203::Channel(); + chan[i].std.setEngine(parent); + } + for (int i=0; i<3; i++) { + chan[i].vol=0x7f; + chan[i].outVol=0x7f; + } + for (int i=3; i<6; i++) { + chan[i].vol=0x0f; + } + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + sampleBank=0; + + delay=0; + + extMode=false; + + // LFO + immWrite(0x22,0x08); + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); +} + +bool DivPlatformYM2203::isStereo() { + return false; +} + +bool DivPlatformYM2203::keyOffAffectsArp(int ch) { + return (ch>2); +} + +void DivPlatformYM2203::notifyInsChange(int ins) { + for (int i=0; i<6; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } + ay->notifyInsChange(ins); +} + +void DivPlatformYM2203::notifyInsDeletion(void* ins) { + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2203::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); +} + +void DivPlatformYM2203::setFlags(unsigned int flags) { + unsigned char ayFlags=32; + if (flags==3) { + chipClock=3000000.0; + ayFlags=36; + } else if (flags==2) { + chipClock=4000000.0; + ayFlags=35; + } else if (flags==1) { + chipClock=COLOR_PAL*4.0/5.0; + ayFlags=33; + } else { + chipClock=COLOR_NTSC; + ayFlags=32; + } + ay->setFlags(ayFlags); + rate=fm->sample_rate(chipClock); + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } +} + +int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<6; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + fm=new ymfm::ym2203(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); + setFlags(flags); + + reset(); + return 16; +} + +void DivPlatformYM2203::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } + ay->quit(); + delete ay; + delete fm; +} + +DivPlatformYM2203::~DivPlatformYM2203() { +} diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h new file mode 100644 index 000000000..498963ab4 --- /dev/null +++ b/src/engine/platform/ym2203.h @@ -0,0 +1,135 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _YM2203_H +#define _YM2203_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ymfm/ymfm_opn.h" + +#include "ay.h" + +class DivYM2203Interface: public ymfm::ymfm_interface { + +}; + +class DivPlatformYM2203: public DivDispatch { + protected: + const unsigned short chanOffs[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + + struct Channel { + DivInstrumentFM state; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + int vol, outVol; + int sample; + unsigned char pan; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + portaPauseFreq(0), + note(0), + ins(-1), + psgMode(1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(15), + sample(-1), + pan(3) {} + }; + Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; + bool isMuted[6]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ymfm::ym2203* fm; + ymfm::ym2203::output_data fmout; + DivYM2203Interface iface; + unsigned char regPool[512]; + unsigned char lastBusy; + + DivPlatformAY8910* ay; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[512]; + short pendingWrites[512]; + + double NOTE_OPN(int ch, int note); + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void setSkipRegisterWrites(bool val); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2203(); +}; +#endif diff --git a/src/engine/platform/ym2203shared.h b/src/engine/platform/ym2203shared.h new file mode 100644 index 000000000..5aec79161 --- /dev/null +++ b/src/engine/platform/ym2203shared.h @@ -0,0 +1,45 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +static unsigned short opOffs[4]={ + 0x00, 0x04, 0x08, 0x0c +}; +static bool isOutput[8][4]={ + // 1 3 2 4 + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,true ,true}, + {false,true ,true ,true}, + {false,true ,true ,true}, + {true ,true ,true ,true}, +}; +static unsigned char dtTable[8]={ + 7,6,5,0,1,2,3,4 +}; + +static int orderedOps[4]={ + 0,2,1,3 +}; + +#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_FREQBASE 4720270 diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 436ca6891..c16b7312d 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1263,7 +1263,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, {"F1", "F2", "F3", "S1", "S2", "S3"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY} + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler ); sysDefs[DIV_SYSTEM_PC98]=new DivSysDef( diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 792f16d5f..4ea2de0c2 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -834,6 +834,8 @@ const int availableSystems[]={ DIV_SYSTEM_YMU759, DIV_SYSTEM_DUMMY, DIV_SYSTEM_SOUND_UNIT, + DIV_SYSTEM_OPN, + DIV_SYSTEM_PC98, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 28a57f861..2e7eb9cfd 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -439,6 +439,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-26K)", { + DIV_SYSTEM_OPN, 64, 0, 3, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (48K)", { DIV_SYSTEM_AY8910, 64, 0, 2, From ff0319780e83f7f4a5f2d487a4e8ebdcbf3daa2c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 03:41:02 -0500 Subject: [PATCH 13/70] OPN: VGM export - good night --- src/engine/platform/ym2203.cpp | 27 +- src/engine/platform/ym2203.h | 9 +- src/engine/platform/ym2608.cpp | 1449 ++++++++++++++++++++++++++++++++ src/engine/platform/ym2608.h | 130 +++ src/engine/sysDef.cpp | 2 +- src/engine/vgmOps.cpp | 17 + src/gui/sysConf.cpp | 15 + 7 files changed, 1627 insertions(+), 22 deletions(-) create mode 100644 src/engine/platform/ym2608.cpp create mode 100644 src/engine/platform/ym2608.h diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index e18d76d2a..83233e8f5 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -27,8 +27,8 @@ #include "fmshared_OPN.h" -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 +static unsigned char konOffs[3]={ + 0, 1, 2 }; #define CHIP_DIVIDER 32 @@ -287,11 +287,6 @@ const char* DivPlatformYM2203::getEffectName(unsigned char effect) { return NULL; } -double DivPlatformYM2203::NOTE_OPN(int ch, int note) { - // FM - return NOTE_FNUM_BLOCK(note,11); -} - void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os; @@ -314,9 +309,9 @@ void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t l if (!writes.empty()) { if (--delay<1) { QueuedWrite& w=writes.front(); - fm->write(0x0+((w.addr>>8)<<1),w.addr); - fm->write(0x1+((w.addr>>8)<<1),w.val); - regPool[w.addr&0x1ff]=w.val; + fm->write(0x0,w.addr); + fm->write(0x1,w.val); + regPool[w.addr&0xff]=w.val; writes.pop(); delay=16; } @@ -520,7 +515,7 @@ void DivPlatformYM2203::tick(bool sysTick) { } } - for (int i=16; i<512; i++) { + for (int i=16; i<256; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); oldWrites[i]=pendingWrites[i]; @@ -670,7 +665,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } case DIV_CMD_NOTE_PORTA: { if (c.chan>2 || parent->song.linearPitch==2) { // PSG - int destFreq=NOTE_OPN(c.chan,c.value2); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -737,7 +732,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=NOTE_OPN(c.chan,c.value); + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); chan[c.chan].freqChanged=true; break; } @@ -1002,7 +997,7 @@ unsigned char* DivPlatformYM2203::getRegisterPool() { } int DivPlatformYM2203::getRegisterPoolSize() { - return 512; + return 256; } void DivPlatformYM2203::poke(unsigned int addr, unsigned short val) { @@ -1015,7 +1010,7 @@ void DivPlatformYM2203::poke(std::vector& wlist) { void DivPlatformYM2203::reset() { while (!writes.empty()) writes.pop(); - memset(regPool,0,512); + memset(regPool,0,256); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -1032,7 +1027,7 @@ void DivPlatformYM2203::reset() { chan[i].vol=0x0f; } - for (int i=0; i<512; i++) { + for (int i=0; i<256; i++) { oldWrites[i]=-1; pendingWrites[i]=-1; } diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 498963ab4..1a6dad584 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -32,8 +32,8 @@ class DivYM2203Interface: public ymfm::ymfm_interface { class DivPlatformYM2203: public DivDispatch { protected: - const unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + const unsigned short chanOffs[3]={ + 0x00, 0x01, 0x02 }; struct Channel { @@ -101,10 +101,9 @@ class DivPlatformYM2203: public DivDispatch { bool extMode; - short oldWrites[512]; - short pendingWrites[512]; + short oldWrites[256]; + short pendingWrites[256]; - double NOTE_OPN(int ch, int note); friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp new file mode 100644 index 000000000..3b3d55767 --- /dev/null +++ b/src/engine/platform/ym2608.cpp @@ -0,0 +1,1449 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ym2608.h" +#include "sound/ymfm/ymfm.h" +#include "../engine.h" +#include +#include + +#include "ym2610shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 +}; + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetYM2610B[]={ + // SSG + "SSG_FreqL_A", "000", + "SSG_FreqH_A", "001", + "SSG_FreqL_B", "002", + "SSG_FreqH_B", "003", + "SSG_FreqL_C", "004", + "SSG_FreqH_C", "005", + "SSG_FreqNoise", "006", + "SSG_Enable", "007", + "SSG_Volume_A", "008", + "SSG_Volume_B", "009", + "SSG_Volume_C", "00A", + "SSG_FreqL_Env", "00B", + "SSG_FreqH_Env", "00C", + "SSG_Control_Env", "00D", + // ADPCM-B + "ADPCMB_Control", "010", + "ADPCMB_L_R", "011", + "ADPCMB_StartL", "012", + "ADPCMB_StartH", "013", + "ADPCMB_EndL", "014", + "ADPCMB_EndH", "015", + "ADPCMB_FreqL", "019", + "ADPCMB_FreqH", "01A", + "ADPCMB_Volume", "01B", + "ADPCM_Flag", "01C", + // FM (Common) + "FM_Test", "021", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-3) + "FM1_Op1_DT_MULT", "030", + "FM2_Op1_DT_MULT", "031", + "FM3_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "034", + "FM2_Op2_DT_MULT", "035", + "FM3_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "038", + "FM2_Op3_DT_MULT", "039", + "FM3_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03C", + "FM2_Op4_DT_MULT", "03D", + "FM3_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "040", + "FM2_Op1_TL", "041", + "FM3_Op1_TL", "042", + "FM1_Op2_TL", "044", + "FM2_Op2_TL", "045", + "FM3_Op2_TL", "046", + "FM1_Op3_TL", "048", + "FM2_Op3_TL", "049", + "FM3_Op3_TL", "04A", + "FM1_Op4_TL", "04C", + "FM2_Op4_TL", "04D", + "FM3_Op4_TL", "04E", + "FM1_Op1_KS_AR", "050", + "FM2_Op1_KS_AR", "051", + "FM3_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "054", + "FM2_Op2_KS_AR", "055", + "FM3_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "058", + "FM2_Op3_KS_AR", "059", + "FM3_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05C", + "FM2_Op4_KS_AR", "05D", + "FM3_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "060", + "FM2_Op1_AM_DR", "061", + "FM3_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "064", + "FM2_Op2_AM_DR", "065", + "FM3_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "068", + "FM2_Op3_AM_DR", "069", + "FM3_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06C", + "FM2_Op4_AM_DR", "06D", + "FM3_Op4_AM_DR", "06E", + "FM1_Op1_SR", "070", + "FM2_Op1_SR", "071", + "FM3_Op1_SR", "072", + "FM1_Op2_SR", "074", + "FM2_Op2_SR", "075", + "FM3_Op2_SR", "076", + "FM1_Op3_SR", "078", + "FM2_Op3_SR", "079", + "FM3_Op3_SR", "07A", + "FM1_Op4_SR", "07C", + "FM2_Op4_SR", "07D", + "FM3_Op4_SR", "07E", + "FM1_Op1_SL_RR", "080", + "FM2_Op1_SL_RR", "081", + "FM3_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "084", + "FM2_Op2_SL_RR", "085", + "FM3_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "088", + "FM2_Op3_SL_RR", "089", + "FM3_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08C", + "FM2_Op4_SL_RR", "08D", + "FM3_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "090", + "FM2_Op1_SSG_EG", "091", + "FM3_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "094", + "FM2_Op2_SSG_EG", "095", + "FM3_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "098", + "FM2_Op3_SSG_EG", "099", + "FM3_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09C", + "FM2_Op4_SSG_EG", "09D", + "FM3_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A0", + "FM2_FNum1", "0A1", + "FM3_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A4", + "FM2_FNum2", "0A5", + "FM3_(Op1)FNum2", "0A6", + "FM3_Op2_FNum1", "0A8", + "FM3_Op3_FNum1", "0A9", + "FM3_Op4_FNum1", "0AA", + "FM3_Op2_FNum2", "0AC", + "FM3_Op3_FNum2", "0AD", + "FM3_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B0", + "FM2_FB_ALG", "0B1", + "FM3_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B4", + "FM2_Pan_LFO", "0B5", + "FM3_Pan_LFO", "0B6", + // ADPCM-A + "ADPCMA_Control", "100", + "ADPCMA_MVol", "101", + "ADPCMA_Test", "102", + "ADPCMA_Ch1_Vol", "108", + "ADPCMA_Ch2_Vol", "109", + "ADPCMA_Ch3_Vol", "10A", + "ADPCMA_Ch4_Vol", "10B", + "ADPCMA_Ch5_Vol", "10C", + "ADPCMA_Ch6_Vol", "10D", + "ADPCMA_Ch1_StL", "110", + "ADPCMA_Ch2_StL", "111", + "ADPCMA_Ch3_StL", "112", + "ADPCMA_Ch4_StL", "113", + "ADPCMA_Ch5_StL", "114", + "ADPCMA_Ch6_StL", "115", + "ADPCMA_Ch1_StH", "118", + "ADPCMA_Ch2_StH", "119", + "ADPCMA_Ch3_StH", "11A", + "ADPCMA_Ch4_StH", "11B", + "ADPCMA_Ch5_StH", "11C", + "ADPCMA_Ch6_StH", "11D", + "ADPCMA_Ch1_EdL", "120", + "ADPCMA_Ch2_EdL", "121", + "ADPCMA_Ch3_EdL", "122", + "ADPCMA_Ch4_EdL", "123", + "ADPCMA_Ch5_EdL", "124", + "ADPCMA_Ch6_EdL", "125", + "ADPCMA_Ch1_EdH", "128", + "ADPCMA_Ch2_EdH", "129", + "ADPCMA_Ch3_EdH", "12A", + "ADPCMA_Ch4_EdH", "12B", + "ADPCMA_Ch5_EdH", "12C", + "ADPCMA_Ch6_EdH", "12D", + // FM (Channel 4-6) + "FM4_Op1_DT_MULT", "130", + "FM5_Op1_DT_MULT", "131", + "FM6_Op1_DT_MULT", "132", + "FM4_Op2_DT_MULT", "134", + "FM5_Op2_DT_MULT", "135", + "FM6_Op2_DT_MULT", "136", + "FM4_Op3_DT_MULT", "138", + "FM5_Op3_DT_MULT", "139", + "FM6_Op3_DT_MULT", "13A", + "FM4_Op4_DT_MULT", "13C", + "FM5_Op4_DT_MULT", "13D", + "FM6_Op4_DT_MULT", "13E", + "FM4_Op1_TL", "140", + "FM5_Op1_TL", "141", + "FM6_Op1_TL", "142", + "FM4_Op2_TL", "144", + "FM5_Op2_TL", "145", + "FM6_Op2_TL", "146", + "FM4_Op3_TL", "148", + "FM5_Op3_TL", "149", + "FM6_Op3_TL", "14A", + "FM4_Op4_TL", "14C", + "FM5_Op4_TL", "14D", + "FM6_Op4_TL", "14E", + "FM4_Op1_KS_AR", "150", + "FM5_Op1_KS_AR", "151", + "FM6_Op1_KS_AR", "152", + "FM4_Op2_KS_AR", "154", + "FM5_Op2_KS_AR", "155", + "FM6_Op2_KS_AR", "156", + "FM4_Op3_KS_AR", "158", + "FM5_Op3_KS_AR", "159", + "FM6_Op3_KS_AR", "15A", + "FM4_Op4_KS_AR", "15C", + "FM5_Op4_KS_AR", "15D", + "FM6_Op4_KS_AR", "15E", + "FM4_Op1_AM_DR", "160", + "FM5_Op1_AM_DR", "161", + "FM6_Op1_AM_DR", "162", + "FM4_Op2_AM_DR", "164", + "FM5_Op2_AM_DR", "165", + "FM6_Op2_AM_DR", "166", + "FM4_Op3_AM_DR", "168", + "FM5_Op3_AM_DR", "169", + "FM6_Op3_AM_DR", "16A", + "FM4_Op4_AM_DR", "16C", + "FM5_Op4_AM_DR", "16D", + "FM6_Op4_AM_DR", "16E", + "FM4_Op1_SR", "170", + "FM5_Op1_SR", "171", + "FM6_Op1_SR", "172", + "FM4_Op2_SR", "174", + "FM5_Op2_SR", "175", + "FM6_Op2_SR", "176", + "FM4_Op3_SR", "178", + "FM5_Op3_SR", "179", + "FM6_Op3_SR", "17A", + "FM4_Op4_SR", "17C", + "FM5_Op4_SR", "17D", + "FM6_Op4_SR", "17E", + "FM4_Op1_SL_RR", "180", + "FM5_Op1_SL_RR", "181", + "FM6_Op1_SL_RR", "182", + "FM4_Op2_SL_RR", "184", + "FM5_Op2_SL_RR", "185", + "FM6_Op2_SL_RR", "186", + "FM4_Op3_SL_RR", "188", + "FM5_Op3_SL_RR", "189", + "FM6_Op3_SL_RR", "18A", + "FM4_Op4_SL_RR", "18C", + "FM5_Op4_SL_RR", "18D", + "FM6_Op4_SL_RR", "18E", + "FM4_Op1_SSG_EG", "190", + "FM5_Op1_SSG_EG", "191", + "FM6_Op1_SSG_EG", "192", + "FM4_Op2_SSG_EG", "194", + "FM5_Op2_SSG_EG", "195", + "FM6_Op2_SSG_EG", "196", + "FM4_Op3_SSG_EG", "198", + "FM5_Op3_SSG_EG", "199", + "FM6_Op3_SSG_EG", "19A", + "FM4_Op4_SSG_EG", "19C", + "FM5_Op4_SSG_EG", "19D", + "FM6_Op4_SSG_EG", "19E", + "FM4_FNum1", "1A0", + "FM5_FNum1", "1A1", + "FM6_FNum1", "1A2", + "FM4_FNum2", "1A4", + "FM5_FNum2", "1A5", + "FM6_FNum2", "1A6", + "FM4_FB_ALG", "1B0", + "FM5_FB_ALG", "1B1", + "FM6_FB_ALG", "1B2", + "FM4_Pan_LFO", "1B4", + "FM5_Pan_LFO", "1B5", + "FM6_Pan_LFO", "1B6", + NULL +}; + +const char** DivPlatformYM2610B::getRegisterSheet() { + return regCheatSheetYM2610B; +} + +const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x20: + return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; + break; + case 0x21: + return "21xx: Set SSG noise frequency (0 to 1F)"; + break; + case 0x22: + return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; + break; + case 0x23: + return "23xx: Set SSG envelope period low byte"; + break; + case 0x24: + return "24xx: Set SSG envelope period high byte"; + break; + case 0x25: + return "25xx: SSG envelope slide up"; + break; + case 0x26: + return "26xx: SSG envelope slide down"; + break; + case 0x29: + return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; + break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; + } + return NULL; +} + +double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { + if (ch>8) { // ADPCM-B + return NOTE_ADPCMB(note); + } else if (ch>5) { // PSG + return NOTE_PERIODIC(note); + } + // FM + return NOTE_FNUM_BLOCK(note,11); +} + +double DivPlatformYM2610B::NOTE_ADPCMB(int note) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); + } + return 0; +} + +void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + + ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + fmChan[i]=fme->debug_channel(i); + adpcmAChan[i]=aae->debug_channel(i); + } + + for (size_t h=start; hwrite(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + delay=4; + } + } + + fm->generate(&fmout); + + os[0]=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + + + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=6; i<9; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + } + + for (int i=9; i<15; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + } + + oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + } +} + +void DivPlatformYM2610B::tick(bool sysTick) { + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + + // FM + for (int i=0; i<6; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + 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); + } + } + } + + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); + } else { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,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); + } + } + } + } + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + 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); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } + immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } + chan[i].keyOff=false; + } + } + // ADPCM-B + if (chan[15].furnacePCM) { + chan[15].std.next(); + + if (chan[15].std.vol.had) { + chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; + immWrite(0x1b,chan[15].outVol); + } + + if (chan[15].std.arp.had) { + if (!chan[15].inPorta) { + if (chan[15].std.arp.mode) { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val); + } else { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val); + } + } + chan[15].freqChanged=true; + } else { + if (chan[15].std.arp.mode && chan[15].std.arp.finished) { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].note); + chan[15].freqChanged=true; + } + } + } + if (chan[15].freqChanged) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); + } else { + chan[15].freq=0; + } + immWrite(0x19,chan[15].freq&0xff); + immWrite(0x1a,(chan[15].freq>>8)&0xff); + chan[15].freqChanged=false; + } + + for (int i=16; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<6; i++) { + if (i==2 && extMode) continue; + if (chan[i].freqChanged) { + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + } + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } +} + +int DivPlatformYM2610B::dispatch(DivCommand c) { + if (c.chan>5 && c.chan<9) { + c.chan-=6; + return ay->dispatch(c); + } + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + if (c.chan>14) { // ADPCM-B + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + immWrite(0x1b,chan[c.chan].outVol); + } + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } + } else { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); + chan[c.chan].freqChanged=true; + } + break; + } + if (c.chan>8) { // ADPCM-A + if (skipRegisterWrites) break; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x110+c.chan-9,0); + immWrite(0x118+c.chan-9,0); + immWrite(0x120+c.chan-9,0); + immWrite(0x128+c.chan-9,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x110+c.chan-9,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-9,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-9,(end>>8)&0xff); + immWrite(0x128+c.chan-9,end>>16); + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x100,0x00|(1<<(c.chan-9))); + break; + } + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroInit(ins); + if (c.chan<6) { + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + } + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan>14) { + immWrite(0x10,0x01); // reset + break; + } + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan>14) { + immWrite(0x10,0x01); // reset + break; + } + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + if (c.chan>14) { // ADPCM-B + immWrite(0x1b,chan[c.chan].outVol); + break; + } + if (c.chan>8) { // ADPCM-A + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + if (c.value==0 && c.value2==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); + } + if (c.chan>14) { + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + break; + } + if (c.chan>8) { + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (c.chan>5 || parent->song.linearPitch==2) { // PSG, ADPCM-B + int destFreq=NOTE_OPNB(c.chan,c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); + int newFreq; + bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value; + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value; + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + // check for octave boundary + // what the heck! + if (!chan[c.chan].portaPause) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + iface.sampleBank=sampleBank; + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_FB: { + if (c.chan>5) break; + chan[c.chan].state.fb=c.value&7; + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.chan>5) break; + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan>14) return 255; + if (c.chan>8) return 31; + if (c.chan>5) return 15; + return 127; + break; + case DIV_CMD_PRE_PORTA: + if (c.chan>5) { + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformYM2610B::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>14) { // ADPCM-B + immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + } + if (ch>8) { // ADPCM-A + immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + return; + } + if (ch>5) { // PSG + ay->muteChannel(ch-6,mute); + return; + } + // FM + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); +} + +void DivPlatformYM2610B::forceIns() { + for (int i=0; i<6; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + 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_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + for (int i=9; i<16; i++) { + chan[i].insChanged=true; + } + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); +} + +void* DivPlatformYM2610B::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2610B::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformYM2610B::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2610B::getRegisterPoolSize() { + return 512; +} + +void DivPlatformYM2610B::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformYM2610B::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformYM2610B::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + fm->reset(); + for (int i=0; i<16; i++) { + chan[i]=DivPlatformYM2610B::Channel(); + chan[i].std.setEngine(parent); + } + for (int i=0; i<6; i++) { + chan[i].vol=0x7f; + chan[i].outVol=0x7f; + } + for (int i=6; i<9; i++) { + chan[i].vol=0x0f; + } + for (int i=9; i<15; i++) { + chan[i].vol=0x1f; + } + chan[15].vol=0xff; + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + sampleBank=0; + + delay=0; + + extMode=false; + + // LFO + immWrite(0x22,0x08); + + // PCM volume + immWrite(0x101,0x3f); // A + immWrite(0x1b,0xff); // B + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); +} + +bool DivPlatformYM2610B::isStereo() { + return true; +} + +bool DivPlatformYM2610B::keyOffAffectsArp(int ch) { + return (ch>5); +} + +void DivPlatformYM2610B::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } + ay->notifyInsChange(ins); +} + +void DivPlatformYM2610B::notifyInsDeletion(void* ins) { + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); +} + +int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2610Base::init(p, channels, sugRate, flags); + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + chipClock=8000000; + rate=chipClock/16; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + fm=new ymfm::ym2610b(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); + reset(); + return 16; +} + +void DivPlatformYM2610B::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } + ay->quit(); + delete ay; + delete fm; + DivPlatformYM2610Base::quit(); +} + +DivPlatformYM2610B::~DivPlatformYM2610B() { +} diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h new file mode 100644 index 000000000..0de28f6fd --- /dev/null +++ b/src/engine/platform/ym2608.h @@ -0,0 +1,130 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _YM2608_H +#define _YM2608_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ymfm/ymfm_opn.h" + +#include "ym2610.h" + +class DivPlatformYM2608: public DivPlatformYM2610Base { + protected: + const unsigned short chanOffs[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + + struct Channel { + DivInstrumentFM state; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + int vol, outVol; + int sample; + unsigned char pan; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + portaPauseFreq(0), + note(0), + ins(-1), + psgMode(1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(15), + sample(-1), + pan(3) {} + }; + Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ymfm::ym2608* fm; + ymfm::ym2608::output_data fmout; + unsigned char regPool[512]; + unsigned char lastBusy; + + DivPlatformAY8910* ay; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[512]; + short pendingWrites[512]; + + double NOTE_OPNB(int ch, int note); + double NOTE_ADPCMB(int note); + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void setSkipRegisterWrites(bool val); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2608(); +}; +#endif diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index c16b7312d..934789890 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1259,7 +1259,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_OPN]=new DivSysDef( - "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0, false, + "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0x151, false, {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, {"F1", "F2", "F3", "S1", "S2", "S3"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 0a995e521..53eddc1b2 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -530,6 +530,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_OPN: + w->writeC(5|baseAddr1); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: @@ -949,6 +954,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + case DIV_SYSTEM_OPN: + if (!hasOPN) { + hasOPN=disCont[i].dispatch->chipClock; + willExport[i]=true; + writeDACSamples=true; + } else if (!(hasOPN&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasOPN|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index edde54f8a..9ac2e4bd9 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -355,6 +355,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_OPN: { + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { + copyOfFlags=(flags&0x80000000)|0; + } + if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) { + copyOfFlags=(flags&0x80000000)|1; + } + if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) { + copyOfFlags=(flags&0x80000000)|2; + } + if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) { + copyOfFlags=(flags&0x80000000)|3; + } + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: From a58d49a8318d941a24c65a90b6eef988050523a6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 16:09:23 -0500 Subject: [PATCH 14/70] GUI: more mobile-specific changes --- CMakeLists.txt | 6 ++++++ src/gui/gui.cpp | 19 ++++++++++++++++++- src/gui/gui.h | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b415fb99d..2e9e9c6a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,13 @@ option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendor option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) set(DEPENDENCIES_INCLUDE_DIRS "") + +if (ANDROID) +set(DEPENDENCIES_DEFINES "IS_MOBILE") +else() set(DEPENDENCIES_DEFINES "") +endif() + set(DEPENDENCIES_COMPILE_OPTIONS "") set(DEPENDENCIES_LIBRARIES "") set(DEPENDENCIES_LIBRARY_DIRS "") diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index edcac7fc5..c8e90237e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -59,7 +59,7 @@ extern "C" { #define BACKUP_FUR "/backup.fur" #endif -#ifdef ANDROID +#ifdef IS_MOBILE #define MOBILE_UI_DEFAULT true #else #define MOBILE_UI_DEFAULT false @@ -2453,6 +2453,15 @@ bool FurnaceGUI::loop() { wantCaptureKeyboard=ImGui::GetIO().WantTextInput; + if (wantCaptureKeyboard!=oldWantCaptureKeyboard) { + oldWantCaptureKeyboard=wantCaptureKeyboard; + if (wantCaptureKeyboard) { + SDL_StartTextInput(); + } else { + SDL_StopTextInput(); + } + } + if (wantCaptureKeyboard) { WAKE_UP; } @@ -2775,9 +2784,11 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("settings")) { +#ifndef IS_MOBILE if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { doAction(GUI_ACTION_FULLSCREEN); } +#endif if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { lockLayout=!lockLayout; } @@ -3657,7 +3668,11 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); lockLayout=e->getConfBool("lockLayout",false); +#ifdef IS_MOBILE + fullScreen=true; +#else fullScreen=e->getConfBool("fullScreen",false); +#endif mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT); syncSettings(); @@ -3679,6 +3694,7 @@ bool FurnaceGUI::init() { #endif SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1"); + SDL_SetHint("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH","1"); SDL_Init(SDL_INIT_VIDEO); @@ -3870,6 +3886,7 @@ FurnaceGUI::FurnaceGUI(): displayExporting(false), vgmExportLoop(true), wantCaptureKeyboard(false), + oldWantCaptureKeyboard(false), displayMacroMenu(false), displayNew(false), fullScreen(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 64aa78c36..e02a065c7 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -769,7 +769,7 @@ class FurnaceGUI { String mmlString[17]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, displayMacroMenu; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos; bool willExport[32]; int vgmExportVersion; From 5f17aa0e0329ce6af1a4b8b9d017ab154deb4023 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 19:10:12 -0500 Subject: [PATCH 15/70] documentation mistake --- papers/doc/7-systems/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index fe5af1aba..d87cfd204 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -17,6 +17,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Yamaha YM2612 standalone](ym2612.md) - [Yamaha YM2151 standalone](ym2151.md) - [SegaPCM](segapcm.md) +- [Capcom QSound](qsound.md) - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) From f5ef1ad472b3fb6dff0e929bfe6be59e4cffe3a3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:25:13 -0500 Subject: [PATCH 16/70] more documentation work --- papers/doc/7-systems/ym2203.md | 101 ++++++++++++++++++++++++++++++++ papers/doc/7-systems/ym2608.md | 101 ++++++++++++++++++++++++++++++++ papers/doc/7-systems/ym2610b.md | 6 +- 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 papers/doc/7-systems/ym2203.md create mode 100644 papers/doc/7-systems/ym2608.md diff --git a/papers/doc/7-systems/ym2203.md b/papers/doc/7-systems/ym2203.md new file mode 100644 index 000000000..18f051e8d --- /dev/null +++ b/papers/doc/7-systems/ym2203.md @@ -0,0 +1,101 @@ +# Yamaha YM2203 (OPN) + +a cost-reduced version of the YM2151 (OPM). +it only has 3 FM channels instead of 8 and removes the LFO. + +however it does contain an AY/SSG part which provides 3 channels of square wave with noise and envelope. + +this chip was used in the PC-88/PC-98 series of computers, and in some arcade boards. + +several variants of this chip were released as well, with more features. + +# effects + +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. +- `15xx`: set operator 4 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4). + - `y` is the mutliplier. +- `18xx`: toggle extended channel 3 mode. + - 0 disables it and 1 enables it. + - only in extended channel 3 system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. +- `1Dxx`: set attack of operator 4. +- `20xx`: set SSG channel mode. `xx` may be one of the following: + - `00`: square + - `01`: noise + - `02`: square and noise + - `03`: nothing (apparently) + - `04`: envelope and square + - `05`: envelope and noise + - `06`: envelope and square and noise + - `07`: nothing +- `21xx`: set noise frequency. `xx` is a value between 00 and 1F. +- `22xy`: set envelope mode. + - `x` sets the envelope shape, which may be one of the following: + - `0: \___` decay + - `4: /___` attack once + - `8: \\\\` saw + - `9: \___` decay + - `A: \/\/` inverse obelisco + - `B: \¯¯¯` decay once + - `C: ////` inverse saw + - `D: /¯¯¯` attack + - `E: /\/\` obelisco + - `F: /___` attack once + - if `y` is 1 then the envelope will affect this channel. +- `23xx`: set envelope period low byte. +- `24xx`: set envelope period high byte. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable SSG auto-envelope mode. + - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. + - `x` is the numerator. + - `y` is the denominator. + - if `x` or `y` are 0 this will disable auto-envelope mode. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SSG-EG of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value (0-8). + - values between 0 and 7 set SSG-EG. + - value 8 disables it. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. diff --git a/papers/doc/7-systems/ym2608.md b/papers/doc/7-systems/ym2608.md new file mode 100644 index 000000000..51720d1b0 --- /dev/null +++ b/papers/doc/7-systems/ym2608.md @@ -0,0 +1,101 @@ +# Yamaha YM2608 (OPNA) + +like YM2203, but with twice the FM channels, an ADPCM channel and built-in drums ("rhythm")! + +it was one of the available sound chips for the PC-98 series of computers. + +the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-in drums have been replaced with 6 sample channels. + +# effects + +- `10xy`: set LFO parameters. + - `x` toggles the LFO. + - `y` sets its speed. +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. +- `15xx`: set operator 4 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4). + - `y` is the mutliplier. +- `18xx`: toggle extended channel 3 mode. + - 0 disables it and 1 enables it. + - only in extended channel 3 system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. +- `1Dxx`: set attack of operator 4. +- `20xx`: set SSG channel mode. `xx` may be one of the following: + - `00`: square + - `01`: noise + - `02`: square and noise + - `03`: nothing (apparently) + - `04`: envelope and square + - `05`: envelope and noise + - `06`: envelope and square and noise + - `07`: nothing +- `21xx`: set noise frequency. `xx` is a value between 00 and 1F. +- `22xy`: set envelope mode. + - `x` sets the envelope shape, which may be one of the following: + - `0: \___` decay + - `4: /___` attack once + - `8: \\\\` saw + - `9: \___` decay + - `A: \/\/` inverse obelisco + - `B: \¯¯¯` decay once + - `C: ////` inverse saw + - `D: /¯¯¯` attack + - `E: /\/\` obelisco + - `F: /___` attack once + - if `y` is 1 then the envelope will affect this channel. +- `23xx`: set envelope period low byte. +- `24xx`: set envelope period high byte. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable SSG auto-envelope mode. + - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. + - `x` is the numerator. + - `y` is the denominator. + - if `x` or `y` are 0 this will disable auto-envelope mode. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SSG-EG of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value (0-8). + - values between 0 and 7 set SSG-EG. + - value 8 disables it. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index 17f274559..4133681ff 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -16,9 +16,9 @@ it is backward compatible with the original chip. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - `y` is the mutliplier. -- `18xx`: toggle extended channel 2 mode. +- `18xx`: toggle extended channel 3 mode. - 0 disables it and 1 enables it. - - only in extended channel 2 system. + - only in extended channel 3 system. - `19xx`: set attack of all operators. - `1Axx`: set attack of operator 1. - `1Bxx`: set attack of operator 2. @@ -95,4 +95,4 @@ it is backward compatible with the original chip. - `5Cxx`: set D2R/SR of operator 1. - `5Dxx`: set D2R/SR of operator 2. - `5Exx`: set D2R/SR of operator 3. -- `5Fxx`: set D2R/SR of operator 4. \ No newline at end of file +- `5Fxx`: set D2R/SR of operator 4. From 4e2e542c7a2b935964ecffa33b02a2eee491c0f4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:25:59 -0500 Subject: [PATCH 17/70] OPN: implement muting and remove panning --- src/engine/platform/ym2203.cpp | 121 ++++++++++++++++----------------- src/engine/platform/ym2203.h | 4 +- 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 83233e8f5..722dc51bb 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -51,7 +51,6 @@ const char* regCheatSheetYM2203[]={ "SSG_Control_Env", "00D", // FM (Common) "FM_Test", "021", - "FM_LFOFreq", "022", "ClockA1", "024", "ClockA2", "025", "ClockB", "026", @@ -157,9 +156,6 @@ const char* regCheatSheetYM2203[]={ "FM1_FB_ALG", "0B0", "FM2_FB_ALG", "0B1", "FM3_FB_ALG", "0B2", - "FM1_Pan_LFO", "0B4", - "FM2_Pan_LFO", "0B5", - "FM3_Pan_LFO", "0B6", NULL }; @@ -169,9 +165,6 @@ const char** DivPlatformYM2203::getRegisterSheet() { const char* DivPlatformYM2203::getEffectName(unsigned char effect) { switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; case 0x11: return "11xx: Set feedback (0 to 7)"; break; @@ -313,7 +306,7 @@ void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x1,w.val); regPool[w.addr&0xff]=w.val; writes.pop(); - delay=16; + delay=6; } } @@ -363,10 +356,14 @@ void DivPlatformYM2203::tick(bool sysTick) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + 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); + } } } } @@ -387,11 +384,6 @@ void DivPlatformYM2203::tick(bool sysTick) { } } - if (chan[i].std.panL.had) { - chan[i].pan=chan[i].std.panL.val&3; - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); - } - if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { chan[i].pitch2+=chan[i].std.pitch.val; @@ -429,14 +421,6 @@ void DivPlatformYM2203::tick(bool sysTick) { chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } - if (chan[i].std.fms.had) { - chan[i].state.fms=chan[i].std.fms.val; - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); - } - if (chan[i].std.ams.had) { - chan[i].state.ams=chan[i].std.ams.val; - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); - } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -467,10 +451,14 @@ void DivPlatformYM2203::tick(bool sysTick) { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + 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); + } } } if (m.rs.had) { @@ -574,13 +562,17 @@ int DivPlatformYM2203::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - if (!chan[c.chan].active || chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); - } + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - if (chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } if (chan[c.chan].insChanged) { @@ -594,7 +586,6 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } if (chan[c.chan].insChanged) { rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); - rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); } chan[c.chan].insChanged=false; @@ -631,10 +622,14 @@ int DivPlatformYM2203::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } break; @@ -649,15 +644,6 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } chan[c.chan].ins=c.value; break; - case DIV_CMD_PANNING: { - if (c.value==0 && c.value2==0) { - chan[c.chan].pan=3; - } else { - chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); - } - rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); - break; - } case DIV_CMD_PITCH: { chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; @@ -736,10 +722,6 @@ int DivPlatformYM2203::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; } - case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); - break; - } case DIV_CMD_FM_FB: { if (c.chan>2) break; chan[c.chan].state.fb=c.value&7; @@ -759,10 +741,14 @@ int DivPlatformYM2203::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } break; } @@ -944,8 +930,19 @@ void DivPlatformYM2203::muteChannel(int ch, bool mute) { ay->muteChannel(ch-3,mute); return; } - // FM - rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[ch]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[ch].state.op[j]; + if (isMuted[ch]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[ch].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } } void DivPlatformYM2203::forceIns() { @@ -953,10 +950,14 @@ void DivPlatformYM2203::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + 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_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); @@ -966,7 +967,6 @@ void DivPlatformYM2203::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -1039,9 +1039,6 @@ void DivPlatformYM2203::reset() { extMode=false; - // LFO - immWrite(0x22,0x08); - ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 1a6dad584..ab527eeaa 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -45,7 +45,6 @@ class DivPlatformYM2203: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; - unsigned char pan; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -75,8 +74,7 @@ class DivPlatformYM2203: public DivDispatch { hardReset(false), vol(0), outVol(15), - sample(-1), - pan(3) {} + sample(-1) {} }; Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; From cdf4c55f0f5aaa8d8a6be7fc4625bf635c4f26c3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:39:46 -0500 Subject: [PATCH 18/70] SCC: fix forceIns (finally) fixes #447 --- src/engine/platform/scc.cpp | 13 ++++++++++++- src/engine/platform/scc.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 96ec2dfd2..88132b124 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -105,6 +105,11 @@ void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformSCC::updateWave(int ch) { int dstCh=(!isPlus && ch>=4)?3:ch; + if (ch==3) { + lastUpdated34=3; + } else if (ch==4) { + lastUpdated34=4; + } for (int i=0; i<32; i++) { rWrite(dstCh*32+i,(unsigned char)chan[ch].ws.output[i]-128); } @@ -281,10 +286,15 @@ void DivPlatformSCC::forceIns() { for (int i=0; i<5; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; - if (chan[i].active) { + if (isPlus || i<3) { updateWave(i); } } + if (!isPlus) { + if (lastUpdated34>=3) { + updateWave(lastUpdated34); + } + } } void* DivPlatformSCC::getChanState(int ch) { @@ -318,6 +328,7 @@ void DivPlatformSCC::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } + lastUpdated34=0; } bool DivPlatformSCC::isStereo() { diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h index 58059532f..11d6521c7 100644 --- a/src/engine/platform/scc.h +++ b/src/engine/platform/scc.h @@ -57,6 +57,7 @@ class DivPlatformSCC: public DivDispatch { DivDispatchOscBuffer* oscBuf[5]; bool isMuted[5]; unsigned char writeOscBuf; + int lastUpdated34; scc_core* scc; bool isPlus; From 0e03bf6bec5a1dafcac82db9401ebda0d34f7550 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:49:09 -0500 Subject: [PATCH 19/70] GUI: fix default vScroll for pitch macro --- src/gui/insEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 82b47fb69..cfe544582 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1152,8 +1152,8 @@ void FurnaceGUI::drawMacros(std::vector& macros) { i.macro->vZoom=24; i.macro->vScroll=120-12; } else if (i.macro->name=="pitch") { - i.macro->vZoom=64; - i.macro->vScroll=1024-32; + i.macro->vZoom=128; + i.macro->vScroll=2048-64; } else { i.macro->vZoom=i.max-i.min; i.macro->vScroll=0; From 76adccc0fc5a8cc1f2f254e0cea1a3a237e76653 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 03:01:35 -0500 Subject: [PATCH 20/70] GUI: even more improvements to macro editor --- src/gui/insEdit.cpp | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index cfe544582..567c0f3be 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1192,18 +1192,24 @@ void FurnaceGUI::drawMacros(std::vector& macros) { processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } if (i.macro->open) { - if (ImGui::IsItemHovered() && ctrlWheeling) { - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { - i.macro->vZoom+=wheelY; - if (i.macro->vZoom<1) i.macro->vZoom=1; - if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min; - if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) { - i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + if (ImGui::IsItemHovered()) { + if (ctrlWheeling) { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vZoom<1) i.macro->vZoom=1; + if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min; + if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) { + i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + } else { + macroPointSize+=wheelY; + if (macroPointSize<1) macroPointSize=1; + if (macroPointSize>256) macroPointSize=256; } - } else { - macroPointSize+=wheelY; - if (macroPointSize<1) macroPointSize=1; - if (macroPointSize>256) macroPointSize=256; + } else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; } } @@ -1214,12 +1220,16 @@ void FurnaceGUI::drawMacros(std::vector& macros) { ImS64 contentsV=(i.max-i.min); ImGui::SameLine(0.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); - scrollbarPos.Min.x-=ImGui::GetStyle().ItemSpacing.x; scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; - scrollbarPos.Max.x-=ImGui::GetStyle().ItemSpacing.x; scrollbarPos.Max.y+=i.height*dpiScale; ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; From 613b538fb9d13136a94cb1b744f32c85f7dac053 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 03:29:34 -0500 Subject: [PATCH 21/70] GUI: add setting to bring old macro vSlider back --- src/gui/gui.h | 2 ++ src/gui/insEdit.cpp | 47 ++++++++++++++++++++++++++++---------------- src/gui/settings.cpp | 10 +++++++++- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index e02a065c7..0501e2422 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -890,6 +890,7 @@ class FurnaceGUI { int hiddenSystems; int horizontalDataView; int noMultiSystem; + int oldMacroVSlider; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -974,6 +975,7 @@ class FurnaceGUI { hiddenSystems(0), horizontalDataView(0), noMultiSystem(0), + oldMacroVSlider(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 567c0f3be..0080f1da2 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1077,7 +1077,7 @@ void FurnaceGUI::drawMacros(std::vector& macros) { float loopIndicator[256]; int index=0; - float reservedSpace=ImGui::GetStyle().ScrollbarSize; + float reservedSpace=(settings.oldMacroVSlider)?(20.0f*dpiScale+ImGui::GetStyle().ItemSpacing.x):ImGui::GetStyle().ScrollbarSize; if (ImGui::BeginTable("MacroSpace",2)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); @@ -1215,24 +1215,37 @@ void FurnaceGUI::drawMacros(std::vector& macros) { // slider if (!i.isBitfield) { - ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll; - ImS64 availV=i.macro->vZoom; - ImS64 contentsV=(i.max-i.min); + if (settings.oldMacroVSlider) { + ImGui::SameLine(0.0f); + if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) { + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + } else { + ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll; + ImS64 availV=i.macro->vZoom; + ImS64 contentsV=(i.max-i.min); - ImGui::SameLine(0.0f); - ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); - ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); - scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; - scrollbarPos.Max.y+=i.height*dpiScale; - ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); - if (ImGui::IsItemHovered() && ctrlWheeling) { - i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); - if (i.macro->vScroll<0) i.macro->vScroll=0; - if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; - } + ImGui::SameLine(0.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); + ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); + scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; + scrollbarPos.Max.y+=i.height*dpiScale; + ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } - if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { - i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; + if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { + i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; + } } } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 1c22755a8..e6ba74a29 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1147,6 +1147,11 @@ void FurnaceGUI::drawSettings() { settings.sampleLayout=sampleLayoutB; } + bool oldMacroVSliderB=settings.oldMacroVSlider; + if (ImGui::Checkbox("Use classic macro editor vertical slider",&oldMacroVSliderB)) { + settings.oldMacroVSlider=oldMacroVSliderB; + } + bool roundedWindowsB=settings.roundedWindows; if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) { settings.roundedWindows=roundedWindowsB; @@ -1849,6 +1854,7 @@ void FurnaceGUI::syncSettings() { settings.hiddenSystems=e->getConfInt("hiddenSystems",0); settings.horizontalDataView=e->getConfInt("horizontalDataView",0); settings.noMultiSystem=e->getConfInt("noMultiSystem",0); + settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1920,7 +1926,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.moveWindowTitle,0,1); clampSetting(settings.hiddenSystems,0,1); clampSetting(settings.horizontalDataView,0,1); - clampSetting(settings.noMultiSystem,0,1) + clampSetting(settings.noMultiSystem,0,1); + clampSetting(settings.oldMacroVSlider,0,1); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2034,6 +2041,7 @@ void FurnaceGUI::commitSettings() { e->setConf("initialSys",e->encodeSysDesc(settings.initialSys)); e->setConf("horizontalDataView",settings.horizontalDataView); e->setConf("noMultiSystem",settings.noMultiSystem); + e->setConf("oldMacroVSlider",settings.oldMacroVSlider); // colors for (int i=0; i Date: Wed, 11 May 2022 23:39:36 +0200 Subject: [PATCH 22/70] Add emulation settings for YRW801, TG100 and MU5 sample ROMs. --- src/engine/engine.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++ src/engine/engine.h | 14 +++++++- src/gui/gui.cpp | 27 +++++++++++++++ src/gui/gui.h | 13 ++++++-- src/gui/settings.cpp | 43 ++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 3 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 364b0d135..157aea7fe 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -491,6 +491,77 @@ void DivEngine::notifyWaveChange(int wave) { BUSY_END; } +int DivEngine::loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret) { + ret = NULL; + if (path.empty()) { + return 0; + } + logI("loading ROM %s...",path); + FILE* f=ps_fopen(path.c_str(),"rb"); + if (f==NULL) { + logE("error: %s",strerror(errno)); + lastError=strerror(errno); + return -1; + } + if (fseek(f,0,SEEK_END)<0) { + logE("size error: %s",strerror(errno)); + lastError=fmt::sprintf("on seek: %s",strerror(errno)); + fclose(f); + return -1; + } + ssize_t len=ftell(f); + if (len==(SIZE_MAX>>1)) { + logE("could not get file length: %s",strerror(errno)); + lastError=fmt::sprintf("on pre tell: %s",strerror(errno)); + fclose(f); + return -1; + } + if (len<1) { + if (len==0) { + logE("that file is empty!"); + lastError="file is empty"; + } else { + logE("tell error: %s",strerror(errno)); + lastError=fmt::sprintf("on tell: %s",strerror(errno)); + } + fclose(f); + return -1; + } + if (len!=expectedSize) { + logE("ROM size mismatch, expected: %d bytes, was: %d bytes", expectedSize, len); + lastError=fmt::sprintf("ROM size mismatch, expected: %d bytes, was: %d", expectedSize, len); + return -1; + } + if (fseek(f,0,SEEK_SET)<0) { + logE("size error: %s",strerror(errno)); + lastError=fmt::sprintf("on get size: %s",strerror(errno)); + fclose(f); + return -1; + } + unsigned char* file=new unsigned char[len]; + if (fread(file,1,(size_t)len,f)!=(size_t)len) { + logE("read error: %s",strerror(errno)); + lastError=fmt::sprintf("on read: %s",strerror(errno)); + fclose(f); + delete[] file; + return -1; + } + fclose(f); + ret = file; + return 0; +} + +int DivEngine::loadSampleRoms() { + delete[] yrw801Rom; + delete[] tg100Rom; + delete[] mu5Rom; + int error = 0; + error += loadSampleRom(getConfString("yrw801Path",""), 0x200000, yrw801Rom); + error += loadSampleRom(getConfString("tg100Path",""), 0x200000, tg100Rom); + error += loadSampleRom(getConfString("mu5Path",""), 0x200000, mu5Rom); + return error; +} + void DivEngine::renderSamplesP() { BUSY_BEGIN; renderSamples(); @@ -2662,6 +2733,8 @@ bool DivEngine::init() { loadConf(); + loadSampleRoms(); + // set default system preset if (!hasLoadedSomething) { logD("setting default preset"); @@ -2735,5 +2808,8 @@ bool DivEngine::quit() { active=false; delete[] oscBuf[0]; delete[] oscBuf[1]; + delete[] yrw801Rom; + delete[] tg100Rom; + delete[] mu5Rom; return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index e9a5db2e0..701fc70fd 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -397,6 +397,8 @@ class DivEngine { void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); + int loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret); + bool initAudioBackend(); bool deinitAudioBackend(); @@ -783,6 +785,9 @@ class DivEngine { // get register cheatsheet const char** getRegisterSheet(int sys); + // load sample ROMs + int loadSampleRoms(); + // UNSAFE render samples - only execute when locked void renderSamples(); @@ -853,6 +858,10 @@ class DivEngine { // terminate the engine. bool quit(); + unsigned char* yrw801Rom; + unsigned char* tg100Rom; + unsigned char* mu5Rom; + DivEngine(): output(NULL), exportThread(NULL), @@ -927,7 +936,10 @@ class DivEngine { oscReadPos(0), oscWritePos(0), tickMult(1), - processTime(0) { + processTime(0), + yrw801Rom(NULL), + tg100Rom(NULL), + mu5Rom(NULL) { memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool)); memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool)); memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 700e55d09..17f04a9fd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1501,6 +1501,19 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_YRW801_ROM_OPEN: + case GUI_FILE_TG100_ROM_OPEN: + case GUI_FILE_MU5_ROM_OPEN: + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Load ROM", + {"compatible files", "*.rom *.bin", + "all files", ".*"}, + "compatible files{.rom,.bin},.*", + workingDirRom, + dpiScale + ); + break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; @@ -2983,6 +2996,11 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_LAYOUT: workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_YRW801_ROM_OPEN: + case GUI_FILE_TG100_ROM_OPEN: + case GUI_FILE_MU5_ROM_OPEN: + workingDirRom=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; } if (fileDialog->accepted()) { fileName=fileDialog->getFileName(); @@ -3196,6 +3214,15 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_LAYOUT: exportLayout(copyOfName); break; + case GUI_FILE_YRW801_ROM_OPEN: + settings.yrw801Path=copyOfName; + break; + case GUI_FILE_TG100_ROM_OPEN: + settings.tg100Path=copyOfName; + break; + case GUI_FILE_MU5_ROM_OPEN: + settings.mu5Path=copyOfName; + break; } curFileDialog=GUI_FILE_OPEN; } diff --git a/src/gui/gui.h b/src/gui/gui.h index cfbf90e3f..7bf0095ea 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -257,7 +257,10 @@ enum FurnaceGUIFileDialogs { GUI_FILE_IMPORT_LAYOUT, GUI_FILE_EXPORT_COLORS, GUI_FILE_EXPORT_KEYBINDS, - GUI_FILE_EXPORT_LAYOUT + GUI_FILE_EXPORT_LAYOUT, + GUI_FILE_YRW801_ROM_OPEN, + GUI_FILE_TG100_ROM_OPEN, + GUI_FILE_MU5_ROM_OPEN }; enum FurnaceGUIWarnings { @@ -734,7 +737,7 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirRom; String mmlString[17]; String mmlStringW; @@ -792,6 +795,9 @@ class FurnaceGUI { int saaCore; int nesCore; int fdsCore; + String yrw801Path; + String tg100Path; + String mu5Path; int mainFont; int patFont; int audioRate; @@ -879,6 +885,9 @@ class FurnaceGUI { saaCore(1), nesCore(0), fdsCore(0), + yrw801Path(""), + tg100Path(""), + mu5Path(""), mainFont(0), patFont(0), audioRate(44100), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 87ed1fa1f..e139bfcde 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -887,6 +887,33 @@ void FurnaceGUI::drawSettings() { ImGui::Text("FDS core"); ImGui::SameLine(); ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2); + + ImGui::Separator(); + ImGui::Text("Sample ROMs:"); + + ImGui::Text("OPL4 YRW801 path"); + ImGui::SameLine(); + ImGui::InputText("##YRW801Path",&settings.yrw801Path); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##YRW801Load")) { + openFileDialog(GUI_FILE_YRW801_ROM_OPEN); + } + + ImGui::Text("MultiPCM TG100 path"); + ImGui::SameLine(); + ImGui::InputText("##TG100Path",&settings.tg100Path); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##TG100Load")) { + openFileDialog(GUI_FILE_TG100_ROM_OPEN); + } + + ImGui::Text("MultiPCM MU5 path"); + ImGui::SameLine(); + ImGui::InputText("##MU5Path",&settings.mu5Path); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##MU5Load")) { + openFileDialog(GUI_FILE_MU5_ROM_OPEN); + } } ImGui::EndChild(); ImGui::EndTabItem(); @@ -1789,6 +1816,9 @@ void FurnaceGUI::syncSettings() { settings.saaCore=e->getConfInt("saaCore",1); settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); + settings.yrw801Path=e->getConfString("yrw801Path",""); + settings.tg100Path=e->getConfString("tg100Path",""); + settings.mu5Path=e->getConfString("mu5Path",""); settings.mainFont=e->getConfInt("mainFont",0); settings.patFont=e->getConfInt("patFont",0); settings.mainFontPath=e->getConfString("mainFontPath",""); @@ -1960,6 +1990,10 @@ void FurnaceGUI::syncSettings() { } void FurnaceGUI::commitSettings() { + bool sampleRomsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || + settings.tg100Path!=e->getConfString("tg100Path","") || + settings.mu5Path!=e->getConfString("mu5Path",""); + e->setConf("mainFontSize",settings.mainFontSize); e->setConf("patFontSize",settings.patFontSize); e->setConf("iconSize",settings.iconSize); @@ -1975,6 +2009,9 @@ void FurnaceGUI::commitSettings() { e->setConf("saaCore",settings.saaCore); e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); + e->setConf("yrw801Path",settings.yrw801Path); + e->setConf("tg100Path",settings.tg100Path); + e->setConf("mu5Path",settings.mu5Path); e->setConf("mainFont",settings.mainFont); e->setConf("patFont",settings.patFont); e->setConf("mainFontPath",settings.mainFontPath); @@ -2063,6 +2100,12 @@ void FurnaceGUI::commitSettings() { e->saveConf(); + if (sampleRomsChanged) { + if (e->loadSampleRoms()) { + showError(e->getLastError()); + } + } + if (!e->switchMaster()) { showError("could not initialize audio!"); } From 3c3d75c60536219bb5d58a3e9175691ccbe4b24e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 14:21:58 -0500 Subject: [PATCH 23/70] add SCC documentation --- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/scc.md | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 papers/doc/7-systems/scc.md diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index d87cfd204..fcf2e8860 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -29,6 +29,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Yamaha OPL](opl.md) - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) +- [Konami SCC](scc.md) - [Commodore VIC-20](vic20.md) - [Konami VRC6](vrc6.md) - [Famicom Disk System](fds.md) diff --git a/papers/doc/7-systems/scc.md b/papers/doc/7-systems/scc.md new file mode 100644 index 000000000..fb69a9cb8 --- /dev/null +++ b/papers/doc/7-systems/scc.md @@ -0,0 +1,11 @@ +# Konami SCC/SCC+ + +the Sound Creative Chip (SCC) adds 5 channels of wavetable to your MSX! +it was used in (of course) several Konami games, which had better audio quality due to the extra channels provided by this chip (poor AY since nobody used the envelope for bass). + +the only problem? the waveform of the fourth channel is shared with the fifth one due to not enough memory in the chip! +the SCC+ fixes this issue though (while being compatible with SCC games). + +# effects + +- `10xx`: change wave. From 7d7740d96f9082928572d4f8c13bde6e86a8d0cd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 22:08:25 -0500 Subject: [PATCH 24/70] ... #452 cherry-picking --- papers/doc/7-systems/ym2203.md | 4 ++-- papers/doc/7-systems/ym2608.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/papers/doc/7-systems/ym2203.md b/papers/doc/7-systems/ym2203.md index 18f051e8d..6b0bbf8d9 100644 --- a/papers/doc/7-systems/ym2203.md +++ b/papers/doc/7-systems/ym2203.md @@ -1,11 +1,11 @@ # Yamaha YM2203 (OPN) a cost-reduced version of the YM2151 (OPM). -it only has 3 FM channels instead of 8 and removes the LFO. +it only has 3 FM channels instead of 8 and removes stereo, the LFO and DT2 (coarse detune). however it does contain an AY/SSG part which provides 3 channels of square wave with noise and envelope. -this chip was used in the PC-88/PC-98 series of computers, and in some arcade boards. +this chip was used in the NEC PC-88/PC-98 series of computers, the Fujitsu FM-7AV and in some arcade boards. several variants of this chip were released as well, with more features. diff --git a/papers/doc/7-systems/ym2608.md b/papers/doc/7-systems/ym2608.md index 51720d1b0..736258a38 100644 --- a/papers/doc/7-systems/ym2608.md +++ b/papers/doc/7-systems/ym2608.md @@ -1,8 +1,8 @@ # Yamaha YM2608 (OPNA) -like YM2203, but with twice the FM channels, an ADPCM channel and built-in drums ("rhythm")! +like YM2203, but with twice the FM channels, stereo, an ADPCM channel and built-in drums ("rhythm")! -it was one of the available sound chips for the PC-98 series of computers. +it was one of the available sound chips for the NEC PC-88VA and PC-98 series of computers. the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-in drums have been replaced with 6 sample channels. From 1b4256bb2815b0a0e9b81b3deda6aaf08ad202da Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 22:15:03 -0500 Subject: [PATCH 25/70] spelling change: Rom -> ROM - PLEASE READ @Grauw: please update your MultiPCM code. i know it's just a trivial change I made, but in my opinion "ROM" indicates it's an acronym... --- src/engine/engine.cpp | 33 +++++++++++++++++++++------------ src/engine/engine.h | 16 ++++++++-------- src/gui/gui.cpp | 4 ++-- src/gui/gui.h | 2 +- src/gui/settings.cpp | 6 +++--- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index cc1428e15..99e41313c 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -491,7 +491,7 @@ void DivEngine::notifyWaveChange(int wave) { BUSY_END; } -int DivEngine::loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret) { +int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret) { ret = NULL; if (path.empty()) { return 0; @@ -551,14 +551,23 @@ int DivEngine::loadSampleRom(String path, ssize_t expectedSize, unsigned char*& return 0; } -int DivEngine::loadSampleRoms() { - delete[] yrw801Rom; - delete[] tg100Rom; - delete[] mu5Rom; +int DivEngine::loadSampleROMs() { + if (yrw801ROM!=NULL) { + delete[] yrw801ROM; + yrw801ROM=NULL; + } + if (tg100ROM!=NULL) { + delete[] tg100ROM; + tg100ROM=NULL; + } + if (mu5ROM!=NULL) { + delete[] mu5ROM; + mu5ROM=NULL; + } int error = 0; - error += loadSampleRom(getConfString("yrw801Path",""), 0x200000, yrw801Rom); - error += loadSampleRom(getConfString("tg100Path",""), 0x200000, tg100Rom); - error += loadSampleRom(getConfString("mu5Path",""), 0x200000, mu5Rom); + error += loadSampleROM(getConfString("yrw801Path",""), 0x200000, yrw801ROM); + error += loadSampleROM(getConfString("tg100Path",""), 0x200000, tg100ROM); + error += loadSampleROM(getConfString("mu5Path",""), 0x200000, mu5ROM); return error; } @@ -2779,7 +2788,7 @@ bool DivEngine::init() { loadConf(); - loadSampleRoms(); + loadSampleROMs(); // set default system preset if (!hasLoadedSomething) { @@ -2854,8 +2863,8 @@ bool DivEngine::quit() { active=false; delete[] oscBuf[0]; delete[] oscBuf[1]; - delete[] yrw801Rom; - delete[] tg100Rom; - delete[] mu5Rom; + if (yrw801ROM!=NULL) delete[] yrw801ROM; + if (tg100ROM!=NULL) delete[] tg100ROM; + if (mu5ROM!=NULL) delete[] mu5ROM; return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 3338b4a54..8a95e935f 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -397,7 +397,7 @@ class DivEngine { void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); - int loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret); + int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); bool initAudioBackend(); bool deinitAudioBackend(); @@ -786,7 +786,7 @@ class DivEngine { const char** getRegisterSheet(int sys); // load sample ROMs - int loadSampleRoms(); + int loadSampleROMs(); // UNSAFE render samples - only execute when locked void renderSamples(); @@ -861,9 +861,9 @@ class DivEngine { // terminate the engine. bool quit(); - unsigned char* yrw801Rom; - unsigned char* tg100Rom; - unsigned char* mu5Rom; + unsigned char* yrw801ROM; + unsigned char* tg100ROM; + unsigned char* mu5ROM; DivEngine(): output(NULL), @@ -940,9 +940,9 @@ class DivEngine { oscWritePos(0), tickMult(1), processTime(0), - yrw801Rom(NULL), - tg100Rom(NULL), - mu5Rom(NULL) { + yrw801ROM(NULL), + tg100ROM(NULL), + mu5ROM(NULL) { memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool)); memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool)); memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4d237c1d5..abeb569d4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1516,7 +1516,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { {"compatible files", "*.rom *.bin", "all files", ".*"}, "compatible files{.rom,.bin},.*", - workingDirRom, + workingDirROM, dpiScale ); break; @@ -3040,7 +3040,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_YRW801_ROM_OPEN: case GUI_FILE_TG100_ROM_OPEN: case GUI_FILE_MU5_ROM_OPEN: - workingDirRom=fileDialog->getPath()+DIR_SEPARATOR_STR; + workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; } if (fileDialog->accepted()) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 03a3d7e32..61b2d912f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -768,7 +768,7 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirRom; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM; String mmlString[17]; String mmlStringW; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c38e1dc89..f44c2aa92 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1990,7 +1990,7 @@ void FurnaceGUI::syncSettings() { } void FurnaceGUI::commitSettings() { - bool sampleRomsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || + bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || settings.tg100Path!=e->getConfString("tg100Path","") || settings.mu5Path!=e->getConfString("mu5Path",""); @@ -2100,8 +2100,8 @@ void FurnaceGUI::commitSettings() { e->saveConf(); - if (sampleRomsChanged) { - if (e->loadSampleRoms()) { + if (sampleROMsChanged) { + if (e->loadSampleROMs()) { showError(e->getLastError()); } } From f2db15b2ce6720e054eeca0a7828cbd40662f638 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 02:52:43 -0500 Subject: [PATCH 26/70] add OPNA chip (work in progress) --- CMakeLists.txt | 3 + TODO.md | 4 +- src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/rss.h | 543 ++++++++++++++++++++++++ src/engine/platform/ym2203.cpp | 2 + src/engine/platform/ym2608.cpp | 241 ++++++----- src/engine/platform/ym2608.h | 21 +- src/engine/platform/ym2608Interface.cpp | 42 ++ 8 files changed, 744 insertions(+), 116 deletions(-) create mode 100644 src/engine/platform/sound/rss.h create mode 100644 src/engine/platform/ym2608Interface.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e9e9c6a4..4f912faea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,6 +318,8 @@ src/engine/platform/sound/vrcvi/vrcvi.cpp src/engine/platform/sound/scc/scc.cpp +src/engine/platform/ym2608Interface.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -352,6 +354,7 @@ src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/tx81z.cpp src/engine/platform/ym2203.cpp +src/engine/platform/ym2608.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610b.cpp diff --git a/TODO.md b/TODO.md index fc526b29e..52eae1713 100644 --- a/TODO.md +++ b/TODO.md @@ -5,8 +5,6 @@ - input pad - settings - RF5C68 system -- OPN system -- OPNA system - ZX beeper system - Y8950 system - maybe YMU759 ADPCM channel @@ -27,4 +25,4 @@ - Apply button in settings - find and replace - finish wave synth -- add mono/poly note preview button \ No newline at end of file +- add mono/poly note preview button diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 7595d14bb..46efffc31 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/ym2608.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ym2610b.h" @@ -238,6 +239,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_OPN: dispatch=new DivPlatformYM2203; break; + case DIV_SYSTEM_PC98: + dispatch=new DivPlatformYM2608; + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: diff --git a/src/engine/platform/sound/rss.h b/src/engine/platform/sound/rss.h new file mode 100644 index 000000000..1fcd6ada3 --- /dev/null +++ b/src/engine/platform/sound/rss.h @@ -0,0 +1,543 @@ +/* + This data is derived from the chip's output - internal ROM can't be read. + It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal. +*/ + +const unsigned char YM2608_ADPCM_ROM[0x2000] = { + +/* Source: 01BD.ROM */ +/* Length: 448 / 0x000001C0 */ + +0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54, +0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97, +0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0, +0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1, +0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24, +0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99, +0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99, +0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09, +0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8, +0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39, +0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9, +0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A, +0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40, +0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00, +0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12, +0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18, +0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52, +0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02, +0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B, +0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC, +0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A, +0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98, +0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A, +0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99, +0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99, +0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11, +0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31, +0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + +/* Source: 02SD.ROM */ +/* Length: 640 / 0x00000280 */ + +0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1, +0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41, +0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03, +0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18, +0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02, +0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92, +0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28, +0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62, +0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E, +0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09, +0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB, +0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83, +0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85, +0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21, +0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0, +0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80, +0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21, +0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8, +0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F, +0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A, +0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12, +0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36, +0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80, +0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90, +0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99, +0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03, +0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89, +0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18, +0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27, +0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9, +0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39, +0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23, +0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1, +0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3, +0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9, +0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71, +0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A, +0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88, +0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C, +0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10, + +/* Source: 04TOP.ROM */ +/* Length: 5952 / 0x00001740 */ + +0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C, +0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00, +0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E, +0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C, +0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38, +0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81, +0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A, +0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81, +0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4, +0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8, +0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10, +0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29, +0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98, +0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59, +0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3, +0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28, +0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B, +0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B, +0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11, +0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0, +0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90, +0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00, +0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8, +0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29, +0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F, +0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12, +0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8, +0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4, +0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82, +0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11, +0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09, +0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A, +0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3, +0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85, +0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88, +0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80, +0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C, +0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10, +0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81, +0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81, +0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8, +0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A, +0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79, +0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49, +0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA, +0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99, +0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24, +0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7, +0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8, +0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0, +0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15, +0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18, +0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B, +0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C, +0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B, +0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7, +0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14, +0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02, +0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98, +0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88, +0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01, +0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2, +0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D, +0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19, +0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28, +0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21, +0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A, +0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2, +0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87, +0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11, +0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C, +0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A, +0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0, +0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1, +0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11, +0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90, +0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00, +0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2, +0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3, +0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82, +0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00, +0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B, +0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28, +0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79, +0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90, +0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1, +0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05, +0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10, +0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19, +0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D, +0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5, +0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90, +0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49, +0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C, +0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C, +0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0, +0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3, +0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4, +0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4, +0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9, +0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01, +0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88, +0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12, +0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D, +0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02, +0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09, +0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89, +0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A, +0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2, +0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49, +0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50, +0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80, +0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C, +0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C, +0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13, +0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01, +0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18, +0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C, +0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1, +0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3, +0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B, +0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E, +0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90, +0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28, +0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28, +0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E, +0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91, +0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00, +0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71, +0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20, +0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8, +0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03, +0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7, +0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C, +0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09, +0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12, +0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2, +0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6, +0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02, +0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B, +0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC, +0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0, +0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00, +0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A, +0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2, +0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3, +0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F, +0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A, +0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0, +0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84, +0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10, +0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21, +0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F, +0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B, +0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24, +0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3, +0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00, +0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93, +0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10, +0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30, +0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0, +0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7, +0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1, +0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19, +0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B, +0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99, +0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A, +0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C, +0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03, +0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1, +0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A, +0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19, +0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8, +0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11, +0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A, +0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59, +0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F, +0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C, +0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80, +0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5, +0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08, +0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29, +0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1, +0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28, +0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D, +0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A, +0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D, +0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48, +0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A, +0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3, +0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1, +0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18, +0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C, +0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A, +0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19, +0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91, +0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04, +0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29, +0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10, +0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1, +0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0, +0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84, +0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95, +0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11, +0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B, +0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19, +0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10, +0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1, +0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17, +0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A, +0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08, +0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C, +0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1, +0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2, +0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09, +0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20, +0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A, +0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D, +0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82, +0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1, +0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2, +0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99, +0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18, +0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1, +0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1, +0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01, +0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40, +0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A, +0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C, +0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83, +0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12, +0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09, +0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68, +0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92, +0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A, +0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80, +0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82, +0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80, +0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B, +0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B, +0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18, +0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91, +0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1, +0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6, +0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B, +0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89, +0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90, +0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A, +0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01, +0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98, +0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29, +0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D, +0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92, +0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93, +0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88, +0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79, +0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20, +0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59, +0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9, +0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8, +0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07, +0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13, +0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80, +0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB, +0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A, +0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20, +0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0, +0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20, +0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39, +0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92, +0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1, +0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0, +0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41, +0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC, +0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30, +0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18, +0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B, +0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A, +0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09, +0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10, +0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1, +0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96, +0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09, +0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09, +0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A, +0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B, +0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90, +0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0, +0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1, +0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28, +0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18, +0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF, +0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0, +0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02, +0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08, +0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96, +0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88, +0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28, +0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D, +0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0, +0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83, +0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3, +0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04, +0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2, +0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21, +0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98, +0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8, +0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23, +0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39, +0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08, +0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E, +0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14, +0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4, +0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0, +0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88, +0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0, +0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69, +0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08, +0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13, +0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0, +0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA, +0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81, +0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01, +0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B, +0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B, +0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4, +0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18, +0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01, +0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0, +0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5, +0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94, +0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A, +0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A, +0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88, +0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B, +0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01, +0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12, +0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12, +0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D, +0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02, +0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1, +0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82, +0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1, +0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A, +0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A, +0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F, +0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98, +0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1, +0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3, +0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68, +0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91, +0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A, +0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C, +0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90, +0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6, +0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89, +0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B, +0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA, +0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1, +0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93, +0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93, +0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00, +0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B, +0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2, +0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92, +0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89, +0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48, +0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10, +0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29, +0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91, +0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3, +0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90, + +/* Source: 08HH.ROM */ +/* Length: 384 / 0x00000180 */ + +0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3, +0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7, +0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D, +0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80, +0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B, +0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2, +0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91, +0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D, +0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C, +0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5, +0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B, +0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01, +0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92, +0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93, +0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4, +0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3, +0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08, +0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3, +0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88, +0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92, +0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00, +0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4, +0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4, +0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0, + +/* Source: 10TOM.ROM */ +/* Length: 640 / 0x00000280 */ + +0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2, +0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11, +0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1, +0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32, +0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9, +0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01, +0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14, +0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA, +0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81, +0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00, +0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63, +0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB, +0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34, +0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB, +0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31, +0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99, +0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A, +0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30, +0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02, +0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98, +0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11, +0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11, +0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A, +0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35, +0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11, +0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB, +0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42, +0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99, +0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20, +0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81, +0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31, +0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC, +0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33, +0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB, +0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31, +0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99, +0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99, +0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88, +0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12, +0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00, + +/* Source: 20RIM.ROM */ +/* Length: 128 / 0x00000080 */ + +0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A, +0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2, +0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C, +0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91, +0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59, +0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92, +0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6, +0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80, +}; diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 722dc51bb..33809d17e 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -23,6 +23,7 @@ #include #include +#include "sound/ymfm/ymfm_opn.h" #include "ym2203shared.h" #include "fmshared_OPN.h" @@ -1101,6 +1102,7 @@ int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned in oscBuf[i]=new DivDispatchOscBuffer; } fm=new ymfm::ym2203(iface); + fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); // YM2149, 2MHz ay=new DivPlatformAY8910; ay->init(p,3,sugRate,35); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 3b3d55767..ae32c0ebd 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -20,9 +20,11 @@ #include "ym2608.h" #include "sound/ymfm/ymfm.h" #include "../engine.h" +#include "../../ta-log.h" #include #include +#include "sound/ymfm/ymfm_opn.h" #include "ym2610shared.h" #include "fmshared_OPN.h" @@ -33,7 +35,7 @@ static unsigned char konOffs[6]={ #define CHIP_DIVIDER 32 -const char* regCheatSheetYM2610B[]={ +const char* regCheatSheetYM2608[]={ // SSG "SSG_FreqL_A", "000", "SSG_FreqH_A", "001", @@ -181,30 +183,6 @@ const char* regCheatSheetYM2610B[]={ "ADPCMA_Ch4_Vol", "10B", "ADPCMA_Ch5_Vol", "10C", "ADPCMA_Ch6_Vol", "10D", - "ADPCMA_Ch1_StL", "110", - "ADPCMA_Ch2_StL", "111", - "ADPCMA_Ch3_StL", "112", - "ADPCMA_Ch4_StL", "113", - "ADPCMA_Ch5_StL", "114", - "ADPCMA_Ch6_StL", "115", - "ADPCMA_Ch1_StH", "118", - "ADPCMA_Ch2_StH", "119", - "ADPCMA_Ch3_StH", "11A", - "ADPCMA_Ch4_StH", "11B", - "ADPCMA_Ch5_StH", "11C", - "ADPCMA_Ch6_StH", "11D", - "ADPCMA_Ch1_EdL", "120", - "ADPCMA_Ch2_EdL", "121", - "ADPCMA_Ch3_EdL", "122", - "ADPCMA_Ch4_EdL", "123", - "ADPCMA_Ch5_EdL", "124", - "ADPCMA_Ch6_EdL", "125", - "ADPCMA_Ch1_EdH", "128", - "ADPCMA_Ch2_EdH", "129", - "ADPCMA_Ch3_EdH", "12A", - "ADPCMA_Ch4_EdH", "12B", - "ADPCMA_Ch5_EdH", "12C", - "ADPCMA_Ch6_EdH", "12D", // FM (Channel 4-6) "FM4_Op1_DT_MULT", "130", "FM5_Op1_DT_MULT", "131", @@ -305,11 +283,11 @@ const char* regCheatSheetYM2610B[]={ NULL }; -const char** DivPlatformYM2610B::getRegisterSheet() { - return regCheatSheetYM2610B; +const char** DivPlatformYM2608::getRegisterSheet() { + return regCheatSheetYM2608; } -const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { +const char* DivPlatformYM2608::getEffectName(unsigned char effect) { switch (effect) { case 0x10: return "10xy: Setup LFO (x: enable; y: speed)"; @@ -429,7 +407,7 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { return NULL; } -double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { +double DivPlatformYM2608::NOTE_OPNB(int ch, int note) { if (ch>8) { // ADPCM-B return NOTE_ADPCMB(note); } else if (ch>5) { // PSG @@ -439,7 +417,7 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { return NOTE_FNUM_BLOCK(note,11); } -double DivPlatformYM2610B::NOTE_ADPCMB(int note) { +double DivPlatformYM2608::NOTE_ADPCMB(int note) { if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; return parent->calcBaseFreq((double)chipClock/144,off,note,false); @@ -447,9 +425,10 @@ double DivPlatformYM2610B::NOTE_ADPCMB(int note) { return 0; } -void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + /* ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); @@ -463,6 +442,7 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t fmChan[i]=fme->debug_channel(i); adpcmAChan[i]=aae->debug_channel(i); } + */ for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } @@ -505,10 +485,11 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t } oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + */ } } -void DivPlatformYM2610B::tick(bool sysTick) { +void DivPlatformYM2608::tick(bool sysTick) { // PSG ay->tick(sysTick); ay->flushWrites(); @@ -684,7 +665,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { if (chan[15].std.vol.had) { chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; - immWrite(0x1b,chan[15].outVol); + immWrite(0x10b,chan[15].outVol); } if (chan[15].std.arp.had) { @@ -710,8 +691,8 @@ void DivPlatformYM2610B::tick(bool sysTick) { } else { chan[15].freq=0; } - immWrite(0x19,chan[15].freq&0xff); - immWrite(0x1a,(chan[15].freq>>8)&0xff); + immWrite(0x109,chan[15].freq&0xff); + immWrite(0x10a,(chan[15].freq>>8)&0xff); chan[15].freqChanged=false; } @@ -752,7 +733,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } -int DivPlatformYM2610B::dispatch(DivCommand c) { +int DivPlatformYM2608::dispatch(DivCommand c) { if (c.chan>5 && c.chan<9) { c.chan-=6; return ay->dispatch(c); @@ -771,18 +752,19 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x10b,chan[c.chan].outVol); } chan[c.chan].sample=ins->amiga.initSample; if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); + immWrite(0x102,(s->offB>>5)&0xff); + immWrite(0x103,(s->offB>>13)&0xff); int end=s->offB+s->lengthB-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x104,(end>>5)&0xff); + immWrite(0x105,(end>>13)&0xff); + printf("sample want\n"); + immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -791,11 +773,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; } else { - immWrite(0x10,0x01); // reset - immWrite(0x12,0); - immWrite(0x13,0); - immWrite(0x14,0); - immWrite(0x15,0); + immWrite(0x100,0x01); // reset + immWrite(0x102,0); + immWrite(0x103,0); + immWrite(0x104,0); + immWrite(0x105,0); break; } } else { @@ -803,44 +785,30 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x10,0x01); // reset - immWrite(0x12,0); - immWrite(0x13,0); - immWrite(0x14,0); - immWrite(0x15,0); + immWrite(0x100,0x01); // reset + immWrite(0x102,0); + immWrite(0x103,0); + immWrite(0x104,0); + immWrite(0x105,0); break; } DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); + immWrite(0x102,(s->offB>>5)&0xff); + immWrite(0x103,(s->offB>>13)&0xff); int end=s->offB+s->lengthB-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + immWrite(0x104,(end>>5)&0xff); + immWrite(0x105,(end>>13)&0xff); + immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x100,(s->loopStart>=0)?0x90:0x80); // start/repeat chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); chan[c.chan].freqChanged=true; } break; } - if (c.chan>8) { // ADPCM-A + if (c.chan>8) { // RSS TODO: improve rhythm writing strategy if (skipRegisterWrites) break; - if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x100,0x80|(1<<(c.chan-9))); - immWrite(0x110+c.chan-9,0); - immWrite(0x118+c.chan-9,0); - immWrite(0x120+c.chan-9,0); - immWrite(0x128+c.chan-9,0); - break; - } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x110+c.chan-9,(s->offA>>8)&0xff); - immWrite(0x118+c.chan-9,s->offA>>16); - int end=s->offA+s->lengthA-1; - immWrite(0x120+c.chan-9,(end>>8)&0xff); - immWrite(0x128+c.chan-9,end>>16); - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); - immWrite(0x100,0x00|(1<<(c.chan-9))); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x10,0x00|(1<<(c.chan-9))); break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); @@ -894,11 +862,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } case DIV_CMD_NOTE_OFF: if (c.chan>14) { - immWrite(0x10,0x01); // reset + immWrite(0x100,0x01); // reset break; } if (c.chan>8) { - immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x10,0x80|(1<<(c.chan-9))); break; } chan[c.chan].keyOff=true; @@ -908,11 +876,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; case DIV_CMD_NOTE_OFF_ENV: if (c.chan>14) { - immWrite(0x10,0x01); // reset + immWrite(0x100,0x01); // reset break; } if (c.chan>8) { - immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x10,0x80|(1<<(c.chan-9))); break; } chan[c.chan].keyOff=true; @@ -929,11 +897,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].outVol=c.value; } if (c.chan>14) { // ADPCM-B - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x10b,chan[c.chan].outVol); break; } if (c.chan>8) { // ADPCM-A - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } for (int i=0; i<4; i++) { @@ -964,11 +932,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } if (c.chan>14) { - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); break; } if (c.chan>8) { - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); @@ -1263,13 +1231,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { return 1; } -void DivPlatformYM2610B::muteChannel(int ch, bool mute) { +void DivPlatformYM2608::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (ch>14) { // ADPCM-B - immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + //immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); } if (ch>8) { // ADPCM-A - immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + immWrite(0x18+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); return; } if (ch>5) { // PSG @@ -1280,7 +1248,7 @@ void DivPlatformYM2610B::muteChannel(int ch, bool mute) { rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); } -void DivPlatformYM2610B::forceIns() { +void DivPlatformYM2608::forceIns() { for (int i=0; i<6; i++) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -1316,31 +1284,31 @@ void DivPlatformYM2610B::forceIns() { ay->getRegisterWrites().clear(); } -void* DivPlatformYM2610B::getChanState(int ch) { +void* DivPlatformYM2608::getChanState(int ch) { return &chan[ch]; } -DivDispatchOscBuffer* DivPlatformYM2610B::getOscBuffer(int ch) { +DivDispatchOscBuffer* DivPlatformYM2608::getOscBuffer(int ch) { return oscBuf[ch]; } -unsigned char* DivPlatformYM2610B::getRegisterPool() { +unsigned char* DivPlatformYM2608::getRegisterPool() { return regPool; } -int DivPlatformYM2610B::getRegisterPoolSize() { +int DivPlatformYM2608::getRegisterPoolSize() { return 512; } -void DivPlatformYM2610B::poke(unsigned int addr, unsigned short val) { +void DivPlatformYM2608::poke(unsigned int addr, unsigned short val) { immWrite(addr,val); } -void DivPlatformYM2610B::poke(std::vector& wlist) { +void DivPlatformYM2608::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); } -void DivPlatformYM2610B::reset() { +void DivPlatformYM2608::reset() { while (!writes.empty()) writes.pop(); memset(regPool,0,512); if (dumpWrites) { @@ -1348,7 +1316,7 @@ void DivPlatformYM2610B::reset() { } fm->reset(); for (int i=0; i<16; i++) { - chan[i]=DivPlatformYM2610B::Channel(); + chan[i]=DivPlatformYM2608::Channel(); chan[i].std.setEngine(parent); } for (int i=0; i<6; i++) { @@ -1379,23 +1347,30 @@ void DivPlatformYM2610B::reset() { immWrite(0x22,0x08); // PCM volume - immWrite(0x101,0x3f); // A - immWrite(0x1b,0xff); // B + immWrite(0x11,0x3f); // A + immWrite(0x10b,0xff); // B + + // ADPCM limit + immWrite(0x10d,0xff); + immWrite(0x10c,0xff); + + // enable 6 channel mode + immWrite(0x29,0x80); ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); } -bool DivPlatformYM2610B::isStereo() { +bool DivPlatformYM2608::isStereo() { return true; } -bool DivPlatformYM2610B::keyOffAffectsArp(int ch) { +bool DivPlatformYM2608::keyOffAffectsArp(int ch) { return (ch>5); } -void DivPlatformYM2610B::notifyInsChange(int ins) { +void DivPlatformYM2608::notifyInsChange(int ins) { for (int i=0; i<16; i++) { if (chan[i].ins==ins) { chan[i].insChanged=true; @@ -1404,17 +1379,59 @@ void DivPlatformYM2610B::notifyInsChange(int ins) { ay->notifyInsChange(ins); } -void DivPlatformYM2610B::notifyInsDeletion(void* ins) { +void DivPlatformYM2608::notifyInsDeletion(void* ins) { ay->notifyInsDeletion(ins); } -void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { +void DivPlatformYM2608::setSkipRegisterWrites(bool value) { DivDispatch::setSkipRegisterWrites(value); ay->setSkipRegisterWrites(value); } -int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { - DivPlatformYM2610Base::init(p, channels, sugRate, flags); +const void* DivPlatformYM2608::getSampleMem(int index) { + return index == 0 ? adpcmBMem : NULL; +} + +size_t DivPlatformYM2608::getSampleMemCapacity(int index) { + return index == 0 ? 2097152 : 0; +} + +size_t DivPlatformYM2608::getSampleMemUsage(int index) { + return index == 0 ? adpcmBMemLen : 0; +} + +void DivPlatformYM2608::renderSamples() { + memset(adpcmBMem,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of ADPCM memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos); + logW("out of ADPCM memory for sample %d!",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; +} + +int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + adpcmBMem=new unsigned char[getSampleMemCapacity(0)]; + adpcmBMemLen=0; + iface.adpcmBMem=adpcmBMem; + iface.sampleBank=0; dumpWrites=false; skipRegisterWrites=false; for (int i=0; i<16; i++) { @@ -1422,11 +1439,13 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i oscBuf[i]=new DivDispatchOscBuffer; } chipClock=8000000; - rate=chipClock/16; + fm=new ymfm::ym2608(iface); + fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); + rate=fm->sample_rate(chipClock); + printf("rate: %d\n",rate); for (int i=0; i<16; i++) { oscBuf[i]->rate=rate; } - fm=new ymfm::ym2610b(iface); // YM2149, 2MHz ay=new DivPlatformAY8910; ay->init(p,3,sugRate,35); @@ -1435,15 +1454,15 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i return 16; } -void DivPlatformYM2610B::quit() { +void DivPlatformYM2608::quit() { for (int i=0; i<16; i++) { delete oscBuf[i]; } ay->quit(); delete ay; delete fm; - DivPlatformYM2610Base::quit(); + delete[] adpcmBMem; } -DivPlatformYM2610B::~DivPlatformYM2610B() { +DivPlatformYM2608::~DivPlatformYM2608() { } diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 0de28f6fd..6845f0a3e 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -24,9 +24,18 @@ #include #include "sound/ymfm/ymfm_opn.h" -#include "ym2610.h" +#include "ay.h" -class DivPlatformYM2608: public DivPlatformYM2610Base { +class DivYM2608Interface: public ymfm::ymfm_interface { + public: + unsigned char* adpcmBMem; + int sampleBank; + uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); + void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); + DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {} +}; + +class DivPlatformYM2608: public DivDispatch { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 @@ -88,6 +97,10 @@ class DivPlatformYM2608: public DivPlatformYM2610Base { ymfm::ym2608::output_data fmout; unsigned char regPool[512]; unsigned char lastBusy; + + unsigned char* adpcmBMem; + size_t adpcmBMemLen; + DivYM2608Interface iface; DivPlatformAY8910* ay; unsigned char sampleBank; @@ -123,6 +136,10 @@ class DivPlatformYM2608: public DivPlatformYM2610Base { void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformYM2608(); diff --git a/src/engine/platform/ym2608Interface.cpp b/src/engine/platform/ym2608Interface.cpp new file mode 100644 index 000000000..62cd8899f --- /dev/null +++ b/src/engine/platform/ym2608Interface.cpp @@ -0,0 +1,42 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "sound/ymfm/ymfm.h" +#include "ym2608.h" +#include "../engine.h" + +#include "sound/rss.h" + +uint8_t DivYM2608Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { + switch (type) { + case ymfm::ACCESS_ADPCM_A: + return YM2608_ADPCM_ROM[address&0x1fff]; + case ymfm::ACCESS_ADPCM_B: + if (adpcmBMem==NULL) { + return 0; + } + return adpcmBMem[address&0xffffff]; + default: + return 0; + } + return 0; +} + +void DivYM2608Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { +} From db2ebc49307bcc9762456209594ec2f95540425d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 03:18:14 -0500 Subject: [PATCH 27/70] OPNA: improvements --- src/engine/engine.cpp | 8 +++++--- src/engine/platform/ym2608.cpp | 2 -- src/engine/sysDef.cpp | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 99e41313c..320425c7f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1612,7 +1612,9 @@ int DivEngine::addInstrument(int refChan) { *ins=song.nullInsQSound; } ins->name=fmt::sprintf("Instrument %d",insCount); - ins->type=prefType; + if (prefType!=DIV_INS_NULL) { + ins->type=prefType; + } saveLock.lock(); song.ins.push_back(ins); song.insLen=insCount+1; @@ -2310,9 +2312,9 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { // 1. check which channels are viable for this instrument DivInstrument* insInst=getIns(ins); - if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type) notInViableChannel=true; + if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type && getPreferInsType(finalChan)!=DIV_INS_NULL) notInViableChannel=true; for (int i=0; i=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) { + if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) { if (insInst->type==DIV_INS_OPL) { if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) { isViable[i]=true; diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index ae32c0ebd..31ecba9c3 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -762,7 +762,6 @@ int DivPlatformYM2608::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); - printf("sample want\n"); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { @@ -1442,7 +1441,6 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned in fm=new ymfm::ym2608(iface); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); rate=fm->sample_rate(chipClock); - printf("rate: %d\n",rate); for (int i=0; i<16; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 934789890..3621c77e9 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1274,7 +1274,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler ); sysDefs[DIV_SYSTEM_OPL]=new DivSysDef( From 34d868522bc3a4c68ea2eae03dcf73de0c2688b3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 03:18:29 -0500 Subject: [PATCH 28/70] update Bubble System and SegaPCM docs --- papers/doc/7-systems/bubblesystem.md | 12 +++++------- papers/doc/7-systems/segapcm.md | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/papers/doc/7-systems/bubblesystem.md b/papers/doc/7-systems/bubblesystem.md index 0c363163e..7b14e90ea 100644 --- a/papers/doc/7-systems/bubblesystem.md +++ b/papers/doc/7-systems/bubblesystem.md @@ -1,14 +1,12 @@ # Bubble System WSG -a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System. +a Konami-made 2 channel wavetable sound generator logic used on the Bubble System arcade board, configured with K005289, a 4-bit PROM and DAC. -It's configured with K005289, 4 bit PROM and DAC. +however, the K005289 is just part of the logic used for pitch and wavetable ROM address. +waveform select and volume control are tied with single AY-3-8910 IO for both channels. +another AY-3-8910 IO is used for reading sound hardware status. -Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address. -Waveform select and Volume control are tied with single AY-3-8910 IO for both channels. -Another AY-3-8910 IO is used for reading sound hardware status. - -Furnace emulates this configurations as single system, waveform format is 15 level and 32 width. +Furnace emulates this configuration as single system with 32x16 wavetables. # effects diff --git a/papers/doc/7-systems/segapcm.md b/papers/doc/7-systems/segapcm.md index 1b3ed63fb..364626332 100644 --- a/papers/doc/7-systems/segapcm.md +++ b/papers/doc/7-systems/segapcm.md @@ -4,7 +4,7 @@ yep, that's right! 16 channels of PCM! -a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 24 channels, and later they joined the software mixing gang. +a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 28 channels, and later they joined the software mixing gang. # effects - `20xx`: set PCM frequency. From 4a563a416e901711348752fdd1c75bf03f15f068 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 14:59:36 -0500 Subject: [PATCH 29/70] prepare for Y8950/YMU759 ADPCM --- CMakeLists.txt | 2 +- src/engine/platform/opl.cpp | 96 ++++++++++++++++++++++++++- src/engine/platform/opl.h | 21 +++++- src/engine/platform/oplAInterface.cpp | 38 +++++++++++ 4 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/engine/platform/oplAInterface.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f912faea..dd2624944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,8 +318,8 @@ src/engine/platform/sound/vrcvi/vrcvi.cpp src/engine/platform/sound/scc/scc.cpp +src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp - src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 0933934c8..8fd1ede37 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -19,6 +19,7 @@ #include "opl.h" #include "../engine.h" +#include "../../ta-log.h" #include #include @@ -246,19 +247,48 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) { void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { static short o[2]; static int os[2]; + static ymfm::ymfm_output<2> aOut; for (size_t h=start; h=0) { + adpcmB->write(w.addr-7,(w.val&15)|0x80); + OPL3_WriteReg(&fm,w.addr,w.val&0xc0); + } else { + OPL3_WriteReg(&fm,w.addr,w.val); + } + break; + case 7: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 21: case 22: case 23: + if (adpcmChan>=0) { + adpcmB->write(w.addr-7,w.val); + } else { + OPL3_WriteReg(&fm,w.addr,w.val); + } + break; + default: + OPL3_WriteReg(&fm,w.addr,w.val); + break; + } regPool[w.addr&511]=w.val; writes.pop(); } OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + if (adpcmChan>=0) { + adpcmB->clock(); + aOut.clear(); + adpcmB->output<2>(aOut,0); + + os[0]+=aOut.data[0]; + os[1]+=aOut.data[1]; + } + for (int i=0; i=0) ? adpcmBMem : NULL; +} + +size_t DivPlatformOPL::getSampleMemCapacity(int index) { + return (index==0 && adpcmChan>=0) ? 2097152 : 0; +} + +size_t DivPlatformOPL::getSampleMemUsage(int index) { + return (index==0 && adpcmChan>=0) ? adpcmBMemLen : 0; +} + +void DivPlatformOPL::renderSamples() { + if (adpcmChan<0) return; + memset(adpcmBMem,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of ADPCM memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos); + logW("out of ADPCM memory for sample %d!",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; +} + int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -1437,6 +1515,14 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f } setFlags(flags); + if (adpcmChan>=0) { + adpcmBMem=new unsigned char[getSampleMemCapacity(0)]; + adpcmBMemLen=0; + iface.adpcmBMem=adpcmBMem; + iface.sampleBank=0; + adpcmB=new ymfm::adpcm_b_engine(iface,2); + } + reset(); return totalChans; } @@ -1445,6 +1531,10 @@ void DivPlatformOPL::quit() { for (int i=0; i<18; i++) { delete oscBuf[i]; } + if (adpcmChan>=0) { + delete adpcmB; + delete[] adpcmBMem; + } } DivPlatformOPL::~DivPlatformOPL() { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 64db4d665..c4e0215d7 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -23,6 +23,16 @@ #include "../macroInt.h" #include #include "../../../extern/opl/opl3.h" +#include "sound/ymfm/ymfm_adpcm.h" + +class DivOPLAInterface: public ymfm::ymfm_interface { + public: + unsigned char* adpcmBMem; + int sampleBank; + uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); + void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); + DivOPLAInterface(): adpcmBMem(NULL), sampleBank(0) {} +}; class DivPlatformOPL: public DivDispatch { protected: @@ -73,13 +83,18 @@ class DivPlatformOPL: public DivDispatch { }; std::queue writes; opl3_chip fm; + unsigned char* adpcmBMem; + size_t adpcmBMemLen; + DivOPLAInterface iface; + + ymfm::adpcm_b_engine* adpcmB; const unsigned char** slotsNonDrums; const unsigned char** slotsDrums; const unsigned char** slots; const unsigned short* chanMap; const unsigned char* outChanMap; double chipFreqBase; - int delay, oplType, chans, melodicChans, totalChans; + int delay, oplType, chans, melodicChans, totalChans, adpcmChan; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; @@ -127,6 +142,10 @@ class DivPlatformOPL: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char* getEffectName(unsigned char effect); + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformOPL(); diff --git a/src/engine/platform/oplAInterface.cpp b/src/engine/platform/oplAInterface.cpp new file mode 100644 index 000000000..999fb47d1 --- /dev/null +++ b/src/engine/platform/oplAInterface.cpp @@ -0,0 +1,38 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "sound/ymfm/ymfm.h" +#include "opl.h" +#include "../engine.h" + +uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t address) { + switch (type) { + case ymfm::ACCESS_ADPCM_B: + if (adpcmBMem==NULL) { + return 0; + } + return adpcmBMem[address&0xffffff]; + default: + return 0; + } + return 0; +} + +void DivOPLAInterface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { +} From 7218710268acefe5710339102bf66f06a1951a79 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 16:52:44 -0500 Subject: [PATCH 30/70] add Y8950 system --- src/engine/dispatchContainer.cpp | 8 ++ src/engine/platform/opl.cpp | 184 +++++++++++++++++++++++++++++-- src/engine/platform/opl.h | 10 +- src/engine/sysDef.cpp | 10 +- src/gui/guiConst.cpp | 2 + src/gui/presets.cpp | 26 +++++ 6 files changed, 225 insertions(+), 15 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 46efffc31..e74dd8225 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -273,6 +273,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformOPL; ((DivPlatformOPL*)dispatch)->setOPLType(3,true); break; + case DIV_SYSTEM_Y8950: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(8950,false); + break; + case DIV_SYSTEM_Y8950_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(8950,true); + break; case DIV_SYSTEM_OPZ: dispatch=new DivPlatformTX81Z; break; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 8fd1ede37..97a382a18 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -286,7 +286,8 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ adpcmB->output<2>(aOut,0); os[0]+=aOut.data[0]; - os[1]+=aOut.data[1]; + os[1]+=aOut.data[0]; + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; } for (int i=0; i=0 && chan[adpcmChan].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); + } + return 0; +} + void DivPlatformOPL::tick(bool sysTick) { for (int i=0; i=0) { + if (chan[adpcmChan].furnacePCM) { + chan[adpcmChan].std.next(); + + if (chan[adpcmChan].std.vol.had) { + chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(64,chan[adpcmChan].std.vol.val))/64; + immWrite(18,chan[adpcmChan].outVol); + } + + if (chan[adpcmChan].std.arp.had) { + if (!chan[adpcmChan].inPorta) { + if (chan[adpcmChan].std.arp.mode) { + chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].std.arp.val); + } else { + chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note+(signed char)chan[adpcmChan].std.arp.val); + } + } + chan[adpcmChan].freqChanged=true; + } else { + if (chan[adpcmChan].std.arp.mode && chan[adpcmChan].std.arp.finished) { + chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note); + chan[adpcmChan].freqChanged=true; + } + } + } + if (chan[adpcmChan].freqChanged) { + if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; + chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off); + } else { + chan[adpcmChan].freq=0; + } + immWrite(16,chan[adpcmChan].freq&0xff); + immWrite(17,(chan[adpcmChan].freq>>8)&0xff); + chan[adpcmChan].freqChanged=false; + } + } + for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -655,13 +704,77 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { } int DivPlatformOPL::dispatch(DivCommand c) { - if (c.chan>=totalChans) return 0; + if (c.chan>=totalChans && c.chan!=adpcmChan) return 0; // ineffective in 4-op mode - if (oplType==3 && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) { + if (oplType==3 && c.chan!=adpcmChan && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) { if (chan[c.chan-1].fourOp) return 0; } switch (c.cmd) { case DIV_CMD_NOTE_ON: { + if (c.chan==adpcmChan) { // ADPCM + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + immWrite(18,chan[c.chan].outVol); + } + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(9,(s->offB>>2)&0xff); + immWrite(10,(s->offB>>10)&0xff); + int end=s->offB+s->lengthB-1; + immWrite(11,(end>>2)&0xff); + immWrite(12,(end>>10)&0xff); + immWrite(8,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(7,0x01); // reset + immWrite(9,0); + immWrite(10,0); + immWrite(11,0); + immWrite(12,0); + break; + } + } else { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(7,0x01); // reset + immWrite(9,0); + immWrite(10,0); + immWrite(11,0); + immWrite(12,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(9,(s->offB>>2)&0xff); + immWrite(10,(s->offB>>10)&0xff); + int end=s->offB+s->lengthB-1; + immWrite(11,(end>>2)&0xff); + immWrite(12,(end>>10)&0xff); + immWrite(8,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(7,(s->loopStart>=0)?0x90:0x80); // start/repeat + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); + chan[c.chan].freqChanged=true; + } + break; + } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPL); if (chan[c.chan].insChanged) { @@ -744,11 +857,19 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: + if (c.chan==adpcmChan) { + immWrite(7,0x01); // reset + break; + } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: + if (c.chan==adpcmChan) { + immWrite(7,0x01); // reset + break; + } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -758,7 +879,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].std.release(); break; case DIV_CMD_VOLUME: { - if (pretendYMU) { + if (pretendYMU && c.chan!=adpcmChan) { c.value=pow(((double)c.value/127.0),0.5)*63.0; if (c.value<0) c.value=0; if (c.value>63) c.value=63; @@ -767,6 +888,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } + if (c.chan==adpcmChan) { // ADPCM-B + immWrite(18,chan[c.chan].outVol); + break; + } int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; for (int i=0; ichan[c.chan].baseFreq) { @@ -857,13 +983,22 @@ int DivPlatformOPL::dispatch(DivCommand c) { } break; } + case DIV_CMD_SAMPLE_BANK: + if (adpcmChan<0) break; + sampleBank=c.value; + if (sampleBank>(int)(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + iface.sampleBank=sampleBank; + break; case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value)):(NOTE_FREQUENCY(c.value)); chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; break; } case DIV_CMD_FM_LFO: { + if (c.chan==adpcmChan) break; if (c.value&2) { dvb=c.value&1; } else { @@ -873,6 +1008,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { + if (c.chan==adpcmChan) break; chan[c.chan].state.fb=c.value&7; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (isMuted[c.chan]) { @@ -889,6 +1025,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { + if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value>=ops) break; DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; @@ -900,6 +1037,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { + if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value>=ops) break; DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; @@ -919,6 +1057,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { + if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { for (int i=0; i=0) { + chan[adpcmChan]=DivPlatformOPL::Channel(); + chan[adpcmChan].std.setEngine(parent); + chan[adpcmChan].vol=0xff; + chan[adpcmChan].outVol=0xff; + + adpcmB->reset(); + + // volume + immWrite(18,0xff); + // ADPCM limit + immWrite(20,0xff); + immWrite(19,0xff); + } if (oplType<3) for (int i=0; i=0) ? 2097152 : 0; + return (index==0 && adpcmChan>=0) ? 262144 : 0; } size_t DivPlatformOPL::getSampleMemUsage(int index) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index c4e0215d7..a67d50380 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -40,8 +40,8 @@ class DivPlatformOPL: public DivDispatch { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, note, ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp, hardReset; + int freq, baseFreq, pitch, pitch2, note, ins, sample; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM, inPorta, fourOp, hardReset; int vol, outVol; unsigned char pan; void macroInit(DivInstrument* which) { @@ -57,13 +57,14 @@ class DivPlatformOPL: public DivDispatch { pitch2(0), note(0), ins(-1), + sample(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - furnaceDac(false), + furnacePCM(false), inPorta(false), fourOp(false), hardReset(false), @@ -94,7 +95,7 @@ class DivPlatformOPL: public DivDispatch { const unsigned short* chanMap; const unsigned char* outChanMap; double chipFreqBase; - int delay, oplType, chans, melodicChans, totalChans, adpcmChan; + int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; @@ -112,6 +113,7 @@ class DivPlatformOPL: public DivDispatch { int octave(int freq); int toFreq(int freq); + double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3621c77e9..85e5eb9cc 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1736,7 +1736,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA} + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA}, + {}, + oplEffectHandler, + fmOPLPostEffectHandler ); sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef( @@ -1744,7 +1747,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM"}, {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA} + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA}, + {}, + oplEffectHandler, + fmOPLPostEffectHandler ); sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 4ea2de0c2..88e9c7c26 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -841,6 +841,8 @@ const int availableSystems[]={ DIV_SYSTEM_VRC7, DIV_SYSTEM_OPL, DIV_SYSTEM_OPL_DRUMS, + DIV_SYSTEM_Y8950, + DIV_SYSTEM_Y8950_DRUMS, DIV_SYSTEM_OPL2, DIV_SYSTEM_OPL2_DRUMS, DIV_SYSTEM_OPL3, diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index dd003803a..65ad3a2f3 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -106,6 +106,18 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha Y8950", { + DIV_SYSTEM_Y8950, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha Y8950 (drums mode)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM3812", { DIV_SYSTEM_OPL2, 64, 0, 0, @@ -411,6 +423,20 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-AUDIO", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_Y8950, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-AUDIO (drums mode)", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "MSX + MSX-MUSIC", { DIV_SYSTEM_AY8910, 64, 0, 16, From 315018ff24a0140434ae584cfeba400f3f5a450d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 17:22:00 -0500 Subject: [PATCH 31/70] YMU759: ADPCM --- src/engine/platform/opl.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 97a382a18..5ba5342f6 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -568,6 +568,7 @@ void DivPlatformOPL::tick(bool sysTick) { } } if (chan[adpcmChan].freqChanged) { + printf("freq changed\n"); if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off); @@ -733,7 +734,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(8,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(8,2); immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; @@ -768,10 +769,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(8,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(7,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + immWrite(8,2); + immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + int freq=(65536.0*(double)s->rate)/(double)rate; + immWrite(16,freq&0xff); + immWrite(17,(freq>>8)&0xff); } break; } @@ -947,6 +949,9 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==adpcmChan) { + if (!chan[c.chan].furnacePCM) break; + } chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; From 1ffbfe17d458733773ea141579f9a9a8f3e39b4e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 17:40:05 -0500 Subject: [PATCH 32/70] Y8950/YMU759: ADPCM muting --- src/engine/platform/opl.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 5ba5342f6..fe8fd4397 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -285,9 +285,13 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ aOut.clear(); adpcmB->output<2>(aOut,0); - os[0]+=aOut.data[0]; - os[1]+=aOut.data[0]; - oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; + if (!isMuted[adpcmChan]) { + os[0]+=aOut.data[0]; + os[1]+=aOut.data[0]; + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; + } else { + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0; + } } for (int i=0; i Date: Fri, 13 May 2022 17:41:01 -0500 Subject: [PATCH 33/70] fix loading of pre-0.4 .dmf modules --- src/engine/fileOps.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index e0f207948..99b46319d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -654,7 +654,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } for (int j=0; j0x05) { // current pattern format + if (ds.version>0x08) { // current pattern format for (int k=0; kdata[k][0]=reader.readS(); @@ -741,11 +741,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { unsigned char vol=reader.readC(); unsigned char fx=reader.readC(); unsigned char fxVal=reader.readC(); - pat->data[k][3]=(vol==0x80)?-1:vol; + pat->data[k][3]=(vol==0x80 || vol==0xff)?-1:vol; // effect - pat->data[k][4]=(fx==0x80)?-1:fx; - pat->data[k][5]=(fxVal==0x80)?-1:fxVal; - // instrument wasn't stored back then + pat->data[k][4]=(fx==0x80 || fx==0xff)?-1:fx; + pat->data[k][5]=(fxVal==0x80 || fx==0xff)?-1:fxVal; + // instrument + if (ds.version>0x05) { + pat->data[k][2]=reader.readC(); + if (pat->data[k][2]==0x80 || pat->data[k][2]==0xff) pat->data[k][2]=-1; + } } } } From 9936c66184ba9d391b3982040085e8f59e52e0dc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 17:41:13 -0500 Subject: [PATCH 34/70] OPL: remove printf --- src/engine/platform/opl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fe8fd4397..20b95c9b9 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -572,7 +572,6 @@ void DivPlatformOPL::tick(bool sysTick) { } } if (chan[adpcmChan].freqChanged) { - printf("freq changed\n"); if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off); From be0cbfa1f767d8e844d6075b595040abcff1e514 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 18:03:35 -0500 Subject: [PATCH 35/70] GUI: fix non-spaced MML string parsing fixes #457 --- src/gui/gui.cpp | 80 +++++++++++-------------------------------------- src/gui/gui.h | 2 -- 2 files changed, 18 insertions(+), 64 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index abeb569d4..e424de045 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -194,21 +194,6 @@ void FurnaceGUI::decodeKeyMap(std::map& map, String source) { } } -void FurnaceGUI::encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel) { - target=""; - char buf[32]; - for (int i=0; imacroMax) macro[macroLen]=macroMax; - macroLen++; - buf=0; - } - break; - case '|': - if (macroLoop==-1) { - macroLoop=macroLen; - } - break; - case '/': - if (macroRel==-1) { - macroRel=macroLen; - } - break; - } - if (macroLen>=128) break; - } - if (hasVal && macroLen<128) { - hasVal=false; - macro[macroLen]=buf; - if (macro[macroLen]macroMax) macro[macroLen]=macroMax; - macroLen++; - buf=0; - } -} - void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel) { int buf=0; bool negaBuf=false; @@ -370,11 +308,29 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe } break; case '|': + if (hasVal) { + hasVal=false; + macro[macroLen]=negaBuf?-buf:buf; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + macroLen++; + buf=0; + } if (macroLoop==-1) { macroLoop=macroLen; } break; case '/': + if (hasVal) { + hasVal=false; + macro[macroLen]=negaBuf?-buf:buf; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + macroLen++; + buf=0; + } if (macroRel==-1) { macroRel=macroLen; } diff --git a/src/gui/gui.h b/src/gui/gui.h index 61b2d912f..4017dccaa 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1318,8 +1318,6 @@ class FurnaceGUI { void initSystemPresets(); void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false); - void encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel); - void decodeMMLStr(String& source, unsigned char* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel); void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel); void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false); From 77481f23cb128230c9aea7be03444a1608d36ada Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 18:46:52 -0500 Subject: [PATCH 36/70] prepare to implement sample map --- src/engine/instrument.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 47fc95ddc..18cefd017 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -311,6 +311,32 @@ struct DivInstrumentAmiga { int noteFreq[120]; short noteMap[120]; + /** + * get the sample at specified note. + * @return the sample. + */ + inline short getSample(int note) { + if (useNoteMap) { + if (note<0) note=0; + if (note>119) note=119; + return noteMap[note]; + } + return initSample; + } + + /** + * get the sample frequency at specified note. + * @return the frequency, or -1 if not using note map. + */ + inline int getFreq(int note) { + if (useNoteMap) { + if (note<0) note=0; + if (note>119) note=119; + return noteFreq[note]; + } + return -1; + } + DivInstrumentAmiga(): initSample(0), useNoteMap(false), From bf11e85bff650fcaee45241a3e453b9e935c1231 Mon Sep 17 00:00:00 2001 From: LovelyA72 <29054052+LovelyA72@users.noreply.github.com> Date: Fri, 13 May 2022 21:52:33 -0400 Subject: [PATCH 37/70] Enable loading of common Chinese characters --- src/gui/gui.h | 2 ++ src/gui/settings.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/gui/gui.h b/src/gui/gui.h index 4017dccaa..957ee16ae 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -867,6 +867,7 @@ class FurnaceGUI { int roundedButtons; int roundedMenus; int loadJapanese; + int loadChinese; int fmLayout; int sampleLayout; int waveLayout; @@ -955,6 +956,7 @@ class FurnaceGUI { roundedButtons(1), roundedMenus(0), loadJapanese(0), + loadChinese(0), fmLayout(0), sampleLayout(0), waveLayout(0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f44c2aa92..741705aee 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -978,6 +978,19 @@ void FurnaceGUI::drawSettings() { ); } + bool loadChineseB=settings.loadChinese; + if (ImGui::Checkbox("Display Chinese (Simplified) characters",&loadChineseB)) { + settings.loadChinese=loadChineseB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Only toggle this option if you have enough graphics memory.\n" + "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" + "请在确保你有足够的显存后再启动此设定\n" + "这是一个在ImGui实现动态字体加载之前的临时解决方案" + ); + } + ImGui::Separator(); ImGui::Text("Orders row number format:"); @@ -1855,6 +1868,7 @@ void FurnaceGUI::syncSettings() { settings.roundedButtons=e->getConfInt("roundedButtons",1); settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); + settings.loadChinese=e->getConfInt("loadChinese",0); settings.fmLayout=e->getConfInt("fmLayout",0); settings.sampleLayout=e->getConfInt("sampleLayout",0); settings.waveLayout=e->getConfInt("waveLayout",0); @@ -1931,6 +1945,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedButtons,0,1); clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); + clampSetting(settings.loadChinese,0,1); clampSetting(settings.fmLayout,0,3); clampSetting(settings.susPosition,0,1); clampSetting(settings.effectCursorDir,0,2); @@ -2048,6 +2063,7 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedButtons",settings.roundedButtons); e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); + e->setConf("loadChinese",settings.loadChinese); e->setConf("fmLayout",settings.fmLayout); e->setConf("sampleLayout",settings.sampleLayout); e->setConf("waveLayout",settings.waveLayout); @@ -2638,6 +2654,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { if (settings.loadJapanese) { range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese()); } + if (settings.loadChinese) { + range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon()); + } // I'm terribly sorry range.UsedChars[0x80>>5]=0; From 14e0189993448db643491916db0d11ddc13c63da Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 21:44:34 -0500 Subject: [PATCH 38/70] WonderSwan: fix forceIns() messing with noise --- src/engine/platform/swan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index b089394a7..22c35511d 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -448,6 +448,7 @@ void DivPlatformSwan::muteChannel(int ch, bool mute) { } void DivPlatformSwan::forceIns() { + noise=0; for (int i=0; i<4; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; From d7ec3b13c5db274b7dfde0385311c74d118bf8fd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 22:43:00 -0500 Subject: [PATCH 39/70] ADPCM-B: fixes to compatible mode --- src/engine/platform/ym2608.cpp | 11 +++++++---- src/engine/platform/ym2610.cpp | 7 +++++-- src/engine/platform/ym2610b.cpp | 7 +++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 31ecba9c3..ec41a8c64 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -797,10 +797,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); - immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x100,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x109,freq&0xff); + immWrite(0x10a,(freq>>8)&0xff); } break; } @@ -942,6 +943,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; @@ -1022,6 +1024,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 397f6a8f1..ba29263b6 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -840,8 +840,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x19,freq&0xff); + immWrite(0x1a,(freq>>8)&0xff); } break; } @@ -997,6 +998,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==13 && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; @@ -1077,6 +1079,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { + if (c.chan==13 && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index cd40fe139..b5afce5e2 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -818,8 +818,9 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x19,freq&0xff); + immWrite(0x1a,(freq>>8)&0xff); } break; } @@ -975,6 +976,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; @@ -1055,6 +1057,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; From 07871eacc3967f89d101316097168ceaca04ec4c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 22:55:17 -0500 Subject: [PATCH 40/70] Amiga: linear pitch oopsie --- src/engine/platform/amiga.cpp | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index df742d352..c665b9165 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { @@ -238,7 +238,6 @@ int DivPlatformAmiga::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); - double off=1.0; if (ins->amiga.useWave) { chan[c.chan].useWave=true; chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1; @@ -252,17 +251,9 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } else { chan[c.chan].sample=ins->amiga.initSample; chan[c.chan].useWave=false; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } } if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value)); + chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value)); } if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chan[c.chan].sample=-1; @@ -329,16 +320,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { case DIV_CMD_NOTE_PORTA: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].sample=ins->amiga.initSample; - double off=1.0; - if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } - int destFreq=round(off*NOTE_PERIODIC_NOROUND(c.value2)); + int destFreq=round(NOTE_PERIODIC_NOROUND(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -361,16 +343,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - double off=1.0; - if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } - chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)))); + chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; From 0a0ddfa661f5e7cdfd8f59cf8806621c5827c508 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 23:04:40 -0500 Subject: [PATCH 41/70] partially bring up sample map --- src/engine/platform/amiga.cpp | 4 ++-- src/engine/platform/genesis.cpp | 2 +- src/engine/platform/mmc5.cpp | 2 +- src/engine/platform/nes.cpp | 2 +- src/engine/platform/opl.cpp | 2 +- src/engine/platform/pce.cpp | 2 +- src/engine/platform/qsound.cpp | 2 +- src/engine/platform/segapcm.cpp | 2 +- src/engine/platform/su.cpp | 5 +++-- src/engine/platform/swan.cpp | 2 +- src/engine/platform/vera.cpp | 2 +- src/engine/platform/vrc6.cpp | 2 +- src/engine/platform/x1_010.cpp | 2 +- src/engine/platform/ym2608.cpp | 2 +- src/engine/platform/ym2610.cpp | 2 +- src/engine/platform/ym2610b.cpp | 2 +- 16 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index c665b9165..e93e3419e 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -249,7 +249,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } } } else { - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); chan[c.chan].useWave=false; } if (c.value!=DIV_NOTE_NULL) { @@ -319,7 +319,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { break; case DIV_CMD_NOTE_PORTA: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value2); int destFreq=round(NOTE_PERIODIC_NOROUND(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index b35d09eef..a8d5c5258 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -547,7 +547,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.chan==5 && dacMode) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { // Furnace mode - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 9775e6397..abfdfd571 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -192,7 +192,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { if (c.chan==2) { // PCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index ef3b51f06..427fdb423 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -381,7 +381,7 @@ int DivPlatformNES::dispatch(DivCommand c) { if (c.chan==4) { // PCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 20b95c9b9..949d104b8 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -730,7 +730,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(18,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(9,(s->offB>>2)&0xff); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 9838f987f..775cdc1a4 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -270,7 +270,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { if (chan[c.chan].pcm) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { - chan[c.chan].dacSample=ins->amiga.initSample; + chan[c.chan].dacSample=ins->amiga.getSample(c.value); if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index e9b4e8a48..c07e07141 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -390,7 +390,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value); } diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 346daeae1..a16f0e161 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -162,7 +162,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { - chan[c.chan].pcm.sample=ins->amiga.initSample; + chan[c.chan].pcm.sample=ins->amiga.getSample(c.value); if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { chan[c.chan].pcm.sample=-1; if (dumpWrites) { diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 4af605547..724fb5901 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -186,7 +186,8 @@ void DivPlatformSoundUnit::tick(bool sysTick) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - DivSample* sample=parent->getSample(ins->amiga.initSample); + // TODO: sample map? + DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); if (sample!=NULL) { double off=0.25; if (sample->centerRate<1) { @@ -204,7 +205,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { if (chan[i].keyOn) { if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - DivSample* sample=parent->getSample(ins->amiga.initSample); + DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); if (sample!=NULL) { unsigned int sampleEnd=sample->offSU+sample->samples; if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 22c35511d..dbec54593 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -263,7 +263,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { dacPos=0; dacPeriod=0; if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 65a7452ee..52554f3cc 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -260,7 +260,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { rWriteLo(c.chan,2,chan[c.chan].vol); } else { - chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.initSample; + chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value); if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { chan[16].pcm.sample=-1; } diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 4e8117942..ba3815a62 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -243,7 +243,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { if (chan[c.chan].pcm) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { - chan[c.chan].dacSample=ins->amiga.initSample; + chan[c.chan].dacSample=ins->amiga.getSample(c.value); if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index f1711243c..c161b56b3 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -559,7 +559,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (chan[c.chan].furnacePCM) { chan[c.chan].pcm=true; chan[c.chan].macroInit(ins); - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); chWrite(c.chan,4,(s->offX1_010>>12)&0xff); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index ec41a8c64..a6855550c 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -754,7 +754,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x10b,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x102,(s->offB>>5)&0xff); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index ba29263b6..0797110c7 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -795,7 +795,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x12,(s->offB>>8)&0xff); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index b5afce5e2..c9d29736c 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -773,7 +773,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x12,(s->offB>>8)&0xff); From 895765b68ad99637e25eeeec8bcbc6905119e92d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 00:22:23 -0500 Subject: [PATCH 42/70] and here's your ZX beeper no real hardware yet specs of this dispatch may change at any time to fit a driver --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/zxbeeper.cpp | 332 +++++++++++++++++++++++++++++++ src/engine/platform/zxbeeper.h | 108 ++++++++++ src/engine/sysDef.cpp | 2 +- src/gui/guiConst.cpp | 3 +- src/gui/insEdit.cpp | 6 +- 7 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/zxbeeper.cpp create mode 100644 src/engine/platform/zxbeeper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dd2624944..52cef63c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -374,6 +374,7 @@ src/engine/platform/lynx.cpp src/engine/platform/su.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp +src/engine/platform/zxbeeper.cpp src/engine/platform/bubsyswsg.cpp src/engine/platform/n163.cpp src/engine/platform/pet.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index e74dd8225..d175b2539 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -59,6 +59,7 @@ #include "platform/scc.h" #include "platform/dummy.h" #include "../ta-log.h" +#include "platform/zxbeeper.h" #include "song.h" void DivDispatchContainer::setRates(double gotRate) { @@ -294,6 +295,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_PCSPKR: dispatch=new DivPlatformPCSpeaker; break; + case DIV_SYSTEM_SFX_BEEPER: + dispatch=new DivPlatformZXBeeper; + break; case DIV_SYSTEM_LYNX: dispatch=new DivPlatformLynx; break; diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp new file mode 100644 index 000000000..086ff0051 --- /dev/null +++ b/src/engine/platform/zxbeeper.cpp @@ -0,0 +1,332 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "zxbeeper.h" +#include "../engine.h" +#include + +#define CHIP_FREQBASE 8192*6 + +const char** DivPlatformZXBeeper::getRegisterSheet() { + return NULL; +} + +const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Toggle noise mode"; + break; + case 0x12: + return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)"; + break; + case 0x13: + return "13xx: Set LFO speed"; + break; + case 0x17: + return "17xx: Toggle PCM mode"; + break; + } + return NULL; +} + +void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hchan[curChan].sPosition) { + if (!isMuted[curChan] && chan[curChan].outVol) sOffTimer+=chan[curChan].duty; + } + if (++curChan>=6) curChan=0; + + bufL[h]=o?16384:0; + } +} + +void DivPlatformZXBeeper::tick(bool sysTick) { + for (int i=0; i<6; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&1)*MIN(1,chan[i].std.vol.val)); + } + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; + chan[i].freqChanged=true; + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + if (chan[i].active) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE); + if (chan[i].freq>65535) chan[i].freq=65535; + } + if (chan[i].keyOn) { + //rWrite(16+i*5,0x80); + //chWrite(i,0x04,0x80|chan[i].vol); + } + if (chan[i].keyOff) { + chan[i].freq=0; + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformZXBeeper::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].pcm=false; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].duty=c.value; + break; + case DIV_CMD_SAMPLE_MODE: + chan[c.chan].pcm=c.value; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformZXBeeper::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformZXBeeper::forceIns() { + for (int i=0; i<6; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } +} + +void* DivPlatformZXBeeper::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformZXBeeper::getRegisterPool() { + return regPool; +} + +int DivPlatformZXBeeper::getRegisterPoolSize() { + return 112; +} + +void DivPlatformZXBeeper::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<6; i++) { + chan[i]=DivPlatformZXBeeper::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + lastPan=0xff; + memset(tempL,0,32*sizeof(int)); + memset(tempR,0,32*sizeof(int)); + cycles=0; + curChan=0; + sOffTimer=0; + sampleBank=0; +} + +bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformZXBeeper::notifyWaveChange(int wave) { +} + +void DivPlatformZXBeeper::notifyInsDeletion(void* ins) { + for (int i=0; i<6; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformZXBeeper::setFlags(unsigned int flags) { + if (flags&1) { // technically there is no PAL PC Engine but oh well... + chipClock=COLOR_PAL*4.0/5.0; + } else { + chipClock=COLOR_NTSC; + } + rate=chipClock/4; + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } +} + +void DivPlatformZXBeeper::poke(unsigned int addr, unsigned short val) { + +} + +void DivPlatformZXBeeper::poke(std::vector& wlist) { + +} + +int DivPlatformZXBeeper::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<6; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 6; +} + +void DivPlatformZXBeeper::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } +} + +DivPlatformZXBeeper::~DivPlatformZXBeeper() { +} diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h new file mode 100644 index 000000000..3a8b9ac2c --- /dev/null +++ b/src/engine/platform/zxbeeper.h @@ -0,0 +1,108 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _ZXBEEPER_H +#define _ZXBEEPER_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" + +class DivPlatformZXBeeper: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample, ins; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; + signed char vol, outVol; + unsigned short sPosition; + unsigned char duty; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + dacPeriod(0), + dacRate(0), + dacPos(0), + dacSample(-1), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + noise(false), + pcm(false), + furnaceDac(false), + vol(1), + outVol(1), + sPosition(0), + duty(3) {} + }; + Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; + bool isMuted[6]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char lastPan; + + int cycles, curChan, sOffTimer, delay; + int tempL[32]; + int tempR[32]; + unsigned char sampleBank, lfoMode, lfoSpeed; + unsigned char regPool[128]; + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformZXBeeper(); +}; + +#endif diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 85e5eb9cc..a83e88a73 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1326,7 +1326,7 @@ void DivEngine::registerSystems() { {"Square"}, {"SQ"}, {DIV_CH_PULSE}, - {DIV_INS_STD} + {DIV_INS_BEEPER} ); sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef( diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 88e9c7c26..a3474dce0 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -101,7 +101,7 @@ const char* insTypes[DIV_INS_MAX]={ "Konami SCC/Bubble System WSG", "FM (OPZ)", "POKEY", - "PC Beeper", + "Beeper", "WonderSwan", "Atari Lynx", "VERA", @@ -831,6 +831,7 @@ const int availableSystems[]={ DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_PCSPKR, + DIV_SYSTEM_SFX_BEEPER, DIV_SYSTEM_YMU759, DIV_SYSTEM_DUMMY, DIV_SYSTEM_SOUND_UNIT, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0080f1da2..5cfbd9765 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2874,7 +2874,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_GB) { volMax=0; } - if (ins->type==DIV_INS_PET) { + if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) { volMax=1; } if (ins->type==DIV_INS_FDS) { @@ -2906,6 +2906,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty/Int"; dutyMax=10; } + if (ins->type==DIV_INS_BEEPER) { + dutyLabel="Pulse Width"; + dutyMax=255; + } if (ins->type==DIV_INS_AY8930) { dutyMax=255; } From cb63921747acedfbc64eb6667829c1fa001f4479 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 00:31:14 -0500 Subject: [PATCH 43/70] ZX beeper: set a more audible duty --- TODO.md | 4 +--- src/engine/platform/zxbeeper.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 52eae1713..45d042792 100644 --- a/TODO.md +++ b/TODO.md @@ -5,9 +5,7 @@ - input pad - settings - RF5C68 system -- ZX beeper system -- Y8950 system -- maybe YMU759 ADPCM channel +- ZX beeper system overlay percussion - ADPCM chips - Game Boy envelope macro/sequence - rewrite the system name detection function anyway diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index 3a8b9ac2c..a91b518b0 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -62,7 +62,7 @@ class DivPlatformZXBeeper: public DivDispatch { vol(1), outVol(1), sPosition(0), - duty(3) {} + duty(64) {} }; Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; From c685b60a3cd4464e24aa881bed13a064e94abb0b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 00:36:45 -0500 Subject: [PATCH 44/70] ZX beeper: "register" view --- src/engine/platform/zxbeeper.cpp | 6 ++++-- src/engine/platform/zxbeeper.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp index 086ff0051..145cfe5b5 100644 --- a/src/engine/platform/zxbeeper.cpp +++ b/src/engine/platform/zxbeeper.cpp @@ -250,11 +250,12 @@ DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) { } unsigned char* DivPlatformZXBeeper::getRegisterPool() { - return regPool; + ulaOut=sOffTimer?0x10:0x08; + return &ulaOut; } int DivPlatformZXBeeper::getRegisterPoolSize() { - return 112; + return 1; } void DivPlatformZXBeeper::reset() { @@ -274,6 +275,7 @@ void DivPlatformZXBeeper::reset() { curChan=0; sOffTimer=0; sampleBank=0; + ulaOut=0; } bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) { diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index a91b518b0..4e5feabfc 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -73,7 +73,7 @@ class DivPlatformZXBeeper: public DivDispatch { QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; std::queue writes; - unsigned char lastPan; + unsigned char lastPan, ulaOut; int cycles, curChan, sOffTimer, delay; int tempL[32]; From 031a0fc1339b04744334d9bf13f6d0f36e02f81c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:02:28 -0500 Subject: [PATCH 45/70] OPNA: RSS write optimization --- src/engine/platform/ym2608.cpp | 20 ++++++++++++++++---- src/engine/platform/ym2608.h | 1 + 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index a6855550c..f610467ab 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -696,6 +696,11 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[15].freqChanged=false; } + if (writeRSSOff) { + immWrite(0x10,0x80|writeRSSOff); + writeRSSOff=0; + } + for (int i=16; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -731,6 +736,11 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[i].keyOn=false; } } + + if (writeRSSOn) { + immWrite(0x10,writeRSSOn); + writeRSSOn=0; + } } int DivPlatformYM2608::dispatch(DivCommand c) { @@ -805,10 +815,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) { } break; } - if (c.chan>8) { // RSS TODO: improve rhythm writing strategy + if (c.chan>8) { // RSS if (skipRegisterWrites) break; + writeRSSOn|=(1<<(c.chan-9)); immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); - immWrite(0x10,0x00|(1<<(c.chan-9))); break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); @@ -866,7 +876,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } if (c.chan>8) { - immWrite(0x10,0x80|(1<<(c.chan-9))); + writeRSSOff|=1<<(c.chan-9); break; } chan[c.chan].keyOff=true; @@ -880,7 +890,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } if (c.chan>8) { - immWrite(0x10,0x80|(1<<(c.chan-9))); + writeRSSOff|=1<<(c.chan-9); break; } chan[c.chan].keyOff=true; @@ -1340,6 +1350,8 @@ void DivPlatformYM2608::reset() { lastBusy=60; sampleBank=0; + writeRSSOff=0; + writeRSSOn=0; delay=0; diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 6845f0a3e..5c6736295 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -104,6 +104,7 @@ class DivPlatformYM2608: public DivDispatch { DivPlatformAY8910* ay; unsigned char sampleBank; + unsigned char writeRSSOff, writeRSSOn; int delay; From ae5dcd3746d925afe7a0caa9637a531c44273513 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:05:05 -0500 Subject: [PATCH 46/70] OPNA: proper ADPCM muting --- src/engine/platform/ym2608.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index f610467ab..5e058dab3 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -817,7 +817,9 @@ int DivPlatformYM2608::dispatch(DivCommand c) { } if (c.chan>8) { // RSS if (skipRegisterWrites) break; - writeRSSOn|=(1<<(c.chan-9)); + if (!isMuted[c.chan]) { + writeRSSOn|=(1<<(c.chan-9)); + } immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } @@ -942,7 +944,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } if (c.chan>14) { - immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); break; } if (c.chan>8) { @@ -1246,7 +1248,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { void DivPlatformYM2608::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (ch>14) { // ADPCM-B - //immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + immWrite(0x101,(isMuted[ch]?0:(chan[ch].pan<<6))|2); } if (ch>8) { // ADPCM-A immWrite(0x18+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); From 06e00ab17b661312577be5a2368203c213699a54 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:07:32 -0500 Subject: [PATCH 47/70] OPNA: per-channel oscilloscope --- src/engine/platform/sound/ymfm/ymfm_opn.h | 6 ++++++ src/engine/platform/ym2608.cpp | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 1b47885b9..7fc76dac5 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -546,6 +546,12 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + // get the engine + fm_engine* debug_fm_engine() { return &m_fm; } + ssg_engine* debug_ssg_engine() { return &m_ssg; } + adpcm_a_engine* debug_adpcm_a_engine() { return &m_adpcm_a; } + adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; } + protected: // internal helpers void update_prescale(uint8_t prescale); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 5e058dab3..4f05fc01c 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -428,7 +428,6 @@ double DivPlatformYM2608::NOTE_ADPCMB(int note) { void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - /* ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); @@ -442,7 +441,6 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l fmChan[i]=fme->debug_channel(i); adpcmAChan[i]=aae->debug_channel(i); } - */ for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } @@ -485,7 +482,6 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l } oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); - */ } } From 6288b485fc8b7c7e26d5e5e61925618b9407c9d1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:09:24 -0500 Subject: [PATCH 48/70] oops --- src/engine/platform/ym2608.cpp | 2 +- src/engine/platform/ym2610.cpp | 2 +- src/engine/platform/ym2610b.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 4f05fc01c..236e6a087 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -428,7 +428,7 @@ double DivPlatformYM2608::NOTE_ADPCMB(int note) { void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ym2608::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 0797110c7..ce3e4b7c1 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -470,7 +470,7 @@ double DivPlatformYM2610::NOTE_ADPCMB(int note) { void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index c9d29736c..5a36aaaf4 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -450,7 +450,7 @@ double DivPlatformYM2610B::NOTE_ADPCMB(int note) { void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); From 297ab01d4c236779b281d8be74bac2cf33edd748 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:12:23 -0500 Subject: [PATCH 49/70] OPN: per-channel oscilloscope --- src/engine/platform/sound/ymfm/ymfm_opn.h | 2 ++ src/engine/platform/ym2203.cpp | 31 ++++++----------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 7fc76dac5..74d2b01dd 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -465,6 +465,8 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + fm_engine* debug_fm_engine() { return &m_fm; } + protected: // internal helpers void update_prescale(uint8_t prescale); diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 33809d17e..10fde1f29 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -284,19 +284,12 @@ const char* DivPlatformYM2203::getEffectName(unsigned char effect) { void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os; - /*ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); - ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); - ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); - ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + ymfm::ym2203::fm_engine* fme=fm->debug_fm_engine(); - ymfm::ssg_engine::output_data ssgOut; - - ymfm::fm_channel>* fmChan[6]; - ymfm::adpcm_a_channel* adpcmAChan[6]; - for (int i=0; i<6; i++) { + ymfm::fm_channel>* fmChan[3]; + for (int i=0; i<3; i++) { fmChan[i]=fme->debug_channel(i); - adpcmAChan[i]=aae->debug_channel(i); - }*/ + } for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } - ssge->get_last_out(ssgOut); - for (int i=6; i<9; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + for (int i=3; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]; } - - for (int i=9; i<15; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); - } - - oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); - */ } } From 1ba5ac67661387523bf738fc9d0af8a8f037a5ff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:46:06 -0500 Subject: [PATCH 50/70] GUI: add ability to select entire row when clicking on row number --- TODO.md | 1 - src/gui/cursor.cpp | 49 ++++++++++++++++++++++++++++++++------------- src/gui/gui.cpp | 3 ++- src/gui/gui.h | 8 ++++---- src/gui/pattern.cpp | 15 ++++++++++++-- 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/TODO.md b/TODO.md index 45d042792..34f7d1d7d 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,6 @@ - try to find out why does VSlider not accept keyboard input - finish lock layout - if macros have release, note off should release them -- add ability to select entire row when clicking on row number - store edit/followOrders/followPattern state in config - add ability to select a column by double clicking - add ability to move selection by dragging diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 6bf4e76ff..972beee02 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -21,28 +21,48 @@ #include "actionUtil.h" -void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) { +void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) { + DETERMINE_FIRST_LAST; + if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) { curNibble=false; } - cursor.xCoarse=xCoarse; - cursor.xFine=xFine; - cursor.y=y; - selStart.xCoarse=xCoarse; - selStart.xFine=xFine; - selStart.y=y; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; + + if (fullRow) { + selStart.xCoarse=firstChannel; + selStart.xFine=0; + selEnd.xCoarse=lastChannel-1; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selStart.y=y; + selEnd.y=y; + } else { + cursor.xCoarse=xCoarse; + cursor.xFine=xFine; + cursor.y=y; + selStart.xCoarse=xCoarse; + selStart.xFine=xFine; + selStart.y=y; + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + } selecting=true; + selectingFull=fullRow; e->setMidiBaseChan(cursor.xCoarse); } -void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) { +void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) { if (!selecting) return; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; + if (selectingFull) { + DETERMINE_LAST; + selEnd.xCoarse=lastChannel-1; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.y=y; + } else { + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + } } void FurnaceGUI::finishSelection() { @@ -66,6 +86,7 @@ void FurnaceGUI::finishSelection() { selEnd.xFine^=selStart.xFine; } selecting=false; + selectingFull=false; // boundary check int chanCount=e->getTotalChannelCount(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e424de045..4f5a5e92f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2350,7 +2350,7 @@ bool FurnaceGUI::loop() { } sampleDragActive=false; if (selecting) { - cursor=selEnd; + if (!selectingFull) cursor=selEnd; finishSelection(); demandScrollX=true; if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && @@ -3975,6 +3975,7 @@ FurnaceGUI::FurnaceGUI(): chanOscDocked(false), */ selecting(false), + selectingFull(false), curNibble(false), orderNibble(false), followOrders(true), diff --git a/src/gui/gui.h b/src/gui/gui.h index 957ee16ae..182f04085 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1016,7 +1016,7 @@ class FurnaceGUI { */ SelectionPoint selStart, selEnd, cursor; - bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; + bool selecting, selectingFull, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; @@ -1121,7 +1121,7 @@ class FurnaceGUI { ImVec2 patWindowPos, patWindowSize; // pattern view specific - ImVec2 threeChars, twoChars; + ImVec2 fourChars, threeChars, twoChars; SelectionPoint sel1, sel2; int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; @@ -1262,8 +1262,8 @@ class FurnaceGUI { void commitSettings(); void processDrags(int dragX, int dragY); - void startSelection(int xCoarse, int xFine, int y); - void updateSelection(int xCoarse, int xFine, int y); + void startSelection(int xCoarse, int xFine, int y, bool fullRow=false); + void updateSelection(int xCoarse, int xFine, int y, bool fullRow=false); void finishSelection(); void moveCursor(int x, int y, bool select); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index d284b08fc..ae9bedb2c 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -88,11 +88,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } } // row number + ImGui::PushStyleColor(ImGuiCol_Text,rowIndexColor); + if (settings.patRowsBase==1) { - ImGui::TextColored(rowIndexColor," %.2X ",i); + snprintf(id,31," %.2X ##PR_%d",i,i); } else { - ImGui::TextColored(rowIndexColor,"%3d ",i); + snprintf(id,31,"%3d ##PR_%d",i,i); } + ImGui::Selectable(id,false,ImGuiSelectableFlags_NoPadWithHalfSpacing,fourChars); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { + updateSelection(0,0,i,true); + } + if (ImGui::IsItemClicked()) { + startSelection(0,0,i,true); + } + ImGui::PopStyleColor(); // for each column for (int j=0; j