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/CMakeLists.txt b/CMakeLists.txt index a620711c..c353d2c0 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 "") @@ -319,6 +325,8 @@ src/engine/platform/sound/es550x/es5506.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 @@ -352,6 +360,9 @@ 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/ym2203ext.cpp +src/engine/platform/ym2608.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610b.cpp @@ -371,6 +382,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 @@ -441,6 +453,7 @@ src/gui/settings.cpp src/gui/songInfo.cpp src/gui/songNotes.cpp src/gui/stats.cpp +src/gui/subSongs.cpp src/gui/sysConf.cpp src/gui/sysEx.cpp src/gui/util.cpp diff --git a/TODO.md b/TODO.md index d52477aa..234d76a9 100644 --- a/TODO.md +++ b/TODO.md @@ -11,11 +11,7 @@ - input pad - settings - RF5C68 system -- OPN system -- OPNA 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 @@ -26,11 +22,9 @@ - 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 - 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/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index fe5af1ab..fcf2e886 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) @@ -28,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/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/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. 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. diff --git a/papers/doc/7-systems/ym2203.md b/papers/doc/7-systems/ym2203.md new file mode 100644 index 00000000..6b0bbf8d --- /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 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 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. + +# 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..736258a3 --- /dev/null +++ b/papers/doc/7-systems/ym2608.md @@ -0,0 +1,101 @@ +# Yamaha YM2608 (OPNA) + +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 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. + +# 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. diff --git a/papers/format.md b/papers/format.md index e6d35e83..c7672c29 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,11 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 95: Furnace dev95 +- 94: Furnace dev94 +- 93: Furnace dev93 +- 92: Furnace dev92 +- 91: Furnace dev91 - 90: Furnace dev90 - 89: Furnace dev89 - 88: Furnace dev88 @@ -122,26 +127,26 @@ size | description -----|------------------------------------ 4 | "INFO" block ID 4 | reserved - 1 | time base - 1 | speed 1 - 1 | speed 2 - 1 | initial arpeggio time - 4f | ticks per second + 1 | time base (of first song) + 1 | speed 1 (of first song) + 1 | speed 2 (of first song) + 1 | initial arpeggio time (of first song) + 4f | ticks per second (of first song) | - 60 is NTSC | - 50 is PAL - 2 | pattern length + 2 | pattern length (of first song) | - the limit is 256. - 2 | orders length + 2 | orders length (of first song) | - the limit is 256 (>=80) or 127 (<80). - 1 | highlight A - 1 | highlight B + 1 | highlight A (of first song) + 1 | highlight B (of first song) 2 | instrument count | - the limit is 256. 2 | wavetable count | - the limit is 256. 2 | sample count | - the limit is 256. - 4 | pattern count + 4 | pattern count (global) 32 | list of sound chips | - possible soundchips: | - 0x00: end of list @@ -215,6 +220,7 @@ size | description | - 0xb5: tildearrow Sound Unit - 8 channels | - 0xb6: OPN extended - 9 channels | - 0xb7: PC-98 extended - 19 channels + | - 0xb8: YMZ280B - 8 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels @@ -257,20 +263,20 @@ size | description 4?? | pointers to wavetables 4?? | pointers to samples 4?? | pointers to patterns - ??? | orders + ??? | orders (of first song) | - a table of bytes | - size=channels*ordLen | - read orders then channels | - the maximum value of a cell is FF (>=80) or 7F (<80). - ??? | effect columns + ??? | effect columns (of first song) | - size=channels - 1?? | channel hide status + 1?? | channel hide status (of first song) | - size=channels - 1?? | channel collapse status + 1?? | channel collapse status (of first song) | - size=channels - S?? | channel names + S?? | channel names (of first song) | - a list of channelCount C strings - S?? | channel short names + S?? | channel short names (of first song) | - same as above STR | song comment 4f | master volume, 1.0f=100% (>=59) @@ -291,6 +297,55 @@ size | description 1 | pitch macro is linear (>=90) or reserved 1 | pitch slide speed in full linear pitch mode (>=94) or reserved 18 | reserved + --- | **additional subsongs** (>=95) + STR | first subsong name + STR | first subsong comment + 1 | number of additional subsongs + 3 | reserved + 4?? | pointers to subsong data +``` + +# subsong + +from version 95 onwards, Furnace supports storing multiple songs on a single file. +the way it's currently done is really weird, but it provides for some backwards compatibility (previous versions will only load the first subsong which is already defined in the `INFO` block). + +``` +size | description +-----|------------------------------------ + 4 | "SONG" block ID + 4 | reserved + 1 | time base + 1 | speed 1 + 1 | speed 2 + 1 | initial arpeggio time + 4f | ticks per second + | - 60 is NTSC + | - 50 is PAL + 2 | pattern length + | - the limit is 256. + 2 | orders length + | - the limit is 256. + 1 | highlight A + 1 | highlight B + 4 | reserved + STR | subsong name + STR | subsong comment + ??? | orders + | - a table of bytes + | - size=channels*ordLen + | - read orders then channels + | - the maximum value of a cell is FF. + ??? | effect columns + | - size=channels + 1?? | channel hide status + | - size=channels + 1?? | channel collapse status + | - size=channels + S?? | channel names + | - a list of channelCount C strings + S?? | channel short names + | - same as above ``` # instrument @@ -743,6 +798,8 @@ size | description 1 | depth | - 0: ZX Spectrum overlay drum (1-bit) | - 1: 1-bit NES DPCM (1-bit) + | - 2: AICA ADPCM + | - 3: YMZ ADPCM | - 4: QSound ADPCM | - 5: ADPCM-A | - 6: ADPCM-B @@ -769,7 +826,8 @@ size | description 4 | reserved 2 | channel 2 | pattern index - 4 | reserved + 2 | subsong (>=95) or reserved + 2 | reserved ??? | pattern data | - size: rows*(4+effectColumns*2)*2 | - read shorts in this order: diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 4bedf6ab..eebf93de 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -179,6 +179,7 @@ enum DivDispatchCmds { DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp) DIV_CMD_ES5506_ENVELOPE_K1RAMP, // (ramp, slowdown) DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown) + DIV_CMD_ES5506_PAUSE, // (value) DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index d3f61025..89e2ae67 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -29,6 +29,9 @@ #include "platform/c64.h" #include "platform/arcade.h" #include "platform/tx81z.h" +#include "platform/ym2203.h" +#include "platform/ym2203ext.h" +#include "platform/ym2608.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ym2610b.h" @@ -58,6 +61,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) { @@ -235,6 +239,15 @@ 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_OPN_EXT: + dispatch=new DivPlatformYM2203Ext; + break; + case DIV_SYSTEM_PC98: + dispatch=new DivPlatformYM2608; + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: @@ -266,6 +279,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; @@ -279,6 +300,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/engine.cpp b/src/engine/engine.cpp index 286a799b..c435ef11 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -139,18 +139,18 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { int nextRow=0; int effectVal=0; DivPattern* pat[DIV_MAX_CHANS]; - for (int i=0; iordersLen; i++) { for (int j=0; jord[j][i],false); } - for (int j=nextRow; jpatLen; j++) { nextRow=0; for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { nextOrder=i+1; nextRow=effectVal; } @@ -491,6 +491,86 @@ 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() { + 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); + return error; +} + void DivEngine::renderSamplesP() { BUSY_BEGIN; renderSamples(); @@ -616,6 +696,7 @@ void DivEngine::createNew(const int* description) { saveLock.lock(); song.unload(); song=DivSong(); + changeSong(0); if (description!=NULL) { initSongWithDesc(description); } @@ -637,45 +718,55 @@ void DivEngine::swapChannels(int src, int dest) { } for (int i=0; i<256; i++) { - song.orders.ord[dest][i]^=song.orders.ord[src][i]; - song.orders.ord[src][i]^=song.orders.ord[dest][i]; - song.orders.ord[dest][i]^=song.orders.ord[src][i]; + curOrders->ord[dest][i]^=curOrders->ord[src][i]; + curOrders->ord[src][i]^=curOrders->ord[dest][i]; + curOrders->ord[dest][i]^=curOrders->ord[src][i]; - DivPattern* prev=song.pat[src].data[i]; - song.pat[src].data[i]=song.pat[dest].data[i]; - song.pat[dest].data[i]=prev; + DivPattern* prev=curPat[src].data[i]; + curPat[src].data[i]=curPat[dest].data[i]; + curPat[dest].data[i]=prev; } - song.pat[src].effectCols^=song.pat[dest].effectCols; - song.pat[dest].effectCols^=song.pat[src].effectCols; - song.pat[src].effectCols^=song.pat[dest].effectCols; + curPat[src].effectCols^=curPat[dest].effectCols; + curPat[dest].effectCols^=curPat[src].effectCols; + curPat[src].effectCols^=curPat[dest].effectCols; - String prevChanName=song.chanName[src]; - String prevChanShortName=song.chanShortName[src]; - bool prevChanShow=song.chanShow[src]; - bool prevChanCollapse=song.chanCollapse[src]; + String prevChanName=curSubSong->chanName[src]; + String prevChanShortName=curSubSong->chanShortName[src]; + bool prevChanShow=curSubSong->chanShow[src]; + bool prevChanCollapse=curSubSong->chanCollapse[src]; - song.chanName[src]=song.chanName[dest]; - song.chanShortName[src]=song.chanShortName[dest]; - song.chanShow[src]=song.chanShow[dest]; - song.chanCollapse[src]=song.chanCollapse[dest]; - song.chanName[dest]=prevChanName; - song.chanShortName[dest]=prevChanShortName; - song.chanShow[dest]=prevChanShow; - song.chanCollapse[dest]=prevChanCollapse; + curSubSong->chanName[src]=curSubSong->chanName[dest]; + curSubSong->chanShortName[src]=curSubSong->chanShortName[dest]; + curSubSong->chanShow[src]=curSubSong->chanShow[dest]; + curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest]; + curSubSong->chanName[dest]=prevChanName; + curSubSong->chanShortName[dest]=prevChanShortName; + curSubSong->chanShow[dest]=prevChanShow; + curSubSong->chanCollapse[dest]=prevChanCollapse; } void DivEngine::stompChannel(int ch) { logV("stomping channel %d",ch); for (int i=0; i<256; i++) { - song.orders.ord[ch][i]=0; + curOrders->ord[ch][i]=0; } - song.pat[ch].wipePatterns(); - song.pat[ch].effectCols=1; - song.chanName[ch]=""; - song.chanShortName[ch]=""; - song.chanShow[ch]=true; - song.chanCollapse[ch]=false; + curPat[ch].wipePatterns(); + curPat[ch].effectCols=1; + curSubSong->chanName[ch]=""; + curSubSong->chanShortName[ch]=""; + curSubSong->chanShow[ch]=true; + curSubSong->chanCollapse[ch]=false; +} + +void DivEngine::changeSong(size_t songIndex) { + if (songIndex>=song.subsong.size()) return; + curSubSong=song.subsong[songIndex]; + curPat=song.subsong[songIndex]->pat; + curOrders=&song.subsong[songIndex]->orders; + curSubSongIndex=songIndex; + curOrder=0; + curRow=0; } void DivEngine::swapChannelsP(int src, int dest) { @@ -688,6 +779,41 @@ void DivEngine::swapChannelsP(int src, int dest) { BUSY_END; } +void DivEngine::changeSongP(size_t index) { + if (index>=song.subsong.size()) return; + if (index==curSubSongIndex) return; + stop(); + BUSY_BEGIN; + saveLock.lock(); + changeSong(index); + saveLock.unlock(); + BUSY_END; +} + +int DivEngine::addSubSong() { + if (song.subsong.size()>=127) return -1; + BUSY_BEGIN; + saveLock.lock(); + song.subsong.push_back(new DivSubSong); + saveLock.unlock(); + BUSY_END; + return song.subsong.size()-1; +} + +bool DivEngine::removeSubSong(int index) { + if (song.subsong.size()<=1) return false; + stop(); + BUSY_BEGIN; + saveLock.lock(); + song.subsong[index]->clearData(); + delete song.subsong[index]; + song.subsong.erase(song.subsong.begin()+index); + changeSong(0); + saveLock.unlock(); + BUSY_END; + return true; +} + void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { int chanCount=chans; quitDispatch(); @@ -760,6 +886,7 @@ bool DivEngine::addSystem(DivSystem which) { return true; } +// TODO: maybe issue with subsongs? bool DivEngine::removeSystem(int index, bool preserveOrder) { if (song.systemLen<=1) { lastError="cannot remove the last one"; @@ -1245,15 +1372,15 @@ void DivEngine::reset() { } extValue=0; extValuePresent=0; - speed1=song.speed1; - speed2=song.speed2; + speed1=curSubSong->speed1; + speed2=curSubSong->speed2; firstTick=false; nextSpeed=speed1; divider=60; - if (song.customTempo) { - divider=song.hz; + if (curSubSong->customTempo) { + divider=curSubSong->hz; } else { - if (song.pal) { + if (curSubSong->pal) { divider=60; } else { divider=50; @@ -1408,6 +1535,10 @@ int DivEngine::getRow() { return curRow; } +size_t DivEngine::getCurrentSubSong() { + return curSubSongIndex; +} + unsigned char DivEngine::getSpeed1() { return speed1; } @@ -1417,9 +1548,9 @@ unsigned char DivEngine::getSpeed2() { } float DivEngine::getHz() { - if (song.customTempo) { - return song.hz; - } else if (song.pal) { + if (curSubSong->customTempo) { + return curSubSong->hz; + } else if (curSubSong->pal) { return 60.0; } else { return 50.0; @@ -1526,6 +1657,7 @@ void DivEngine::unmuteAll() { } int DivEngine::addInstrument(int refChan) { + if (song.ins.size()>=256) return -1; BUSY_BEGIN; DivInstrument* ins=new DivInstrument; int insCount=(int)song.ins.size(); @@ -1544,7 +1676,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; @@ -1554,6 +1688,10 @@ int DivEngine::addInstrument(int refChan) { } int DivEngine::addInstrumentPtr(DivInstrument* which) { + if (song.ins.size()>=256) { + delete which; + return -1; + } BUSY_BEGIN; saveLock.lock(); song.ins.push_back(which); @@ -1584,10 +1722,10 @@ void DivEngine::delInstrument(int index) { song.insLen=song.ins.size(); for (int i=0; idata[k][2]>index) { - song.pat[i].data[j]->data[k][2]--; + if (curPat[i].data[j]==NULL) continue; + for (int k=0; kpatLen; k++) { + if (curPat[i].data[j]->data[k][2]>index) { + curPat[i].data[j]->data[k][2]--; } } } @@ -1598,6 +1736,7 @@ void DivEngine::delInstrument(int index) { } int DivEngine::addWave() { + if (song.wave.size()>=256) return -1; BUSY_BEGIN; saveLock.lock(); DivWavetable* wave=new DivWavetable; @@ -1610,37 +1749,48 @@ int DivEngine::addWave() { } bool DivEngine::addWaveFromFile(const char* path) { + if (song.wave.size()>=256) { + lastError="too many wavetables!"; + return false; + } FILE* f=ps_fopen(path,"rb"); if (f==NULL) { + lastError=fmt::sprintf("%s",strerror(errno)); return false; } unsigned char* buf; ssize_t len; if (fseek(f,0,SEEK_END)!=0) { fclose(f); + lastError=fmt::sprintf("could not seek to end: %s",strerror(errno)); return false; } len=ftell(f); if (len<0) { fclose(f); + lastError=fmt::sprintf("could not determine file size: %s",strerror(errno)); return false; } if (len==(SIZE_MAX>>1)) { fclose(f); + lastError="file size is invalid!"; return false; } if (len==0) { fclose(f); + lastError="file is empty"; return false; } if (fseek(f,0,SEEK_SET)!=0) { fclose(f); + lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno)); return false; } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { logW("did not read entire wavetable file buffer!"); delete[] buf; + lastError=fmt::sprintf("could not read entire file: %s",strerror(errno)); return false; } fclose(f); @@ -1720,6 +1870,7 @@ bool DivEngine::addWaveFromFile(const char* path) { } catch (EndOfFileException& e) { delete wave; delete[] buf; + lastError="premature end of file"; return false; } @@ -1746,6 +1897,7 @@ void DivEngine::delWave(int index) { } int DivEngine::addSample() { + if (song.sample.size()>=256) return -1; BUSY_BEGIN; saveLock.lock(); DivSample* sample=new DivSample; @@ -1760,6 +1912,10 @@ int DivEngine::addSample() { } int DivEngine::addSampleFromFile(const char* path) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return -1; + } BUSY_BEGIN; warnings=""; @@ -1957,18 +2113,18 @@ void DivEngine::delSample(int index) { void DivEngine::addOrder(bool duplicate, bool where) { unsigned char order[DIV_MAX_CHANS]; - if (song.ordersLen>=0xff) return; + if (curSubSong->ordersLen>=0xff) return; BUSY_BEGIN_SOFT; if (duplicate) { for (int i=0; iord[i][curOrder]; } } else { bool used[256]; for (int i=0; iordersLen; j++) { + used[curOrders->ord[i][j]]=true; } order[i]=0xff; for (int j=0; j<256; j++) { @@ -1982,19 +2138,19 @@ void DivEngine::addOrder(bool duplicate, bool where) { if (where) { // at the end saveLock.lock(); for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; icurOrder; j--) { - song.orders.ord[i][j]=song.orders.ord[i][j-1]; + for (int j=curSubSong->ordersLen; j>curOrder; j--) { + curOrders->ord[i][j]=curOrders->ord[i][j-1]; } - song.orders.ord[i][curOrder+1]=order[i]; + curOrders->ord[i][curOrder+1]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); curOrder++; if (playing && !freelance) { @@ -2006,21 +2162,21 @@ void DivEngine::addOrder(bool duplicate, bool where) { void DivEngine::deepCloneOrder(bool where) { unsigned char order[DIV_MAX_CHANS]; - if (song.ordersLen>=0xff) return; + if (curSubSong->ordersLen>=0xff) return; warnings=""; BUSY_BEGIN_SOFT; for (int i=0; iord[i][curOrder]; // find free slot for (int j=0; j<256; j++) { logD("finding free slot in %d...",j); - if (song.pat[i].data[j]==NULL) { + if (curPat[i].data[j]==NULL) { int origOrd=order[i]; order[i]=j; - DivPattern* oldPat=song.pat[i].getPattern(origOrd,false); - DivPattern* pat=song.pat[i].getPattern(j,true); + DivPattern* oldPat=curPat[i].getPattern(origOrd,false); + DivPattern* pat=curPat[i].getPattern(j,true); memcpy(pat->data,oldPat->data,256*32*sizeof(short)); logD("found at %d",j); didNotFind=false; @@ -2034,19 +2190,19 @@ void DivEngine::deepCloneOrder(bool where) { if (where) { // at the end saveLock.lock(); for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; icurOrder; j--) { - song.orders.ord[i][j]=song.orders.ord[i][j-1]; + for (int j=curSubSong->ordersLen; j>curOrder; j--) { + curOrders->ord[i][j]=curOrders->ord[i][j-1]; } - song.orders.ord[i][curOrder+1]=order[i]; + curOrders->ord[i][curOrder+1]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); curOrder++; if (playing && !freelance) { @@ -2057,17 +2213,17 @@ void DivEngine::deepCloneOrder(bool where) { } void DivEngine::deleteOrder() { - if (song.ordersLen<=1) return; + if (curSubSong->ordersLen<=1) return; BUSY_BEGIN_SOFT; saveLock.lock(); for (int i=0; iordersLen; j++) { + curOrders->ord[i][j]=curOrders->ord[i][j+1]; } } - song.ordersLen--; + curSubSong->ordersLen--; saveLock.unlock(); - if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1; + if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1; if (playing && !freelance) { playSub(false); } @@ -2082,9 +2238,9 @@ void DivEngine::moveOrderUp() { } saveLock.lock(); for (int i=0; iord[i][curOrder]^=curOrders->ord[i][curOrder-1]; + curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder]; + curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1]; } saveLock.unlock(); curOrder--; @@ -2096,15 +2252,15 @@ void DivEngine::moveOrderUp() { void DivEngine::moveOrderDown() { BUSY_BEGIN_SOFT; - if (curOrder>=song.ordersLen-1) { + if (curOrder>=curSubSong->ordersLen-1) { BUSY_END; return; } saveLock.lock(); for (int i=0; iord[i][curOrder]^=curOrders->ord[i][curOrder+1]; + curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder]; + curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1]; } saveLock.unlock(); curOrder++; @@ -2117,12 +2273,12 @@ void DivEngine::moveOrderDown() { void DivEngine::exchangeIns(int one, int two) { for (int i=0; idata[k][2]==one) { - song.pat[i].data[j]->data[k][2]=two; - } else if (song.pat[i].data[j]->data[k][2]==two) { - song.pat[i].data[j]->data[k][2]=one; + if (curPat[i].data[j]==NULL) continue; + for (int k=0; kpatLen; k++) { + if (curPat[i].data[j]->data[k][2]==one) { + curPat[i].data[j]->data[k][2]=two; + } else if (curPat[i].data[j]->data[k][2]==two) { + curPat[i].data[j]->data[k][2]=one; } } } @@ -2244,9 +2400,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; @@ -2326,7 +2482,7 @@ void DivEngine::autoNoteOffAll() { void DivEngine::setOrder(unsigned char order) { BUSY_BEGIN_SOFT; curOrder=order; - if (order>=song.ordersLen) curOrder=0; + if (order>=curSubSong->ordersLen) curOrder=0; if (playing && !freelance) { playSub(false); } @@ -2349,15 +2505,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { void DivEngine::setSongRate(float hz, bool pal) { BUSY_BEGIN; saveLock.lock(); - song.pal=!pal; - song.hz=hz; + curSubSong->pal=!pal; + curSubSong->hz=hz; // what? - song.customTempo=true; + curSubSong->customTempo=true; divider=60; - if (song.customTempo) { - divider=song.hz; + if (curSubSong->customTempo) { + divider=curSubSong->hz; } else { - if (song.pal) { + if (curSubSong->pal) { divider=60; } else { divider=50; @@ -2722,6 +2878,8 @@ bool DivEngine::init() { loadConf(); + loadSampleROMs(); + // set default system preset if (!hasLoadedSomething) { logD("setting default preset"); @@ -2795,5 +2953,9 @@ bool DivEngine::quit() { active=false; delete[] oscBuf[0]; delete[] oscBuf[1]; + if (yrw801ROM!=NULL) delete[] yrw801ROM; + if (tg100ROM!=NULL) delete[] tg100ROM; + if (mu5ROM!=NULL) delete[] mu5ROM; + song.unload(); return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 1ef501f6..dc8ed044 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 "dev94" -#define DIV_ENGINE_VERSION 94 +#define DIV_VERSION "dev95" +#define DIV_ENGINE_VERSION 95 // for imports #define DIV_VERSION_MOD 0xff01 @@ -188,6 +188,7 @@ typedef std::function EffectProcess; struct DivSysDef { const char* name; const char* nameJ; + const char* description; unsigned char id; unsigned char id_DMF; int channels; @@ -203,7 +204,7 @@ struct DivSysDef { EffectProcess postEffectFunc; DivSysDef( const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans, - bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, + bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, const char* desc, std::initializer_list chNames, std::initializer_list chShortNames, std::initializer_list chTypes, @@ -213,6 +214,7 @@ struct DivSysDef { EffectProcess postFxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}): name(sysName), nameJ(sysNameJ), + description(desc), id(fileID), id_DMF(fileID_DMF), channels(chans), @@ -302,6 +304,7 @@ class DivEngine { bool hasLoadedSomething; int softLockCount; int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed; + size_t curSubSongIndex; double divider; int cycles; double clockDrift; @@ -399,6 +402,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(); @@ -409,8 +414,14 @@ class DivEngine { void swapChannels(int src, int dest); void stompChannel(int ch); + // change song (UNSAFE) + void changeSong(size_t songIndex); + public: DivSong song; + DivOrders* curOrders; + DivChannelData* curPat; + DivSubSong* curSubSong; DivInstrument* tempIns; DivSystem sysOfChan[DIV_MAX_CHANS]; int dispatchOfChan[DIV_MAX_CHANS]; @@ -601,6 +612,9 @@ class DivEngine { // get current row int getRow(); + // get current subsong + size_t getCurrentSubSong(); + // get speed 1 unsigned char getSpeed1(); @@ -785,6 +799,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(); @@ -794,6 +811,15 @@ class DivEngine { // public swap channels void swapChannelsP(int src, int dest); + // public change song + void changeSongP(size_t index); + + // add subsong + int addSubSong(); + + // remove subsong + bool removeSubSong(int index); + // change system void changeSystem(int index, DivSystem which, bool preserveOrder=true); @@ -858,6 +884,10 @@ class DivEngine { // terminate the engine. bool quit(); + unsigned char* yrw801ROM; + unsigned char* tg100ROM; + unsigned char* mu5ROM; + DivEngine(): output(NULL), exportThread(NULL), @@ -891,6 +921,7 @@ class DivEngine { curOrder(0), remainingLoops(-1), nextSpeed(3), + curSubSongIndex(0), divider(60), cycles(0), clockDrift(0), @@ -926,13 +957,18 @@ class DivEngine { metroAmp(0.0f), metroVol(1.0f), totalProcessed(0), + curOrders(NULL), + curPat(NULL), tempIns(NULL), oscBuf{NULL,NULL}, oscSize(1), 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)); @@ -947,6 +983,8 @@ class DivEngine { sysFileMapFur[i]=DIV_SYSTEM_NULL; sysFileMapDMF[i]=DIV_SYSTEM_NULL; } + + changeSong(0); } }; #endif diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 53d03285..362bf3c9 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -184,57 +184,57 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logI("reading module data..."); if (ds.version>0x0c) { - ds.hilightA=reader.readC(); - ds.hilightB=reader.readC(); + ds.subsong[0]->hilightA=reader.readC(); + ds.subsong[0]->hilightB=reader.readC(); } - ds.timeBase=reader.readC(); - ds.speed1=reader.readC(); - if (ds.version>0x05) { - ds.speed2=reader.readC(); - ds.pal=reader.readC(); - ds.hz=(ds.pal)?60:50; - ds.customTempo=reader.readC(); + ds.subsong[0]->timeBase=reader.readC(); + ds.subsong[0]->speed1=reader.readC(); + if (ds.version>0x07) { + ds.subsong[0]->speed2=reader.readC(); + ds.subsong[0]->pal=reader.readC(); + ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50; + ds.subsong[0]->customTempo=reader.readC(); } else { - ds.speed2=ds.speed1; + ds.subsong[0]->speed2=ds.subsong[0]->speed1; } if (ds.version>0x0a) { String hz=reader.readString(3); - if (ds.customTempo) { + if (ds.subsong[0]->customTempo) { try { - ds.hz=std::stoi(hz); + ds.subsong[0]->hz=std::stoi(hz); } catch (std::exception& e) { logW("invalid custom Hz!"); - ds.hz=60; + ds.subsong[0]->hz=60; } } } if (ds.version>0x17) { - ds.patLen=reader.readI(); + ds.subsong[0]->patLen=reader.readI(); } else { - ds.patLen=(unsigned char)reader.readC(); + ds.subsong[0]->patLen=(unsigned char)reader.readC(); } - ds.ordersLen=(unsigned char)reader.readC(); + ds.subsong[0]->ordersLen=(unsigned char)reader.readC(); - if (ds.patLen<0) { + if (ds.subsong[0]->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } - if (ds.patLen>256) { + if (ds.subsong[0]->patLen>256) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } - if (ds.ordersLen<0) { + if (ds.subsong[0]->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } - if (ds.ordersLen>127) { + if (ds.subsong[0]->ordersLen>127) { logE("song is too long!"); lastError="song is too long!"; delete[] file; @@ -242,54 +242,54 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } if (ds.version<20 && ds.version>3) { - ds.arpLen=reader.readC(); + ds.subsong[0]->arpLen=reader.readC(); } else { - ds.arpLen=1; + ds.subsong[0]->arpLen=1; } if (ds.system[0]==DIV_SYSTEM_YMU759) { - switch (ds.timeBase) { + switch (ds.subsong[0]->timeBase) { case 0: - ds.hz=248; + ds.subsong[0]->hz=248; break; case 1: - ds.hz=200; + ds.subsong[0]->hz=200; break; case 2: - ds.hz=100; + ds.subsong[0]->hz=100; break; case 3: - ds.hz=50; + ds.subsong[0]->hz=50; break; case 4: - ds.hz=25; + ds.subsong[0]->hz=25; break; case 5: - ds.hz=20; + ds.subsong[0]->hz=20; break; default: - ds.hz=248; + ds.subsong[0]->hz=248; break; } - ds.customTempo=true; - ds.timeBase=0; + ds.subsong[0]->customTempo=true; + ds.subsong[0]->timeBase=0; addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } logV("%x",reader.tell()); - logI("reading pattern matrix (%d * %d = %d)...",ds.ordersLen,getChannelCount(ds.system[0]),ds.ordersLen*getChannelCount(ds.system[0])); + logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0])); for (int i=0; i0x7f) { - logE("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); - lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + ds.subsong[0]->orders.ord[i][j]=reader.readC(); + if (ds.subsong[0]->orders.ord[i][j]>0x7f) { + logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); + lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); delete[] file; return false; } if (ds.version>0x18) { // 1.1 pattern names - ds.pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); + ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); } } if (ds.version>0x03 && ds.version<0x06 && i<16) { @@ -637,9 +637,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logV("%x",reader.tell()); - logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.ordersLen); + logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen); for (int i=0; ipat[i]; if (ds.version<0x0a) { chan.effectCols=1; } else { @@ -652,10 +652,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { delete[] file; return false; } - for (int j=0; j0x05) { // current pattern format - for (int k=0; kordersLen; j++) { + DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true); + if (ds.version>0x08) { // current pattern format + for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readS(); // octave @@ -723,7 +723,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } else { // historic pattern format if (i<16) pat->data[0][2]=historicColIns[i]; - for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readC(); // octave @@ -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; + } } } } @@ -784,7 +788,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { pitch=reader.readC(); vol=reader.readC(); } - if (ds.version<=0x05) { + if (ds.version<=0x08) { sample->rate=ymuSampleRate*400; } if (ds.version>0x15) { @@ -794,15 +798,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->depth=DIV_SAMPLE_DEPTH_16BIT; } } else { - if (ds.version>0x05) { + if (ds.version>0x08) { sample->depth=DIV_SAMPLE_DEPTH_16BIT; } else { // it appears samples were stored as ADPCM back then - sample->depth=DIV_SAMPLE_DEPTH_ADPCM_B; + sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM; } } if (length>0) { - if (ds.version>0x05) { + if (ds.version>0x08) { if (ds.version<0x0b) { data=new short[1+(length/2)]; reader.read(data,length); @@ -838,8 +842,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { delete[] data; } else { - // ADPCM? - // it appears to be a slightly modified version of ADPCM-B! + // YMZ ADPCM adpcmData=new unsigned char[length]; logV("%x",reader.tell()); reader.read(adpcmData,length); @@ -850,7 +853,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logE("%d: error while initializing sample!",i); } - memcpy(sample->dataB,adpcmData,length); + memcpy(sample->dataZ,adpcmData,length); delete[] adpcmData; } } @@ -902,6 +905,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -926,13 +930,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned int insPtr[256]; unsigned int wavePtr[256]; unsigned int samplePtr[256]; + unsigned int subSongPtr[256]; std::vector patPtr; + int numberOfSubSongs=0; char magic[5]; memset(magic,0,5); SafeReader reader=SafeReader(file,len); warnings=""; try { DivSong ds; + DivSubSong* subSong=ds.subsong[0]; if (!reader.seek(16,SEEK_SET)) { logE("premature end of file!"); @@ -1042,44 +1049,44 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } reader.readI(); - ds.timeBase=reader.readC(); - ds.speed1=reader.readC(); - ds.speed2=reader.readC(); - ds.arpLen=reader.readC(); - ds.hz=reader.readF(); - ds.pal=(ds.hz>=53); - ds.customTempo=true; + subSong->timeBase=reader.readC(); + subSong->speed1=reader.readC(); + subSong->speed2=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + subSong->pal=(subSong->hz>=53); + subSong->customTempo=true; - ds.patLen=reader.readS(); - ds.ordersLen=reader.readS(); + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); - ds.hilightA=reader.readC(); - ds.hilightB=reader.readC(); + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); ds.insLen=reader.readS(); ds.waveLen=reader.readS(); ds.sampleLen=reader.readS(); int numberOfPats=reader.readI(); - if (ds.patLen<0) { + if (subSong->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } - if (ds.patLen>256) { + if (subSong->patLen>256) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } - if (ds.ordersLen<0) { + if (subSong->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } - if (ds.ordersLen>256) { + if (subSong->ordersLen>256) { logE("song is too long!"); lastError="song is too long!"; delete[] file; @@ -1292,18 +1299,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(samplePtr,ds.sampleLen*4); for (int i=0; iordersLen); for (int i=0; iordersLen; j++) { + subSong->orders.ord[i][j]=reader.readC(); } } for (int i=0; i8) { - logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols); - lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectCols); + subSong->pat[i].effectCols=reader.readC(); + if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>8) { + logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); + lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); delete[] file; return false; } @@ -1311,25 +1318,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version>=39) { for (int i=0; ichanShow[i]=reader.readC(); } for (int i=0; ichanCollapse[i]=reader.readC(); } if (ds.version<92) { for (int i=0; i0) ds.chanCollapse[i]=3; + if (subSong->chanCollapse[i]>0) subSong->chanCollapse[i]=3; } } for (int i=0; ichanName[i]=reader.readString(); } for (int i=0; ichanShortName[i]=reader.readString(); } ds.notes=reader.readString(); @@ -1402,6 +1409,88 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // subsongs + if (ds.version>=95) { + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + numberOfSubSongs=(unsigned char)reader.readC(); + reader.readC(); // reserved + reader.readC(); + reader.readC(); + // pointers + for (int i=0; itimeBase=reader.readC(); + subSong->speed1=reader.readC(); + subSong->speed2=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + subSong->pal=(subSong->hz>=53); + subSong->customTempo=true; + + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); + + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); + + reader.readI(); // reserved + + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + + logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen); + for (int j=0; jordersLen; k++) { + subSong->orders.ord[j][k]=reader.readC(); + } + } + + for (int i=0; ipat[i].effectCols=reader.readC(); + } + + for (int i=0; ichanShow[i]=reader.readC(); + } + + for (int i=0; ichanCollapse[i]=reader.readC(); + } + + for (int i=0; ichanName[i]=reader.readString(); + } + + for (int i=0; ichanShortName[i]=reader.readString(); + } + } + } + // read instruments for (int i=0; i=95) { + subs=reader.readS(); + } else { + reader.readS(); + } + reader.readS(); - logD("- %d, %d",chan,index); + logD("- %d, %d, %d",subs,chan,index); if (chan<0 || chan>=tchans) { logE("pattern channel out of range!",i); @@ -1590,14 +1685,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { delete[] file; return false; } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; + } - DivPattern* pat=ds.pat[chan].getPattern(index,true); - for (int j=0; jpat[chan].getPattern(index,true); + for (int j=0; jpatLen; j++) { pat->data[j][0]=reader.readS(); pat->data[j][1]=reader.readS(); pat->data[j][2]=reader.readS(); pat->data[j][3]=reader.readS(); - for (int k=0; kpat[chan].effectCols; k++) { pat->data[j][4+(k<<1)]=reader.readS(); pat->data[j][5+(k<<1)]=reader.readS(); } @@ -1619,6 +1721,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -1750,8 +1853,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.sampleLen=ds.sample.size(); // orders - ds.ordersLen=ordCount=reader.readC(); - if (ds.ordersLen<1 || ds.ordersLen>127) { + ds.subsong[0]->ordersLen=ordCount=reader.readC(); + if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) { logD("invalid order count!"); throw EndOfFileException(&reader,reader.tell()); } @@ -1771,7 +1874,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { unsigned char pat=reader.readC(); if (pat>patMax) patMax=pat; for (int j=0; jorders.ord[j][i]=pat; } } @@ -1788,7 +1891,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } // patterns - ds.patLen=64; + ds.subsong[0]->patLen=64; for (int ch=0; chpat[ch].getPattern(pat,true); } for (int row=0; row<64; row++) { for (int ch=0; chdata; + auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data; short lastPitchEffect=-1; short lastEffectState[5]={-1,-1,-1,-1,-1}; short setEffectState[5]={-1,-1,-1,-1,-1}; @@ -1998,25 +2101,25 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } } } - ds.pat[ch].effectCols=fxCols; + ds.subsong[0]->pat[ch].effectCols=fxCols; } - ds.pal=false; - ds.hz=50; - ds.customTempo=false; + ds.subsong[0]->pal=false; + ds.subsong[0]->hz=50; + ds.subsong[0]->customTempo=false; ds.systemLen=(chCount+3)/4; for(int i=0; i1 || bypassLimits)?2:0); // PAL } for(int i=0; ichanShow[i]=true; + ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1); + ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1); } for(int i=chCount; ipat[i].effectCols=1; + ds.subsong[0]->chanShow[i]=false; } // instrument creation @@ -2034,6 +2137,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -2128,8 +2232,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { newVibrato=reader.readI(); } if (blockVersion>=4) { - ds.hilightA=reader.readI(); - ds.hilightB=reader.readI(); + ds.subsong[0]->hilightA=reader.readI(); + ds.subsong[0]->hilightB=reader.readI(); } if (expansions&8) if (blockVersion>=5) { // N163 channels n163Chans=reader.readI(); @@ -2145,12 +2249,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { logV("custom Hz: %d",customHz); logV("new vibrato: %d",newVibrato); logV("N163 channels: %d",n163Chans); - logV("highlight 1: %d",ds.hilightA); - logV("highlight 2: %d",ds.hilightB); + logV("highlight 1: %d",ds.subsong[0]->hilightA); + logV("highlight 2: %d",ds.subsong[0]->hilightB); logV("split point: %d",speedSplitPoint); if (customHz!=0) { - ds.hz=customHz; + ds.subsong[0]->hz=customHz; } // initialize channels @@ -2211,7 +2315,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { for (int j=0; j<=totalSongs; j++) { unsigned char effectCols=reader.readC(); if (j==0) { - ds.pat[i].effectCols=effectCols+1; + ds.subsong[0]->pat[i].effectCols=effectCols+1; } logV("- song %d has %d effect columns",j,effectCols); } @@ -2542,15 +2646,55 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return false; } +struct PatToWrite { + unsigned short subsong, chan, pat; + PatToWrite(unsigned short s, unsigned short c, unsigned short p): + subsong(s), + chan(c), + pat(p) {} +}; + SafeWriter* DivEngine::saveFur(bool notPrimary) { saveLock.lock(); - int insPtr[256]; - int wavePtr[256]; - int samplePtr[256]; + std::vector subSongPtr; + std::vector insPtr; + std::vector wavePtr; + std::vector samplePtr; std::vector patPtr; - size_t ptrSeek; + size_t ptrSeek, subSongPtrSeek; + size_t subSongIndex=0; + DivSubSong* subSong=song.subsong[subSongIndex]; warnings=""; + // fail if values are out of range + /* + if (subSong->ordersLen>256) { + logE("maximum song length is 256!"); + lastError="maximum song length is 256"; + return NULL; + } + if (subSong->patLen>256) { + logE("maximum pattern length is 256!"); + lastError="maximum pattern length is 256"; + return NULL; + } + */ + if (song.ins.size()>256) { + logE("maximum number of instruments is 256!"); + lastError="maximum number of instruments is 256"; + return NULL; + } + if (song.wave.size()>256) { + logE("maximum number of wavetables is 256!"); + lastError="maximum number of wavetables is 256"; + return NULL; + } + if (song.sample.size()>256) { + logE("maximum number of samples is 256!"); + lastError="maximum number of samples is 256"; + return NULL; + } + if (!notPrimary) { song.isDMF=false; song.version=DIV_ENGINE_VERSION; @@ -2577,14 +2721,17 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // high short is channel // low short is pattern number - std::vector patsToWrite; + std::vector patsToWrite; bool alreadyAdded[256]; for (int i=0; iordersLen; k++) { + if (alreadyAdded[subs->orders.ord[i][k]]) continue; + patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); + alreadyAdded[subs->orders.ord[i][k]]=true; + } } } @@ -2592,15 +2739,15 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->write("INFO",4); w->writeI(0); - w->writeC(song.timeBase); - w->writeC(song.speed1); - w->writeC(song.speed2); - w->writeC(song.arpLen); - w->writeF(song.hz); - w->writeS(song.patLen); - w->writeS(song.ordersLen); - w->writeC(song.hilightA); - w->writeC(song.hilightB); + w->writeC(subSong->timeBase); + w->writeC(subSong->speed1); + w->writeC(subSong->speed2); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); w->writeS(song.insLen); w->writeS(song.waveLen); w->writeS(song.sampleLen); @@ -2677,29 +2824,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } for (int i=0; iwriteC(song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); } } for (int i=0; iwriteC(song.pat[i].effectCols); + w->writeC(subSong->pat[i].effectCols); } for (int i=0; iwriteC(song.chanShow[i]); + w->writeC(subSong->chanShow[i]); } for (int i=0; iwriteC(song.chanCollapse[i]); + w->writeC(subSong->chanCollapse[i]); } for (int i=0; iwriteString(song.chanName[i],false); + w->writeString(subSong->chanName[i],false); } for (int i=0; iwriteString(song.chanShortName[i],false); + w->writeString(subSong->chanShortName[i],false); } w->writeString(song.notes,false); @@ -2724,25 +2871,86 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; i<18; i++) { w->writeC(0); } + + // subsong list + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + w->writeC((unsigned char)(song.subsong.size()-1)); + w->writeC(0); // reserved + w->writeC(0); + w->writeC(0); + subSongPtrSeek=w->tell(); + // subsong pointers (we'll seek here later) + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(0); + } + + /// SUBSONGS + for (subSongIndex=1; subSongIndextell()); + w->write("SONG",4); + w->writeI(0); + + w->writeC(subSong->timeBase); + w->writeC(subSong->speed1); + w->writeC(subSong->speed2); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); + w->writeI(0); // reserved + + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + + for (int i=0; iordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); + } + } + + for (int i=0; iwriteC(subSong->pat[i].effectCols); + } + + for (int i=0; iwriteC(subSong->chanShow[i]); + } + + for (int i=0; iwriteC(subSong->chanCollapse[i]); + } + + for (int i=0; iwriteString(subSong->chanName[i],false); + } + + for (int i=0; iwriteString(subSong->chanShortName[i],false); + } + } /// INSTRUMENT for (int i=0; itell(); + insPtr.push_back(w->tell()); ins->putInsData(w); } /// WAVETABLE for (int i=0; itell(); + wavePtr.push_back(w->tell()); wave->putWaveData(w); } /// SAMPLE for (int i=0; itell(); + samplePtr.push_back(w->tell()); w->write("SMPL",4); w->writeI(0); @@ -2759,23 +2967,24 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } /// PATTERN - for (int i: patsToWrite) { - DivPattern* pat=song.pat[i>>16].getPattern(i&0xffff,false); + for (PatToWrite& i: patsToWrite) { + DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); w->write("PATR",4); w->writeI(0); - w->writeS(i>>16); - w->writeS(i&0xffff); + w->writeS(i.chan); + w->writeS(i.pat); + w->writeS(i.subsong); - w->writeI(0); // reserved + w->writeS(0); // reserved - for (int j=0; jpatLen; j++) { w->writeS(pat->data[j][0]); // note w->writeS(pat->data[j][1]); // octave w->writeS(pat->data[j][2]); // instrument w->writeS(pat->data[j][3]); // volume - w->write(&pat->data[j][4],2*song.pat[i>>16].effectCols*2); // effects + w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects } w->writeString(pat->name,false); @@ -2788,21 +2997,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeI(insPtr[i]); } - // wavetable pointers (we'll seek here later) + // wavetable pointers for (int i=0; iwriteI(wavePtr[i]); } - // sample pointers (we'll seek here later) + // sample pointers for (int i=0; iwriteI(samplePtr[i]); } - // pattern pointers (we'll seek here later) + // pattern pointers for (int i: patPtr) { w->writeI(i); } + /// SUBSONG POINTERS + w->seek(subSongPtrSeek,SEEK_SET); + + // subsong pointers + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(subSongPtr[i]); + } + saveLock.unlock(); return w; } @@ -2873,7 +3090,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { return NULL; } // fail if values are out of range - if (song.ordersLen>127) { + if (curSubSong->ordersLen>127) { logE("maximum .dmf song length is 127!"); lastError="maximum .dmf song length is 127"; return NULL; @@ -2889,10 +3106,10 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { return NULL; } for (int i=0; i0x7f) { - logE("order %d, %d is out of range (0-127)!",song.orders.ord[i][j]); - lastError=fmt::sprintf("order %d, %d is out of range (0-127)",song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + if (curOrders->ord[i][j]>0x7f) { + logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]); + lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]); return NULL; } } @@ -2935,31 +3152,35 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { // song info w->writeString(song.name,true); w->writeString(song.author,true); - w->writeC(song.hilightA); - w->writeC(song.hilightB); + w->writeC(curSubSong->hilightA); + w->writeC(curSubSong->hilightB); - w->writeC(song.timeBase); - w->writeC(song.speed1); - w->writeC(song.speed2); - w->writeC(song.pal); - w->writeC(song.customTempo); + w->writeC(curSubSong->timeBase); + w->writeC(curSubSong->speed1); + w->writeC(curSubSong->speed2); + w->writeC(curSubSong->pal); + w->writeC(curSubSong->customTempo); char customHz[4]; memset(customHz,0,4); - snprintf(customHz,4,"%d",(int)song.hz); + snprintf(customHz,4,"%d",(int)curSubSong->hz); w->write(customHz,3); - w->writeI(song.patLen); - w->writeC(song.ordersLen); + w->writeI(curSubSong->patLen); + w->writeC(curSubSong->ordersLen); for (int i=0; iwriteC(song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + w->writeC(curOrders->ord[i][j]); if (version>=25) { - DivPattern* pat=song.pat[i].getPattern(j,false); + DivPattern* pat=curPat[i].getPattern(j,false); w->writeString(pat->name,true); } } } + if (song.subsong.size()>1) { + addWarning("only the currently selected subsong will be saved"); + } + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { addWarning("absolute duty/cutoff macro not available in .dmf!"); addWarning("duty precision will be lost"); @@ -3121,15 +3342,15 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } for (int i=0; iwriteC(song.pat[i].effectCols); + w->writeC(curPat[i].effectCols); - for (int j=0; jordersLen; j++) { + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); + for (int k=0; kpatLen; k++) { w->writeS(pat->data[k][0]); // note w->writeS(pat->data[k][1]); // octave w->writeS(pat->data[k][3]); // volume - w->write(&pat->data[k][4],2*song.pat[i].effectCols*2); // effects + w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects w->writeS(pat->data[k][2]); // instrument } } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index f62b2c7a..e57d652b 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -385,6 +385,45 @@ struct DivInstrumentAmiga { TransWave transWave; std::vector transWaveMap; + /** + * 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].ind; + } + 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 noteMap[note].freq; + } + return -1; + } + + /** + * get the sample reversed flag at specified note. + * @return the reversed flag. + */ + inline bool getReversed(int note) { + if (useNoteMap) { + if (note<0) note=0; + if (note>119) note=119; + return noteMap[note].reversed; + } + return reversed; + } + DivInstrumentAmiga(): initSample(0), reversed(false), diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 4f4663ca..0a561376 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -49,7 +49,7 @@ void DivChannelData::wipePatterns() { } } -void DivPattern::copyOn(DivPattern *dest) { +void DivPattern::copyOn(DivPattern* dest) { dest->name=name; memcpy(dest->data,data,sizeof(data)); } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index d1198298..db10ae4b 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; @@ -250,19 +249,11 @@ 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 (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; @@ -328,17 +319,8 @@ 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; - 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)); + 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) { 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; diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index dbc5aa82..94546ed1 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -27,7 +27,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} } -#define CHIP_DIVIDER 8 +#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8) const char* regCheatSheetAY[]={ "FreqL_A", "0", @@ -589,6 +589,7 @@ void DivPlatformAY8910::poke(std::vector& wlist) { } void DivPlatformAY8910::setFlags(unsigned int flags) { + clockSel=(flags>>7)&1; switch (flags&15) { case 1: chipClock=COLOR_PAL*2.0/5.0; @@ -620,6 +621,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { case 10: chipClock=2097152; break; + case 11: + chipClock=COLOR_NTSC; + break; + case 12: + chipClock=3600000; + break; default: chipClock=COLOR_NTSC/2.0; break; @@ -632,7 +639,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { if (ay!=NULL) delete ay; switch ((flags>>4)&3) { case 1: - ay=new ym2149_device(rate); + ay=new ym2149_device(rate,clockSel); sunsoft=false; intellivision=false; break; @@ -654,7 +661,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { } ay->device_start(); - stereo=flags>>6; + stereo=(flags>>6)&1; } int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 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 6fb34096..8ec2aea2 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -27,7 +27,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite2(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_DIVIDER 4 +#define CHIP_DIVIDER (clockSel?8:4) const char* regCheatSheetAY8930[]={ "FreqL_A", "00", @@ -64,7 +64,7 @@ const char* regCheatSheetAY8930[]={ void DivPlatformAY8930::immWrite(unsigned char a, unsigned char v) { if ((int)bank!=(a>>4)) { bank=a>>4; - immWrite2(0x0d, 0xa0|(bank<<4)|ayEnvMode[0]); + immWrite2(0x0d, 0xa0|(bank<<4)|chan[0].envelope.mode); } if (a==0x0d) { immWrite2(0x0d,0xa0|(bank<<4)|(v&15)); @@ -258,8 +258,8 @@ void DivPlatformAY8930::tick(bool sysTick) { rWrite(0x16+i,chan[i].std.ex1.val); } if (chan[i].std.ex2.had) { - ayEnvMode[i]=chan[i].std.ex2.val; - rWrite(regMode[i],ayEnvMode[i]); + chan[i].envelope.mode=chan[i].std.ex2.val; + rWrite(regMode[i],chan[i].envelope.mode); } if (chan[i].std.ex3.had) { chan[i].autoEnvNum=chan[i].std.ex3.val; @@ -296,29 +296,29 @@ void DivPlatformAY8930::tick(bool sysTick) { if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { - ayEnvPeriod[i]=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + chan[i].envelope.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); } chan[i].freqChanged=false; } - if (ayEnvSlide[i]!=0) { - ayEnvSlideLow[i]+=ayEnvSlide[i]; - while (ayEnvSlideLow[i]>7) { - ayEnvSlideLow[i]-=8; - if (ayEnvPeriod[i]<0xffff) { - ayEnvPeriod[i]++; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + if (chan[i].envelope.slide!=0) { + chan[i].envelope.slideLow+=chan[i].envelope.slide; + while (chan[i].envelope.slideLow>7) { + chan[i].envelope.slideLow-=8; + if (chan[i].envelope.period<0xffff) { + chan[i].envelope.period++; + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); } } - while (ayEnvSlideLow[i]<-7) { - ayEnvSlideLow[i]+=8; - if (ayEnvPeriod[i]>0) { - ayEnvPeriod[i]--; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); + while (chan[i].envelope.slideLow<-7) { + chan[i].envelope.slideLow+=8; + if (chan[i].envelope.period>0) { + chan[i].envelope.period--; + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); } } } @@ -435,8 +435,8 @@ int DivPlatformAY8930::dispatch(DivCommand c) { rWrite(0x06,c.value); break; case DIV_CMD_AY_ENVELOPE_SET: - ayEnvMode[c.chan]=c.value>>4; - rWrite(regMode[c.chan],ayEnvMode[c.chan]); + chan[c.chan].envelope.mode=c.value>>4; + rWrite(regMode[c.chan],chan[c.chan].envelope.mode); if (c.value&15) { chan[c.chan].psgMode|=4; } else { @@ -449,19 +449,19 @@ int DivPlatformAY8930::dispatch(DivCommand c) { } break; case DIV_CMD_AY_ENVELOPE_LOW: - ayEnvPeriod[c.chan]&=0xff00; - ayEnvPeriod[c.chan]|=c.value; - immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]); - immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8); + chan[c.chan].envelope.period&=0xff00; + chan[c.chan].envelope.period|=c.value; + immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period); + immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8); break; case DIV_CMD_AY_ENVELOPE_HIGH: - ayEnvPeriod[c.chan]&=0xff; - ayEnvPeriod[c.chan]|=c.value<<8; - immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]); - immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8); + chan[c.chan].envelope.period&=0xff; + chan[c.chan].envelope.period|=c.value<<8; + immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period); + immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8); break; case DIV_CMD_AY_ENVELOPE_SLIDE: - ayEnvSlide[c.chan]=c.value; + chan[c.chan].envelope.slide=c.value; break; case DIV_CMD_AY_NOISE_MASK_AND: ayNoiseAnd=c.value; @@ -526,9 +526,9 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) { void DivPlatformAY8930::forceIns() { for (int i=0; i<3; i++) { chan[i].insChanged=true; - immWrite(regPeriodL[i],ayEnvPeriod[i]); - immWrite(regPeriodH[i],ayEnvPeriod[i]>>8); - immWrite(regMode[i],ayEnvMode[i]); + immWrite(regPeriodL[i],chan[i].envelope.period); + immWrite(regPeriodH[i],chan[i].envelope.period>>8); + immWrite(regMode[i],chan[i].envelope.mode); } } @@ -556,10 +556,10 @@ void DivPlatformAY8930::reset() { chan[i]=DivPlatformAY8930::Channel(); chan[i].std.setEngine(parent); chan[i].vol=31; - ayEnvPeriod[i]=0; - ayEnvMode[i]=0; - ayEnvSlide[i]=0; - ayEnvSlideLow[i]=0; + chan[i].envelope.period=0; + chan[i].envelope.mode=0; + chan[i].envelope.slide=0; + chan[i].envelope.slideLow=0; } if (dumpWrites) { addWrite(0xffffffff,0); @@ -610,6 +610,7 @@ void DivPlatformAY8930::poke(std::vector& wlist) { } void DivPlatformAY8930::setFlags(unsigned int flags) { + clockSel=(flags>>7)&1; switch (flags&15) { case 1: chipClock=COLOR_PAL*2.0/5.0; @@ -641,6 +642,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { case 10: chipClock=2097152; break; + case 11: + chipClock=COLOR_NTSC; + break; + case 12: + chipClock=3600000; + break; default: chipClock=COLOR_NTSC/2.0; break; @@ -650,7 +657,7 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { oscBuf[i]->rate=rate; } - stereo=flags>>6; + stereo=(flags>>6)&1; } int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -662,7 +669,7 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); - ay=new ay8930_device(rate); + ay=new ay8930_device(rate,clockSel); ay->device_start(); ayBufLen=65536; for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen]; diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 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/es5506.cpp b/src/engine/platform/es5506.cpp index 7e74caee..b131215d 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -111,6 +111,9 @@ const char* DivPlatformES5506::getEffectName(unsigned char effect) { case 0x11: return "11xx: Set filter mode (00 to 03)"; break; + case 0x12: + return "120x: Set pause (bit 0)"; + break; case 0x14: return "14xx: Set filter coefficient K1 low byte"; break; @@ -456,6 +459,15 @@ void DivPlatformES5506::tick(bool sysTick) { } } } + // control macros + if (!chan[i].keyOn) { + if (chan[i].active && chan[i].std.alg.had) { + if (chan[i].pcm.pause!=(bool)(chan[i].std.alg.val&1)) { + chan[i].pcm.pause=chan[i].std.alg.val&1; + pageWriteMask(0x00|i,0x5f,0x00,chan[i].pcm.pause?0x0002:0x0000,0x0002); + } + } + } // update registers if (chan[i].volChanged.changed) { if (!isMuted[i]) { // calculate volume (16 bit) @@ -631,6 +643,9 @@ void DivPlatformES5506::tick(bool sysTick) { loopFlag|=0x0018; break; } + if (chan[i].pcm.pause) { + loopFlag|=0x0002; + } // Run sample pageWrite(0x00|i,0x06,chan[i].envelope.ecount); // Clear ECOUNT pageWriteMask(0x00|i,0x5f,0x00,loopFlag,0x3cff); @@ -661,78 +676,86 @@ void DivPlatformES5506::tick(bool sysTick) { int DivPlatformES5506::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); - DivInstrumentAmiga::NoteMap& noteMapind=ins->amiga.noteMap[c.value]; - DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[ins->amiga.transWave.ind]; - int sample=ins->amiga.initSample; - if (ins->amiga.transWave.enable) { - sample=transWaveInd.ind; - } else if (ins->amiga.useNoteMap) { - ins->amiga.noteMap[c.value].ind; - } - if (sample>=0 && samplesong.sampleLen) { - chan[c.chan].pcm.index=sample; - chan[c.chan].transWave.enable=ins->amiga.transWave.enable; - chan[c.chan].transWave.sliceEnable=ins->amiga.transWave.sliceEnable; - chan[c.chan].transWave.ind=ins->amiga.transWave.ind; - DivSample* s=parent->getSample(sample); - // get frequency offset - double off=1.0; - double center=s->centerRate; - if (center<1) { - off=1.0; - } else { - off=(double)center/8363.0; - } - if (ins->amiga.useNoteMap) { - off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)c.value-48.0)/12.0)); - chan[c.chan].pcm.note=c.value; - } - // get loop mode, transwave loop - double loopStart=(double)s->loopStart; - double loopEnd=(double)s->loopEnd; - DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT; + bool sampleVaild=false; + if (((ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (c.value>=0 && c.value<120)) || + ((!ins->amiga.useNoteMap && ins->amiga.transWave.enable) && (ins->amiga.transWave.ind>=0 && ins->amiga.transWave.indamiga.transWaveMap.size())) || + ((!ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (ins->amiga.initSample>=0 && ins->amiga.initSamplesong.sampleLen))) { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + DivInstrumentAmiga::NoteMap& noteMapind=ins->amiga.noteMap[c.value]; + DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[ins->amiga.transWave.ind]; + int sample=ins->amiga.initSample; if (ins->amiga.transWave.enable) { - if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) { - loopMode=transWaveInd.loopMode; - } else if (!s->isLoopable()) { // default - loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG; - } - // get loop position - loopStart=(double)transWaveInd.loopStart; - loopEnd=(double)transWaveInd.loopEnd; - if (ins->amiga.transWave.sliceEnable) { // sliced loop position? - chan[c.chan].transWave.updateSize(s->samples,loopStart,loopEnd); - chan[c.chan].transWave.slice=transWaveInd.slice; - chan[c.chan].transWave.slicePos(transWaveInd.slice); - loopStart=transWaveInd.sliceStart; - loopEnd=transWaveInd.sliceEnd; - } + sample=transWaveInd.ind; + } else if (ins->amiga.useNoteMap) { + sample=noteMapind.ind; } - // get reversed - bool reversed=ins->amiga.reversed; - if (ins->amiga.transWave.enable&&transWaveInd.reversed!=2) { - reversed=transWaveInd.reversed; - } else if (ins->amiga.useNoteMap&¬eMapind.reversed!=2) { - reversed=noteMapind.reversed; + if (sample>=0 && samplesong.sampleLen) { + sampleVaild=true; + chan[c.chan].pcm.index=sample; + chan[c.chan].pcm.pause=(chan[c.chan].std.alg.will)?(chan[c.chan].std.alg.val&1):false; + chan[c.chan].transWave.enable=ins->amiga.transWave.enable; + chan[c.chan].transWave.sliceEnable=ins->amiga.transWave.sliceEnable; + chan[c.chan].transWave.ind=ins->amiga.transWave.ind; + DivSample* s=parent->getSample(sample); + // get frequency offset + double off=1.0; + double center=s->centerRate; + if (center<1) { + off=1.0; + } else { + off=(double)center/8363.0; + } + if (ins->amiga.useNoteMap) { + off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)c.value-48.0)/12.0)); + chan[c.chan].pcm.note=c.value; + } + // get loop mode, transwave loop + double loopStart=(double)s->loopStart; + double loopEnd=(double)s->loopEnd; + DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT; + if (ins->amiga.transWave.enable) { + if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) { + loopMode=transWaveInd.loopMode; + } else if (!s->isLoopable()) { // default + loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG; + } + // get loop position + loopStart=(double)transWaveInd.loopStart; + loopEnd=(double)transWaveInd.loopEnd; + if (ins->amiga.transWave.sliceEnable) { // sliced loop position? + chan[c.chan].transWave.updateSize(s->samples,loopStart,loopEnd); + chan[c.chan].transWave.slice=transWaveInd.slice; + chan[c.chan].transWave.slicePos(transWaveInd.slice); + loopStart=transWaveInd.sliceStart; + loopEnd=transWaveInd.sliceEnd; + } + } + // get reversed + bool reversed=ins->amiga.reversed; + if (ins->amiga.transWave.enable&&transWaveInd.reversed!=2) { + reversed=transWaveInd.reversed; + } else if (ins->amiga.useNoteMap&¬eMapind.reversed!=2) { + reversed=noteMapind.reversed; + } + const unsigned int start=s->offES5506<<10; + const unsigned int length=s->samples-1; + const unsigned int end=start+(length<<11); + chan[c.chan].pcm.loopMode=loopMode; + chan[c.chan].pcm.freqOffs=PITCH_OFFSET*off; + chan[c.chan].pcm.reversed=reversed; + chan[c.chan].pcm.bank=(s->offES5506>>22)&3; + chan[c.chan].pcm.start=start; + chan[c.chan].pcm.end=end; + chan[c.chan].pcm.length=length; + chan[c.chan].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800; + chan[c.chan].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80; + chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xffff; + chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xffff; + chan[c.chan].filter=ins->es5506.filter; + chan[c.chan].envelope=ins->es5506.envelope; } - const unsigned int start=s->offES5506<<10; - const unsigned int length=s->samples-1; - const unsigned int end=start+(length<<11); - chan[c.chan].pcm.loopMode=loopMode; - chan[c.chan].pcm.freqOffs=PITCH_OFFSET*off; - chan[c.chan].pcm.reversed=reversed; - chan[c.chan].pcm.bank=(s->offES5506>>22)&3; - chan[c.chan].pcm.start=start; - chan[c.chan].pcm.end=end; - chan[c.chan].pcm.length=length; - chan[c.chan].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800; - chan[c.chan].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80; - chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xffff; - chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xffff; - chan[c.chan].filter=ins->es5506.filter; - chan[c.chan].envelope=ins->es5506.envelope; - } else { + } + if (!sampleVaild) { chan[c.chan].pcm.index=-1; chan[c.chan].filter=DivInstrumentES5506::Filter(); chan[c.chan].envelope=DivInstrumentES5506::Envelope(); @@ -864,6 +887,15 @@ int DivPlatformES5506::dispatch(DivCommand c) { chan[c.chan].envelope.k2Slow=c.value2&1; chan[c.chan].envChanged.k2Ramp=1; break; + // controls + case DIV_CMD_ES5506_PAUSE: + if (chan[c.chan].active) { + if (chan[c.chan].pcm.pause!=(bool)(c.value&1)) { + chan[c.chan].pcm.pause=c.value&1; + pageWriteMask(0x00|i,0x5f,0x00,chan[c.chan].pcm.pause?0x0002:0x0000,0x0002); + } + } + break; case DIV_CMD_NOTE_PORTA: { int nextFreq=chan[c.chan].baseFreq; const int destFreq=NOTE_ES5506(c.chan,c.value2); diff --git a/src/engine/platform/es5506.h b/src/engine/platform/es5506.h index 4745cad4..aab02897 100644 --- a/src/engine/platform/es5506.h +++ b/src/engine/platform/es5506.h @@ -36,7 +36,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { int note; double freqOffs; double nextFreqOffs; - bool reversed; + bool reversed, pause; unsigned int bank; unsigned int start; unsigned int end; @@ -51,6 +51,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { freqOffs(1.0), nextFreqOffs(1.0), reversed(false), + pause(false), bank(0), start(0), end(0), diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 9c992999..c7728731 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -549,7 +549,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 896b88c4..97b5aa15 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -193,7 +193,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 3a9ad039..c351e52a 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -382,7 +382,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 0933934c..949d104b 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,53 @@ 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); + + 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=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); @@ -588,6 +671,7 @@ int DivPlatformOPL::toFreq(int freq) { void DivPlatformOPL::muteChannel(int ch, bool mute) { isMuted[ch]=mute; + if (ch==adpcmChan) return; if (oplType<3 && ch=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.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); + 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,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,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; + } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPL); if (chan[c.chan].insChanged) { @@ -714,11 +863,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; @@ -728,7 +885,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; @@ -737,6 +894,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) { @@ -827,13 +992,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 { @@ -843,6 +1017,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]) { @@ -859,6 +1034,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]; @@ -870,6 +1046,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]; @@ -889,6 +1066,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) ? adpcmBMem : NULL; +} + +size_t DivPlatformOPL::getSampleMemCapacity(int index) { + return (index==0 && adpcmChan>=0) ? 262144 : 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 +1690,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 +1706,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..a67d5038 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: @@ -30,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) { @@ -47,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), @@ -73,13 +84,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, sampleBank; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; @@ -97,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); @@ -127,6 +144,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) { +} diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index d9c4cd33..ba613683 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 d82100a2..0992cd13 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/scc.cpp b/src/engine/platform/scc.cpp index 87a42afe..88132b12 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -21,9 +21,9 @@ #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); }} +#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", @@ -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); } @@ -153,11 +158,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; } } @@ -281,7 +286,14 @@ void DivPlatformSCC::forceIns() { for (int i=0; i<5; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; - updateWave(i); + if (isPlus || i<3) { + updateWave(i); + } + } + if (!isPlus) { + if (lastUpdated34>=3) { + updateWave(lastUpdated34); + } } } @@ -316,6 +328,7 @@ void DivPlatformSCC::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } + lastUpdated34=0; } bool DivPlatformSCC::isStereo() { @@ -326,7 +339,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 +373,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/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; diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 7ce320bb..e811175f 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/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 4d19e7de..3dddf82c 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -1021,10 +1021,8 @@ void ay8910_device::ay8910_write_reg(int r, int v) m_tone[2].set_duty(m_regs[AY_CDUTY]); break; case AY_NOISEAND: - m_noise_and=m_regs[AY_NOISEAND]; - break; case AY_NOISEOR: - m_noise_or=m_regs[AY_NOISEOR]; + // No action required break; default: m_regs[r] = 0; // reserved, set as 0 @@ -1047,7 +1045,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) if (!m_ready) { for (int chan = 0; chan < m_streams; chan++) - memset(outputs[chan],0,outLen*sizeof(short)); + memset(outputs[chan],0,outLen*sizeof(short)); } /* The 8910 has three outputs, each output is the mix of one of the three */ @@ -1063,8 +1061,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) for (int chan = 0; chan < NUM_CHANNELS; chan++) { tone = &m_tone[chan]; - const int period = std::max(1,tone->period); - tone->count += is_expanded_mode() ? 16 : (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; + const int period = tone->period * (m_step_mul << 1); + tone->count += is_expanded_mode() ? 32 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 1 : 2); while (tone->count >= period) { tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f; @@ -1073,8 +1071,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) } } - m_count_noise++; - if (m_count_noise >= noise_period()) + const int period_noise = (int)(noise_period()) * m_step_mul; + if ((++m_count_noise) >= period_noise) { /* toggle the prescaler output. Noise is no different to * channels. @@ -1082,39 +1080,27 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) m_count_noise = 0; m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1); - if (!m_prescale_noise || is_expanded_mode()) // AY8930 noise generator rate is twice compares as compatibility mode + if (is_expanded_mode()) // AY8930 noise generator rate is twice? compares as compatibility mode { - if (is_expanded_mode()) { - // This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR. - // Using AND/OR gates, specific periods can be "filtered" out. - // A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation. - - // The period of the noise is determined by this value. - // The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask. - unsigned int noiseValuePeriod = ((m_rng & 0xFF & m_noise_and) | m_noise_or); - - // Clock the noise value. - if (m_noise_value >= noiseValuePeriod) { - m_noise_value = 0; - - // When everything is finally said and done, a 1bit latch is flipped. - // This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel. - m_noise_latch ^= 1; - - // The 17-bit LFSR is updated, using an XOR across bits 0 and 2. - unsigned int feedback = (m_rng & 1) ^ ((m_rng >> 2) & 1); - m_rng >>= 1; - m_rng |= (feedback << 16); - } - m_noise_value++; - } else { - /* The Random Number Generator of the 8910 is a 17-bit shift */ - /* register. The input to the shift register is bit0 XOR bit3 */ - /* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */ - m_rng ^= (((m_rng & 1) ^ ((m_rng >> 3) & 1)) << 17); - m_rng >>= 1; - } + // This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR. + // Using AND/OR gates, specific periods can be "filtered" out. + // A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation. + + // The period of the noise is determined by this value. + // The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask. + if ((++m_noise_value) >= (((unsigned char)(m_rng) & noise_and()) | noise_or())) // Clock the noise value. + { + m_noise_value = 0; + + // When everything is finally said and done, a 1bit latch is flipped. + // This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel. + m_noise_out ^= 1; + + noise_rng_tick(); + } } + else if (!m_prescale_noise) + noise_rng_tick(); } for (int chan = 0; chan < NUM_CHANNELS; chan++) @@ -1129,9 +1115,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) envelope = &m_envelope[chan]; if (envelope->holding == 0) { - const int period = envelope->period * m_step; - envelope->count++; - if (envelope->count >= period) + const int period = envelope->period * m_env_step_mul; + if ((++envelope->count) >= period) { envelope->count = 0; envelope->step--; @@ -1263,20 +1248,14 @@ void ay8910_device::device_start() } -void ay8910_device::ay8910_reset_ym(bool ay8930) +void ay8910_device::ay8910_reset_ym() { m_active = false; m_register_latch = 0; - if (ay8930) { - m_rng = 0x1ffff; - } else { - m_rng = 1; - } + m_rng = (m_feature & PSG_HAS_EXPANDED_MODE) ? 0x1ffff : 1; m_mode = 0; // ay-3-8910 compatible mode - m_noise_and = 0xff; - m_noise_or = 0; - m_noise_value = 0; - m_noise_latch = 0; + m_noise_value = 0; + m_noise_out = 0; for (int chan = 0; chan < NUM_CHANNELS; chan++) { m_tone[chan].reset(); @@ -1335,7 +1314,7 @@ void ay8910_device::ay8910_write_ym(int addr, unsigned char data) unsigned char ay8910_device::ay8910_read_ym() { - int r = m_register_latch + get_register_bank(); + unsigned char r = m_register_latch + get_register_bank(); if (!m_active) return 0xff; // high impedance @@ -1380,7 +1359,7 @@ unsigned char ay8910_device::ay8910_read_ym() void ay8910_device::device_reset() { - ay8910_reset_ym(chip_type == AY8930); + ay8910_reset_ym(); } /************************************* @@ -1452,28 +1431,27 @@ ay8910_device::ay8910_device(unsigned int clock) } ay8910_device::ay8910_device(device_type type, unsigned int clock, - psg_type_t psg_type, int streams, int ioports, int feature) + psg_type_t psg_type, int streams, int ioports, int feature, bool clk_sel) : chip_type(type), - m_type(psg_type), + m_type(psg_type), m_streams(streams), m_ready(0), m_active(false), m_register_latch(0), m_last_enable(0), m_prescale_noise(0), + m_noise_value(0), m_count_noise(0), m_rng(0), - m_noise_and(0), - m_noise_or(0), - m_noise_value(0), - m_noise_latch(0), + m_noise_out(0), m_mode(0), m_env_step_mask((!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 0x0f : 0x1f), - m_step( (feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY) ? 2 : 1), + m_step_mul( ((feature & PSG_HAS_INTERNAL_DIVIDER) || ((feature & PSG_PIN26_IS_CLKSEL) && clk_sel)) ? 2 : 1), + m_env_step_mul( ((feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY)) ? (m_step_mul << 1) : m_step_mul), m_zero_is_off( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 1 : 0), m_par( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param), m_par_env( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param_env), - m_flags(AY8910_LEGACY_OUTPUT), + m_flags(AY8910_LEGACY_OUTPUT | (((feature & PSG_PIN26_IS_CLKSEL) && clk_sel) ? YM2149_PIN26_LOW : 0)), m_feature(feature) { memset(&m_regs,0,sizeof(m_regs)); @@ -1485,16 +1463,16 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock, m_res_load[0] = m_res_load[1] = m_res_load[2] = 1000; //Default values for resistor loads // TODO : measure ay8930 volume parameters (PSG_TYPE_YM for temporary 5 bit handling) - set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type); + set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type, clk_sel); } -void ay8910_device::set_type(psg_type_t psg_type) +void ay8910_device::set_type(psg_type_t psg_type, bool clk_sel) { m_type = psg_type; if (psg_type == PSG_TYPE_AY) { m_env_step_mask = 0x0f; - m_step = 2; + m_env_step_mul = is_clock_divided() ? 4 : 2; m_zero_is_off = 1; m_par = &ay8910_param; m_par_env = &ay8910_param; @@ -1502,11 +1480,15 @@ void ay8910_device::set_type(psg_type_t psg_type) else { m_env_step_mask = 0x1f; - m_step = (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1; + m_env_step_mul = is_clock_divided() ? 2 : 1; m_zero_is_off = 0; m_par = &ym2149_param; m_par_env = &ym2149_param_env; } + if (m_feature & PSG_HAS_EXPANDED_MODE) + m_env_step_mul <<= 1; + + set_clock_sel(clk_sel); } @@ -1535,24 +1517,24 @@ ay8914_device::ay8914_device(unsigned int clock) -ay8930_device::ay8930_device(unsigned int clock) - : ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE) +ay8930_device::ay8930_device(unsigned int clock, bool clk_sel) + : ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE, clk_sel) { } -ym2149_device::ym2149_device(unsigned int clock) - : ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL) +ym2149_device::ym2149_device(unsigned int clock, bool clk_sel) + : ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel) { } -ym3439_device::ym3439_device(unsigned int clock) - : ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL) +ym3439_device::ym3439_device(unsigned int clock, bool clk_sel) + : ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel) { } diff --git a/src/engine/platform/sound/ay8910.h b/src/engine/platform/sound/ay8910.h index c9de6da0..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(); } @@ -97,7 +102,24 @@ public: void data_w(unsigned char data); // /RES - void reset_w(unsigned char data = 0) { ay8910_reset_ym(chip_type == AY8930); } + void reset_w(unsigned char data = 0) { ay8910_reset_ym(); } + + // Clock select pin + void set_clock_sel(bool clk_sel) + { + if (m_feature & PSG_PIN26_IS_CLKSEL) + { + if (clk_sel) + m_flags |= YM2149_PIN26_LOW; + else + m_flags &= ~YM2149_PIN26_LOW; + + m_step_mul = is_clock_divided() ? 2 : 1; + m_env_step_mul = (!(m_feature & PSG_HAS_EXPANDED_MODE)) && (m_type == PSG_TYPE_AY) ? (m_step_mul << 1) : m_step_mul; + if (m_feature & PSG_HAS_EXPANDED_MODE) + m_env_step_mul <<= 1; + } + } // use this when BC1 == A0; here, BC1=0 selects 'data' and BC1=1 selects 'latch address' void data_address_w(int offset, unsigned char data) { ay8910_write_ym(~offset & 1, data); } // note that directly connecting BC1 to A0 puts data on 0 and address on 1 @@ -127,7 +149,7 @@ public: // internal interface for PSG component of YM device // FIXME: these should be private, but vector06 accesses them directly - ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT); + ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT, bool clk_sel = false); // device-level overrides void device_start(); @@ -138,7 +160,7 @@ public: void ay8910_write_ym(int addr, unsigned char data); unsigned char ay8910_read_ym(); - void ay8910_reset_ym(bool ay8930); + void ay8910_reset_ym(); private: static constexpr int NUM_CHANNELS = 3; @@ -189,7 +211,7 @@ private: void reset() { - period = 0; + period = 1; volume = 0; duty = 0; count = 0; @@ -199,7 +221,7 @@ private: void set_period(unsigned char fine, unsigned char coarse) { - period = fine | (coarse << 8); + period = std::max(1, fine | (coarse << 8)); } void set_volume(unsigned char val) @@ -223,7 +245,7 @@ private: void reset() { - period = 0; + period = 1; count = 0; step = 0; volume = 0; @@ -235,7 +257,7 @@ private: void set_period(unsigned char fine, unsigned char coarse) { - period = fine | (coarse << 8); + period = std::max(1, fine | (coarse << 8)); } void set_shape(unsigned char shape, unsigned char mask) @@ -258,6 +280,18 @@ private: } }; + inline void noise_rng_tick() + { + // The Random Number Generator of the 8910 is a 17-bit shift + // register. The input to the shift register is bit0 XOR bit3 + // (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. + + if (m_feature & PSG_HAS_EXPANDED_MODE) // AY8930 LFSR algorithm is slightly different, verified from manual + m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 2)) << 16); + else + m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 3)) << 16); + } + // inlines inline bool tone_enable(int chan) { return BIT(m_regs[AY_ENABLE], chan); } inline unsigned char tone_volume(tone_t *tone) { return tone->volume & (is_expanded_mode() ? 0x1f : 0x0f); } @@ -266,14 +300,19 @@ private: inline unsigned char get_envelope_chan(int chan) { return is_expanded_mode() ? chan : 0; } inline bool noise_enable(int chan) { return BIT(m_regs[AY_ENABLE], 3 + chan); } - inline unsigned char noise_period() { return is_expanded_mode() ? m_regs[AY_NOISEPER] & 0xff : m_regs[AY_NOISEPER] & 0x1f; } - inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_latch & 1 : m_rng & 1; } + inline unsigned char noise_period() { return std::max(1, is_expanded_mode() ? (m_regs[AY_NOISEPER] & 0xff) : (m_regs[AY_NOISEPER] & 0x1f)); } + inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_out & 1 : m_rng & 1; } inline bool is_expanded_mode() { return ((m_feature & PSG_HAS_EXPANDED_MODE) && ((m_mode & 0xe) == 0xa)); } inline unsigned char get_register_bank() { return is_expanded_mode() ? (m_mode & 0x1) << 4 : 0; } + inline unsigned char noise_and() { return m_regs[AY_NOISEAND] & 0xff; } + inline unsigned char noise_or() { return m_regs[AY_NOISEOR] & 0xff; } + + inline bool is_clock_divided() { return ((m_feature & PSG_HAS_INTERNAL_DIVIDER) || ((m_feature & PSG_PIN26_IS_CLKSEL) && (m_flags & YM2149_PIN26_LOW))); } + // internal helpers - void set_type(psg_type_t psg_type); + void set_type(psg_type_t psg_type, bool clk_sel); inline float mix_3D(); void ay8910_write_reg(int r, int v); void build_mixer_table(); @@ -284,22 +323,21 @@ private: int m_ready; //sound_stream *m_channel; bool m_active; - int m_register_latch; + unsigned char m_register_latch; unsigned char m_regs[16 * 2]; int m_last_enable; tone_t m_tone[NUM_CHANNELS]; envelope_t m_envelope[NUM_CHANNELS]; unsigned char m_prescale_noise; - int m_count_noise; - int m_rng; - unsigned int m_noise_and; - unsigned int m_noise_or; - unsigned int m_noise_value; - unsigned int m_noise_latch; + signed short m_noise_value; + signed short m_count_noise; + unsigned int m_rng; + unsigned char m_noise_out; unsigned char m_mode; unsigned char m_env_step_mask; /* init parameters ... */ - int m_step; + int m_step_mul; + int m_env_step_mul; int m_zero_is_off; unsigned char m_vol_enabled[NUM_CHANNELS]; const ay_ym_param *m_par; @@ -337,19 +375,19 @@ public: class ay8930_device : public ay8910_device { public: - ay8930_device(unsigned int clock); + ay8930_device(unsigned int clock, bool clk_sel = false); }; class ym2149_device : public ay8910_device { public: - ym2149_device(unsigned int clock); + ym2149_device(unsigned int clock, bool clk_sel = false); }; class ym3439_device : public ay8910_device { public: - ym3439_device(unsigned int clock); + ym3439_device(unsigned int clock, bool clk_sel = false); }; class ymz284_device : public ay8910_device diff --git a/src/engine/platform/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/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 1b47885b..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); @@ -546,6 +548,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/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 24506031..f8e5e2aa 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); @@ -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; diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 13cdad81..5ca16458 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; } @@ -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/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 2bb4d1cf..08e3f428 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/ym2203.cpp b/src/engine/platform/ym2203.cpp new file mode 100644 index 00000000..10fde1f2 --- /dev/null +++ b/src/engine/platform/ym2203.cpp @@ -0,0 +1,1111 @@ +/** + * 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 "sound/ymfm/ymfm_opn.h" +#include "ym2203shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[3]={ + 0, 1, 2 +}; + +#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", + "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", + NULL +}; + +const char** DivPlatformYM2203::getRegisterSheet() { + return regCheatSheetYM2203; +} + +const char* DivPlatformYM2203::getEffectName(unsigned char effect) { + switch (effect) { + 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; +} + +void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os; + + ymfm::ym2203::fm_engine* fme=fm->debug_fm_engine(); + + ymfm::fm_channel>* fmChan[3]; + for (int i=0; i<3; i++) { + fmChan[i]=fme->debug_channel(i); + } + + for (size_t h=start; hwrite(0x0,w.addr); + fm->write(0x1,w.val); + regPool[w.addr&0xff]=w.val; + writes.pop(); + delay=6; + } + } + + 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<3; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + for (int i=3; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]; + } + } +} + +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 (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.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.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)); + } + 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 (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 (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<256; 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 (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + 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)); + } + 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 (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + 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_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_FNUM_BLOCK(c.value2,11); + 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_FNUM_BLOCK(c.value,11); + chan[c.chan].freqChanged=true; + 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 (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + 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; + } + 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() { + 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 (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); + } + } + 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)); + 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 256; +} + +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,256); + 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<256; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + sampleBank=0; + + delay=0; + + extMode=false; + + 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); + fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); + // 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..ab527eea --- /dev/null +++ b/src/engine/platform/ym2203.h @@ -0,0 +1,132 @@ +/** + * 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[3]={ + 0x00, 0x01, 0x02 + }; + + 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; + 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) {} + }; + 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[256]; + short pendingWrites[256]; + + 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/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp new file mode 100644 index 00000000..9ca77e9f --- /dev/null +++ b/src/engine/platform/ym2203ext.cpp @@ -0,0 +1,564 @@ +/** + * 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 "ym2203ext.h" +#include "../engine.h" +#include + +#include "ym2610shared.h" +#include "fmshared_OPN.h" + +int DivPlatformYM2203Ext::dispatch(DivCommand c) { + if (c.chan<2) { + return DivPlatformYM2203::dispatch(c); + } + if (c.chan>5) { + c.chan-=3; + return DivPlatformYM2203::dispatch(c); + } + int ch=c.chan-2; + int ordch=orderedOps[ch]; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + // TODO: how does this work?! + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else { + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } + } + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+0x70,op.d2r&31); + rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+0x90,op.ssgEnv&15); + } + if (opChan[ch].insChanged) { // TODO how does this work? + rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + } + opChan[ch].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].portaPause=false; + opChan[ch].freqChanged=true; + } + opChan[ch].keyOn=true; + opChan[ch].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + opChan[ch].keyOff=true; + opChan[ch].keyOn=false; + opChan[ch].active=false; + break; + case DIV_CMD_VOLUME: { + opChan[ch].vol=c.value; + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } + break; + } + case DIV_CMD_GET_VOLUME: { + return opChan[ch].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (opChan[ch].ins!=c.value || c.value2==1) { + opChan[ch].insChanged=true; + } + opChan[ch].ins=c.value; + break; + case DIV_CMD_PANNING: { + if (c.value==0 && c.value2==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=(c.value2>0)|((c.value>0)<<1); + } + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + if (parent->song.sharedExtStat) { + for (int i=0; i<4; i++) { + if (ch==i) continue; + opChan[i].pan=opChan[ch].pan; + } + } + rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + opChan[ch].pitch=c.value; + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (parent->song.linearPitch==2) { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>opChan[ch].baseFreq) { + opChan[ch].baseFreq+=c.value; + if (opChan[ch].baseFreq>=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } else { + opChan[ch].baseFreq-=c.value; + if (opChan[ch].baseFreq<=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } + opChan[ch].freqChanged=true; + if (return2) { + //opChan[ch].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 (opChan[ch].portaPause) { + opChan[ch].baseFreq=opChan[ch].portaPauseFreq; + } + if (destFreq>opChan[ch].baseFreq) { + newFreq=opChan[ch].baseFreq+c.value; + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=opChan[ch].baseFreq-c.value; + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + // what the heck! + if (!opChan[ch].portaPause) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800); + } + } + if ((newFreq&0x7ff)0) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800); + } + } + } + opChan[ch].portaPause=false; + opChan[ch].freqChanged=true; + opChan[ch].baseFreq=newFreq; + if (return2) return 2; + break; + } + case DIV_CMD_LEGATO: { + opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_MULT: { // TODO + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { // TODO + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM); + if (isOutput[ins->fm.alg][c.value]) { + rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,c.value2); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[2].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+0x50,(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[2].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[2]|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[2].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[2]|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[2].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[2]|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[2].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[2]|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[2].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[2]|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[2].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[2]|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[2].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[2]|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[2].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[2]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_PRE_PORTA: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +static int opChanOffsL[4]={ + 0xa9, 0xaa, 0xa8, 0xa2 +}; + +static int opChanOffsH[4]={ + 0xad, 0xae, 0xac, 0xa6 +}; + +void DivPlatformYM2203Ext::tick(bool sysTick) { + if (extMode) { + bool writeSomething=false; + unsigned char writeMask=2; + for (int i=0; i<4; i++) { + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn || opChan[i].keyOff) { + writeSomething=true; + writeMask&=~(1<<(4+i)); + opChan[i].keyOff=false; + } + } + if (writeSomething) { + immWrite(0x28,writeMask); + } + } + + DivPlatformYM2203::tick(sysTick); + + bool writeNoteOn=false; + unsigned char writeMask=2; + if (extMode) for (int i=0; i<4; i++) { + if (opChan[i].freqChanged) { + if (parent->song.linearPitch==2) { + opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); + int block=(opChan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + opChan[i].freq=(block<<11)|fNum; + } + if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; + immWrite(opChanOffsH[i],opChan[i].freq>>8); + immWrite(opChanOffsL[i],opChan[i].freq&0xff); + } + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn) { + writeNoteOn=true; + writeMask|=1<<(4+i); + opChan[i].keyOn=false; + } + } + if (writeNoteOn) { + immWrite(0x28,writeMask); + } +} + +void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) { + if (ch<2) { + DivPlatformYM2203::muteChannel(ch,mute); + return; + } + if (ch>5) { + DivPlatformYM2203::muteChannel(ch-3,mute); + return; + } + isOpMuted[ch-2]=mute; + + int ordch=orderedOps[ch-2]; + DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM); + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch-2]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } +} + +void DivPlatformYM2203Ext::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 (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); + } + } + 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)); + 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(); + for (int i=0; i<4; i++) { + opChan[i].insChanged=true; + if (opChan[i].active) { + opChan[i].keyOn=true; + opChan[i].freqChanged=true; + } + } +} + +void* DivPlatformYM2203Ext::getChanState(int ch) { + if (ch>=6) return &chan[ch-3]; + if (ch>=2) return &opChan[ch-2]; + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2203Ext::getOscBuffer(int ch) { + if (ch>=6) return oscBuf[ch-3]; + if (ch<3) return oscBuf[ch]; + return NULL; +} + +void DivPlatformYM2203Ext::reset() { + DivPlatformYM2203::reset(); + + for (int i=0; i<4; i++) { + opChan[i]=DivPlatformYM2203Ext::OpChannel(); + opChan[i].vol=127; + } + + // channel 2 mode + immWrite(0x27,0x40); + extMode=true; +} + +bool DivPlatformYM2203Ext::keyOffAffectsArp(int ch) { + return (ch>8); +} + +void DivPlatformYM2203Ext::notifyInsChange(int ins) { + DivPlatformYM2203::notifyInsChange(ins); + for (int i=0; i<4; i++) { + if (opChan[i].ins==ins) { + opChan[i].insChanged=true; + } + } +} + +int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2203::init(parent,channels,sugRate,flags); + for (int i=0; i<4; i++) { + isOpMuted[i]=false; + } + + reset(); + return 19; +} + +void DivPlatformYM2203Ext::quit() { + DivPlatformYM2203::quit(); +} + +DivPlatformYM2203Ext::~DivPlatformYM2203Ext() { +} diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h new file mode 100644 index 00000000..7dde3abb --- /dev/null +++ b/src/engine/platform/ym2203ext.h @@ -0,0 +1,51 @@ +/** + * 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 "../dispatch.h" + +#include "ym2203.h" + +class DivPlatformYM2203Ext: public DivPlatformYM2203 { + struct OpChannel { + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; + int vol; + unsigned char pan; + OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + }; + OpChannel opChan[4]; + bool isOpMuted[4]; + friend void putDispatchChan(void*,int,int); + public: + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2203Ext(); +}; 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/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp new file mode 100644 index 00000000..65d36a6e --- /dev/null +++ b/src/engine/platform/ym2608.cpp @@ -0,0 +1,1479 @@ +/** + * 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 "../../ta-log.h" +#include +#include + +#include "sound/ymfm/ymfm_opn.h" +#include "ym2610shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 +}; + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetYM2608[]={ + // 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", + // 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** DivPlatformYM2608::getRegisterSheet() { + return regCheatSheetYM2608; +} + +const char* DivPlatformYM2608::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 DivPlatformYM2608::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 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); + } + return 0; +} + +void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + + 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(); + + 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 DivPlatformYM2608::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(0x10b,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(0x109,chan[15].freq&0xff); + immWrite(0x10a,(chan[15].freq>>8)&0xff); + 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); + 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; + } + } + + if (writeRSSOn) { + immWrite(0x10,writeRSSOn); + writeRSSOn=0; + } +} + +int DivPlatformYM2608::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(0x10b,chan[c.chan].outVol); + } + 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); + immWrite(0x103,(s->offB>>13)&0xff); + 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))|2); + immWrite(0x100,(s->isLoopable())?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(0x100,0x01); // reset + immWrite(0x102,0); + immWrite(0x103,0); + immWrite(0x104,0); + immWrite(0x105,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(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(0x102,(s->offB>>5)&0xff); + immWrite(0x103,(s->offB>>13)&0xff); + 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))|2); + immWrite(0x100,(s->isLoopable())?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; + } + if (c.chan>8) { // RSS + if (skipRegisterWrites) break; + 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; + } + 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(0x100,0x01); // reset + break; + } + if (c.chan>8) { + writeRSSOff|=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(0x100,0x01); // reset + break; + } + if (c.chan>8) { + writeRSSOff|=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(0x10b,chan[c.chan].outVol); + break; + } + if (c.chan>8) { // ADPCM-A + 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++) { + 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(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + break; + } + if (c.chan>8) { + 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)); + 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; + } + 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: { + 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; + } + 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 DivPlatformYM2608::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>14) { // ADPCM-B + 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)); + 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 DivPlatformYM2608::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* DivPlatformYM2608::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2608::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformYM2608::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2608::getRegisterPoolSize() { + return 512; +} + +void DivPlatformYM2608::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformYM2608::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformYM2608::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]=DivPlatformYM2608::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; + writeRSSOff=0; + writeRSSOn=0; + + delay=0; + + extMode=false; + + // LFO + immWrite(0x22,0x08); + + // PCM volume + 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 DivPlatformYM2608::isStereo() { + return true; +} + +bool DivPlatformYM2608::keyOffAffectsArp(int ch) { + return (ch>5); +} + +void DivPlatformYM2608::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } + ay->notifyInsChange(ins); +} + +void DivPlatformYM2608::notifyInsDeletion(void* ins) { + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2608::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); +} + +const void* DivPlatformYM2608::getSampleMem(int index) { + return index == 0 ? adpcmBMem : NULL; +} + +size_t DivPlatformYM2608::getSampleMemCapacity(int index) { + return index == 0 ? 262144 : 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++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + chipClock=8000000; + fm=new ymfm::ym2608(iface); + fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); + rate=fm->sample_rate(chipClock); + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); + reset(); + return 16; +} + +void DivPlatformYM2608::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } + ay->quit(); + delete ay; + delete fm; + delete[] adpcmBMem; +} + +DivPlatformYM2608::~DivPlatformYM2608() { +} diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h new file mode 100644 index 00000000..5c673629 --- /dev/null +++ b/src/engine/platform/ym2608.h @@ -0,0 +1,148 @@ +/** + * 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 "ay.h" + +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 + }; + + 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; + + unsigned char* adpcmBMem; + size_t adpcmBMemLen; + DivYM2608Interface iface; + + DivPlatformAY8910* ay; + unsigned char sampleBank; + unsigned char writeRSSOff, writeRSSOn; + + 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); + 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(); +}; +#endif diff --git a/src/engine/platform/ym2608Interface.cpp b/src/engine/platform/ym2608Interface.cpp new file mode 100644 index 00000000..242721f0 --- /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&0x3ffff]; + default: + return 0; + } + return 0; +} + +void DivYM2608Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { +} diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 948479e9..850a8423 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(); @@ -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); @@ -839,9 +839,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat + 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 ccba08de..04c012c3 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(); @@ -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); @@ -817,9 +817,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat + 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; diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp new file mode 100644 index 00000000..145cfe5b --- /dev/null +++ b/src/engine/platform/zxbeeper.cpp @@ -0,0 +1,334 @@ +/** + * 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() { + ulaOut=sOffTimer?0x10:0x08; + return &ulaOut; +} + +int DivPlatformZXBeeper::getRegisterPoolSize() { + return 1; +} + +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; + ulaOut=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..4e5feabf --- /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(64) {} + }; + 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, ulaOut; + + 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/playback.cpp b/src/engine/playback.cpp index 0a4acbee..b7b35b38 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -30,7 +30,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; void DivEngine::nextOrder() { curRow=0; if (repeatPattern) return; - if (++curOrder>=song.ordersLen) { + if (++curOrder>=curSubSong->ordersLen) { endOfSong=true; curOrder=0; } @@ -181,6 +181,7 @@ const char* cmdName[]={ "ES5506_ENVELOPE_RVRAMP", "ES5506_ENVELOPE_K1RAMP", "ES5506_ENVELOPE_K2RAMP", + "ES5506_PAUSE", "ALWAYS_SET_VOLUME" }; @@ -277,28 +278,55 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char void DivEngine::processRow(int i, bool afterDelay) { int whatOrder=afterDelay?chan[i].delayOrder:curOrder; int whatRow=afterDelay?chan[i].delayRow:curRow; - DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false); + DivPattern* pat=curPat[i].getPattern(curOrders->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<(curSubSong->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; @@ -389,7 +417,7 @@ void DivEngine::processRow(int i, bool afterDelay) { bool panChanged=false; // effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -397,24 +425,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); @@ -550,7 +560,7 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xe0: // arp speed if (effectVal>0) { - song.arpLen=effectVal; + curSubSong->arpLen=effectVal; } break; case 0xe1: // portamento up @@ -730,7 +740,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].noteOnInhibit=false; // post effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -748,10 +758,10 @@ void DivEngine::nextRow() { strcpy(pb1,""); strcpy(pb3,""); for (int i=0; iord[i][curOrder]); strcat(pb1,pb); - DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false); + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][curOrder],false); snprintf(pb2,4095,"\x1b[37m %s", formatNote(pat->data[curRow][0],pat->data[curRow][1])); strcat(pb3,pb2); @@ -767,7 +777,7 @@ void DivEngine::nextRow() { snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]); strcat(pb3,pb2); } - for (int j=0; jdata[curRow][4+(j<<1)]==-1) { strcat(pb3,"\x1b[m--"); } else { @@ -799,32 +809,32 @@ void DivEngine::nextRow() { if (changeOrd==-2) changeOrd=curOrder+1; if (changeOrd<=curOrder) endOfSong=true; curOrder=changeOrd; - if (curOrder>=song.ordersLen) { + if (curOrder>=curSubSong->ordersLen) { curOrder=0; endOfSong=true; } changeOrd=-1; } if (haltOn==DIV_HALT_PATTERN) halted=true; - } else if (playing) if (++curRow>=song.patLen) { + } else if (playing) if (++curRow>=curSubSong->patLen) { nextOrder(); if (haltOn==DIV_HALT_PATTERN) halted=true; } if (song.brokenSpeedSel) { - if ((song.patLen&1) && curOrder&1) { - ticks=((curRow&1)?speed2:speed1)*(song.timeBase+1); + if ((curSubSong->patLen&1) && curOrder&1) { + ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); nextSpeed=(curRow&1)?speed1:speed2; } else { - ticks=((curRow&1)?speed1:speed2)*(song.timeBase+1); + ticks=((curRow&1)?speed1:speed2)*(curSubSong->timeBase+1); nextSpeed=(curRow&1)?speed2:speed1; } } else { if (speedAB) { - ticks=speed2*(song.timeBase+1); + ticks=speed2*(curSubSong->timeBase+1); nextSpeed=speed1; } else { - ticks=speed1*(song.timeBase+1); + ticks=speed1*(curSubSong->timeBase+1); nextSpeed=speed2; } speedAB=!speedAB; @@ -832,7 +842,7 @@ void DivEngine::nextRow() { // post row details for (int i=0; iord[i][curOrder],false); if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) { if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { if (!chan[i].legato) { @@ -841,7 +851,7 @@ void DivEngine::nextRow() { if (song.oneTickCut) { bool doPrepareCut=true; - for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { doPrepareCut=false; break; @@ -1010,7 +1020,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { if (--chan[i].arpTicks<1) { - chan[i].arpTicks=song.arpLen; + chan[i].arpTicks=curSubSong->arpLen; chan[i].arpStage++; if (chan[i].arpStage>2) chan[i].arpStage=0; switch (chan[i].arpStage) { @@ -1051,7 +1061,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } - if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond); + if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond); } if (haltOn==DIV_HALT_TICK) halted=true; @@ -1328,11 +1338,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (!freelance && stepPlay!=-1 && subticks==1) { unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC); if (realPos>=size) realPos=size-1; - if (song.hilightA>0) { - if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1; + if (curSubSong->hilightA>0) { + if ((curRow%curSubSong->hilightA)==0 && ticks==1) metroTick[realPos]=1; } - if (song.hilightB>0) { - if ((curRow%song.hilightB)==0 && ticks==1) metroTick[realPos]=2; + if (curSubSong->hilightB>0) { + if ((curRow%curSubSong->hilightB)==0 && ticks==1) metroTick[realPos]=2; } } if (nextTick()) { diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 007a4e17..54d9241c 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -97,6 +97,18 @@ bool DivSample::initInternal(DivSampleDepth d, int count) { dataDPCM=new unsigned char[lengthDPCM]; memset(dataDPCM,0,lengthDPCM); break; + case DIV_SAMPLE_DEPTH_AICA_ADPCM: // AICA ADPCM + if (dataAICA!=NULL) delete[] dataAICA; + lengthAICA=(count+1)/2; + dataAICA=new unsigned char[(lengthAICA+255)&(~0xff)]; + memset(dataAICA,0,(lengthAICA+255)&(~0xff)); + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM + if (dataZ!=NULL) delete[] dataZ; + lengthZ=(count+1)/2; + dataZ=new unsigned char[(lengthZ+255)&(~0xff)]; + memset(dataZ,0,(lengthZ+255)&(~0xff)); + break; case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM if (dataQSoundA!=NULL) delete[] dataQSoundA; lengthQSoundA=(count+1)/2; @@ -666,6 +678,12 @@ void DivSample::render() { } break; } + case DIV_SAMPLE_DEPTH_AICA_ADPCM: // AICA ADPCM + aica_decode(dataAICA,data16,samples); + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM + ymz_decode(dataZ,data16,samples); + break; case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM bs_decode(dataQSoundA,data16,samples); break; @@ -718,6 +736,14 @@ void DivSample::render() { if (accum>127) accum=127; } } + if (depth!=DIV_SAMPLE_DEPTH_AICA_ADPCM) { // AICA ADPCM + if (!initInternal(DIV_SAMPLE_DEPTH_AICA_ADPCM,samples)) return; + aica_encode(data16,dataAICA,(samples+511)&(~0x1ff)); + } + if (depth!=DIV_SAMPLE_DEPTH_YMZ_ADPCM) { // YMZ ADPCM + if (!initInternal(DIV_SAMPLE_DEPTH_YMZ_ADPCM,samples)) return; + ymz_encode(data16,dataZ,(samples+511)&(~0x1ff)); + } if (depth!=DIV_SAMPLE_DEPTH_QSOUND_ADPCM) { // QSound ADPCM if (!initInternal(DIV_SAMPLE_DEPTH_QSOUND_ADPCM,samples)) return; bs_encode(data16,dataQSoundA,samples); @@ -754,6 +780,10 @@ void* DivSample::getCurBuf() { return data1; case DIV_SAMPLE_DEPTH_1BIT_DPCM: return dataDPCM; + case DIV_SAMPLE_DEPTH_AICA_ADPCM: + return dataAICA; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + return dataZ; case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: return dataQSoundA; case DIV_SAMPLE_DEPTH_ADPCM_A: @@ -782,6 +812,10 @@ unsigned int DivSample::getCurBufLen() { return length1; case DIV_SAMPLE_DEPTH_1BIT_DPCM: return lengthDPCM; + case DIV_SAMPLE_DEPTH_AICA_ADPCM: + return lengthAICA; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + return lengthZ; case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: return lengthQSoundA; case DIV_SAMPLE_DEPTH_ADPCM_A: @@ -896,6 +930,8 @@ DivSample::~DivSample() { if (data16) delete[] data16; if (data1) delete[] data1; if (dataDPCM) delete[] dataDPCM; + if (dataAICA) delete[] dataAICA; + if (dataZ) delete[] dataZ; if (dataQSoundA) delete[] dataQSoundA; if (dataA) delete[] dataA; if (dataB) delete[] dataB; diff --git a/src/engine/sample.h b/src/engine/sample.h index 06e1bbdc..78c138c1 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -38,6 +38,8 @@ enum DivResampleFilters { enum DivSampleDepth: unsigned char { DIV_SAMPLE_DEPTH_1BIT=0, DIV_SAMPLE_DEPTH_1BIT_DPCM=1, + DIV_SAMPLE_DEPTH_AICA_ADPCM=2, + DIV_SAMPLE_DEPTH_YMZ_ADPCM=3, DIV_SAMPLE_DEPTH_QSOUND_ADPCM=4, DIV_SAMPLE_DEPTH_ADPCM_A=5, DIV_SAMPLE_DEPTH_ADPCM_B=6, @@ -103,6 +105,8 @@ struct DivSample { // valid values are: // - 0: ZX Spectrum overlay drum (1-bit) // - 1: 1-bit NES DPCM (1-bit) + // - 2: AICA ADPCM + // - 3: YMZ ADPCM // - 4: QSound ADPCM // - 5: ADPCM-A // - 6: ADPCM-B @@ -118,6 +122,8 @@ struct DivSample { short* data16; // 16 unsigned char* data1; // 0 unsigned char* dataDPCM; // 1 + unsigned char* dataAICA; // 2 + unsigned char* dataZ; // 3 unsigned char* dataQSoundA; // 4 unsigned char* dataA; // 5 unsigned char* dataB; // 6 @@ -125,8 +131,8 @@ struct DivSample { unsigned char* dataBRR; // 9 unsigned char* dataVOX; // 10 - unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; - unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; + unsigned int length8, length16, length1, lengthDPCM, lengthAICA, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; + unsigned int off8, off16, off1, offDPCM, offAICA, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX; unsigned int offSegaPCM, offQSound, offX1_010, offES5506, offSU; unsigned int samples; @@ -267,6 +273,8 @@ struct DivSample { data16(NULL), data1(NULL), dataDPCM(NULL), + dataAICA(NULL), + dataZ(NULL), dataQSoundA(NULL), dataA(NULL), dataB(NULL), @@ -277,6 +285,8 @@ struct DivSample { length16(0), length1(0), lengthDPCM(0), + lengthAICA(0), + lengthZ(0), lengthQSoundA(0), lengthA(0), lengthB(0), @@ -287,6 +297,8 @@ struct DivSample { off16(0), off1(0), offDPCM(0), + offAICA(0), + offZ(0), offQSoundA(0), offA(0), offB(0), diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 7a815eb1..216a1bc4 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -19,7 +19,7 @@ #include "song.h" -void DivSong::clearSongData() { +void DivSubSong::clearData() { for (int i=0; iclearData(); + delete i; + } + subsong.clear(); + subsong.push_back(new DivSubSong); +} + void DivSong::clearInstruments() { for (DivInstrument* i: ins) { delete i; @@ -71,7 +80,9 @@ void DivSong::unload() { sample.clear(); sampleLen=0; - for (int i=0; iclearData(); + delete i; } + subsong.clear(); } diff --git a/src/engine/song.h b/src/engine/song.h index 042cf019..81265485 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -65,7 +65,9 @@ enum DivSystem { DIV_SYSTEM_MMC5, DIV_SYSTEM_N163, DIV_SYSTEM_OPN, + DIV_SYSTEM_OPN_EXT, DIV_SYSTEM_PC98, + DIV_SYSTEM_PC98_EXT, DIV_SYSTEM_OPL, DIV_SYSTEM_OPL2, DIV_SYSTEM_OPL3, @@ -109,6 +111,44 @@ enum DivSystem { DIV_SYSTEM_MAX // boundary for max system number }; +struct DivSubSong { + String name, notes; + unsigned char hilightA, hilightB; + unsigned char timeBase, speed1, speed2, arpLen; + bool pal; + bool customTempo; + float hz; + int patLen, ordersLen; + + DivOrders orders; + DivChannelData pat[DIV_MAX_CHANS]; + + bool chanShow[DIV_MAX_CHANS]; + unsigned char chanCollapse[DIV_MAX_CHANS]; + String chanName[DIV_MAX_CHANS]; + String chanShortName[DIV_MAX_CHANS]; + + void clearData(); + + DivSubSong(): + hilightA(4), + hilightB(16), + timeBase(0), + speed1(6), + speed2(6), + arpLen(1), + pal(true), + customTempo(false), + hz(60.0), + patLen(64), + ordersLen(1) { + for (int i=0; i ins; - DivChannelData pat[DIV_MAX_CHANS]; std::vector wave; std::vector sample; - bool chanShow[DIV_MAX_CHANS]; - unsigned char chanCollapse[DIV_MAX_CHANS]; + std::vector subsong; DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound; DivWavetable nullWave; @@ -377,7 +418,6 @@ struct DivSong { */ void clearSamples(); - /** * unloads the song, freeing all memory associated with it. * use before destroying the object. @@ -401,17 +441,6 @@ struct DivSong { manInfo(""), createdDate(""), revisionDate(""), - hilightA(4), - hilightB(16), - timeBase(0), - speed1(6), - speed2(6), - arpLen(1), - pal(true), - customTempo(false), - hz(60.0), - patLen(64), - ordersLen(1), insLen(0), waveLen(0), sampleLen(0), @@ -457,10 +486,7 @@ struct DivSong { systemPan[i]=0; systemFlags[i]=0; } - for (int i=0; ichans) return "??"; - if (!song.chanName[chan].empty()) return song.chanName[chan].c_str(); + if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str(); if (sysDefs[sysOfChan[chan]]==NULL) return "??"; const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]]; @@ -293,7 +293,7 @@ const char* DivEngine::getChannelName(int chan) { const char* DivEngine::getChannelShortName(int chan) { if (chan<0 || chan>chans) return "??"; - if (!song.chanShortName[chan].empty()) return song.chanShortName[chan].c_str(); + if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str(); if (sysDefs[sysOfChan[chan]]==NULL) return "??"; const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]]; @@ -734,6 +734,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YMU759]=new DivSysDef( "Yamaha YMU759 (MA-2)", NULL, 0x01, 0x01, 17, true, false, 0, false, + "a chip which found its way inside mobile phones in the 2000's.\nas proprietary as it is, it passed away after losing to MP3 in the mobile hardware battle.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM" }, // name {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM" }, // short {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_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM }, // type @@ -742,16 +743,19 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_GENESIS]=new DivSysDef( "Sega Genesis/Mega Drive", "セガメガドライブ", 0x02, 0x02, 10, true, true, 0, true, + "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_GENESIS_EXT]=new DivSysDef( "Sega Genesis Extended Channel 3", NULL, 0x42, 0x42, 13, true, true, 0, true, + "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_SMS]=new DivSysDef( "TI SN76489", NULL, 0x03, 0x03, 4, false, true, 0x150, false, + "a square/noise sound chip found on the Sega Master System, ColecoVision, Tandy, TI's own 99/4A and a few other places.", {"Square 1", "Square 2", "Square 3", "Noise"}, {"S1", "S2", "S3", "NO"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, @@ -771,11 +775,13 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SMS_OPLL]=new DivSysDef( "Sega Master System + FM Expansion", NULL, 0x43, 0x43, 13, true, true, 0, true, + "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_GB]=new DivSysDef( "Game Boy", NULL, 0x04, 0x04, 4, false, true, 0x161, false, + "the most popular portable game console of the era.", {"Pulse 1", "Pulse 2", "Wavetable", "Noise"}, {"S1", "S2", "WA", "NO"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE}, @@ -804,6 +810,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_PCE]=new DivSysDef( "PC Engine/TurboGrafx-16", NULL, 0x05, 0x05, 6, false, true, 0x161, false, + "an '80's game console with a wavetable sound chip, popular in Japan.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, @@ -835,6 +842,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_NES]=new DivSysDef( "NES (Ricoh 2A03)", NULL, 0x06, 0x06, 5, false, true, 0x161, false, + "also known as Famicom in Japan, it's the most well-known game console of the '80's.", {"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"}, {"S1", "S2", "TR", "NO", "PCM"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE, DIV_CH_PCM}, @@ -866,16 +874,19 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_NES_VRC7]=new DivSysDef( "NES + Konami VRC7", NULL, 0x46, 0x46, 11, true, true, 0, true, + "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_NES_FDS]=new DivSysDef( "Famicom Disk System", NULL, 0, 0x86, 6, false, true, 0, true, + "", {}, {}, {}, {} ); sysDefs[DIV_SYSTEM_C64_6581]=new DivSysDef( "Commodore 64 (6581)", NULL, 0x47, 0x47, 3, false, true, 0, false, + "this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.", {"Channel 1", "Channel 2", "Channel 3"}, {"CH1", "CH2", "CH3"}, {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, @@ -887,6 +898,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_C64_8580]=new DivSysDef( "Commodore 64 (8580)", NULL, 0x07, 0x07, 3, false, true, 0, false, + "this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.\nthis is the newer revision of the chip.", {"Channel 1", "Channel 2", "Channel 3"}, {"CH1", "CH2", "CH3"}, {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, @@ -898,6 +910,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_ARCADE]=new DivSysDef( "DefleCade", NULL, 0x08, 0x08, 13, true, false, 0, true, + "", {}, {}, {}, {} ); @@ -914,6 +927,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef( "Neo Geo CD", NULL, 0x09, 0x09, 13, true, true, 0x151, false, + "like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.", {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"}, {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -925,6 +939,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef( "Neo Geo CD Extended Channel 2", NULL, 0x49, 0x49, 16, true, true, 0x151, false, + "like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"}, {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, {DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -936,6 +951,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef( "AY-3-8910", NULL, 0x80, 0, 3, false, true, 0x151, false, + "this chip is everywhere! ZX Spectrum, MSX, Amstrad CPC, Intellivision, Vectrex...\nthe discovery of envelope bass helped it beat the SN76489 with ease.", {"PSG 1", "PSG 2", "PSG 3"}, {"S1", "S2", "S3"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, @@ -947,6 +963,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_AMIGA]=new DivSysDef( "Amiga", NULL, 0x81, 0, 4, false, true, 0, false, + "a computer from the '80's with full sampling capabilities, giving it a sound ahead of its time.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -973,6 +990,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2151]=new DivSysDef( "Yamaha YM2151 (OPM)", NULL, 0x82, 0, 8, true, false, 0x150, false, + "this was Yamaha's first integrated FM chip.\nit was used in several synthesizers, computers and arcade boards.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, @@ -998,6 +1016,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef( "Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false, + "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, {"F1", "F2", "F3", "F4", "F5", "F6"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, @@ -1009,6 +1028,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_TIA]=new DivSysDef( "Atari 2600", NULL, 0x84, 0, 2, false, true, 0, false, + "it's a challenge to make music on this chip which barely has musical capabilities...", {"Channel 1", "Channel 2"}, {"CH1", "CH2"}, {DIV_CH_WAVE, DIV_CH_WAVE}, @@ -1029,6 +1049,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SAA1099]=new DivSysDef( "Philips SAA1099", NULL, 0x97, 0, 6, false, true, 0x171, false, + "supposedly an upgrade from the AY-3-8910, this was present on the Creative Music System (Game Blaster) and SAM Coupé.", {"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6"}, {"S1", "S2", "S3", "S4", "S5", "S6"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, @@ -1055,6 +1076,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_AY8930]=new DivSysDef( "Microchip AY8930", NULL, 0x9a, 0, 3, false, true, 0x151, false, + "an improved version of the AY-3-8910 with a bigger frequency range, duty cycles, configurable noise and per-channel envelopes!", {"PSG 1", "PSG 2", "PSG 3"}, {"S1", "S2", "S3"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, @@ -1077,6 +1099,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_VIC20]=new DivSysDef( "Commodore VIC-20", NULL, 0x85, 0, 4, false, true, 0, false, + "Commodore's successor to the PET.\nits square wave channels are more than just square...", {"Low", "Mid", "High", "Noise"}, {"LO", "MID", "HI", "NO"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, @@ -1087,6 +1110,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_PET]=new DivSysDef( "Commodore PET", NULL, 0x86, 0, 1, false, true, 0, false, + "one channel of 1-bit wavetable which is better (and worse) than the PC Speaker.", {"Wave"}, {"PET"}, {DIV_CH_PULSE}, @@ -1097,6 +1121,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SNES]=new DivSysDef( "SNES", NULL, 0x87, 0, 8, false, true, 0, false, + "FM? nah... samples! Nintendo's answer to Sega.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1105,6 +1130,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_VRC6]=new DivSysDef( "Konami VRC6", NULL, 0x88, 0, 3, false, true, 0, false, + "an expansion chip for the Famicom, featuring a quirky sawtooth channel.", {"VRC6 1", "VRC6 2", "VRC6 Saw"}, {"V1", "V2", "VS"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE}, @@ -1138,6 +1164,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPLL]=new DivSysDef( "Yamaha YM2413 (OPLL)", NULL, 0x89, 0, 9, true, false, 0x150, false, + "cost-reduced version of the OPL with 16 patches and only one of them is user-configurable.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, {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}, @@ -1149,6 +1176,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_FDS]=new DivSysDef( "Famicom Disk System (chip)", NULL, 0x8a, 0, 1, false, true, 0x161, false, + "a disk drive for the Famicom which also contains one wavetable channel.", {"FDS"}, {"FDS"}, {DIV_CH_WAVE}, @@ -1183,6 +1211,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_MMC5]=new DivSysDef( "MMC5", NULL, 0x8b, 0, 3, false, true, 0, false, + "an expansion chip for the Famicom, featuring a little-known PCM channel.", {"Pulse 1", "Pulse 2", "PCM"}, {"S1", "S2", "PCM"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM}, @@ -1205,6 +1234,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_N163]=new DivSysDef( "Namco 163", NULL, 0x8c, 0, 8, false, true, 0, false, + "an expansion chip for the Famicom, with full wavetable.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, @@ -1259,23 +1289,56 @@ 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, true, 0x151, false, + "cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)", {"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_OPN_EXT]=new DivSysDef( + "Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false, + "cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies", + {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "PSG 1", "PSG 2", "PSG 3"}, + {"F1", "F2", "O1", "O2", "O3", "O4", "S1", "S2", "S3"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, + {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}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler ); sysDefs[DIV_SYSTEM_PC98]=new DivSysDef( - "Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, false, 0, false, + "Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0, false, + "OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.", {"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_PC98_EXT]=new DivSysDef( + "Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0, false, + "OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies", + {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, + {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, 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_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( "Yamaha YM3526 (OPL)", NULL, 0x8f, 0, 9, true, false, 0x151, false, + "OPN, but what if you only had two operators, no stereo, no detune and a lower ADSR parameter range?", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, {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}, @@ -1287,6 +1350,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPL2]=new DivSysDef( "Yamaha YM3812 (OPL2)", NULL, 0x90, 0, 9, true, false, 0x151, false, + "OPL, but what if you had more waveforms to choose than the normal sine?", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"}, {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}, @@ -1298,6 +1362,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPL3]=new DivSysDef( "Yamaha YMF262 (OPL3)", NULL, 0x91, 0, 18, true, false, 0x151, false, + "OPL2, but what if you had twice the channels, 4-op mode, stereo and even more waveforms?", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, @@ -1309,6 +1374,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef( "MultiPCM", NULL, 0x92, 0, 28, false, true, 0, false, + "how many channels of PCM do you want?\nMultiPCM: yes", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1317,14 +1383,16 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef( "PC Speaker", NULL, 0x93, 0, 1, false, true, 0, false, + "good luck!", {"Square"}, {"SQ"}, {DIV_CH_PULSE}, - {DIV_INS_STD} + {DIV_INS_BEEPER} ); sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef( "POKEY", NULL, 0x94, 0, 4, false, true, 0, false, + "TIA, but better and more flexible.\nused in the Atari 8-bit family of computers (400/800/XL/XE).", {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, @@ -1333,6 +1401,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef( "Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0, false, + "this is like SNES' sound chip but without interpolation and the rest of nice bits.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1341,6 +1410,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef( "WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false, + "developed by the same team under the Game Boy and the Virtual Boy...", {"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_WAVE, DIV_CH_PCM, DIV_CH_WAVE, DIV_CH_NOISE}, @@ -1372,6 +1442,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPZ]=new DivSysDef( "Yamaha YM2414 (OPZ)", NULL, 0x98, 0, 8, true, false, 0, false, + "like OPM, but with more waveforms, fixed frequency mode and totally... undocumented.\nused in the Yamaha TX81Z and some other synthesizers.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, @@ -1392,6 +1463,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_POKEMINI]=new DivSysDef( "Pokémon Mini", NULL, 0x99, 0, 1, false, true, 0, false, + "this one is like PC Speaker but has duty cycles.", {"Square"}, {"SQ"}, {DIV_CH_PULSE}, @@ -1400,6 +1472,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SEGAPCM]=new DivSysDef( "SegaPCM", NULL, 0x9b, 0, 16, false, true, 0x151, false, + "used in some Sega arcade boards (like OutRun), and usually paired with a YM2151.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1411,6 +1484,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef( "Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0, false, + "a console which failed to sell well due to its headache-inducing features.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_NOISE}, @@ -1419,6 +1493,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef( "Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false, + "like OPLL, but even more cost reductions applied. three less FM channels, and no drums mode...", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, {"F1", "F2", "F3", "F4", "F5", "F6"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, @@ -1430,6 +1505,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2610B]=new DivSysDef( "Yamaha YM2610B (OPNB-B)", NULL, 0x9e, 0, 16, true, false, 0x151, false, + "so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {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_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1441,6 +1517,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef( "ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, + "the ZX Spectrum only had a basic beeper capable of...\n...a bunch of thin pulses and tons of other interesting stuff!\nFurnace provides a thin pulse system.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, @@ -1449,6 +1526,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef( "Yamaha YM2612 (OPN2) Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false, + "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6"}, {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, @@ -1459,11 +1537,14 @@ 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, + "a wavetable chip made by Konami for use with the MSX.\nthe last channel shares its wavetable with the previous one though.", {"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 { @@ -1482,6 +1563,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPL_DRUMS]=new DivSysDef( "Yamaha YM3526 (OPL) with drums", NULL, 0xa2, 0, 11, true, false, 0x151, false, + "the OPL chip but with drums mode enabled.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, {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}, @@ -1493,6 +1575,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPL2_DRUMS]=new DivSysDef( "Yamaha YM3812 (OPL2) with drums", NULL, 0xa3, 0, 11, true, false, 0x151, false, + "the OPL2 chip but with drums mode enabled.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, {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}, @@ -1504,6 +1587,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPL3_DRUMS]=new DivSysDef( "Yamaha YMF262 (OPL3) with drums", NULL, 0xa4, 0, 20, true, false, 0x151, false, + "the OPL3 chip but with drums mode enabled.", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, 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}, @@ -1515,6 +1599,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2610_FULL]=new DivSysDef( "Yamaha YM2610 (OPNB)", NULL, 0xa5, 0, 14, true, false, 0x151, false, + "this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.", {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1526,6 +1611,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2610_FULL_EXT]=new DivSysDef( "Yamaha YM2610 (OPNB) Extended Channel 2", NULL, 0xa6, 0, 17, true, false, 0x151, false, + "this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1537,6 +1623,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef( "Yamaha YM2413 (OPLL) with drums", NULL, 0xa7, 0, 11, true, false, 0x150, false, + "the OPLL chips but with drums mode turned on.", {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, {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}, @@ -1548,6 +1635,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_LYNX]=new DivSysDef( "Atari Lynx", NULL, 0xa8, 0, 4, false, true, 0, false, + "a portable console made by Atari. it has all of Atari's trademark waveforms.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, @@ -1566,6 +1654,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_QSOUND]=new DivSysDef( "Capcom QSound", NULL, 0xe0, 0, 19, false, true, 0x161, false, + "used in some of Capcom's arcade boards. surround-like sampled sound with echo.", {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, @@ -1596,6 +1685,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_VERA]=new DivSysDef( "VERA", NULL, 0xac, 0, 17, false, true, 0, false, + "the chip used in a computer design created by The 8-Bit Guy.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM}, @@ -1618,6 +1708,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_YM2610B_EXT]=new DivSysDef( "Yamaha YM2610B (OPNB-B) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false, + "so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1629,6 +1720,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef( "SegaPCM (compatible 5-channel mode)", NULL, 0xa9, 0, 5, false, true, 0x151, false, + "this is the same thing as SegaPCM, but only exposes 5 of the channels for compatibility with DefleMask.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"P1", "P2", "P3", "P4", "P5"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1640,6 +1732,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_X1_010]=new DivSysDef( "Seta/Allumer X1-010", NULL, 0xb0, 0, 16, false, true, 0x171, false, + "a sound chip used in several Seta/Allumer-manufactured arcade boards with too many channels of wavetable sound, which also are capable of sampled sound.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, @@ -1690,6 +1783,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_BUBSYS_WSG]=new DivSysDef( "Konami Bubble System WSG", NULL, 0xad, 0, 2, false, true, 0, false, + "this is the wavetable part of the Bubble System, which also had two AY-3-8910s.", {"Channel 1", "Channel 2"}, {"CH1", "CH2"}, {DIV_CH_WAVE, DIV_CH_WAVE}, @@ -1701,6 +1795,7 @@ void DivEngine::registerSystems() { // to Grauw: feel free to change this to 24 during development of OPL4's PCM part. sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef( "Yamaha OPL4", NULL, 0xae, 0, 42, true, true, 0, false, + "like OPL3, but this time it also has a 24-channel version of MultiPCM.", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, 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_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1709,6 +1804,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef( "Yamaha OPL4 with drums", NULL, 0xaf, 0, 44, true, true, 0, false, + "the OPL4 but with drums mode turned on.", {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, 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_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1717,6 +1813,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef( "Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0/*0x171*/, false, + "a sample chip used in the Ensoniq's unique transwave synthesizers, \nand SoundScape series PC ISA soundcards...\nthat's just yet another (partially)SB compatible one with emulated OPL3 and MIDI ROMpler.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1742,14 +1839,6 @@ void DivEngine::registerSystems() { case 0x17: // filter coefficient K2, 8 bit MSB dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2,ch,(effectVal&0xff)<<8,0xff00)); break; - case 0x18: // filter coefficient K1 slide up - case 0x19: // filter coefficient K1 slide down - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1_SLIDE,ch,effectVal,effect&0x01)); - break; - case 0x1a: // filter coefficient K2 slide up - case 0x1b: // filter coefficient K2 slide down - dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2_SLIDE,ch,effectVal,effect&0x01)); - break; case 0x20: case 0x21: // envelope ECOUNT dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_COUNT,ch,((effect&0x01)<<8)|effectVal)); @@ -1779,35 +1868,66 @@ void DivEngine::registerSystems() { break; } return true; + }, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x12: // pause + dispatchCmd(DivCommand(DIV_CMD_ES5506_PAUSE,ch,effectVal&1)); + break; + case 0x18: // filter coefficient K1 slide up + case 0x19: // filter coefficient K1 slide down + dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1_SLIDE,ch,effectVal,effect&0x01)); + break; + case 0x1a: // filter coefficient K2 slide up + case 0x1b: // filter coefficient K2 slide down + dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2_SLIDE,ch,effectVal,effect&0x01)); + break; + default: + return false; + break; + } + return true; } ); sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef( "Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0, false, + "like OPL but with an ADPCM channel.", {"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( "Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0, false, + "the Y8950 chip, in drums mode.", {"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( - "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0, false, + "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false, + "this is a variant of Konami's SCC chip with the last channel's wavetable being independent.", {"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( "tildearrow Sound Unit", NULL, 0xb5, 0, 8, false, true, 0, false, + "tildearrow's fantasy sound chip. put SID, AY and VERA in a blender, and you get this!", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, @@ -1817,6 +1937,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef( "OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0, false, + "an ADPCM sound chip manufactured by OKI and used in many arcade boards.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, @@ -1825,6 +1946,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef( "OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0, false, + "an ADPCM sound chip manufactured by OKI and used in the Sharp X68000.", {"Sample"}, {"PCM"}, {DIV_CH_PCM}, @@ -1833,6 +1955,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef( "Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, + "this is a system designed for testing purposes..", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 2b2d3362..373cf1ed 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -564,6 +564,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: @@ -637,6 +642,71 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_SCC: + 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<0x8a) { + w->writeC(0xd2); + 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(baseAddr2|2); + w->writeC((write.addr-0x8a)&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr<0x90) { + w->writeC(0xd2); + 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(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; @@ -876,11 +946,41 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { } break; case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: + case DIV_SYSTEM_AY8930: { if (!hasAY) { + bool hasClockDivider=false; // Configurable clock divider + bool hasStereo=true; // Stereo hasAY=disCont[i].dispatch->chipClock; - ayConfig=(song.system[i]==DIV_SYSTEM_AY8930)?3:0; ayFlags=1; + if (song.system[i]==DIV_SYSTEM_AY8930) { // AY8930 + ayConfig=0x03; + hasClockDivider=true; + } else { + switch ((song.systemFlags[i]>>4)&3) { + default: + case 0: // AY8910 + ayConfig=0x00; + break; + case 1: // YM2149 + ayConfig=0x10; + hasClockDivider=true; + break; + case 2: // Sunsoft 5B + ayConfig=0x10; + ayFlags|=0x12; // Clock internally divided, Single sound output + hasStereo=false; // due to above, can't be per-channel stereo configurable + break; + case 3: // AY8914 + ayConfig=0x04; + break; + } + } + if (hasClockDivider && ((song.systemFlags[i]>>7)&1)) { + ayFlags|=0x10; + } + if (hasStereo && ((song.systemFlags[i]>>6)&1)) { + ayFlags|=0x80; + } willExport[i]=true; } else if (!(hasAY&0x40000000)) { isSecond[i]=true; @@ -889,6 +989,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + } case DIV_SYSTEM_SAA1099: if (!hasSAA) { hasSAA=disCont[i].dispatch->chipClock; @@ -924,6 +1025,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: @@ -1042,6 +1155,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; } diff --git a/src/gui/about.cpp b/src/gui/about.cpp index ce543f5f..76fc80ad 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -91,7 +91,7 @@ const char* aboutLine[]={ "ymfm by Aaron Giles", "MAME SN76496 by Nicola Salmoria", "MAME AY-3-8910 by Couriersud", - "with AY8930 fixes by Eulous", + "with AY8930 fixes by Eulous, cam900 and Grauw", "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", diff --git a/src/gui/actionUtil.h b/src/gui/actionUtil.h index 752a5442..81685af4 100644 --- a/src/gui/actionUtil.h +++ b/src/gui/actionUtil.h @@ -20,7 +20,7 @@ #define DETERMINE_FIRST \ int firstChannel=0; \ for (int i=0; igetTotalChannelCount(); i++) { \ - if (e->song.chanShow[i]) { \ + if (e->curSubSong->chanShow[i]) { \ firstChannel=i; \ break; \ } \ @@ -29,7 +29,7 @@ #define DETERMINE_LAST \ int lastChannel=0; \ for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ - if (e->song.chanShow[i]) { \ + if (e->curSubSong->chanShow[i]) { \ lastChannel=i+1; \ break; \ } \ diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 397ac2f6..c8807561 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -38,7 +38,7 @@ void FurnaceGUI::drawChannels() { ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Checkbox("##Visible",&e->song.chanShow[i]); + ImGui::Checkbox("##Visible",&e->curSubSong->chanShow[i]); ImGui::SameLine(); ImGui::BeginDisabled(i==0); if (ImGui::Button(ICON_FA_CHEVRON_UP)) { @@ -53,10 +53,10 @@ void FurnaceGUI::drawChannels() { ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); + ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->curSubSong->chanName[i]); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]); + ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->curSubSong->chanShortName[i]); ImGui::PopID(); } ImGui::EndTable(); diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 6bf4e76f..24488073 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->curPat[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->curPat[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(); @@ -73,21 +94,21 @@ void FurnaceGUI::finishSelection() { if (selStart.xCoarse<0) selStart.xCoarse=0; if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; if (selStart.y<0) selStart.y=0; - if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1; + if (selStart.y>=e->curSubSong->patLen) selStart.y=e->curSubSong->patLen-1; if (selEnd.xCoarse<0) selEnd.xCoarse=0; if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; if (selEnd.y<0) selEnd.y=0; - if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1; + if (selEnd.y>=e->curSubSong->patLen) selEnd.y=e->curSubSong->patLen-1; if (cursor.xCoarse<0) cursor.xCoarse=0; if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; if (cursor.y<0) cursor.y=0; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; - if (e->song.chanCollapse[selStart.xCoarse]==3) { + if (e->curSubSong->chanCollapse[selStart.xCoarse]==3) { selStart.xFine=0; } - if (e->song.chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->song.chanCollapse[selEnd.xCoarse])) { - selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + if (e->curSubSong->chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->curSubSong->chanCollapse[selEnd.xCoarse])) { + selEnd.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; } e->setMidiBaseChan(cursor.xCoarse); @@ -105,7 +126,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { demandScrollX=true; if (x>0) { for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?(4-e->song.chanCollapse[cursor.xCoarse]):(3+e->song.pat[cursor.xCoarse].effectCols*2))) { + if (++cursor.xFine>=(e->curSubSong->chanCollapse[cursor.xCoarse]?(4-e->curSubSong->chanCollapse[cursor.xCoarse]):(3+e->curPat[cursor.xCoarse].effectCols*2))) { cursor.xFine=0; if (++cursor.xCoarse>=lastChannel) { if (settings.wrapHorizontal!=0 && !select) { @@ -113,10 +134,10 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (settings.wrapHorizontal==2) y++; } else { cursor.xCoarse=lastChannel-1; - cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?(3-e->song.chanCollapse[cursor.xCoarse]):(2+e->song.pat[cursor.xCoarse].effectCols*2); + cursor.xFine=e->curSubSong->chanCollapse[cursor.xCoarse]?(3-e->curSubSong->chanCollapse[cursor.xCoarse]):(2+e->curPat[cursor.xCoarse].effectCols*2); } } else { - while (!e->song.chanShow[cursor.xCoarse]) { + while (!e->curSubSong->chanShow[cursor.xCoarse]) { cursor.xCoarse++; if (cursor.xCoarse>=e->getTotalChannelCount()) break; } @@ -129,21 +150,21 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; if (settings.wrapHorizontal==2) y--; } else { cursor.xCoarse=firstChannel; cursor.xFine=0; } } else { - while (!e->song.chanShow[cursor.xCoarse]) { + while (!e->curSubSong->chanShow[cursor.xCoarse]) { cursor.xCoarse--; if (cursor.xCoarse<0) break; } - if (e->song.chanCollapse[cursor.xCoarse]) { - cursor.xFine=3-e->song.chanCollapse[cursor.xCoarse]; + if (e->curSubSong->chanCollapse[cursor.xCoarse]) { + cursor.xFine=3-e->curSubSong->chanCollapse[cursor.xCoarse]; } else { - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; } } } @@ -154,18 +175,18 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (y>0) { for (int i=0; i=e->song.patLen) { + if (cursor.y>=e->curSubSong->patLen) { if (settings.wrapVertical!=0 && !select) { cursor.y=0; if (settings.wrapVertical==2) { - if ((!e->isPlaying() || !followPattern) && curOrder<(e->song.ordersLen-1)) { + if ((!e->isPlaying() || !followPattern) && curOrder<(e->curSubSong->ordersLen-1)) { setOrder(curOrder+1); } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } } } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } } } @@ -174,7 +195,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { cursor.y--; if (cursor.y<0) { if (settings.wrapVertical!=0 && !select) { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; if (settings.wrapVertical==2) { if ((!e->isPlaying() || !followPattern) && curOrder>0) { setOrder(curOrder-1); @@ -208,7 +229,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) { do { cursor.xCoarse--; if (cursor.xCoarse<0) break; - } while (!e->song.chanShow[cursor.xCoarse]); + } while (!e->curSubSong->chanShow[cursor.xCoarse]); if (cursor.xCoarse=e->getTotalChannelCount()) break; - } while (!e->song.chanShow[cursor.xCoarse]); + } while (!e->curSubSong->chanShow[cursor.xCoarse]); if (cursor.xCoarse>=lastChannel) { if (overflow) { cursor.xCoarse=firstChannel; @@ -269,14 +290,14 @@ void FurnaceGUI::moveCursorTop(bool select) { void FurnaceGUI::moveCursorBottom(bool select) { finishSelection(); curNibble=false; - if (cursor.y==e->song.patLen-1) { + if (cursor.y==e->curSubSong->patLen-1) { DETERMINE_LAST; cursor.xCoarse=lastChannel-1; if (cursor.xCoarse<0) cursor.xCoarse=0; - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; demandScrollX=true; } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } if (!select) { selStart=cursor; @@ -289,7 +310,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { void FurnaceGUI::editAdvance() { finishSelection(); cursor.y+=editStep; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; selStart=cursor; selEnd=cursor; updateScroll(cursor.y); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index d41eacf1..c988ab69 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -153,6 +153,8 @@ void FurnaceGUI::drawDebug() { ImGui::Text("rate: %d",sample->rate); ImGui::Text("centerRate: %d",sample->centerRate); ImGui::Text("loopStart: %d",sample->loopStart); + ImGui::Text("loopEnd: %d",sample->loopEnd); + ImGui::Text("loopMode: %d",(int)(sample->loopMode)); ImGui::Text("loopOffP: %d",sample->loopOffP); ImGui::Text("depth: %d",sample->depth); ImGui::Text("length8: %d",sample->length8); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 1488a727..82f3da50 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -470,7 +470,7 @@ void FurnaceGUI::doAction(int what) { e->unmuteAll(); break; case GUI_ACTION_PAT_NEXT_ORDER: - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } break; @@ -481,21 +481,21 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_PAT_COLLAPSE: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - if (e->song.chanCollapse[cursor.xCoarse]==0) { - e->song.chanCollapse[cursor.xCoarse]=3; - } else if (e->song.chanCollapse[cursor.xCoarse]>0) { - e->song.chanCollapse[cursor.xCoarse]--; + if (e->curSubSong->chanCollapse[cursor.xCoarse]==0) { + e->curSubSong->chanCollapse[cursor.xCoarse]=3; + } else if (e->curSubSong->chanCollapse[cursor.xCoarse]>0) { + e->curSubSong->chanCollapse[cursor.xCoarse]--; } break; case GUI_ACTION_PAT_INCREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectCols++; - if (e->song.pat[cursor.xCoarse].effectCols>8) e->song.pat[cursor.xCoarse].effectCols=8; + e->curPat[cursor.xCoarse].effectCols++; + if (e->curPat[cursor.xCoarse].effectCols>8) e->curPat[cursor.xCoarse].effectCols=8; break; case GUI_ACTION_PAT_DECREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectCols--; - if (e->song.pat[cursor.xCoarse].effectCols<1) e->song.pat[cursor.xCoarse].effectCols=1; + e->curPat[cursor.xCoarse].effectCols--; + if (e->curPat[cursor.xCoarse].effectCols<1) e->curPat[cursor.xCoarse].effectCols=1; break; case GUI_ACTION_PAT_INTERPOLATE: doInterpolate(); @@ -525,14 +525,22 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_ADD: curIns=e->addInstrument(cursor.xCoarse); - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + MARK_MODIFIED; + } break; case GUI_ACTION_INS_LIST_DUPLICATE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { int prevIns=curIns; curIns=e->addInstrument(cursor.xCoarse); - (*e->song.ins[curIns])=(*e->song.ins[prevIns]); - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + (*e->song.ins[curIns])=(*e->song.ins[prevIns]); + MARK_MODIFIED; + } } break; case GUI_ACTION_INS_LIST_OPEN: @@ -571,14 +579,22 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_ADD: curWave=e->addWave(); - MARK_MODIFIED; + if (curWave==-1) { + showError("too many wavetables!"); + } else { + MARK_MODIFIED; + } break; case GUI_ACTION_WAVE_LIST_DUPLICATE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { int prevWave=curWave; curWave=e->addWave(); - (*e->song.wave[curWave])=(*e->song.wave[prevWave]); - MARK_MODIFIED; + if (curWave==-1) { + showError("too many wavetables!"); + } else { + (*e->song.wave[curWave])=(*e->song.wave[prevWave]); + MARK_MODIFIED; + } } break; case GUI_ACTION_WAVE_LIST_OPEN: @@ -614,33 +630,41 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); + if (curSample==-1) { + showError("too many samples!"); + } else { + MARK_MODIFIED; + } updateSampleTex=true; - MARK_MODIFIED; break; case GUI_ACTION_SAMPLE_LIST_DUPLICATE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { DivSample* prevSample=e->getSample(curSample); curSample=e->addSample(); - updateSampleTex=true; - e->lockEngine([this,prevSample]() { - DivSample* sample=e->getSample(curSample); - if (sample!=NULL) { - sample->rate=prevSample->rate; - sample->centerRate=prevSample->centerRate; - sample->name=prevSample->name; - sample->loopStart=prevSample->loopStart; - sample->loopEnd=prevSample->loopEnd; - sample->loopMode=prevSample->loopMode; - sample->depth=prevSample->depth; - if (sample->init(prevSample->samples)) { - if (prevSample->getCurBuf()!=NULL) { - memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); + if (curSample==-1) { + showError("too many samples!"); + } else { + e->lockEngine([this,prevSample]() { + DivSample* sample=e->getSample(curSample); + if (sample!=NULL) { + sample->rate=prevSample->rate; + sample->centerRate=prevSample->centerRate; + sample->name=prevSample->name; + sample->loopStart=prevSample->loopStart; + sample->loopEnd=prevSample->loopEnd; + sample->loopMode=prevSample->loopMode; + sample->depth=prevSample->depth; + if (sample->init(prevSample->samples)) { + if (prevSample->getCurBuf()!=NULL) { + memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); + } } } - } - e->renderSamples(); - }); - MARK_MODIFIED; + e->renderSamples(); + }); + MARK_MODIFIED; + } + updateSampleTex=true; } break; case GUI_ACTION_SAMPLE_LIST_OPEN: @@ -1156,11 +1180,15 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; curIns=e->addInstrument(cursor.xCoarse); - e->song.ins[curIns]->type=DIV_INS_AMIGA; - e->song.ins[curIns]->name=sample->name; - e->song.ins[curIns]->amiga.initSample=curSample; - nextWindow=GUI_WINDOW_INS_EDIT; - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + e->song.ins[curIns]->type=DIV_INS_AMIGA; + e->song.ins[curIns]->name=sample->name; + e->song.ins[curIns]->amiga.initSample=curSample; + nextWindow=GUI_WINDOW_INS_EDIT; + MARK_MODIFIED; + } break; } @@ -1170,7 +1198,7 @@ void FurnaceGUI::doAction(int what) { } break; case GUI_ACTION_ORDERS_DOWN: - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } break; @@ -1183,7 +1211,7 @@ void FurnaceGUI::doAction(int what) { orderCursor=firstChannel; break; } - } while (!e->song.chanShow[orderCursor]); + } while (!e->curSubSong->chanShow[orderCursor]); break; } case GUI_ACTION_ORDERS_RIGHT: { @@ -1195,20 +1223,20 @@ void FurnaceGUI::doAction(int what) { orderCursor=lastChannel-1; break; } - } while (!e->song.chanShow[orderCursor]); + } while (!e->curSubSong->chanShow[orderCursor]); break; } case GUI_ACTION_ORDERS_INCREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { - e->song.orders.ord[orderCursor][curOrder]++; + if (e->curOrders->ord[orderCursor][curOrder]<0x7f) { + e->curOrders->ord[orderCursor][curOrder]++; } break; } case GUI_ACTION_ORDERS_DECREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - if (e->song.orders.ord[orderCursor][curOrder]>0) { - e->song.orders.ord[orderCursor][curOrder]--; + if (e->curOrders->ord[orderCursor][curOrder]>0) { + e->curOrders->ord[orderCursor][curOrder]--; } break; } diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 722c38f2..199a7de0 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -45,7 +45,7 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Edit Step"); ImGui::SameLine(); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -148,7 +148,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -217,7 +217,7 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Step"); ImGui::SetNextItemWidth(avail); if (ImGui::InputInt("##EditStep",&editStep,0,0)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -317,7 +317,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SetCursorPosX(cursor); ImGui::SetNextItemWidth(avail); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 8832b7fb..ea12eb59 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -44,8 +44,8 @@ const char* noteNameNormal(short note, short octave) { void FurnaceGUI::prepareUndo(ActionType action) { switch (action) { case GUI_UNDO_CHANGE_ORDER: - oldOrders=e->song.orders; - oldOrdersLen=e->song.ordersLen; + memcpy(&oldOrders,e->curOrders,sizeof(DivOrders)); + oldOrdersLen=e->curSubSong->ordersLen; break; case GUI_UNDO_PATTERN_EDIT: case GUI_UNDO_PATTERN_DELETE: @@ -63,7 +63,7 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false)->copyOn(oldPat[i]); + e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; } @@ -78,18 +78,19 @@ void FurnaceGUI::makeUndo(ActionType action) { s.selEnd=selEnd; s.order=curOrder; s.nibble=curNibble; + size_t subSong=e->getCurrentSubSong(); switch (action) { case GUI_UNDO_CHANGE_ORDER: for (int i=0; isong.orders.ord[i][j]) { - s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); + if (oldOrders.ord[i][j]!=e->curOrders->ord[i][j]) { + s.ord.push_back(UndoOrderData(subSong,i,j,oldOrders.ord[i][j],e->curOrders->ord[i][j])); } } } s.oldOrdersLen=oldOrdersLen; - s.newOrdersLen=e->song.ordersLen; - if (oldOrdersLen!=e->song.ordersLen) { + s.newOrdersLen=e->curSubSong->ordersLen; + if (oldOrdersLen!=e->curSubSong->ordersLen) { doPush=true; } if (!s.ord.empty()) { @@ -112,11 +113,11 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false); - for (int j=0; jsong.patLen; j++) { + DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false); + for (int j=0; jcurSubSong->patLen; j++) { for (int k=0; k<32; k++) { if (p->data[j][k]!=oldPat[i]->data[j][k]) { - s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); + s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); } } } @@ -137,15 +138,15 @@ void FurnaceGUI::makeUndo(ActionType action) { void FurnaceGUI::doSelectAll() { finishSelection(); curNibble=false; - if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectCols*2) { - if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern + if (selStart.xFine==0 && selEnd.xFine==2+e->curPat[selEnd.xCoarse].effectCols*2) { + if (selStart.y==0 && selEnd.y==e->curSubSong->patLen-1) { // select entire pattern selStart.xCoarse=0; selStart.xFine=0; selEnd.xCoarse=e->getTotalChannelCount()-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; } else { // select entire column selStart.y=0; - selEnd.y=e->song.patLen-1; + selEnd.y=e->curSubSong->patLen-1; } } else { int selStartX=0; @@ -153,26 +154,26 @@ void FurnaceGUI::doSelectAll() { // find row position for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { + if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } } for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { + if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } } float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); - if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down + if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->curSubSong->patLen-1)) { // up-down selStart.y=0; - selEnd.y=e->song.patLen-1; + selEnd.y=e->curSubSong->patLen-1; } else { // left-right selStart.xFine=0; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; } } } @@ -198,9 +199,9 @@ void FurnaceGUI::doDelete() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.patLen; j++) { - if (jsong.patLen-1) { + for (int j=selStart.y; jcurSubSong->patLen; j++) { + if (jcurSubSong->patLen-1) { if (iFine==0) { pat->data[j][iFine]=pat->data[j+1][iFine]; } @@ -267,11 +268,11 @@ void FurnaceGUI::doInsert() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { + for (int j=e->curSubSong->patLen-1; j>=selStart.y; j--) { if (j==selStart.y) { if (iFine==0) { pat->data[j][iFine]=0; @@ -299,9 +300,9 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][0],pat->data[j][1]); if (cut) { @@ -432,7 +433,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { int j=cursor.y; char note[4]; - for (size_t i=2; isong.patLen; i++) { + for (size_t i=2; icurSubSong->patLen; i++) { size_t charPos=0; int iCoarse=cursor.xCoarse; int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; @@ -440,10 +441,10 @@ void FurnaceGUI::doPaste(PasteMode mode) { String& line=data[i]; while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); if (line[charPos]=='|') { iCoarse++; - if (iCoarsesong.chanShow[iCoarse]) { + if (iCoarsecurSubSong->chanShow[iCoarse]) { iCoarse++; if (iCoarse>=lastChannel) break; } @@ -530,7 +531,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { - if (iFine<(3+e->song.pat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; + if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; } } } @@ -543,7 +544,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; - if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && curOrdersong.ordersLen-1) { + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->curSubSong->patLen && curOrdercurSubSong->ordersLen-1) { j=0; curOrder++; } @@ -554,7 +555,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { } if (settings.cursorPastePos) { cursor.y=j; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; updateScroll(cursor.y); } @@ -567,8 +568,8 @@ void FurnaceGUI::doChangeIns(int ins) { int iCoarse=selStart.xCoarse; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); for (int j=selStart.y; j<=selEnd.y; j++) { if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { pat->data[j][2]=ins; @@ -587,9 +588,9 @@ void FurnaceGUI::doInterpolate() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][iFine+1]; } for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->song.patLen) break; + if ((j+selStart.y)>=e->curSubSong->patLen) break; if ((j%multiplier)!=0) { if (iFine==0) { pat->data[j+selStart.y][0]=0; @@ -921,9 +922,10 @@ void FurnaceGUI::doUndo() { switch (us.type) { case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.oldOrdersLen; + e->curSubSong->ordersLen=us.oldOrdersLen; for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.oldVal; + e->changeSongP(i.subSong); + e->curOrders->ord[i.chan][i.ord]=i.oldVal; } break; case GUI_UNDO_PATTERN_EDIT: @@ -942,7 +944,8 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + e->changeSongP(i.subSong); + DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; } if (!e->isPlaying() || !followPattern) { @@ -967,9 +970,10 @@ void FurnaceGUI::doRedo() { switch (us.type) { case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.newOrdersLen; + e->curSubSong->ordersLen=us.newOrdersLen; for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.newVal; + e->changeSongP(i.subSong); + e->curOrders->ord[i.chan][i.ord]=i.newVal; } break; case GUI_UNDO_PATTERN_EDIT: @@ -988,7 +992,8 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + e->changeSongP(i.subSong); + DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.newVal; } if (!e->isPlaying()) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index edcac7fc..d2d8a9e1 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 @@ -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; } @@ -837,9 +793,9 @@ void FurnaceGUI::prepareLayout() { } float FurnaceGUI::calcBPM(int s1, int s2, float hz) { - float hl=e->song.hilightA; + float hl=e->curSubSong->hilightA; if (hl<=0.0f) hl=4.0f; - float timeBase=e->song.timeBase+1; + float timeBase=e->curSubSong->timeBase+1; float speedSum=s1+s2; if (timeBase<1.0f) timeBase=1.0f; if (speedSum<1.0f) speedSum=1.0f; @@ -901,7 +857,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { } void FurnaceGUI::noteInput(int num, int key, int vol) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); @@ -945,7 +901,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { } void FurnaceGUI::valueInput(int num, bool direct, int target) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); if (target==-1) target=cursor.xFine+1; if (direct) { @@ -1009,7 +965,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { editAdvance(); } else { if (settings.effectCursorDir==2) { - if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectCols*2))) { + if (++cursor.xFine>=(3+(e->curPat[cursor.xCoarse].effectCols*2))) { cursor.xFine=3; } } else { @@ -1167,7 +1123,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int num=valueKeys.at(ev.key.keysym.sym); if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { e->lockSave([this,num]() { - e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num); + e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; @@ -1176,7 +1132,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { orderCursor++; if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; } else if (orderEditMode==3) { - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } } @@ -1507,6 +1463,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; @@ -2062,7 +2031,7 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::PopFont(); ImGui::SameLine(); if (ImGui::Button("Set")) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); latchIns=pat->data[cursor.y][2]; latchVol=pat->data[cursor.y][3]; latchEffect=pat->data[cursor.y][4]; @@ -2381,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 && @@ -2453,6 +2422,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,10 +2753,12 @@ 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); } - if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { +#endif + if (ImGui::MenuItem("lock layout (not working!)",NULL,lockLayout)) { lockLayout=!lockLayout; } if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { @@ -2798,6 +2778,7 @@ bool FurnaceGUI::loop() { } if (ImGui::BeginMenu("window")) { if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen; + if (ImGui::MenuItem("subsongs",BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS),subSongsOpen)) subSongsOpen=!subSongsOpen; if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen; if (ImGui::MenuItem("samples",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST),sampleListOpen)) sampleListOpen=!sampleListOpen; @@ -2837,12 +2818,12 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); - ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); + ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); } else { bool hasInfo=false; String info; if (cursor.xCoarse>=0 && cursor.xCoarsegetTotalChannelCount()) { - DivPattern* p=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],false); + DivPattern* p=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false); if (cursor.xFine>=0) switch (cursor.xFine) { case 0: // note if (p->data[cursor.y][0]>0) { @@ -2903,6 +2884,7 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); + drawSubSongs(); drawPattern(); drawEditControls(); drawSongInfo(); @@ -3013,6 +2995,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(); @@ -3176,8 +3163,11 @@ bool FurnaceGUI::loop() { break; } case GUI_FILE_WAVE_OPEN: - e->addWaveFromFile(copyOfName.c_str()); - MARK_MODIFIED; + if (!e->addWaveFromFile(copyOfName.c_str())) { + showError("cannot load wavetable! ("+e->getLastError()+")"); + } else { + MARK_MODIFIED; + } break; case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); @@ -3226,6 +3216,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; } @@ -3278,7 +3277,7 @@ bool FurnaceGUI::loop() { } ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove)) { + if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { ImGui::SetWindowPos(ImVec2(((scrW*dpiScale)-ImGui::GetWindowSize().x)*0.5,((scrH*dpiScale)-ImGui::GetWindowSize().y)*0.5)); drawNewSong(); ImGui::EndPopup(); @@ -3494,7 +3493,7 @@ bool FurnaceGUI::loop() { stop(); e->lockEngine([this]() { for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],true); + DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],true); memset(pat->data,-1,256*32*sizeof(short)); for (int j=0; j<256; j++) { pat->data[j][0]=0; @@ -3653,12 +3652,23 @@ bool FurnaceGUI::init() { regViewOpen=e->getConfBool("regViewOpen",false); logOpen=e->getConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",false); + subSongsOpen=e->getConfBool("subSongsOpen",true); 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); + edit=e->getConfBool("edit",false); + followOrders=e->getConfBool("followOrders",true); + followPattern=e->getConfBool("followPattern",true); + orderEditMode=e->getConfInt("orderEditMode",0); + if (orderEditMode<0) orderEditMode=0; + if (orderEditMode>3) orderEditMode=3; syncSettings(); @@ -3666,6 +3676,8 @@ bool FurnaceGUI::init() { dpiScale=settings.dpiScale; } + initSystemPresets(); + #if !(defined(__APPLE__) || defined(_WIN32)) unsigned char* furIcon=getFurnaceIcon(); SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000); @@ -3679,6 +3691,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); @@ -3831,6 +3844,7 @@ bool FurnaceGUI::finish() { e->setConf("regViewOpen",regViewOpen); e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); + e->setConf("subSongsOpen",subSongsOpen); // commit last window size e->setConf("lastWindowWidth",scrW); @@ -3841,6 +3855,10 @@ bool FurnaceGUI::finish() { e->setConf("lockLayout",lockLayout); e->setConf("fullScreen",fullScreen); e->setConf("mobileUI",mobileUI); + e->setConf("edit",edit); + e->setConf("followOrders",followOrders); + e->setConf("followPattern",followPattern); + e->setConf("orderEditMode",orderEditMode); for (int i=0; i systems; - FurnaceGUISysCategory(const char* n): - name(n) {} + FurnaceGUISysCategory(const char* n, const char* d): + name(n), + description(d) {} FurnaceGUISysCategory(): - name(NULL) {} + name(NULL), + description(NULL) {} }; static const char* modeDummy[1]={NULL}; @@ -767,11 +777,11 @@ 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; - 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; @@ -825,6 +835,9 @@ class FurnaceGUI { int saaCore; int nesCore; int fdsCore; + String yrw801Path; + String tg100Path; + String mu5Path; int mainFont; int patFont; int audioRate; @@ -863,6 +876,7 @@ class FurnaceGUI { int roundedButtons; int roundedMenus; int loadJapanese; + int loadChinese; int fmLayout; int sampleLayout; int waveLayout; @@ -892,6 +906,7 @@ class FurnaceGUI { int hiddenSystems; int horizontalDataView; int noMultiSystem; + int oldMacroVSlider; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -911,6 +926,9 @@ class FurnaceGUI { saaCore(1), nesCore(0), fdsCore(0), + yrw801Path(""), + tg100Path(""), + mu5Path(""), mainFont(0), patFont(0), audioRate(44100), @@ -947,6 +965,7 @@ class FurnaceGUI { roundedButtons(1), roundedMenus(0), loadJapanese(0), + loadChinese(0), fmLayout(0), sampleLayout(0), waveLayout(0), @@ -976,6 +995,7 @@ class FurnaceGUI { hiddenSystems(0), horizontalDataView(0), noMultiSystem(0), + oldMacroVSlider(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -996,16 +1016,18 @@ class FurnaceGUI { bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; + bool subSongsOpen; /* there ought to be a better way... bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked; bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked; bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked; bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked; + bool subSongsDocked; */ 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]; @@ -1110,7 +1132,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; @@ -1232,6 +1254,7 @@ class FurnaceGUI { void drawNewSong(); void drawLog(); void drawEffectList(); + void drawSubSongs(); void parseKeybinds(); void promptKey(int which); @@ -1251,8 +1274,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); @@ -1309,8 +1332,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); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index a59a253d..1614a51b 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", @@ -116,8 +116,8 @@ const char* insTypes[DIV_INS_MAX]={ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={ "1-bit PCM", "1-bit DPCM", - NULL, - NULL, + "Yamaha AICA", + "YMZ/YMU ADPCM", "QSound ADPCM", "ADPCM-A", "ADPCM-B", @@ -492,6 +492,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_CHANNELS", "Channels", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_LOG", "Log Viewer", 0), + D("WINDOW_SUBSONGS", "Subsongs", 0), D("EFFECT_LIST", "Effect List", 0), D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), @@ -838,14 +839,21 @@ 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, + DIV_SYSTEM_OPN, + DIV_SYSTEM_OPN_EXT, + DIV_SYSTEM_PC98, + DIV_SYSTEM_PC98_EXT, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, 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/insEdit.cpp b/src/gui/insEdit.cpp index 2b802044..7ef745dd 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -191,8 +191,12 @@ const char* es5506EnvelopeModes[3]={ "k1 slowdown", "k2 slowdown", NULL }; -const char* es5506ControlModes[3]={ - "pause", "transwave slice", NULL +const char* es5506ControlModes[2]={ + "pause", NULL +}; + +const char* transwaveControlModes[2]={ + "slice", NULL }; const char* oneBit[2]={ @@ -1135,7 +1139,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); @@ -1211,8 +1215,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; @@ -1251,37 +1255,60 @@ 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; } } // 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); - 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)); + 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; + } } } @@ -3110,7 +3137,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) { @@ -3145,6 +3172,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; } @@ -3406,7 +3437,9 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Envelope K1 ramp",&ins->std.ex6Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Envelope K2 ramp",&ins->std.ex7Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506EnvelopeModes)); - macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506ControlModes)); + macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506ControlModes)); + macroList.push_back(FurnaceGUIMacroDesc("Transwave control",&ins->std.fbMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,transwaveControlModes)); + macroList.push_back(FurnaceGUIMacroDesc("Transwave slice",&ins->std.fmsMacro,0,4095,160,uiColors[GUI_COLOR_MACRO_OTHER])); } if (ins->type==DIV_INS_SU) { macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,suControlBits)); diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index 2bf11fb5..0dea772b 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -18,6 +18,7 @@ */ #include "gui.h" +#include void FurnaceGUI::drawNewSong() { bool accepted=false; @@ -27,44 +28,53 @@ void FurnaceGUI::drawNewSong() { ImGui::Text("Choose a System!"); ImGui::PopFont(); - if (ImGui::BeginTable("sysPicker",2)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); + ImVec2 avail=ImGui::GetContentRegionAvail(); + avail.y-=ImGui::GetFrameHeightWithSpacing(); - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableNextColumn(); - ImGui::Text("Categories"); - ImGui::TableNextColumn(); - ImGui::Text("Systems"); + if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { + if (ImGui::BeginTable("sysPicker",2,ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); - ImGui::TableNextRow(); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Categories"); + ImGui::TableNextColumn(); + ImGui::Text("Systems"); - // CATEGORIES - ImGui::TableNextColumn(); - int index=0; - for (FurnaceGUISysCategory& i: sysCategories) { - if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ - newSongCategory=index; - } - index++; - } + ImGui::TableNextRow(); - // SYSTEMS - ImGui::TableNextColumn(); - if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { - for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { - nextDesc=i.definition.data(); - accepted=true; + // CATEGORIES + ImGui::TableNextColumn(); + int index=0; + for (FurnaceGUISysCategory& i: sysCategories) { + if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ + newSongCategory=index; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s",i.description); + } + index++; } + + // SYSTEMS + ImGui::TableNextColumn(); + if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { + for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { + nextDesc=i.definition.data(); + accepted=true; + } + } + ImGui::EndTable(); + } + ImGui::EndTable(); } - - ImGui::EndTable(); } + ImGui::EndChild(); if (ImGui::Button("I'm feeling lucky")) { if (sysCategories.size()==0) { diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 2311a398..e9886a1a 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -37,7 +37,7 @@ void FurnaceGUI::drawOrders() { ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale); int displayChans=0; for (int i=0; igetTotalChannelCount(); i++) { - if (e->song.chanShow[i]) displayChans++; + if (e->curSubSong->chanShow[i]) displayChans++; } ImGui::PushFont(patFont); bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x))); @@ -56,12 +56,12 @@ void FurnaceGUI::drawOrders() { ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); for (int i=0; igetTotalChannelCount(); i++) { - if (!e->song.chanShow[i]) continue; + if (!e->curSubSong->chanShow[i]) continue; ImGui::TableNextColumn(); ImGui::Text("%s",e->getChannelShortName(i)); } ImGui::PopStyleColor(); - for (int i=0; isong.ordersLen; i++) { + for (int i=0; icurSubSong->ordersLen; i++) { ImGui::TableNextRow(0,lineHeight); if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); ImGui::TableNextColumn(); @@ -84,16 +84,16 @@ void FurnaceGUI::drawOrders() { } ImGui::PopStyleColor(); for (int j=0; jgetTotalChannelCount(); j++) { - if (!e->song.chanShow[j]) continue; + if (!e->curSubSong->chanShow[j]) continue; ImGui::TableNextColumn(); - DivPattern* pat=e->song.pat[j].getPattern(e->song.orders.ord[j][i],false); + DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false); /*if (!pat->name.empty()) { snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i); } else {*/ - snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->song.orders.ord[j][i],j,i); + snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i); //} - ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->song.orders.ord[j][i]==e->song.orders.ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) { if (curOrder==i) { if (orderEditMode==0) { @@ -101,10 +101,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]<0xff) e->song.orders.ord[k][i]++; + if (e->curOrders->ord[k][i]<0xff) e->curOrders->ord[k][i]++; } } else { - if (e->song.orders.ord[j][i]<0xff) e->song.orders.ord[j][i]++; + if (e->curOrders->ord[j][i]<0xff) e->curOrders->ord[j][i]++; } }); e->walkSong(loopOrder,loopRow,loopEnd); @@ -137,10 +137,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--; + if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--; } } else { - if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--; + if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--; } }); e->walkSong(loopOrder,loopRow,loopEnd); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index d284b08f..b7ca4bed 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -43,21 +43,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int return; } // check if we are in range - if (ord<0 || ord>=e->song.ordersLen) { + if (ord<0 || ord>=e->curSubSong->ordersLen) { return; } - if (i<0 || i>=e->song.patLen) { + if (i<0 || i>=e->curSubSong->patLen) { return; } bool isPushing=false; ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE]; ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE]; ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX]; - if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2]; rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI2]; - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1]; rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI1]; @@ -68,9 +68,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); - } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + } else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1])); } } else { @@ -79,24 +79,34 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); - } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + } else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1])); } else { isPushing=false; } } // 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