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 37108452..702cb00e 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 b257e3bb..f93d34bd 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 3fec732b..41c54c57 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 5f477e12..67420f48 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 4d19e7de..bfd1b450 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 c9de6da0..952d9415 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 3dd24e0a..156a67cd 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 8005ee0d..199c6217 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 d1583894..169a3f02 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 c0852ea0..39d38869 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 156a67cd..60ca9448 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 199c6217..b38a1366 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 11be4e96..e9a5db2e 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 41c54c57..12259689 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 bfd1b450..b620eaf7 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 952d9415..b71d9ad9 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 a5436e93..39aecb98 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 702cb00e..cb309d99 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 12259689..c8f46dd2 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 b620eaf7..3dddf82c 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 b71d9ad9..314383f5 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 87a42afe..125425a5 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 4c88794d..d8a37cbb 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 125425a5..96ec2dfd 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 c5664840..436ca689 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 53800a52..f402322b 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 f402322b..0a995e52 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 feec7d96..6a27dc65 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 58aa55b9..65a7452e 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 28a57f86..efb73ec5 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 db0a51a5..b415fb99 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 c04276ea..7595d14b 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 00000000..e18d76d2 --- /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 00000000..498963ab --- /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 00000000..5aec7916 --- /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 436ca689..c16b7312 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 792f16d5..4ea2de0c 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 28a57f86..2e7eb9cf 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 e18d76d2..83233e8f 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 498963ab..1a6dad58 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 00000000..3b3d5576 --- /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 00000000..0de28f6f --- /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 c16b7312..93478989 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 0a995e52..53eddc1b 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 edde54f8..9ac2e4bd 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 b415fb99..2e9e9c6a 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 edcac7fc..c8e90237 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 64aa78c3..e02a065c 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 fe5af1ab..d87cfd20 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 00000000..18f051e8 --- /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 00000000..51720d1b --- /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 17f27455..4133681f 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 83233e8f..722dc51b 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 1a6dad58..ab527eea 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 96ec2dfd..88132b12 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 58059532..11d6521c 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 82b47fb6..cfe54458 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 cfe54458..567c0f3b 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 e02a065c..0501e242 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 567c0f3b..0080f1da 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 1c22755a..e6ba74a2 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 364b0d13..157aea7f 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 e9a5db2e..701fc70f 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 700e55d0..17f04a9f 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 cfbf90e3..7bf0095e 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 87ed1fa1..e139bfcd 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 d87cfd20..fcf2e886 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 00000000..fb69a9cb --- /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 18f051e8..6b0bbf8d 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 51720d1b..736258a3 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 cc1428e1..99e41313 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 3338b4a5..8a95e935 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 4d237c1d..abeb569d 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 03a3d7e3..61b2d912 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 c38e1dc8..f44c2aa9 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 2e9e9c6a..4f912fae 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 fc526b29..52eae171 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 7595d14b..46efffc3 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 00000000..1fcd6ada --- /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 722dc51b..33809d17 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 3b3d5576..ae32c0eb 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 0de28f6f..6845f0a3 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 00000000..62cd8899 --- /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 99e41313..320425c7 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 ae32c0eb..31ecba9c 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 93478989..3621c77e 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 0c363163..7b14e90e 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 1b3ed63f..36462633 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 4f912fae..dd262494 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 0933934c..8fd1ede3 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 64db4d66..c4e0215d 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 00000000..999fb47d --- /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 46efffc3..e74dd822 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 8fd1ede3..97a382a1 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 c4e0215d..a67d5038 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 3621c77e..85e5eb9c 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 4ea2de0c..88e9c7c2 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 dd003803..65ad3a2f 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 97a382a1..5ba5342f 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 5ba5342f..fe8fd439 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 e0f20794..99b46319 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 fe8fd439..20b95c9b 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 abeb569d..e424de04 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 61b2d912..4017dcca 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 47fc95dd..18cefd01 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 4017dcca..957ee16a 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 f44c2aa9..741705ae 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 b089394a..22c35511 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 31ecba9c..ec41a8c6 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 397f6a8f..ba29263b 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 cd40fe13..b5afce5e 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 df742d35..c665b916 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 c665b916..e93e3419 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 b35d09ee..a8d5c525 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 9775e639..abfdfd57 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 ef3b51f0..427fdb42 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 20b95c9b..949d104b 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 9838f987..775cdc1a 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 e9b4e8a4..c07e0714 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 346daeae..a16f0e16 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 4af60554..724fb590 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 22c35511..dbec5459 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 65a7452e..52554f3c 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 4e811794..ba3815a6 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 f1711243..c161b56b 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 ec41a8c6..a6855550 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 ba29263b..0797110c 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 b5afce5e..c9d29736 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 dd262494..52cef63c 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 e74dd822..d175b253 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 00000000..086ff005 --- /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 00000000..3a8b9ac2 --- /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 85e5eb9c..a83e88a7 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 88e9c7c2..a3474dce 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 0080f1da..5cfbd976 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 52eae171..45d04279 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 3a8b9ac2..a91b518b 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 086ff005..145cfe5b 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 a91b518b..4e5feabf 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 a6855550..f610467a 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 6845f0a3..5c673629 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 f610467a..5e058dab 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 1b47885b..7fc76dac 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 5e058dab..4f05fc01 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 4f05fc01..236e6a08 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 0797110c..ce3e4b7c 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 c9d29736..5a36aaaf 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 7fc76dac..74d2b01d 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 33809d17..10fde1f2 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 45d04279..34f7d1d7 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 6bf4e76f..972beee0 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 e424de04..4f5a5e92 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 957ee16a..182f0408 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 d284b08f..ae9bedb2 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