diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13ef95c7..293c4cd0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,8 +187,6 @@ jobs: export USE_WAE=ON export CMAKE_EXTRA_ARGS=() if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then - # 1. Go to hell - export USE_WAE=OFF CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}') # Force static linking diff --git a/CMakeLists.txt b/CMakeLists.txt index f6b807dd..2e9e9c6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,13 @@ option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendor option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) set(DEPENDENCIES_INCLUDE_DIRS "") + +if (ANDROID) +set(DEPENDENCIES_DEFINES "IS_MOBILE") +else() set(DEPENDENCIES_DEFINES "") +endif() + set(DEPENDENCIES_COMPILE_OPTIONS "") set(DEPENDENCIES_LIBRARIES "") set(DEPENDENCIES_LIBRARY_DIRS "") @@ -310,6 +316,8 @@ src/engine/platform/sound/vic20sound.c src/engine/platform/sound/vrcvi/vrcvi.cpp +src/engine/platform/sound/scc/scc.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -343,6 +351,7 @@ src/engine/platform/nes.cpp src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/tx81z.cpp +src/engine/platform/ym2203.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610b.cpp @@ -367,6 +376,7 @@ src/engine/platform/n163.cpp src/engine/platform/pet.cpp src/engine/platform/vic20.cpp src/engine/platform/vrc6.cpp +src/engine/platform/scc.cpp src/engine/platform/dummy.cpp ) diff --git a/README.md b/README.md index 1eac106d..caa4714c 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Available options: | Name | Default | Description | | :--: | :-----: | ----------- | -| `BUILD_GUI` | `ON` if not building for Android, otherwise `OFF` | Build the tracker (disable to build only a headless player) | +| `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) | | `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available | | `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one | | `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one | diff --git a/TODO.md b/TODO.md index c11d4755..fc526b29 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,6 @@ - OPNA system - ZX beeper system - Y8950 system -- SCC/SCC+ system - maybe YMU759 ADPCM channel - ADPCM chips - Game Boy envelope macro/sequence diff --git a/papers/format.md b/papers/format.md index 7ab67800..e6d35e83 100644 --- a/papers/format.md +++ b/papers/format.md @@ -232,6 +232,9 @@ size | description 4f | A-4 tuning 1 | limit slides (>=36) or reserved 1 | linear pitch (>=36) or reserved + | - 0: non-linaer + | - 1: only pitch change (04xy/E5xx) linear + | - 2: full linear (>=94) 1 | loop modality (>=36) or reserved 1 | proper noise layout (>=42) or reserved 1 | wave duty is volume (>=42) or reserved @@ -286,7 +289,8 @@ size | description 1 | weird f-num/block-based chip pitch slides (>=85) or reserved 1 | SN duty macro always resets phase (>=86) or reserved 1 | pitch macro is linear (>=90) or reserved - 19 | reserved + 1 | pitch slide speed in full linear pitch mode (>=94) or reserved + 18 | reserved ``` # instrument diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index f44ed0d5..7595d14b 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -29,6 +29,7 @@ #include "platform/c64.h" #include "platform/arcade.h" #include "platform/tx81z.h" +#include "platform/ym2203.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ym2610b.h" @@ -54,6 +55,7 @@ #include "platform/vrc6.h" #include "platform/fds.h" #include "platform/mmc5.h" +#include "platform/scc.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -233,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_TIA: dispatch=new DivPlatformTIA; break; + case DIV_SYSTEM_OPN: + dispatch=new DivPlatformYM2203; + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: @@ -314,6 +319,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_MMC5: dispatch=new DivPlatformMMC5; break; + case DIV_SYSTEM_SCC: + dispatch=new DivPlatformSCC; + ((DivPlatformSCC*)dispatch)->setChipModel(false); + break; + case DIV_SYSTEM_SCC_PLUS: + dispatch=new DivPlatformSCC; + ((DivPlatformSCC*)dispatch)->setChipModel(true); + break; case DIV_SYSTEM_SOUND_UNIT: dispatch=new DivPlatformSoundUnit; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e9bd3f10..63949bfc 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -986,38 +986,66 @@ int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) }*/ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) { + if (song.linearPitch==2) { // full linear + return (note<<7); + } double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0); return period? (clock/base)/divider: base*(divider/clock); } -unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) { - int bf=calcBaseFreq(clock,divider,note,false); - int block=note/12; - if (block<0) block=0; - if (block>7) block=7; - bf>>=block; - if (bf<0) bf=0; - // octave boundaries - while (bf>0 && bf<644 && block>0) { - bf<<=1; - block--; - } - if (bf>1288) { - while (block<7) { - bf>>=1; - block++; - } - if (bf>((1<500.0) tuning=500.0; \ + int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); \ + int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); \ + int block=(note)/12; \ + if (block<0) block=0; \ + if (block>7) block=7; \ + bf>>=block; \ + if (bf<0) bf=0; \ + /* octave boundaries */ \ + while (bf>0 && bf0) { \ + bf<<=1; \ + block--; \ + } \ + if (bf>boundaryTop) { \ + while (block<7 && bf>boundaryTop) { \ + bf>>=1; \ + block++; \ + } \ + if (bf>((1<0) { + CONVERT_FNUM_BLOCK(bf,blockBits,nbase>>7) + } else { + return bf; + } + } + if (song.linearPitch==1) { // global pitch multiplier int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0)); if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me @@ -1209,7 +1237,7 @@ void DivEngine::reset() { chan[i]=DivChannelState(); if (idispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; chan[i].volume=chan[i].volMax; - if (!song.linearPitch) chan[i].vibratoFine=4; + if (song.linearPitch==0) chan[i].vibratoFine=4; } extValue=0; extValuePresent=0; diff --git a/src/engine/engine.h b/src/engine/engine.h index 728927ae..4a3d8b6a 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 "dev93" -#define DIV_ENGINE_VERSION 93 +#define DIV_VERSION "dev94" +#define DIV_ENGINE_VERSION 94 // for imports #define DIV_VERSION_MOD 0xff01 @@ -484,10 +484,10 @@ class DivEngine { double calcBaseFreq(double clock, double divider, int note, bool period); // calculate base frequency in f-num/block format - unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits); + int calcBaseFreqFNumBlock(double clock, double divider, int note, int bits); // calculate frequency/period - int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0); + int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0, int blockBits=0); // convert panning formats int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8c93ef5b..e0f20794 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -139,7 +139,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // compatibility flags ds.limitSlides=true; - ds.linearPitch=true; + ds.linearPitch=1; ds.loopModality=0; ds.properNoiseLayout=false; ds.waveDutyIsVol=false; @@ -173,12 +173,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.legacyVolumeSlides=false; } - // Neo Geo detune + // Neo Geo detune is caused by Defle running Neo Geo at the wrong clock. + /* if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { ds.tuning=443.23; } + */ logI("reading module data..."); if (ds.version>0x0c) { @@ -948,7 +950,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<37) { // compat flags not stored back then ds.limitSlides=true; - ds.linearPitch=true; + ds.linearPitch=1; ds.loopModality=0; } if (ds.version<43) { @@ -1390,7 +1392,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<19; i++) { + if (ds.version>=94) { + ds.pitchSlideSpeed=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<18; i++) { reader.readC(); } } @@ -1647,7 +1654,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; - ds.linearPitch=false; + ds.linearPitch=0; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; @@ -2700,7 +2707,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.fbPortaPause); w->writeC(song.snDutyReset); w->writeC(song.pitchMacroIsLinear); - for (int i=0; i<19; i++) { + w->writeC(song.pitchSlideSpeed); + for (int i=0; i<18; i++) { w->writeC(0); } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index e78c3a77..df742d35 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); + chan[i].freq=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) { diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 37108452..dbc5aa82 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -251,7 +251,7 @@ void DivPlatformAY8910::tick(bool sysTick) { if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=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].keyOn) { //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 3fec732b..6fb34096 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -280,7 +280,7 @@ void DivPlatformAY8930::tick(bool sysTick) { immWrite(0x1a,ayNoiseOr); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].keyOn) { if (chan[i].insChanged) { diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index b12a92ed..52fb4a50 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -137,7 +137,7 @@ void DivPlatformBubSysWSG::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC); - chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2; + chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>4095) chan[i].freq=4095; k005289->load(i,chan[i].freq); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index c0efdd6f..17b8124d 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -217,7 +217,7 @@ void DivPlatformC64::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>0xffff) chan[i].freq=0xffff; if (chan[i].keyOn) { rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay)); diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 34d614eb..48c33f05 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -61,7 +61,7 @@ void DivPlatformDummy::tick(bool sysTick) { if (chan[i].freqChanged) { chan[i].freqChanged=false; - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,0,chipClock,CHIP_FREQBASE); } } } diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index f3908148..5698f004 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -220,7 +220,7 @@ void DivPlatformFDS::tick(bool sysTick) { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 1fc2b278..963d2fde 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -237,7 +237,7 @@ void DivPlatformGB::tick(bool sysTick) { if (ntPos>255) ntPos=255; chan[i].freq=noiseTable[ntPos]; } else { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 0f665cba..b35d09ee 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -471,17 +471,21 @@ void DivPlatformGenesis::tick(bool sysTick) { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - 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 (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,chipClock,CHIP_FREQBASE,11); + 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; } - if (fNum>2047) fNum=2047; + chan[i].freq=(block<<11)|fNum; } - 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); @@ -495,7 +499,7 @@ void DivPlatformGenesis::tick(bool sysTick) { off=(double)s->centerRate/8363.0; } } - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4)+chan[i].pitch2; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,1,1); dacRate=chan[i].freq*off; if (dacRate<1) dacRate=1; if (dumpWrites) addWrite(0xffff0001,dacRate); @@ -702,6 +706,29 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { + if (parent->song.linearPitch==2) { + 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; + } if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) { int destFreq=parent->calcBaseFreq(1,1,c.value2,false); bool return2=false; @@ -725,6 +752,8 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } 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; @@ -747,13 +776,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) { // check for octave boundary // what the heck! if (!chan[c.chan].portaPause) { - if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) { - chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800); + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); chan[c.chan].portaPause=true; break; } - if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) { - chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800); + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); chan[c.chan].portaPause=true; break; } diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index ae4d74cd..f0c35ecc 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -127,6 +127,31 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { 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; @@ -148,18 +173,18 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } // what the heck! if (!opChan[ch].portaPause) { - if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { if (parent->song.fbPortaPause) { - opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800); + opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); opChan[ch].portaPause=true; break; } else { newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800); } } - if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) { + if ((newFreq&0x7ff)0) { if (parent->song.fbPortaPause) { - opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800); + opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); opChan[ch].portaPause=true; break; } else { @@ -435,17 +460,21 @@ void DivPlatformGenesisExt::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - 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 (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; } - if (fNum>2047) fNum=2047; + opChan[i].freq=(block<<11)|fNum; } - 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); diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 09571061..dd5a2be0 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -201,7 +201,7 @@ void DivPlatformLynx::tick(bool sysTick) { WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4)); chan[i].lfsr=-1; } - chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; WRITE_FEEDBACK(i, chan[i].duty.feedback); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 8446643b..9775e639 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -146,7 +146,7 @@ void DivPlatformMMC5::tick(bool sysTick) { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index ef368203..9318b4f8 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -357,7 +357,7 @@ void DivPlatformN163::tick(bool sysTick) { chan[i].waveUpdated=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].keyOn) { diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 4b301839..ef3b51f0 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -308,7 +308,7 @@ void DivPlatformNES::tick(bool sysTick) { if (ntPos>252) ntPos=252; chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]); } else { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; } diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ff31ad40..0933934c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -511,7 +511,7 @@ void DivPlatformOPL::tick(bool sysTick) { bool updateDrums=false; for (int i=0; icalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>131071) chan[i].freq=131071; int freqt=toFreq(chan[i].freq)+chan[i].pitch2; chan[i].freqH=freqt>>8; @@ -800,19 +800,19 @@ int DivPlatformOPL::dispatch(DivCommand c) { int newFreq; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } - if (!chan[c.chan].portaPause) { + if (!chan[c.chan].portaPause && parent->song.linearPitch!=2) { if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { chan[c.chan].portaPause=true; break; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index b4f0916a..50eab344 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -300,7 +300,7 @@ void DivPlatformOPLL::tick(bool sysTick) { for (int i=0; i<11; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq)+chan[i].pitch2; chan[i].freqL=freqt&0xff; @@ -578,13 +578,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) { int newFreq; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq<=destFreq) { newFreq=destFreq; return2=true; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index c28b81db..9838f987 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -227,7 +227,7 @@ void DivPlatformPCE::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 434ab10b..8ecf6dae 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -223,7 +223,7 @@ void DivPlatformPCSpeaker::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + 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>65535) chan[i].freq=65535; if (chan[i].keyOn) { diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index 8ddee9e8..f2ee6add 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -118,7 +118,7 @@ void DivPlatformPET::tick(bool sysTick) { chan.freqChanged=true; } if (chan.freqChanged || chan.keyOn || chan.keyOff) { - chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2); + chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER); if (chan.freq>257) chan.freq=257; if (chan.freq<2) chan.freq=2; rWrite(8,chan.freq-2); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 6e89e5b6..e9b4e8a4 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -24,7 +24,7 @@ #include #define CHIP_DIVIDER (1248*2) -#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,0x1000,(x)-3,false) +#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,4096,(x)-3,false) #define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }} #define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);} @@ -296,14 +296,8 @@ void DivPlatformQSound::tick(bool sysTick) { uint16_t qsound_addr = 0; uint16_t qsound_loop = 0; uint16_t qsound_end = 0; - double off=1.0; if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[i].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=(double)s->centerRate/24038.0/16.0; - } qsound_bank = 0x8000 | (s->offQSound >> 16); qsound_addr = s->offQSound & 0xffff; @@ -322,15 +316,15 @@ void DivPlatformQSound::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { if (chan[i].std.arp.mode) { - chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp.val); + chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); + chan[i].baseFreq=QS_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=off*QS_NOTE_FREQUENCY(chan[i].note); + chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } @@ -354,7 +348,16 @@ void DivPlatformQSound::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,false,2,chan[i].pitch2); + double off=1.0; + if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=(double)s->centerRate/24038.0/16.0; + } + } + chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0); if (chan[i].freq>0xffff) chan[i].freq=0xffff; if (chan[i].keyOn) { rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank); @@ -388,17 +391,8 @@ int DivPlatformQSound::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { 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].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=(double)s->centerRate/24038.0/16.0; - } - } if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value); } if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chan[c.chan].sample=-1; @@ -467,16 +461,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_NOTE_PORTA: { - double off=1.0; - 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=(double)s->centerRate/24038.0/16.0; - } - } - int destFreq=off*QS_NOTE_FREQUENCY(c.value2); + int destFreq=QS_NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -499,16 +484,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - double off=1.0; - 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=(double)s->centerRate/24038.0/16.0; - } - } - chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0))); + chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 3ecf0a3e..ec5f4f6d 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -200,7 +200,7 @@ void DivPlatformSAA1099::tick(bool sysTick) { rWrite(0x18+(i/3),saaEnv[i/3]); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].freq>=32768) { chan[i].freqH=7; @@ -311,13 +311,13 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*(8-chan[c.chan].freqH); + chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH)); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*(8-chan[c.chan].freqH); + chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH)); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp new file mode 100644 index 00000000..96ec2dfd --- /dev/null +++ b/src/engine/platform/scc.cpp @@ -0,0 +1,391 @@ +/** + * 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 "scc.h" +#include "../engine.h" +#include + +#define CHIP_DIVIDER 16 + +#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", + "Ch2_Wave", "20", + "Ch3_Wave", "40", + "Ch4_5_Wave", "60", + "Ch1_FreqL", "80", + "Ch1_FreqH", "81", + "Ch2_FreqL", "82", + "Ch2_FreqH", "83", + "Ch3_FreqL", "84", + "Ch3_FreqH", "85", + "Ch4_FreqL", "86", + "Ch4_FreqH", "87", + "Ch5_FreqL", "88", + "Ch5_FreqH", "89", + "Ch1_Vol", "8a", + "Ch2_Vol", "8b", + "Ch3_Vol", "8c", + "Ch4_Vol", "8d", + "Ch5_Vol", "8e", + "Output", "8f", + "Test", "e0", + NULL +}; + +const char* regCheatSheetSCCPlus[]={ + "Ch1_Wave", "00", + "Ch2_Wave", "20", + "Ch3_Wave", "40", + "Ch4_Wave", "60", + "Ch5_Wave", "80", + "Ch1_FreqL", "a0", + "Ch1_FreqH", "a1", + "Ch2_FreqL", "a2", + "Ch2_FreqH", "a3", + "Ch3_FreqL", "a4", + "Ch3_FreqH", "a5", + "Ch4_FreqL", "a6", + "Ch4_FreqH", "a7", + "Ch5_FreqL", "a8", + "Ch5_FreqH", "a9", + "Ch1_Vol", "aa", + "Ch2_Vol", "ab", + "Ch3_Vol", "ac", + "Ch4_Vol", "ad", + "Ch5_Vol", "ae", + "Output", "af", + "Test", "c0", + NULL +}; + +const char** DivPlatformSCC::getRegisterSheet() { + return isPlus ? regCheatSheetSCCPlus : regCheatSheetSCC; +} + +const char* DivPlatformSCC::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + } + return NULL; +} + +void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; htick(); + } + short out=(short)scc->out()<<5; + bufL[h]=bufR[h]=out; + + for (int i=0; i<5; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=scc->chan_out(i)<<7; + } + } +} + +void DivPlatformSCC::updateWave(int ch) { + int dstCh=(!isPlus && ch>=4)?3:ch; + for (int i=0; i<32; i++) { + rWrite(dstCh*32+i,(unsigned char)chan[ch].ws.output[i]-128); + } +} + +void DivPlatformSCC::tick(bool sysTick) { + for (int i=0; i<5; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))/15; + rWrite(regBase+10+i,chan[i].outVol); + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); + } else { + chan[i].baseFreq=NOTE_PERIODIC(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_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); + } + } + 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].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } + if (chan[i].freqChanged) { + 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; + } + } +} + +int DivPlatformSCC::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SCC); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].macroInit(ins); + if (!isMuted[c.chan]) { + rWrite(regBase+15,regPool[regBase+15]|(1<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_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(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_SCC)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformSCC::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (mute) { + rWrite(regBase+15,regPool[regBase+15]&~(1<reset(); + for (int i=0; i<5; i++) { + chan[i]=DivPlatformSCC::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255,false); + chan[i].vol=15; + chan[i].outVol=15; + rWrite(regBase+10+i,15); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } +} + +bool DivPlatformSCC::isStereo() { + return false; +} + +void DivPlatformSCC::notifyWaveChange(int wave) { + for (int i=0; i<5; i++) { + if (chan[i].wave==wave) { + chan[i].ws.changeWave1(chan[i].wave); + if (chan[i].active) { + updateWave(i); + } + } + } +} + +void DivPlatformSCC::notifyInsDeletion(void* ins) { + for (int i=0; i<5; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformSCC::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformSCC::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +void DivPlatformSCC::setChipModel(bool isplus) { + isPlus=isplus; +} + +int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + writeOscBuf=0; + for (int i=0; i<5; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + chipClock=COLOR_NTSC/2.0; + rate=chipClock/8; + for (int i=0; i<5; i++) { + oscBuf[i]->rate=rate; + } + if (isPlus) { + scc=new k052539_scc_core; + regBase=0xa0; + } else { + scc=new k051649_scc_core; + regBase=0x80; + } + reset(); + return 5; +} + +void DivPlatformSCC::quit() { + for (int i=0; i<5; i++) { + delete oscBuf[i]; + } + if (scc!=NULL) { + delete scc; + } +} + +DivPlatformSCC::~DivPlatformSCC() { +} diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h new file mode 100644 index 00000000..58059532 --- /dev/null +++ b/src/engine/platform/scc.h @@ -0,0 +1,91 @@ +/** + * 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 _SCC_H +#define _SCC_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "../waveSynth.h" +#include "sound/scc/scc.hpp" + +class DivPlatformSCC: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note, ins; + bool active, insChanged, freqChanged, inPorta; + signed char vol, outVol, wave; + signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system + DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + inPorta(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[5]; + DivDispatchOscBuffer* oscBuf[5]; + bool isMuted[5]; + unsigned char writeOscBuf; + + scc_core* scc; + bool isPlus; + unsigned char regBase; + unsigned char regPool[225]; + void updateWave(int ch); + 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(); + 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 setChipModel(bool isPlus); + void quit(); + ~DivPlatformSCC(); +}; + +#endif diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index f1c66163..be2039cf 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -121,7 +121,7 @@ void DivPlatformSMS::tick(bool sysTick) { } for (int i=0; i<3; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64); if (chan[i].freq>1023) chan[i].freq=1023; if (chan[i].freq<8) chan[i].freq=1; //if (chan[i].actualNote>0x5d) chan[i].freq=0x01; @@ -136,7 +136,7 @@ void DivPlatformSMS::tick(bool sysTick) { } } if (chan[3].freqChanged || updateSNMode) { - chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2); + chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64); if (chan[3].freq>1023) chan[3].freq=1023; if (chan[3].actualNote>0x5d) chan[3].freq=0x01; if (snNoiseMode&2) { // take period from channel 3 diff --git a/src/engine/platform/sound/scc/scc.cpp b/src/engine/platform/sound/scc/scc.cpp new file mode 100644 index 00000000..db74d3f0 --- /dev/null +++ b/src/engine/platform/sound/scc/scc.cpp @@ -0,0 +1,612 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami SCC emulation core + + Konami SCC means "Sound Creative Chip", it's actually MSX MegaROM/RAM Mapper with 5 channel Wavetable sound generator. + + It was first appeared at 1987, F-1 Spirit and Nemesis 2/Gradius 2 for MSX. then several MSX cartridges used that until 1990, Metal Gear 2: Solid Snake. + Even after MSX is discontinued, it was still used at some low-end arcade and amusement hardwares. + and some Third-party MSX utilities still support this due to its market shares. + + There's 2 SCC types: + + K051649 (or simply known as SCC) + This chip is used for MSX MegaROM Mapper, some arcade machines. + Channel 4 and 5 must be share waveform, other channels has its own waveforms. + + K052539 (also known as SCC+) + This chip is used for MSX MegaRAM Mapper (Konami Sound Cartridges for Snatcher/SD Snatcher). + All channels can be has its own waveforms, and also has backward compatibility mode with K051649. + + Based on: + https://www.msx.org/wiki/MegaROM_Mappers + https://www.msx.org/wiki/Konami_051649 + https://www.msx.org/wiki/Konami_052539 + http://bifi.msxnet.org/msxnet/tech/scc + http://bifi.msxnet.org/msxnet/tech/soundcartridge + + K051649 Register Layout + + -------------------------------------------------------------------- + + 4000-bfff MegaROM Mapper + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 4000-5fff xxxx xxxx R Bank page 0 + c000-dfff mirror of 4000-5fff + + 6000-7fff xxxx xxxx R Bank page 1 + e000-ffff mirror of 6000-7fff + + 8000-9fff xxxx xxxx R Bank page 2 + 0000-1fff mirror of 8000-9fff + + a000-bfff xxxx xxxx R Bank page 3 + 2000-3fff mirror of a000-bfff + + -------------------------------------------------------------------- + + 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 5000 --xx xxxx W Bank select, Page 0 + 5001-57ff Mirror of 5000 + + 7000 --xx xxxx W Bank select, Page 1 + 7001-77ff Mirror of 7000 + + 9000 --xx xxxx W Bank select, Page 2 + --11 1111 W SCC Enable + 9001-97ff Mirror of 9000 + + b000 --xx xxxx W Bank select, Page 3 + b001-b7ff Mirror of b000 + + -------------------------------------------------------------------- + + 9800-9fff SCC register + + -------------------------------------------------------------------- + + 9800-987f Waveform + + Address Bit R/W Description + 7654 3210 + + 9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) + 9820-983f xxxx xxxx R/W Channel 1 "" + 9840-985f xxxx xxxx R/W Channel 2 "" + 9860-987f xxxx xxxx R/W Channel 3/4 "" + + 9880-9889 Pitch + + 9880 xxxx xxxx W Channel 0 Pitch LSB + 9881 ---- xxxx W Channel 0 Pitch MSB + 9882 xxxx xxxx W Channel 1 Pitch LSB + 9883 ---- xxxx W Channel 1 Pitch MSB + 9884 xxxx xxxx W Channel 2 Pitch LSB + 9885 ---- xxxx W Channel 2 Pitch MSB + 9886 xxxx xxxx W Channel 3 Pitch LSB + 9887 ---- xxxx W Channel 3 Pitch MSB + 9888 xxxx xxxx W Channel 4 Pitch LSB + 9889 ---- xxxx W Channel 4 Pitch MSB + + 9888-988e Volume + + 988a ---- xxxx W Channel 0 Volume + 988b ---- xxxx W Channel 1 Volume + 988c ---- xxxx W Channel 2 Volume + 988d ---- xxxx W Channel 3 Volume + 988e ---- xxxx W Channel 4 Volume + + 988f ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + + 9890-989f Mirror of 9880-988f + + 98a0-98bf xxxx xxxx R Channel 4 Waveform + + 98e0 x--- ---- W Waveform rotate flag for channel 4 + -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + + 98e1-98ff Mirror of 98e0 + + 9900-9fff Mirror of 9800-98ff + + -------------------------------------------------------------------- + + K052539 Register Layout + + -------------------------------------------------------------------- + + 4000-bfff MegaRAM Mapper + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 4000-5fff xxxx xxxx R/W Bank page 0 + c000-dfff xxxx xxxx R/W "" + + 6000-7fff xxxx xxxx R/W Bank page 1 + e000-ffff xxxx xxxx R/W "" + + 8000-9fff xxxx xxxx R/W Bank page 2 + 0000-1fff xxxx xxxx R/W "" + + a000-bfff xxxx xxxx R/W Bank page 3 + 2000-3fff xxxx xxxx R/W "" + + -------------------------------------------------------------------- + + 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 5000 xxxx xxxx W Bank select, Page 0 + 5001-57ff Mirror of 5000 + + 7000 xxxx xxxx W Bank select, Page 1 + 7001-77ff Mirror of 7000 + + 9000 xxxx xxxx W Bank select, Page 2 + --11 1111 W SCC Enable (SCC Compatible mode) + 9001-97ff Mirror of 9000 + + b000 xxxx xxxx W Bank select, Page 3 + 1--- ---- W SCC+ Enable (SCC+ mode) + b001-b7ff Mirror of b000 + + -------------------------------------------------------------------- + + bffe-bfff Mapper configuration + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + bffe --x- ---- W SCC operation mode + --0- ---- W SCC Compatible mode + --1- ---- W SCC+ mode + ---x ---- W RAM write/Bank select toggle for all Bank pages + ---0 ---- W Bank select enable + ---1 ---- W RAM write enable + ---0 -x-- W RAM write/Bank select toggle for Bank page 2 + ---0 --x- W RAM write/Bank select toggle for Bank page 1 + ---0 ---x W RAM write/Bank select toggle for Bank page 0 + bfff Mirror of bffe + + -------------------------------------------------------------------- + + 9800-9fff SCC Compatible mode register + + -------------------------------------------------------------------- + + 9800-987f Waveform + + Address Bit R/W Description + 7654 3210 + + 9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) + 9820-983f xxxx xxxx R/W Channel 1 "" + 9840-985f xxxx xxxx R/W Channel 2 "" + 9860-987f xxxx xxxx R/W Channel 3/4 "" + + 9880-9889 Pitch + + 9880 xxxx xxxx W Channel 0 Pitch LSB + 9881 ---- xxxx W Channel 0 Pitch MSB + 9882 xxxx xxxx W Channel 1 Pitch LSB + 9883 ---- xxxx W Channel 1 Pitch MSB + 9884 xxxx xxxx W Channel 2 Pitch LSB + 9885 ---- xxxx W Channel 2 Pitch MSB + 9886 xxxx xxxx W Channel 3 Pitch LSB + 9887 ---- xxxx W Channel 3 Pitch MSB + 9888 xxxx xxxx W Channel 4 Pitch LSB + 9889 ---- xxxx W Channel 4 Pitch MSB + + 9888-988e Volume + + 988a ---- xxxx W Channel 0 Volume + 988b ---- xxxx W Channel 1 Volume + 988c ---- xxxx W Channel 2 Volume + 988d ---- xxxx W Channel 3 Volume + 988e ---- xxxx W Channel 4 Volume + + 988f ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + + 9890-989f Mirror of 9880-988f + + 98a0-98bf xxxx xxxx R Channel 4 Waveform + + 98c0 -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + + 98c1-98df Mirror of 98c0 + + 9900-9fff Mirror of 9800-98ff + + -------------------------------------------------------------------- + + b800-bfff SCC+ mode register + + -------------------------------------------------------------------- + + b800-b89f Waveform + + Address Bit R/W Description + 7654 3210 + + b800-b81f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) + b820-b83f xxxx xxxx R/W Channel 1 "" + b840-b85f xxxx xxxx R/W Channel 2 "" + b860-b87f xxxx xxxx R/W Channel 3 "" + b880-b89f xxxx xxxx R/W Channel 3 "" + + b8a0-b8a9 Pitch + + b8a0 xxxx xxxx W Channel 0 Pitch LSB + b8a1 ---- xxxx W Channel 0 Pitch MSB + b8a2 xxxx xxxx W Channel 1 Pitch LSB + b8a3 ---- xxxx W Channel 1 Pitch MSB + b8a4 xxxx xxxx W Channel 2 Pitch LSB + b8a5 ---- xxxx W Channel 2 Pitch MSB + b8a6 xxxx xxxx W Channel 3 Pitch LSB + b8a7 ---- xxxx W Channel 3 Pitch MSB + b8a8 xxxx xxxx W Channel 4 Pitch LSB + b8a9 ---- xxxx W Channel 4 Pitch MSB + + b8a8-b8ae Volume + + b8aa ---- xxxx W Channel 0 Volume + b8ab ---- xxxx W Channel 1 Volume + b8ac ---- xxxx W Channel 2 Volume + b8ad ---- xxxx W Channel 3 Volume + b8ae ---- xxxx W Channel 4 Volume + + b8af ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + + b8b0-b8bf Mirror of b8a0-b8af + + b8c0 -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + + b8c1-b8df Mirror of b8c0 + + b900-bfff Mirror of b800-b8ff + + -------------------------------------------------------------------- + + SCC Frequency calculation: + if 8 bit frequency then + Frequency = Input clock / ((bit 0 to 7 of Pitch input) + 1) + else if 4 bit frequency then + Frequency = Input clock / ((bit 8 to 11 of Pitch input) + 1) + else + Frequency = Input clock / (Pitch input + 1) + +*/ + +#include "scc.hpp" + +// shared SCC features +void scc_core::tick() +{ + m_out = 0; + for (auto & elem : m_voice) + { + elem.tick(); + m_out += elem.out; + } +} + +void scc_core::voice_t::tick() +{ + if (pitch >= 9) // or voice is halted + { + // update counter - Post decrement + u16 temp = counter; + if (m_host.m_test.freq_4bit) // 4 bit frequency mode + { + counter = (counter & ~0x0ff) | (bitfield(bitfield(counter, 0, 8) - 1, 0, 8) << 0); + counter = (counter & ~0xf00) | (bitfield(bitfield(counter, 8, 4) - 1, 0, 4) << 8); + } + else + counter = bitfield(counter - 1, 0, 12); + + // handle counter carry + bool carry = m_host.m_test.freq_8bit ? (bitfield(temp, 0, 8) == 0) : + (m_host.m_test.freq_4bit ? (bitfield(temp, 8, 4) == 0) : + (bitfield(temp, 0, 12) == 0)); + if (carry) + { + addr = bitfield(addr + 1, 0, 5); + counter = pitch; + } + } + // get output + if (enable) + out = (wave[addr] * volume) >> 4; // scale to 11 bit digital output + else + out = 0; +} + +void scc_core::reset() +{ + for (auto & elem : m_voice) + elem.reset(); + + m_test.reset(); + m_out = 0; + std::fill(std::begin(m_reg), std::end(m_reg), 0); +} + +void scc_core::voice_t::reset() +{ + std::fill(std::begin(wave), std::end(wave), 0); + enable = false; + pitch = 0; + volume = 0; + addr = 0; + counter = 0; + out = 0; +} + +// SCC accessors +u8 scc_core::wave_r(bool is_sccplus, u8 address) +{ + u8 ret = 0xff; + const u8 voice = bitfield(address, 5, 3); + if (voice > 4) + return ret; + + u8 wave_addr = bitfield(address, 0, 5); + + if (m_test.rotate) // rotate flag + wave_addr = bitfield(wave_addr + m_voice[voice].addr, 0, 5); + + if (!is_sccplus) + { + if (voice == 3) // rotate voice 3~4 flag + { + if (m_test.rotate4 || m_test.rotate) // rotate flag + wave_addr = bitfield(bitfield(address, 0, 5) + m_voice[3 + m_test.rotate].addr, 0, 5); + } + } + ret = m_voice[voice].wave[wave_addr]; + + return ret; +} + +void scc_core::wave_w(bool is_sccplus, u8 address, u8 data) +{ + if (m_test.rotate) // write protected + return; + + const u8 voice = bitfield(address, 5, 3); + if (voice > 4) + return; + + const u8 wave_addr = bitfield(address, 0, 5); + + if (!is_sccplus) + { + if (((voice >= 3) && m_test.rotate4) || (voice >= 4)) // Ignore if write protected, or voice 4 + return; + if (voice >= 3) // voice 3, 4 shares waveform + { + m_voice[3].wave[wave_addr] = data; + m_voice[4].wave[wave_addr] = data; + } + else + m_voice[voice].wave[wave_addr] = data; + } + else + m_voice[voice].wave[wave_addr] = data; +} + +void scc_core::freq_vol_enable_w(u8 address, u8 data) +{ + const u8 voice_freq = bitfield(address, 1, 3); + const u8 voice_reg = bitfield(address, 0, 4); + // *0-*f Pitch, Volume, Enable + switch (voice_reg) + { + case 0x0: // 0x*0 Voice 0 Pitch LSB + case 0x2: // 0x*2 Voice 1 Pitch LSB + case 0x4: // 0x*4 Voice 2 Pitch LSB + case 0x6: // 0x*6 Voice 3 Pitch LSB + case 0x8: // 0x*8 Voice 4 Pitch LSB + if (m_test.resetpos) // Reset address + m_voice[voice_freq].addr = 0; + m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0x0ff) | data; + break; + case 0x1: // 0x*1 Voice 0 Pitch MSB + case 0x3: // 0x*3 Voice 1 Pitch MSB + case 0x5: // 0x*5 Voice 2 Pitch MSB + case 0x7: // 0x*7 Voice 3 Pitch MSB + case 0x9: // 0x*9 Voice 4 Pitch MSB + if (m_test.resetpos) // Reset address + m_voice[voice_freq].addr = 0; + m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0xf00) | (u16(bitfield(data, 0, 4)) << 8); + break; + case 0xa: // 0x*a Voice 0 Volume + case 0xb: // 0x*b Voice 1 Volume + case 0xc: // 0x*c Voice 2 Volume + case 0xd: // 0x*d Voice 3 Volume + case 0xe: // 0x*e Voice 4 Volume + m_voice[voice_reg - 0xa].volume = bitfield(data, 0, 4); + break; + case 0xf: // 0x*f Enable/Disable flag + m_voice[0].enable = bitfield(data, 0); + m_voice[1].enable = bitfield(data, 1); + m_voice[2].enable = bitfield(data, 2); + m_voice[3].enable = bitfield(data, 3); + m_voice[4].enable = bitfield(data, 4); + break; + } +} + +void k051649_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) +{ + const u8 voice = bitfield(address, 5, 3); + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3/4 Waveform + wave_w(false, address, data); + break; + case 0b100: // 0x80-0x9f Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b111: // 0xe0-0xff Test register + m_test.freq_4bit = bitfield(data, 0); + m_test.freq_8bit = bitfield(data, 1); + m_test.resetpos = bitfield(data, 5); + m_test.rotate = bitfield(data, 6); + m_test.rotate4 = bitfield(data, 7); + break; + } + m_reg[address] = data; +} + +void k052539_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) +{ + const u8 voice = bitfield(address, 5, 3); + if (is_sccplus) + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b100: // 0x80-0x9f Voice 4 Waveform + wave_w(true, address, data); + break; + case 0b101: // 0xa0-0xbf Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b110: // 0xc0-0xdf Test register + m_test.freq_4bit = bitfield(data, 0); + m_test.freq_8bit = bitfield(data, 1); + m_test.resetpos = bitfield(data, 5); + m_test.rotate = bitfield(data, 6); + break; + default: + break; + } + } + else + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3/4 Waveform + wave_w(false, address, data); + break; + case 0b100: // 0x80-0x9f Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b110: // 0xc0-0xdf Test register + m_test.freq_4bit = bitfield(data, 0); + m_test.freq_8bit = bitfield(data, 1); + m_test.resetpos = bitfield(data, 5); + m_test.rotate = bitfield(data, 6); + break; + default: + break; + } + } + m_reg[address] = data; +} + +u8 k051649_scc_core::scc_r(bool is_sccplus, u8 address) +{ + const u8 voice = bitfield(address, 5, 3); + const u8 wave = bitfield(address, 0, 5); + u8 ret = 0xff; + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b101: // 0xa0-0xbf Voice 4 Waveform + ret = wave_r(false, (std::min(4, voice) << 5) | wave); + break; + } + return ret; +} + +u8 k052539_scc_core::scc_r(bool is_sccplus, u8 address) +{ + const u8 voice = bitfield(address, 5, 3); + const u8 wave = bitfield(address, 0, 5); + u8 ret = 0xff; + if (is_sccplus) + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b100: // 0x80-0x9f Voice 4 Waveform + ret = wave_r(true, address); + break; + } + } + else + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b101: // 0xa0-0xbf Voice 4 Waveform + ret = wave_r(false, (std::min(4, voice) << 5) | wave); + break; + } + } + return ret; +} diff --git a/src/engine/platform/sound/scc/scc.hpp b/src/engine/platform/sound/scc/scc.hpp new file mode 100644 index 00000000..e696c403 --- /dev/null +++ b/src/engine/platform/sound/scc/scc.hpp @@ -0,0 +1,137 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami SCC emulation core + + See scc.cpp for more info. +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_SCC_HPP +#define _VGSOUND_EMU_SCC_HPP + +#pragma once + +namespace scc +{ + typedef unsigned char u8; + typedef signed char s8; + typedef unsigned short u16; + typedef signed short s16; + typedef unsigned int u32; + typedef signed int s32; + + // get bitfield, bitfield(input, position, len) + template T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } +} + +using namespace scc; +// shared for SCCs +class scc_core +{ +public: + // constructor + scc_core() + : m_voice{*this,*this,*this,*this,*this} + {}; + virtual ~scc_core(){}; + + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) = 0; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) = 0; + + // internal state + virtual void reset(); + void tick(); + + // getters + s32 out() { return m_out; } // output to DA0...DA10 pin + s32 chan_out(u8 ch) { return m_voice[ch].out; } + u8 reg(u8 address) { return m_reg[address]; } + +protected: + // voice structs + struct voice_t + { + // constructor + voice_t(scc_core &host) : m_host(host) {}; + + // internal state + void reset(); + void tick(); + + // registers + scc_core &m_host; + s8 wave[32] = {0}; // internal waveform + bool enable = false; // output enable flag + u16 pitch = 0; // pitch + u8 volume = 0; // volume + u8 addr = 0; // waveform pointer + u16 counter = 0; // frequency counter + s32 out = 0; // current output + }; + voice_t m_voice[5]; // 5 voices + + // accessor + u8 wave_r(bool is_sccplus, u8 address); + void wave_w(bool is_sccplus, u8 address, u8 data); + void freq_vol_enable_w(u8 address, u8 data); + + struct test_t + { + // constructor + test_t() + : freq_4bit(0) + , freq_8bit(0) + , resetpos(0) + , rotate(0) + , rotate4(0) + { }; + + void reset() + { + freq_4bit = 0; + freq_8bit = 0; + resetpos = 0; + rotate = 0; + rotate4 = 0; + } + + u8 freq_4bit : 1; // 4 bit frequency + u8 freq_8bit : 1; // 8 bit frequency + u8 resetpos : 1; // reset counter after pitch writes + u8 rotate : 1; // rotate and write protect waveform for all channels + u8 rotate4 : 1; // same as above but for channel 4 only + }; + + test_t m_test; // test register + s32 m_out = 0; // output to DA0...10 + + u8 m_reg[256] = {0}; // register pool +}; + +// SCC core +class k051649_scc_core : public scc_core +{ +public: + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) override; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; +}; + +class k052539_scc_core : public k051649_scc_core +{ +public: + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) override; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; +}; + +#endif diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index e80153f0..4af60554 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -183,7 +183,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); + 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); @@ -298,13 +298,13 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*(1+(chan[c.chan].baseFreq>>9)); + chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9))); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*(1+(chan[c.chan].baseFreq>>9)); + chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9))); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 97aa7b9e..b089394a 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -203,7 +203,7 @@ void DivPlatformSwan::tick(bool sysTick) { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (i==1 && pcm && furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 4dcc8f6c..65a7452e 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -151,16 +151,16 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) { if (ch<16) { return parent->calcBaseFreq(chipClock,2097152,note,false); } else { - double off=1.0; + double off=65536.0; if (chan[ch].pcm.sample>=0 && chan[ch].pcm.samplesong.sampleLen) { DivSample* s=parent->getSample(chan[ch].pcm.sample); if (s->centerRate<1) { - off=1.0; + off=65536.0; } else { - off=s->centerRate/8363.0; + off=65536.0*(s->centerRate/8363.0); } } - return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false)); + return (int)(parent->calcBaseFreq(chipClock,off,note,false)); } } @@ -208,7 +208,7 @@ void DivPlatformVERA::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,2097152); if (chan[i].freq>65535) chan[i].freq=65535; rWrite(i,0,chan[i].freq&0xff); rWrite(i,1,(chan[i].freq>>8)&0xff); @@ -237,7 +237,16 @@ void DivPlatformVERA::tick(bool sysTick) { } } if (chan[16].freqChanged) { - chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2); + double off=65536.0; + if (chan[16].pcm.sample>=0 && chan[16].pcm.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[16].pcm.sample); + if (s->centerRate<1) { + off=65536.0; + } else { + off=65536.0*(s->centerRate/8363.0); + } + } + chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2,chipClock,off); if (chan[16].freq>128) chan[16].freq=128; rWritePCMRate(chan[16].freq&0xff); chan[16].freqChanged=false; @@ -248,8 +257,8 @@ int DivPlatformVERA::dispatch(DivCommand c) { int tmp; switch (c.cmd) { case DIV_CMD_NOTE_ON: - if(c.chan<16) { - rWriteLo(c.chan,2,chan[c.chan].vol) + 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; if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { @@ -297,11 +306,15 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { tmp=c.value&0x3f; chan[c.chan].vol=tmp; - rWriteLo(c.chan,2,tmp); + if (chan[c.chan].active) { + rWriteLo(c.chan,2,tmp); + } } else { tmp=c.value&0x0f; chan[c.chan].vol=tmp; - rWritePCMVol(tmp); + if (chan[c.chan].active) { + rWritePCMVol(tmp); + } } break; case DIV_CMD_GET_VOLUME: @@ -401,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/vic20.cpp b/src/engine/platform/vic20.cpp index 33250bfc..1c8c1fe0 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -132,7 +132,7 @@ void DivPlatformVIC20::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (i<3) { chan[i].freq>>=(2-i); } else { diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 835acf4b..4e811794 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -197,9 +197,9 @@ void DivPlatformVRC6::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==2) { // sawtooth - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,14)-1; } else { // pulse - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,16)-1; if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 03cd4f6b..f1711243 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -262,17 +262,17 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l double DivPlatformX1_010::NoteX1_010(int ch, int note) { if (chan[ch].pcm) { // PCM note - double off=1.0; + double off=8192.0; int sample=chan[ch].sample; if (sample>=0 && samplesong.sampleLen) { DivSample* s=parent->getSample(sample); if (s->centerRate<1) { - off=1.0; + off=8192.0; } else { - off=s->centerRate/8363.0; + off=8192.0*(s->centerRate/8363.0); } } - return off*parent->calcBaseFreq(chipClock,8192,note,false); + return parent->calcBaseFreq(chipClock,off,note,false); } // Wavetable note return NOTE_FREQUENCY(note); @@ -487,7 +487,19 @@ void DivPlatformX1_010::tick(bool sysTick) { chan[i].envChanged=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); + double off=8192.0; + if (chan[i].pcm) { + int sample=chan[i].sample; + if (sample>=0 && samplesong.sampleLen) { + DivSample* s=parent->getSample(sample); + if (s->centerRate<1) { + off=8192.0; + } else { + off=8192.0*(s->centerRate/8363.0); + } + } + } + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[i].pcm?off:CHIP_FREQBASE); if (chan[i].pcm) { if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq>255) chan[i].freq=255; diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp new file mode 100644 index 00000000..83233e8f --- /dev/null +++ b/src/engine/platform/ym2203.cpp @@ -0,0 +1,1127 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ym2203.h" +#include "sound/ymfm/ymfm.h" +#include "../engine.h" +#include +#include + +#include "ym2203shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[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", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-3) + "FM1_Op1_DT_MULT", "030", + "FM2_Op1_DT_MULT", "031", + "FM3_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "034", + "FM2_Op2_DT_MULT", "035", + "FM3_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "038", + "FM2_Op3_DT_MULT", "039", + "FM3_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03C", + "FM2_Op4_DT_MULT", "03D", + "FM3_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "040", + "FM2_Op1_TL", "041", + "FM3_Op1_TL", "042", + "FM1_Op2_TL", "044", + "FM2_Op2_TL", "045", + "FM3_Op2_TL", "046", + "FM1_Op3_TL", "048", + "FM2_Op3_TL", "049", + "FM3_Op3_TL", "04A", + "FM1_Op4_TL", "04C", + "FM2_Op4_TL", "04D", + "FM3_Op4_TL", "04E", + "FM1_Op1_KS_AR", "050", + "FM2_Op1_KS_AR", "051", + "FM3_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "054", + "FM2_Op2_KS_AR", "055", + "FM3_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "058", + "FM2_Op3_KS_AR", "059", + "FM3_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05C", + "FM2_Op4_KS_AR", "05D", + "FM3_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "060", + "FM2_Op1_AM_DR", "061", + "FM3_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "064", + "FM2_Op2_AM_DR", "065", + "FM3_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "068", + "FM2_Op3_AM_DR", "069", + "FM3_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06C", + "FM2_Op4_AM_DR", "06D", + "FM3_Op4_AM_DR", "06E", + "FM1_Op1_SR", "070", + "FM2_Op1_SR", "071", + "FM3_Op1_SR", "072", + "FM1_Op2_SR", "074", + "FM2_Op2_SR", "075", + "FM3_Op2_SR", "076", + "FM1_Op3_SR", "078", + "FM2_Op3_SR", "079", + "FM3_Op3_SR", "07A", + "FM1_Op4_SR", "07C", + "FM2_Op4_SR", "07D", + "FM3_Op4_SR", "07E", + "FM1_Op1_SL_RR", "080", + "FM2_Op1_SL_RR", "081", + "FM3_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "084", + "FM2_Op2_SL_RR", "085", + "FM3_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "088", + "FM2_Op3_SL_RR", "089", + "FM3_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08C", + "FM2_Op4_SL_RR", "08D", + "FM3_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "090", + "FM2_Op1_SSG_EG", "091", + "FM3_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "094", + "FM2_Op2_SSG_EG", "095", + "FM3_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "098", + "FM2_Op3_SSG_EG", "099", + "FM3_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09C", + "FM2_Op4_SSG_EG", "09D", + "FM3_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A0", + "FM2_FNum1", "0A1", + "FM3_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A4", + "FM2_FNum2", "0A5", + "FM3_(Op1)FNum2", "0A6", + "FM3_Op2_FNum1", "0A8", + "FM3_Op3_FNum1", "0A9", + "FM3_Op4_FNum1", "0AA", + "FM3_Op2_FNum2", "0AC", + "FM3_Op3_FNum2", "0AD", + "FM3_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B0", + "FM2_FB_ALG", "0B1", + "FM3_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B4", + "FM2_Pan_LFO", "0B5", + "FM3_Pan_LFO", "0B6", + NULL +}; + +const char** DivPlatformYM2203::getRegisterSheet() { + return regCheatSheetYM2203; +} + +const char* DivPlatformYM2203::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x20: + return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; + break; + case 0x21: + return "21xx: Set SSG noise frequency (0 to 1F)"; + break; + case 0x22: + return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; + break; + case 0x23: + return "23xx: Set SSG envelope period low byte"; + break; + case 0x24: + return "24xx: Set SSG envelope period high byte"; + break; + case 0x25: + return "25xx: SSG envelope slide up"; + break; + case 0x26: + return "26xx: SSG envelope slide down"; + break; + case 0x29: + return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; + break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; + } + return NULL; +} + +void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os; + + /*ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + fmChan[i]=fme->debug_channel(i); + adpcmAChan[i]=aae->debug_channel(i); + }*/ + + for (size_t h=start; hwrite(0x0,w.addr); + fm->write(0x1,w.val); + regPool[w.addr&0xff]=w.val; + writes.pop(); + delay=16; + } + } + + fm->generate(&fmout); + + os=fmout.data[0]+((fmout.data[1]+fmout.data[2]+fmout.data[3])>>1); + if (os<-32768) os=-32768; + if (os>32767) os=32767; + + bufL[h]=os; + + /* + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=6; i<9; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + } + + for (int i=9; i<15; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + } + + oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + */ + } +} + +void DivPlatformYM2203::tick(bool sysTick) { + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + + // FM + for (int i=0; i<3; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); + } else { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } + immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } + chan[i].keyOff=false; + } + } + + for (int i=16; i<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 (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + if (c.value==0 && c.value2==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); + } + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (c.chan>2 || parent->song.linearPitch==2) { // PSG + int destFreq=NOTE_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_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_FB: { + if (c.chan>2) break; + chan[c.chan].state.fb=c.value&7; + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + if (c.chan>2) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + if (c.chan>2) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.chan>2) break; + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan>2) return 15; + return 127; + break; + case DIV_CMD_PRE_PORTA: + if (c.chan>2) { + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformYM2203::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>2) { // PSG + ay->muteChannel(ch-3,mute); + return; + } + // FM + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); +} + +void DivPlatformYM2203::forceIns() { + for (int i=0; i<3; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + for (int i=3; i<6; i++) { + chan[i].insChanged=true; + } + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); +} + +void* DivPlatformYM2203::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2203::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformYM2203::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2203::getRegisterPoolSize() { + return 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; + + // LFO + immWrite(0x22,0x08); + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); +} + +bool DivPlatformYM2203::isStereo() { + return false; +} + +bool DivPlatformYM2203::keyOffAffectsArp(int ch) { + return (ch>2); +} + +void DivPlatformYM2203::notifyInsChange(int ins) { + for (int i=0; i<6; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } + ay->notifyInsChange(ins); +} + +void DivPlatformYM2203::notifyInsDeletion(void* ins) { + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2203::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); +} + +void DivPlatformYM2203::setFlags(unsigned int flags) { + unsigned char ayFlags=32; + if (flags==3) { + chipClock=3000000.0; + ayFlags=36; + } else if (flags==2) { + chipClock=4000000.0; + ayFlags=35; + } else if (flags==1) { + chipClock=COLOR_PAL*4.0/5.0; + ayFlags=33; + } else { + chipClock=COLOR_NTSC; + ayFlags=32; + } + ay->setFlags(ayFlags); + rate=fm->sample_rate(chipClock); + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } +} + +int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<6; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + fm=new ymfm::ym2203(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); + setFlags(flags); + + reset(); + return 16; +} + +void DivPlatformYM2203::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } + ay->quit(); + delete ay; + delete fm; +} + +DivPlatformYM2203::~DivPlatformYM2203() { +} diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h new file mode 100644 index 00000000..1a6dad58 --- /dev/null +++ b/src/engine/platform/ym2203.h @@ -0,0 +1,134 @@ +/** + * 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; + unsigned char pan; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + portaPauseFreq(0), + note(0), + ins(-1), + psgMode(1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(15), + sample(-1), + pan(3) {} + }; + Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; + bool isMuted[6]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ymfm::ym2203* fm; + ymfm::ym2203::output_data fmout; + DivYM2203Interface iface; + unsigned char regPool[512]; + unsigned char lastBusy; + + DivPlatformAY8910* ay; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[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/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..3b3d5576 --- /dev/null +++ b/src/engine/platform/ym2608.cpp @@ -0,0 +1,1449 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ym2608.h" +#include "sound/ymfm/ymfm.h" +#include "../engine.h" +#include +#include + +#include "ym2610shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 +}; + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetYM2610B[]={ + // SSG + "SSG_FreqL_A", "000", + "SSG_FreqH_A", "001", + "SSG_FreqL_B", "002", + "SSG_FreqH_B", "003", + "SSG_FreqL_C", "004", + "SSG_FreqH_C", "005", + "SSG_FreqNoise", "006", + "SSG_Enable", "007", + "SSG_Volume_A", "008", + "SSG_Volume_B", "009", + "SSG_Volume_C", "00A", + "SSG_FreqL_Env", "00B", + "SSG_FreqH_Env", "00C", + "SSG_Control_Env", "00D", + // ADPCM-B + "ADPCMB_Control", "010", + "ADPCMB_L_R", "011", + "ADPCMB_StartL", "012", + "ADPCMB_StartH", "013", + "ADPCMB_EndL", "014", + "ADPCMB_EndH", "015", + "ADPCMB_FreqL", "019", + "ADPCMB_FreqH", "01A", + "ADPCMB_Volume", "01B", + "ADPCM_Flag", "01C", + // FM (Common) + "FM_Test", "021", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-3) + "FM1_Op1_DT_MULT", "030", + "FM2_Op1_DT_MULT", "031", + "FM3_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "034", + "FM2_Op2_DT_MULT", "035", + "FM3_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "038", + "FM2_Op3_DT_MULT", "039", + "FM3_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03C", + "FM2_Op4_DT_MULT", "03D", + "FM3_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "040", + "FM2_Op1_TL", "041", + "FM3_Op1_TL", "042", + "FM1_Op2_TL", "044", + "FM2_Op2_TL", "045", + "FM3_Op2_TL", "046", + "FM1_Op3_TL", "048", + "FM2_Op3_TL", "049", + "FM3_Op3_TL", "04A", + "FM1_Op4_TL", "04C", + "FM2_Op4_TL", "04D", + "FM3_Op4_TL", "04E", + "FM1_Op1_KS_AR", "050", + "FM2_Op1_KS_AR", "051", + "FM3_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "054", + "FM2_Op2_KS_AR", "055", + "FM3_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "058", + "FM2_Op3_KS_AR", "059", + "FM3_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05C", + "FM2_Op4_KS_AR", "05D", + "FM3_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "060", + "FM2_Op1_AM_DR", "061", + "FM3_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "064", + "FM2_Op2_AM_DR", "065", + "FM3_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "068", + "FM2_Op3_AM_DR", "069", + "FM3_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06C", + "FM2_Op4_AM_DR", "06D", + "FM3_Op4_AM_DR", "06E", + "FM1_Op1_SR", "070", + "FM2_Op1_SR", "071", + "FM3_Op1_SR", "072", + "FM1_Op2_SR", "074", + "FM2_Op2_SR", "075", + "FM3_Op2_SR", "076", + "FM1_Op3_SR", "078", + "FM2_Op3_SR", "079", + "FM3_Op3_SR", "07A", + "FM1_Op4_SR", "07C", + "FM2_Op4_SR", "07D", + "FM3_Op4_SR", "07E", + "FM1_Op1_SL_RR", "080", + "FM2_Op1_SL_RR", "081", + "FM3_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "084", + "FM2_Op2_SL_RR", "085", + "FM3_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "088", + "FM2_Op3_SL_RR", "089", + "FM3_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08C", + "FM2_Op4_SL_RR", "08D", + "FM3_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "090", + "FM2_Op1_SSG_EG", "091", + "FM3_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "094", + "FM2_Op2_SSG_EG", "095", + "FM3_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "098", + "FM2_Op3_SSG_EG", "099", + "FM3_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09C", + "FM2_Op4_SSG_EG", "09D", + "FM3_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A0", + "FM2_FNum1", "0A1", + "FM3_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A4", + "FM2_FNum2", "0A5", + "FM3_(Op1)FNum2", "0A6", + "FM3_Op2_FNum1", "0A8", + "FM3_Op3_FNum1", "0A9", + "FM3_Op4_FNum1", "0AA", + "FM3_Op2_FNum2", "0AC", + "FM3_Op3_FNum2", "0AD", + "FM3_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B0", + "FM2_FB_ALG", "0B1", + "FM3_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B4", + "FM2_Pan_LFO", "0B5", + "FM3_Pan_LFO", "0B6", + // ADPCM-A + "ADPCMA_Control", "100", + "ADPCMA_MVol", "101", + "ADPCMA_Test", "102", + "ADPCMA_Ch1_Vol", "108", + "ADPCMA_Ch2_Vol", "109", + "ADPCMA_Ch3_Vol", "10A", + "ADPCMA_Ch4_Vol", "10B", + "ADPCMA_Ch5_Vol", "10C", + "ADPCMA_Ch6_Vol", "10D", + "ADPCMA_Ch1_StL", "110", + "ADPCMA_Ch2_StL", "111", + "ADPCMA_Ch3_StL", "112", + "ADPCMA_Ch4_StL", "113", + "ADPCMA_Ch5_StL", "114", + "ADPCMA_Ch6_StL", "115", + "ADPCMA_Ch1_StH", "118", + "ADPCMA_Ch2_StH", "119", + "ADPCMA_Ch3_StH", "11A", + "ADPCMA_Ch4_StH", "11B", + "ADPCMA_Ch5_StH", "11C", + "ADPCMA_Ch6_StH", "11D", + "ADPCMA_Ch1_EdL", "120", + "ADPCMA_Ch2_EdL", "121", + "ADPCMA_Ch3_EdL", "122", + "ADPCMA_Ch4_EdL", "123", + "ADPCMA_Ch5_EdL", "124", + "ADPCMA_Ch6_EdL", "125", + "ADPCMA_Ch1_EdH", "128", + "ADPCMA_Ch2_EdH", "129", + "ADPCMA_Ch3_EdH", "12A", + "ADPCMA_Ch4_EdH", "12B", + "ADPCMA_Ch5_EdH", "12C", + "ADPCMA_Ch6_EdH", "12D", + // FM (Channel 4-6) + "FM4_Op1_DT_MULT", "130", + "FM5_Op1_DT_MULT", "131", + "FM6_Op1_DT_MULT", "132", + "FM4_Op2_DT_MULT", "134", + "FM5_Op2_DT_MULT", "135", + "FM6_Op2_DT_MULT", "136", + "FM4_Op3_DT_MULT", "138", + "FM5_Op3_DT_MULT", "139", + "FM6_Op3_DT_MULT", "13A", + "FM4_Op4_DT_MULT", "13C", + "FM5_Op4_DT_MULT", "13D", + "FM6_Op4_DT_MULT", "13E", + "FM4_Op1_TL", "140", + "FM5_Op1_TL", "141", + "FM6_Op1_TL", "142", + "FM4_Op2_TL", "144", + "FM5_Op2_TL", "145", + "FM6_Op2_TL", "146", + "FM4_Op3_TL", "148", + "FM5_Op3_TL", "149", + "FM6_Op3_TL", "14A", + "FM4_Op4_TL", "14C", + "FM5_Op4_TL", "14D", + "FM6_Op4_TL", "14E", + "FM4_Op1_KS_AR", "150", + "FM5_Op1_KS_AR", "151", + "FM6_Op1_KS_AR", "152", + "FM4_Op2_KS_AR", "154", + "FM5_Op2_KS_AR", "155", + "FM6_Op2_KS_AR", "156", + "FM4_Op3_KS_AR", "158", + "FM5_Op3_KS_AR", "159", + "FM6_Op3_KS_AR", "15A", + "FM4_Op4_KS_AR", "15C", + "FM5_Op4_KS_AR", "15D", + "FM6_Op4_KS_AR", "15E", + "FM4_Op1_AM_DR", "160", + "FM5_Op1_AM_DR", "161", + "FM6_Op1_AM_DR", "162", + "FM4_Op2_AM_DR", "164", + "FM5_Op2_AM_DR", "165", + "FM6_Op2_AM_DR", "166", + "FM4_Op3_AM_DR", "168", + "FM5_Op3_AM_DR", "169", + "FM6_Op3_AM_DR", "16A", + "FM4_Op4_AM_DR", "16C", + "FM5_Op4_AM_DR", "16D", + "FM6_Op4_AM_DR", "16E", + "FM4_Op1_SR", "170", + "FM5_Op1_SR", "171", + "FM6_Op1_SR", "172", + "FM4_Op2_SR", "174", + "FM5_Op2_SR", "175", + "FM6_Op2_SR", "176", + "FM4_Op3_SR", "178", + "FM5_Op3_SR", "179", + "FM6_Op3_SR", "17A", + "FM4_Op4_SR", "17C", + "FM5_Op4_SR", "17D", + "FM6_Op4_SR", "17E", + "FM4_Op1_SL_RR", "180", + "FM5_Op1_SL_RR", "181", + "FM6_Op1_SL_RR", "182", + "FM4_Op2_SL_RR", "184", + "FM5_Op2_SL_RR", "185", + "FM6_Op2_SL_RR", "186", + "FM4_Op3_SL_RR", "188", + "FM5_Op3_SL_RR", "189", + "FM6_Op3_SL_RR", "18A", + "FM4_Op4_SL_RR", "18C", + "FM5_Op4_SL_RR", "18D", + "FM6_Op4_SL_RR", "18E", + "FM4_Op1_SSG_EG", "190", + "FM5_Op1_SSG_EG", "191", + "FM6_Op1_SSG_EG", "192", + "FM4_Op2_SSG_EG", "194", + "FM5_Op2_SSG_EG", "195", + "FM6_Op2_SSG_EG", "196", + "FM4_Op3_SSG_EG", "198", + "FM5_Op3_SSG_EG", "199", + "FM6_Op3_SSG_EG", "19A", + "FM4_Op4_SSG_EG", "19C", + "FM5_Op4_SSG_EG", "19D", + "FM6_Op4_SSG_EG", "19E", + "FM4_FNum1", "1A0", + "FM5_FNum1", "1A1", + "FM6_FNum1", "1A2", + "FM4_FNum2", "1A4", + "FM5_FNum2", "1A5", + "FM6_FNum2", "1A6", + "FM4_FB_ALG", "1B0", + "FM5_FB_ALG", "1B1", + "FM6_FB_ALG", "1B2", + "FM4_Pan_LFO", "1B4", + "FM5_Pan_LFO", "1B5", + "FM6_Pan_LFO", "1B6", + NULL +}; + +const char** DivPlatformYM2610B::getRegisterSheet() { + return regCheatSheetYM2610B; +} + +const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x20: + return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; + break; + case 0x21: + return "21xx: Set SSG noise frequency (0 to 1F)"; + break; + case 0x22: + return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; + break; + case 0x23: + return "23xx: Set SSG envelope period low byte"; + break; + case 0x24: + return "24xx: Set SSG envelope period high byte"; + break; + case 0x25: + return "25xx: SSG envelope slide up"; + break; + case 0x26: + return "26xx: SSG envelope slide down"; + break; + case 0x29: + return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; + break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; + } + return NULL; +} + +double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { + if (ch>8) { // ADPCM-B + return NOTE_ADPCMB(note); + } else if (ch>5) { // PSG + return NOTE_PERIODIC(note); + } + // FM + return NOTE_FNUM_BLOCK(note,11); +} + +double DivPlatformYM2610B::NOTE_ADPCMB(int note) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); + } + return 0; +} + +void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + + ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + fmChan[i]=fme->debug_channel(i); + adpcmAChan[i]=aae->debug_channel(i); + } + + for (size_t h=start; hwrite(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + delay=4; + } + } + + fm->generate(&fmout); + + os[0]=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + + + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=6; i<9; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + } + + for (int i=9; i<15; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + } + + oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + } +} + +void DivPlatformYM2610B::tick(bool sysTick) { + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + + // FM + for (int i=0; i<6; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); + } else { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } + immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } + chan[i].keyOff=false; + } + } + // ADPCM-B + if (chan[15].furnacePCM) { + chan[15].std.next(); + + if (chan[15].std.vol.had) { + chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; + immWrite(0x1b,chan[15].outVol); + } + + if (chan[15].std.arp.had) { + if (!chan[15].inPorta) { + if (chan[15].std.arp.mode) { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val); + } else { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val); + } + } + chan[15].freqChanged=true; + } else { + if (chan[15].std.arp.mode && chan[15].std.arp.finished) { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].note); + chan[15].freqChanged=true; + } + } + } + if (chan[15].freqChanged) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); + } else { + chan[15].freq=0; + } + immWrite(0x19,chan[15].freq&0xff); + immWrite(0x1a,(chan[15].freq>>8)&0xff); + chan[15].freqChanged=false; + } + + for (int i=16; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<6; i++) { + if (i==2 && extMode) continue; + if (chan[i].freqChanged) { + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + } + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } +} + +int DivPlatformYM2610B::dispatch(DivCommand c) { + if (c.chan>5 && c.chan<9) { + c.chan-=6; + return ay->dispatch(c); + } + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + if (c.chan>14) { // ADPCM-B + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + immWrite(0x1b,chan[c.chan].outVol); + } + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } + } else { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); + chan[c.chan].freqChanged=true; + } + break; + } + if (c.chan>8) { // ADPCM-A + if (skipRegisterWrites) break; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x110+c.chan-9,0); + immWrite(0x118+c.chan-9,0); + immWrite(0x120+c.chan-9,0); + immWrite(0x128+c.chan-9,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x110+c.chan-9,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-9,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-9,(end>>8)&0xff); + immWrite(0x128+c.chan-9,end>>16); + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x100,0x00|(1<<(c.chan-9))); + break; + } + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroInit(ins); + if (c.chan<6) { + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + } + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan>14) { + immWrite(0x10,0x01); // reset + break; + } + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan>14) { + immWrite(0x10,0x01); // reset + break; + } + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + if (c.chan>14) { // ADPCM-B + immWrite(0x1b,chan[c.chan].outVol); + break; + } + if (c.chan>8) { // ADPCM-A + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + if (c.value==0 && c.value2==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); + } + if (c.chan>14) { + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + break; + } + if (c.chan>8) { + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (c.chan>5 || parent->song.linearPitch==2) { // PSG, ADPCM-B + int destFreq=NOTE_OPNB(c.chan,c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); + int newFreq; + bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value; + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value; + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + // check for octave boundary + // what the heck! + if (!chan[c.chan].portaPause) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + iface.sampleBank=sampleBank; + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_FB: { + if (c.chan>5) break; + chan[c.chan].state.fb=c.value&7; + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.chan>5) break; + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan>14) return 255; + if (c.chan>8) return 31; + if (c.chan>5) return 15; + return 127; + break; + case DIV_CMD_PRE_PORTA: + if (c.chan>5) { + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformYM2610B::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>14) { // ADPCM-B + immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + } + if (ch>8) { // ADPCM-A + immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + return; + } + if (ch>5) { // PSG + ay->muteChannel(ch-6,mute); + return; + } + // FM + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); +} + +void DivPlatformYM2610B::forceIns() { + for (int i=0; i<6; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + for (int i=9; i<16; i++) { + chan[i].insChanged=true; + } + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); +} + +void* DivPlatformYM2610B::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2610B::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformYM2610B::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2610B::getRegisterPoolSize() { + return 512; +} + +void DivPlatformYM2610B::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformYM2610B::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformYM2610B::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + fm->reset(); + for (int i=0; i<16; i++) { + chan[i]=DivPlatformYM2610B::Channel(); + chan[i].std.setEngine(parent); + } + for (int i=0; i<6; i++) { + chan[i].vol=0x7f; + chan[i].outVol=0x7f; + } + for (int i=6; i<9; i++) { + chan[i].vol=0x0f; + } + for (int i=9; i<15; i++) { + chan[i].vol=0x1f; + } + chan[15].vol=0xff; + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + sampleBank=0; + + delay=0; + + extMode=false; + + // LFO + immWrite(0x22,0x08); + + // PCM volume + immWrite(0x101,0x3f); // A + immWrite(0x1b,0xff); // B + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); +} + +bool DivPlatformYM2610B::isStereo() { + return true; +} + +bool DivPlatformYM2610B::keyOffAffectsArp(int ch) { + return (ch>5); +} + +void DivPlatformYM2610B::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } + ay->notifyInsChange(ins); +} + +void DivPlatformYM2610B::notifyInsDeletion(void* ins) { + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); +} + +int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2610Base::init(p, channels, sugRate, flags); + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + chipClock=8000000; + rate=chipClock/16; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + fm=new ymfm::ym2610b(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); + reset(); + return 16; +} + +void DivPlatformYM2610B::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } + ay->quit(); + delete ay; + delete fm; + DivPlatformYM2610Base::quit(); +} + +DivPlatformYM2610B::~DivPlatformYM2610B() { +} diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h new file mode 100644 index 00000000..0de28f6f --- /dev/null +++ b/src/engine/platform/ym2608.h @@ -0,0 +1,130 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _YM2608_H +#define _YM2608_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ymfm/ymfm_opn.h" + +#include "ym2610.h" + +class DivPlatformYM2608: public DivPlatformYM2610Base { + protected: + const unsigned short chanOffs[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + + struct Channel { + DivInstrumentFM state; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + int vol, outVol; + int sample; + unsigned char pan; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + portaPauseFreq(0), + note(0), + ins(-1), + psgMode(1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(15), + sample(-1), + pan(3) {} + }; + Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ymfm::ym2608* fm; + ymfm::ym2608::output_data fmout; + unsigned char regPool[512]; + unsigned char lastBusy; + + DivPlatformAY8910* ay; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[512]; + short pendingWrites[512]; + + double NOTE_OPNB(int ch, int note); + double NOTE_ADPCMB(int note); + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void setSkipRegisterWrites(bool val); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2608(); +}; +#endif diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 54bbbd21..397f6a8f 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -456,13 +456,13 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { return NOTE_PERIODIC(note); } // FM - return NOTE_FREQUENCY(note); + return NOTE_FNUM_BLOCK(note,11); } double DivPlatformYM2610::NOTE_ADPCMB(int note) { if (chan[13].sample>=0 && chan[13].samplesong.sampleLen) { - double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; - return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); } return 0; } @@ -559,15 +559,15 @@ void DivPlatformYM2610::tick(bool sysTick) { 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); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); + 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_FREQUENCY(chan[i].note); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); chan[i].freqChanged=true; } } @@ -726,7 +726,12 @@ void DivPlatformYM2610::tick(bool sysTick) { } } if (chan[13].freqChanged) { - chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4); + if (chan[13].sample>=0 && chan[13].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; + chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4,chan[13].pitch2,(double)chipClock/144,off); + } else { + chan[13].freq=0; + } immWrite(0x19,chan[13].freq&0xff); immWrite(0x1a,(chan[13].freq>>8)&0xff); chan[13].freqChanged=false; @@ -742,11 +747,24 @@ void DivPlatformYM2610::tick(bool sysTick) { for (int i=0; i<4; i++) { if (i==1 && extMode) continue; if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); - if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq)+chan[i].pitch2; - immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + 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,chipClock,CHIP_FREQBASE,11); + 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) { @@ -756,47 +774,6 @@ void DivPlatformYM2610::tick(bool sysTick) { } } -int DivPlatformYM2610::octave(int freq) { - if (freq>=622.0f*128) { - return 128; - } else if (freq>=622.0f*64) { - return 64; - } else if (freq>=622.0f*32) { - return 32; - } else if (freq>=622.0f*16) { - return 16; - } else if (freq>=622.0f*8) { - return 8; - } else if (freq>=622.0f*4) { - return 4; - } else if (freq>=622.0f*2) { - return 2; - } else { - return 1; - } - return 1; -} - -int DivPlatformYM2610::toFreq(int freq) { - if (freq>=622.0f*128) { - return 0x3800|((freq>>7)&0x7ff); - } else if (freq>=622.0f*64) { - return 0x3000|((freq>>6)&0x7ff); - } else if (freq>=622.0f*32) { - return 0x2800|((freq>>5)&0x7ff); - } else if (freq>=622.0f*16) { - return 0x2000|((freq>>4)&0x7ff); - } else if (freq>=622.0f*8) { - return 0x1800|((freq>>3)&0x7ff); - } else if (freq>=622.0f*4) { - return 0x1000|((freq>>2)&0x7ff); - } else if (freq>=622.0f*2) { - return 0x800|((freq>>1)&0x7ff); - } else { - return freq&0x7ff; - } -} - int DivPlatformYM2610::dispatch(DivCommand c) { if (c.chan>3 && c.chan<7) { c.chan-=4; @@ -928,7 +905,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + 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; @@ -1025,7 +1002,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>3) { // PSG, ADPCM-B + if (c.chan>3 || parent->song.linearPitch==2) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -1048,33 +1025,48 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } - - int destFreq=NOTE_FREQUENCY(c.value2); + 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*octave(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*octave(chan[c.chan].baseFreq); + 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 (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + 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].baseFreq=newFreq; chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; - if (return2) return 2; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } break; } case DIV_CMD_SAMPLE_BANK: diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 45ecb335..92f4ca07 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -61,7 +61,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, note, ins; + 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; @@ -80,6 +80,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { baseFreq(0), pitch(0), pitch2(0), + portaPauseFreq(0), note(0), ins(-1), psgMode(1), @@ -125,8 +126,6 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { short oldWrites[512]; short pendingWrites[512]; - int octave(int freq); - int toFreq(int freq); double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index b36cc87f..cd40fe13 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -436,13 +436,13 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { return NOTE_PERIODIC(note); } // FM - return NOTE_FREQUENCY(note); + return NOTE_FNUM_BLOCK(note,11); } double DivPlatformYM2610B::NOTE_ADPCMB(int note) { if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { - double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; - return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); } return 0; } @@ -538,15 +538,15 @@ void DivPlatformYM2610B::tick(bool sysTick) { 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); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); + 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_FREQUENCY(chan[i].note); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); chan[i].freqChanged=true; } } @@ -704,7 +704,12 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } if (chan[15].freqChanged) { - chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4); + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); + } else { + chan[15].freq=0; + } immWrite(0x19,chan[15].freq&0xff); immWrite(0x1a,(chan[15].freq>>8)&0xff); chan[15].freqChanged=false; @@ -720,11 +725,24 @@ void DivPlatformYM2610B::tick(bool sysTick) { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); - if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq)+chan[i].pitch2; - immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + 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) { @@ -734,47 +752,6 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } -int DivPlatformYM2610B::octave(int freq) { - if (freq>=622.0f*128) { - return 128; - } else if (freq>=622.0f*64) { - return 64; - } else if (freq>=622.0f*32) { - return 32; - } else if (freq>=622.0f*16) { - return 16; - } else if (freq>=622.0f*8) { - return 8; - } else if (freq>=622.0f*4) { - return 4; - } else if (freq>=622.0f*2) { - return 2; - } else { - return 1; - } - return 1; -} - -int DivPlatformYM2610B::toFreq(int freq) { - if (freq>=622.0f*128) { - return 0x3800|((freq>>7)&0x7ff); - } else if (freq>=622.0f*64) { - return 0x3000|((freq>>6)&0x7ff); - } else if (freq>=622.0f*32) { - return 0x2800|((freq>>5)&0x7ff); - } else if (freq>=622.0f*16) { - return 0x2000|((freq>>4)&0x7ff); - } else if (freq>=622.0f*8) { - return 0x1800|((freq>>3)&0x7ff); - } else if (freq>=622.0f*4) { - return 0x1000|((freq>>2)&0x7ff); - } else if (freq>=622.0f*2) { - return 0x800|((freq>>1)&0x7ff); - } else { - return freq&0x7ff; - } -} - int DivPlatformYM2610B::dispatch(DivCommand c) { if (c.chan>5 && c.chan<9) { c.chan-=6; @@ -906,7 +883,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + 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; @@ -1003,7 +980,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>5) { // PSG, ADPCM-B + 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) { @@ -1026,33 +1003,48 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } - - int destFreq=NOTE_FREQUENCY(c.value2); + 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*octave(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*octave(chan[c.chan].baseFreq); + 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 (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + 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].baseFreq=newFreq; chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; - if (return2) return 2; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } break; } case DIV_CMD_SAMPLE_BANK: diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index d1af3069..3a034028 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -35,7 +35,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, note, ins; + 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; @@ -54,6 +54,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { baseFreq(0), pitch(0), pitch2(0), + portaPauseFreq(0), note(0), ins(-1), psgMode(1), @@ -98,8 +99,6 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { short oldWrites[512]; short pendingWrites[512]; - int octave(int freq); - int toFreq(int freq); double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 03b91fcc..440dce6a 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -120,31 +120,74 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + 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*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // what the heck! if (!opChan[ch].portaPause) { - if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { - opChan[ch].portaPause=true; - break; + 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].baseFreq=newFreq; opChan[ch].portaPause=false; opChan[ch].freqChanged=true; + opChan[ch].baseFreq=newFreq; if (return2) return 2; break; } @@ -364,14 +407,24 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2); - if (opChan[i].freq>262143) opChan[i].freq=262143; - int freqt=toFreq(opChan[i].freq); - opChan[i].freqH=freqt>>8; - opChan[i].freqL=freqt&0xff; - immWrite(opChanOffsH[i],opChan[i].freqH); - immWrite(opChanOffsL[i],opChan[i].freqL); - opChan[i].freqChanged=false; + 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) { diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index b416b5c7..4e3f7c5f 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -25,12 +25,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, ins; + 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), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + 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]; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 1ffbd74f..b12fd3de 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -63,7 +63,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].portaPause=false; opChan[ch].freqChanged=true; } @@ -120,36 +120,79 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + 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*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // what the heck! if (!opChan[ch].portaPause) { - if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { - opChan[ch].portaPause=true; - break; + 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].baseFreq=newFreq; 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].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].freqChanged=true; break; } @@ -364,14 +407,24 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2); - if (opChan[i].freq>262143) opChan[i].freq=262143; - int freqt=toFreq(opChan[i].freq); - opChan[i].freqH=freqt>>8; - opChan[i].freqL=freqt&0xff; - immWrite(opChanOffsH[i],opChan[i].freqH); - immWrite(opChanOffsL[i],opChan[i].freqL); - opChan[i].freqChanged=false; + 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) { diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 10be6638..287bf08b 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -25,12 +25,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, ins; + 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), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + 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]; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 224838e8..6a27dc65 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -268,26 +268,53 @@ void DivEngine::processRow(int i, bool afterDelay) { int whatRow=afterDelay?chan[i].delayRow:curRow; DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false); // pre effects - if (!afterDelay) for (int j=0; jdata[whatRow][4+(j<<1)]; - short effectVal=pat->data[whatRow][5+(j<<1)]; + if (!afterDelay) { + bool returnAfterPre=false; + for (int j=0; jdata[whatRow][4+(j<<1)]; + short effectVal=pat->data[whatRow][5+(j<<1)]; - if (effectVal==-1) effectVal=0; - if (effect==0xed && effectVal!=0) { - if (effectVal<=nextSpeed) { - chan[i].rowDelay=effectVal+1; - chan[i].delayOrder=whatOrder; - chan[i].delayRow=whatRow; - if (effectVal==nextSpeed) { - //if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; - } else { - chan[i].delayLocked=false; - } - return; - } else { - chan[i].delayLocked=false; + if (effectVal==-1) effectVal=0; + + switch (effect) { + case 0x09: // speed 1 + if (effectVal>0) speed1=effectVal; + break; + case 0x0f: // speed 2 + if (effectVal>0) speed2=effectVal; + break; + case 0x0b: // change order + if (changeOrd==-1) { + changeOrd=effectVal; + changePos=0; + } + break; + case 0x0d: // next order + if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + break; + case 0xed: // delay + if (effectVal!=0) { + if (effectVal<=nextSpeed) { + chan[i].rowDelay=effectVal+1; + chan[i].delayOrder=whatOrder; + chan[i].delayRow=whatRow; + if (effectVal==nextSpeed) { + //if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; + } else { + chan[i].delayLocked=false; + } + returnAfterPre=true; + } else { + chan[i].delayLocked=false; + } + } + break; } } + if (returnAfterPre) return; } if (chan[i].delayLocked) return; @@ -386,24 +413,6 @@ void DivEngine::processRow(int i, bool afterDelay) { // per-system effect if (!perSystemEffect(i,effect,effectVal)) switch (effect) { - case 0x09: // speed 1 - if (effectVal>0) speed1=effectVal; - break; - case 0x0f: // speed 2 - if (effectVal>0) speed2=effectVal; - break; - case 0x0b: // change order - if (changeOrd==-1) { - changeOrd=effectVal; - changePos=0; - } - break; - case 0x0d: // next order - if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { - changeOrd=-2; - changePos=effectVal; - } - break; case 0x08: // panning (split 4-bit) chan[i].panL=(effectVal>>4)|(effectVal&0xf0); chan[i].panR=(effectVal&15)|((effectVal&15)<<4); @@ -631,7 +640,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].scheduledSlideReset=false; chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); - dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote)); + dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote)); chan[i].portaNote=-1; chan[i].portaSpeed=-1; chan[i].inPorta=false; @@ -954,7 +963,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (!song.noSlidesOnFirstTick || !firstTick) { if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { chan[i].portaSpeed=0; chan[i].oldNote=chan[i].note; chan[i].note=chan[i].portaNote; diff --git a/src/engine/song.h b/src/engine/song.h index 7b823a8b..d8a37cbb 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -301,7 +301,12 @@ struct DivSong { // compatibility flags bool limitSlides; - bool linearPitch; + // linear pitch + // 0: not linear + // 1: only pitch changes (04xy/E5xx) linear + // 2: full linear + unsigned char linearPitch; + unsigned char pitchSlideSpeed; // loop behavior // 0: reset on loop // 1: fake reset on loop @@ -371,7 +376,6 @@ struct DivSong { */ void clearSamples(); - /** * unloads the song, freeing all memory associated with it. * use before destroying the object. @@ -412,7 +416,8 @@ struct DivSong { masterVol(1.0f), tuning(440.0f), limitSlides(false), - linearPitch(true), + linearPitch(2), + pitchSlideSpeed(4), loopModality(0), properNoiseLayout(false), waveDutyIsVol(false), diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index c5664840..93478989 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1259,11 +1259,14 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_OPN]=new DivSysDef( - "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0, false, + "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0x151, false, {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, {"F1", "F2", "F3", "S1", "S2", "S3"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY} + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler ); sysDefs[DIV_SYSTEM_PC98]=new DivSysDef( @@ -1459,11 +1462,13 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SCC]=new DivSysDef( - "Konami SCC", NULL, 0xa1, 0, 5, false, true, 0, false, + "Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, + {}, + waveOnlyEffectHandler ); auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { @@ -1740,11 +1745,13 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( - "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0, false, + "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, + {}, + waveOnlyEffectHandler ); sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef( diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 53800a52..53eddc1b 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -530,6 +530,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_OPN: + w->writeC(5|baseAddr1); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: @@ -598,6 +603,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; @@ -884,6 +954,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + case DIV_SYSTEM_OPN: + if (!hasOPN) { + hasOPN=disCont[i].dispatch->chipClock; + willExport[i]=true; + writeDACSamples=true; + } else if (!(hasOPN&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasOPN|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: @@ -988,6 +1070,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/compatFlags.cpp b/src/gui/compatFlags.cpp index 52f46502..461048d6 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -18,6 +18,8 @@ */ #include "gui.h" +#include "imgui.h" +#include "intConst.h" void FurnaceGUI::drawCompatFlags() { if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) { @@ -32,10 +34,6 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); } - ImGui::Checkbox("Linear pitch control",&e->song.linearPitch); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space"); - } ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); @@ -126,6 +124,35 @@ void FurnaceGUI::drawCompatFlags() { ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space."); } + ImGui::Text("Pitch linearity:"); + if (ImGui::RadioButton("None",e->song.linearPitch==0)) { + e->song.linearPitch=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("like ProTracker/FamiTracker"); + } + if (ImGui::RadioButton("Partial (only 04xy/E5xx)",e->song.linearPitch==1)) { + e->song.linearPitch=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("like DefleMask"); + } + if (ImGui::RadioButton("Full",e->song.linearPitch==2)) { + e->song.linearPitch=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("like Impulse Tracker"); + } + + if (e->song.linearPitch==2) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(120.0f*dpiScale); + if (ImGui::InputScalar("Pitch slide speed multiplier",ImGuiDataType_U8,&e->song.pitchSlideSpeed,&_ONE,&_ONE)) { + if (e->song.pitchSlideSpeed<1) e->song.pitchSlideSpeed=1; + if (e->song.pitchSlideSpeed>64) e->song.pitchSlideSpeed=64; + } + } + ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { e->song.loopModality=0; diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 0de55205..0df3015a 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -47,7 +47,20 @@ void FurnaceGUI::drawInsList() { } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { - doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); + doAction(GUI_ACTION_INS_LIST_OPEN); + } + if (ImGui::BeginPopupContextItem("InsOpenOpt")) { + if (ImGui::MenuItem("replace...")) { + doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); + } + ImGui::Separator(); + if (ImGui::MenuItem("load from TX81Z")) { + doAction(GUI_ACTION_TX81Z_REQUEST); + } + ImGui::EndPopup(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open (insert; right-click to replace)"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index d966b797..2e1dd377 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -25,6 +25,11 @@ #include "actionUtil.h" #include "sampleUtil.h" +const unsigned char avRequest[15]={ + 0xf0, 0x43, 0x20, 0x7e, 0x4c, 0x4d, 0x20, 0x20, 0x38, 0x39, 0x37, 0x36, 0x41, 0x45, 0xf7 +}; + + void FurnaceGUI::doAction(int what) { switch (what) { case GUI_ACTION_OPEN: @@ -141,6 +146,17 @@ void FurnaceGUI::doAction(int what) { fullScreen=!fullScreen; SDL_SetWindowFullscreen(sdlWin,fullScreen?(SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP):0); break; + case GUI_ACTION_TX81Z_REQUEST: { + TAMidiMessage msg; + msg.type=TA_MIDI_SYSEX; + msg.sysExData.reset(new unsigned char[15]); + msg.sysExLen=15; + memcpy(msg.sysExData.get(),avRequest,15); + if (!e->sendMidiMessage(msg)) { + showError("Error while sending request (MIDI output not configured?)"); + } + break; + } case GUI_ACTION_PANIC: e->syncReset(); break; @@ -1251,4 +1267,4 @@ void FurnaceGUI::doAction(int what) { setOrder(curOrder); break; } -} \ No newline at end of file +} diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 327f65ff..f7795504 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 @@ -2469,6 +2469,15 @@ bool FurnaceGUI::loop() { wantCaptureKeyboard=ImGui::GetIO().WantTextInput; + if (wantCaptureKeyboard!=oldWantCaptureKeyboard) { + oldWantCaptureKeyboard=wantCaptureKeyboard; + if (wantCaptureKeyboard) { + SDL_StartTextInput(); + } else { + SDL_StopTextInput(); + } + } + if (wantCaptureKeyboard) { WAKE_UP; } @@ -2791,9 +2800,11 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("settings")) { +#ifndef IS_MOBILE if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { doAction(GUI_ACTION_FULLSCREEN); } +#endif if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { lockLayout=!lockLayout; } @@ -3673,7 +3684,11 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); lockLayout=e->getConfBool("lockLayout",false); +#ifdef IS_MOBILE + fullScreen=true; +#else fullScreen=e->getConfBool("fullScreen",false); +#endif mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT); syncSettings(); @@ -3695,6 +3710,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); @@ -3886,6 +3902,7 @@ FurnaceGUI::FurnaceGUI(): displayExporting(false), vgmExportLoop(true), wantCaptureKeyboard(false), + oldWantCaptureKeyboard(false), displayMacroMenu(false), displayNew(false), fullScreen(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index e0b31a3b..e02a065c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -306,6 +306,7 @@ enum FurnaceGUIActions { GUI_ACTION_FOLLOW_ORDERS, GUI_ACTION_FOLLOW_PATTERN, GUI_ACTION_FULLSCREEN, + GUI_ACTION_TX81Z_REQUEST, GUI_ACTION_PANIC, GUI_ACTION_WINDOW_EDIT_CONTROLS, @@ -768,7 +769,7 @@ class FurnaceGUI { String mmlString[17]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, displayMacroMenu; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos; bool willExport[32]; int vgmExportVersion; @@ -887,7 +888,6 @@ class FurnaceGUI { int eventDelay; int moveWindowTitle; int hiddenSystems; - int insLoadAlwaysReplace; int horizontalDataView; int noMultiSystem; unsigned int maxUndoSteps; @@ -970,9 +970,8 @@ class FurnaceGUI { powerSave(1), absorbInsInput(0), eventDelay(0), - moveWindowTitle(0), + moveWindowTitle(1), hiddenSystems(0), - insLoadAlwaysReplace(1), horizontalDataView(0), noMultiSystem(0), maxUndoSteps(100), diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 3cbd8204..4ea2de0c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -459,6 +459,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("FOLLOW_ORDERS", "Follow orders", 0), D("FOLLOW_PATTERN", "Follow pattern", 0), D("FULLSCREEN", "Toggle full-screen", SDLK_F11), + D("TX81Z_REQUEST", "Request voice from TX81Z", 0), D("PANIC", "Panic", SDLK_F12), D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0), @@ -833,6 +834,8 @@ const int availableSystems[]={ DIV_SYSTEM_YMU759, DIV_SYSTEM_DUMMY, DIV_SYSTEM_SOUND_UNIT, + DIV_SYSTEM_OPN, + DIV_SYSTEM_PC98, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, @@ -858,6 +861,8 @@ const int availableSystems[]={ DIV_SYSTEM_VRC6, DIV_SYSTEM_FDS, DIV_SYSTEM_MMC5, + DIV_SYSTEM_SCC, + DIV_SYSTEM_SCC_PLUS, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a1b78bd1..82b47fb6 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,10 +27,6 @@ #include #include "plot_nolerp.h" -const unsigned char avRequest[15]={ - 0xf0, 0x43, 0x20, 0x7e, 0x4c, 0x4d, 0x20, 0x20, 0x38, 0x39, 0x37, 0x36, 0x41, 0x45, 0xf7 -}; - const char* ssgEnvTypes[8]={ "Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" }; @@ -1364,7 +1360,6 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - // TODO: load replace if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) { doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE); } @@ -1477,14 +1472,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); if (ImGui::Button("Request from TX81Z")) { - TAMidiMessage msg; - msg.type=TA_MIDI_SYSEX; - msg.sysExData.reset(new unsigned char[15]); - msg.sysExLen=15; - memcpy(msg.sysExData.get(),avRequest,15); - if (!e->sendMidiMessage(msg)) { - showError("Error while sending request (MIDI output not configured?)"); - } + doAction(GUI_ACTION_TX81Z_REQUEST); } ImGui::SameLine(); if (ImGui::Button("Send to TX81Z")) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 3dd24e0a..dd003803 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -425,6 +425,26 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SCC", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_SCC, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SCC+", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_SCC_PLUS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-26K)", { + DIV_SYSTEM_OPN, 64, 0, 3, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (48K)", { DIV_SYSTEM_AY8910, 64, 0, 2, @@ -585,7 +605,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "Commander X16", { DIV_SYSTEM_VERA, 64, 0, 0, - DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_YM2151, 32, 0, 0, 0 } )); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c575f962..1c22755a 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -419,11 +419,6 @@ void FurnaceGUI::drawSettings() { settings.restartOnFlagChange=restartOnFlagChangeB; } - bool insLoadAlwaysReplaceB=settings.insLoadAlwaysReplace; - if (ImGui::Checkbox("Always replace currently selected instrument when loading from instrument list",&insLoadAlwaysReplaceB)) { - settings.insLoadAlwaysReplace=insLoadAlwaysReplaceB; - } - bool sysFileDialogB=settings.sysFileDialog; if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) { settings.sysFileDialog=sysFileDialogB; @@ -1850,9 +1845,8 @@ void FurnaceGUI::syncSettings() { settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT); settings.absorbInsInput=e->getConfInt("absorbInsInput",0); settings.eventDelay=e->getConfInt("eventDelay",0); - settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0); + settings.moveWindowTitle=e->getConfInt("moveWindowTitle",1); settings.hiddenSystems=e->getConfInt("hiddenSystems",0); - settings.insLoadAlwaysReplace=e->getConfInt("insLoadAlwaysReplace",1); settings.horizontalDataView=e->getConfInt("horizontalDataView",0); settings.noMultiSystem=e->getConfInt("noMultiSystem",0); @@ -1925,7 +1919,6 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.eventDelay,0,1); clampSetting(settings.moveWindowTitle,0,1); clampSetting(settings.hiddenSystems,0,1); - clampSetting(settings.insLoadAlwaysReplace,0,1); clampSetting(settings.horizontalDataView,0,1); clampSetting(settings.noMultiSystem,0,1) @@ -2039,7 +2032,6 @@ void FurnaceGUI::commitSettings() { e->setConf("moveWindowTitle",settings.moveWindowTitle); e->setConf("hiddenSystems",settings.hiddenSystems); e->setConf("initialSys",e->encodeSysDesc(settings.initialSys)); - e->setConf("insLoadAlwaysReplace",settings.insLoadAlwaysReplace); e->setConf("horizontalDataView",settings.horizontalDataView); e->setConf("noMultiSystem",settings.noMultiSystem); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 8005ee0d..9ac2e4bd 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -355,6 +355,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_OPN: { + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { + copyOfFlags=(flags&0x80000000)|0; + } + if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) { + copyOfFlags=(flags&0x80000000)|1; + } + if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) { + copyOfFlags=(flags&0x80000000)|2; + } + if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) { + copyOfFlags=(flags&0x80000000)|3; + } + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: @@ -367,6 +382,8 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YMU759: case DIV_SYSTEM_PET: + case DIV_SYSTEM_SCC: + case DIV_SYSTEM_SCC_PLUS: ImGui::Text("nothing to configure"); break; default: