From 1e2d5694b969a6b9f527917c0ebda774e4204232 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 25 Feb 2022 01:02:35 +0900 Subject: [PATCH 01/14] Prepare to support YM2610B and with Extended channel 3 mode Allow 8 bit volume for YM2610 ADPCM-B Remove sample mode macro in YM2610, it's always enabled and seperated channels. TODO: ADPCM-B is still not implemented, FM Channel 2 is silenced in extended channel 3 configuration --- CMakeLists.txt | 2 + papers/format.md | 1 + src/engine/dispatchContainer.cpp | 8 + src/engine/engine.cpp | 26 +- src/engine/fileOps.cpp | 8 +- src/engine/platform/ym2610Interface.cpp | 4 +- src/engine/platform/ym2610b.cpp | 965 ++++++++++++++++++++++++ src/engine/platform/ym2610b.h | 102 +++ src/engine/platform/ym2610bext.cpp | 337 +++++++++ src/engine/platform/ym2610bext.h | 51 ++ src/engine/platform/ym2610ext.cpp | 4 - src/engine/platform/ym2610shared.h | 16 +- src/engine/playback.cpp | 36 +- src/engine/song.h | 1 + src/engine/sysDef.cpp | 35 +- src/engine/vgmOps.cpp | 10 + src/gui/debug.cpp | 2 + src/gui/gui.cpp | 6 + 18 files changed, 1592 insertions(+), 22 deletions(-) create mode 100644 src/engine/platform/ym2610b.cpp create mode 100644 src/engine/platform/ym2610b.h create mode 100644 src/engine/platform/ym2610bext.cpp create mode 100644 src/engine/platform/ym2610bext.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b7a669aa..0704bfaef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,6 +311,8 @@ src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp +src/engine/platform/ym2610b.cpp +src/engine/platform/ym2610bext.cpp src/engine/platform/ay.cpp src/engine/platform/ay8930.cpp src/engine/platform/tia.cpp diff --git a/papers/format.md b/papers/format.md index a61bb6266..899fc601a 100644 --- a/papers/format.md +++ b/papers/format.md @@ -156,6 +156,7 @@ size | description | - 0xa6: OPL3 4-op + drums (YMF262) - 14 channels | - 0xa7: OPLL drums (YM2413) - 11 channels | - 0xa8: Atari Lynx - 4 channels + | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, | and has to be flattened. diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 801a3d84f..5ffffa865 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -29,6 +29,8 @@ #include "platform/arcade.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" +#include "platform/ym2610b.h" +#include "platform/ym2610bext.h" #include "platform/ay.h" #include "platform/ay8930.h" #include "platform/tia.h" @@ -181,6 +183,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_YM2610_FULL_EXT: dispatch=new DivPlatformYM2610Ext; break; + case DIV_SYSTEM_YM2610B: + dispatch=new DivPlatformYM2610B; + break; + case DIV_SYSTEM_YM2610B_EXT: + dispatch=new DivPlatformYM2610BExt; + break; case DIV_SYSTEM_AMIGA: dispatch=new DivPlatformAmiga; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9dee6ecfe..95e139180 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -506,6 +506,28 @@ void DivEngine::renderSamples() { memPos+=length+16; } qsoundMemLen=memPos+256; + + // step 5: allocate ADPCM-B samples + if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216]; + + memPos=0; + for (int i=0; i=16777216) { + logW("out of ADPCM-B memory for sample %d!\n",i); + break; + } + if (memPos+s->adpcmBRendLength>=16777216) { + memcpy(adpcmBMem+memPos,s->adpcmBRendData,16777216-memPos); + logW("out of ADPCM-B memory for sample %d!\n",i); + } else { + memcpy(adpcmBMem+memPos,s->adpcmBRendData,s->adpcmBRendLength); + } + s->rendOff=memPos; + memPos+=s->adpcmBRendLength; + } + adpcmBMemLen=memPos+256; + */ } @@ -905,7 +927,9 @@ int DivEngine::getEffectiveSampleRate(int rate) { return 1789773/(1789773/rate); case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: return (31250*MIN(255,(rate*255/31250)))/255; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_QSOUND: + return (24038*MIN(65535,(rate*4096/24038)))/4096; + case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; default: break; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index dd3ff37e2..5177916fd 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -148,7 +148,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } // Neo Geo detune - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) { + 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; } @@ -264,7 +266,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { ins->type=DIV_INS_C64; } - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) { + 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) { if (!ins->mode) { ins->type=DIV_INS_AY; } diff --git a/src/engine/platform/ym2610Interface.cpp b/src/engine/platform/ym2610Interface.cpp index 8d53e48ce..29a186ee1 100644 --- a/src/engine/platform/ym2610Interface.cpp +++ b/src/engine/platform/ym2610Interface.cpp @@ -23,8 +23,8 @@ uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { //printf("wants to read from %x\n",address); - if (type!=ymfm::ACCESS_ADPCM_A) return 0; - return parent->adpcmMem[address&0xffffff]; + if (type!=ymfm::ACCESS_ADPCM_A) return /*s->dataB[address&0xffffff];*/0; + return parent->adpcmMem[address&0xffffff];/*s->dataA[address&0xffffff]*/ /*if (12*sampleBank+(address>>16)>=parent->song.sampleLen) return 0; return parent->song.sample[12*sampleBank+(address>>16)]->adpcmRendData[(address&0xffff)];*/ } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp new file mode 100644 index 000000000..bb3559e94 --- /dev/null +++ b/src/engine/platform/ym2610b.cpp @@ -0,0 +1,965 @@ +/** + * 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 "ym2610b.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* 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; + } + return NULL; +} + +void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + + 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]; + } +} + +void DivPlatformYM2610B::tick() { + // PSG + for (int i=6; i<9; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].outVol<0) chan[i].outVol=0; + if (isMuted[i]) { + rWrite(0x02+i,0); + } else { + rWrite(0x02+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + ayNoiseFreq=31-chan[i].std.duty; + rWrite(0x06,ayNoiseFreq); + } + if (chan[i].std.hadWave) { + chan[i].psgMode=(chan[i].std.wave+1)&7; + if (isMuted[i]) { + rWrite(0x02+i,0); + } else { + rWrite(0x02+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + } + } + if (chan[i].std.hadEx2) { + ayEnvMode=chan[i].std.ex2; + rWrite(0x0d,ayEnvMode); + } + if (chan[i].std.hadEx3) { + chan[i].autoEnvNum=chan[i].std.ex3; + chan[i].freqChanged=true; + if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; + } + if (chan[i].std.hadAlg) { + chan[i].autoEnvDen=chan[i].std.alg; + chan[i].freqChanged=true; + if (!chan[i].std.willEx3) 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); + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].keyOn) { + } + if (chan[i].keyOff) { + rWrite(0x02+i,0); + } + rWrite((i-6)<<1,chan[i].freq&0xff); + rWrite(1+((i-6)<<1),chan[i].freq>>8); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { + ayEnvPeriod=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + chan[i].freqChanged=false; + } + } + + rWrite(0x07, + ~((chan[6].psgMode&1)| + ((chan[7].psgMode&1)<<1)| + ((chan[8].psgMode&1)<<2)| + ((chan[6].psgMode&2)<<2)| + ((chan[7].psgMode&2)<<3)| + ((chan[8].psgMode&2)<<4))); + + if (ayEnvSlide!=0) { + ayEnvSlideLow+=ayEnvSlide; + while (ayEnvSlideLow>7) { + ayEnvSlideLow-=8; + if (ayEnvPeriod<0xffff) { + ayEnvPeriod++; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + } + while (ayEnvSlideLow<-7) { + ayEnvSlideLow+=8; + if (ayEnvPeriod>0) { + ayEnvPeriod--; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + } + } + + // FM + for (int i=0; i<6; i++) { + if (i==1 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.hadVol) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs_b[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.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.hadAlg) { + chan[i].state.alg=chan[i].std.alg; + rWrite(chanOffs_b[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_b[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.hadFb) { + chan[i].state.fb=chan[i].std.fb; + rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.hadFms) { + chan[i].state.fms=chan[i].std.fms; + rWrite(chanOffs_b[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.hadAms) { + chan[i].state.ams=chan[i].std.ams; + rWrite(chanOffs_b[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_b[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.hadAm) { + op.am=m.am; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadAr) { + op.ar=m.ar; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDr) { + op.dr=m.dr; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadMult) { + op.mult=m.mult; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadRr) { + op.rr=m.rr; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadSl) { + op.sl=m.sl; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadTl) { + op.tl=127-m.tl; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (m.hadRs) { + op.rs=m.rs; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDt) { + op.dt=m.dt; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadD2r) { + op.d2r=m.d2r; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.hadSsg) { + op.ssgEnv=m.ssg; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + immWrite(0x28,0x00|konOffs[i]); + chan[i].keyOff=false; + } + } + + for (int i=0; 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==1 && extMode) continue; + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + if (chan[i].freq>262143) chan[i].freq=262143; + int freqt=toFreq(chan[i].freq); + immWrite(chanOffs_b[i]+ADDR_FREQH,freqt>>8); + immWrite(chanOffs_b[i]+ADDR_FREQ,freqt&0xff); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } +} + +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) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + if (c.chan>8) { // ADPCM + 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->song.sample[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); + chan[c.chan].std.init(ins); + if (c.chan<6) { + if (!chan[c.chan].std.willVol) { + chan[c.chan].outVol=chan[c.chan].vol; + } + } + + if (c.chan>5) { // PSG + 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].keyOn=true; + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else { + rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + } + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + for (int i=0; i<6; i++) { + unsigned short baseAddr=chanOffs_b[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_b[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs_b[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_FREQUENCY(c.value); + 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>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.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + if (c.chan>5) { + chan[c.chan].std.release(); + 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.hasVol) { + chan[c.chan].outVol=c.value; + } + if (c.chan>8) { // ADPCM + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + if (c.chan>5) { // PSG + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else { + if (chan[c.chan].active) rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs_b[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: { + switch (c.value) { + case 0x01: + chan[c.chan].pan=1; + break; + case 0x10: + chan[c.chan].pan=2; + break; + default: + chan[c.chan].pan=3; + break; + } + if (c.chan>8) { + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + if (c.chan>5) break; + rWrite(chanOffs_b[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) { // PSG + int destFreq=NOTE_PERIODIC(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 destFreq=NOTE_FREQUENCY(c.value2); + int newFreq; + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value*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); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!chan[c.chan].portaPause) { + if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + 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; + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + iface.sampleBank=sampleBank; + break; + case DIV_CMD_LEGATO: { + if (c.chan>5) { // PSG + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } else { + chan[c.chan].baseFreq=NOTE_FREQUENCY(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_b[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_b[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_b[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_b[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_b[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if (c.chan<6 || c.chan>8) break; + chan[c.chan].psgMode=(c.value+1)&7; + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else if (chan[c.chan].active) { + rWrite(0x02+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + case DIV_CMD_STD_NOISE_FREQ: + if (c.chan<6 || c.chan>8) break; + ayNoiseFreq=31-c.value; + rWrite(0x06,ayNoiseFreq); + break; + case DIV_CMD_AY_ENVELOPE_SET: + if (c.chan<6 || c.chan>8) break; + ayEnvMode=c.value>>4; + rWrite(0x0d,ayEnvMode); + if (c.value&15) { + chan[c.chan].psgMode|=4; + } else { + chan[c.chan].psgMode&=~4; + } + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else { + rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + case DIV_CMD_AY_ENVELOPE_LOW: + if (c.chan<6 || c.chan>8) break; + ayEnvPeriod&=0xff00; + ayEnvPeriod|=c.value; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + break; + case DIV_CMD_AY_ENVELOPE_HIGH: + if (c.chan<6 || c.chan>8) break; + ayEnvPeriod&=0xff; + ayEnvPeriod|=c.value<<8; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + break; + case DIV_CMD_AY_ENVELOPE_SLIDE: + if (c.chan<6 || c.chan>8) break; + ayEnvSlide=c.value; + break; + case DIV_CMD_AY_AUTO_ENVELOPE: + if (c.chan<6 || c.chan>8) break; + chan[c.chan].autoEnvNum=c.value>>4; + chan[c.chan].autoEnvDen=c.value&15; + chan[c.chan].freqChanged=true; + 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].std.init(parent->getIns(chan[c.chan].ins)); + } + } + 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>8) { // ADPCM + immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + return; + } + if (ch>5) { // PSG + if (isMuted[ch]) { + rWrite(0x02+ch,0); + } else { + rWrite(0x02+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); + } + return; + } + // FM + rWrite(chanOffs_b[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_b[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_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs_b[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=6; i<16; i++) { + chan[i].insChanged=true; + } + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + immWrite(0x0d,ayEnvMode); +} + +void* DivPlatformYM2610B::getChanState(int ch) { + return &chan[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(); + } + 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; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + ayEnvPeriod=0; + ayEnvMode=0; + ayEnvSlide=0; + ayEnvSlideLow=0; + ayNoiseFreq=0; + + delay=0; + + extMode=false; + + // AY noise + immWrite(0x06,ayNoiseFreq); + + // LFO + immWrite(0x22,0x08); + + // PCM volume + immWrite(0x101,0x3f); +} + +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; + } + } +} + +void DivPlatformYM2610B::notifyInsDeletion(void* ins) { + for (int i=6; i<9; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + } + chipClock=8000000; + rate=chipClock/16; + iface.parent=parent; + iface.sampleBank=0; + fm=new ymfm::ym2610b(iface); + reset(); + return 16; +} + +void DivPlatformYM2610B::quit() { + delete fm; +} + +DivPlatformYM2610B::~DivPlatformYM2610B() { +} diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h new file mode 100644 index 000000000..f7299efd0 --- /dev/null +++ b/src/engine/platform/ym2610b.h @@ -0,0 +1,102 @@ +/** + * 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 _YM2610B_H +#define _YM2610B_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ymfm/ymfm_opn.h" + +#include "ym2610.h" + +class DivPlatformYM2610B: public DivDispatch { + protected: + struct Channel { + DivInstrumentFM state; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, note; + unsigned char ins, psgMode, autoEnvNum, autoEnvDen; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + int vol, outVol; + unsigned char pan; + DivMacroInt std; + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(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), vol(0), outVol(15), pan(3) {} + }; + Channel chan[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::ym2610b* fm; + ymfm::ym2610b::output_data fmout; + DivYM2610Interface iface; + unsigned char regPool[512]; + unsigned char lastBusy; + + bool dacMode; + int dacPeriod; + int dacRate; + int dacPos; + int dacSample; + int ayNoiseFreq; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[512]; + short pendingWrites[512]; + unsigned char ayEnvMode; + unsigned short ayEnvPeriod; + short ayEnvSlideLow; + short ayEnvSlide; + + int octave(int freq); + int toFreq(int freq); + 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); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2610B(); +}; +#endif diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp new file mode 100644 index 000000000..5c3649164 --- /dev/null +++ b/src/engine/platform/ym2610bext.cpp @@ -0,0 +1,337 @@ +/** + * 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 "ym2610bext.h" +#include "../engine.h" +#include + +#include "ym2610shared.h" + +int DivPlatformYM2610BExt::dispatch(DivCommand c) { + if (c.chan<2) { + return DivPlatformYM2610B::dispatch(c); + } + if (c.chan>5) { + c.chan-=3; + return DivPlatformYM2610B::dispatch(c); + } + int ch=c.chan-2; + int ordch=orderedOps[ch]; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(opChan[ch].ins); + + unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + // TODO: how does this work?! + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else { + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } + } + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+0x70,op.d2r&31); + rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+0x90,op.ssgEnv&15); + } + if (opChan[ch].insChanged) { // TODO how does this work? + rWrite(chanOffs_b[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + } + opChan[ch].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].freqChanged=true; + } + opChan[ch].keyOn=true; + opChan[ch].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + opChan[ch].keyOff=true; + opChan[ch].keyOn=false; + opChan[ch].active=false; + break; + case DIV_CMD_VOLUME: { + opChan[ch].vol=c.value; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } + break; + } + case DIV_CMD_GET_VOLUME: { + return opChan[ch].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (opChan[ch].ins!=c.value || c.value2==1) { + opChan[ch].insChanged=true; + } + opChan[ch].ins=c.value; + break; + case DIV_CMD_PANNING: { + switch (c.value) { + case 0x01: + opChan[ch].pan=1; + break; + case 0x10: + opChan[ch].pan=2; + break; + default: + opChan[ch].pan=3; + break; + } + DivInstrument* ins=parent->getIns(opChan[ch].ins); + // TODO: ??? + rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + opChan[ch].pitch=c.value; + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + int newFreq; + bool return2=false; + if (destFreq>opChan[ch].baseFreq) { + newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq); + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!opChan[ch].portaPause) { + if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { + opChan[ch].portaPause=true; + break; + } + } + opChan[ch].baseFreq=newFreq; + opChan[ch].portaPause=false; + opChan[ch].freqChanged=true; + if (return2) return 2; + break; + } + case DIV_CMD_LEGATO: { + opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_FM_MULT: { // TODO + unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { // TODO + unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + if (isOutput[ins->fm.alg][c.value]) { + rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,c.value2); + } + break; + } + case DIV_CMD_FM_AR: { + DivInstrument* ins=parent->getIns(opChan[ch].ins); + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator op=ins->fm.op[i]; + unsigned short baseAddr=chanOffs_b[2]|opOffs[i]; + rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_PRE_PORTA: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +static int opChanOffsL[4]={ + 0xa9, 0xaa, 0xa8, 0xa2 +}; + +static int opChanOffsH[4]={ + 0xad, 0xae, 0xac, 0xa6 +}; + +void DivPlatformYM2610BExt::tick() { + if (extMode) { + bool writeSomething=false; + unsigned char writeMask=2; + for (int i=0; i<4; i++) { + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn || opChan[i].keyOff) { + writeSomething=true; + writeMask&=~(1<<(4+i)); + opChan[i].keyOff=false; + } + } + if (writeSomething) { + immWrite(0x28,writeMask); + } + } + + DivPlatformYM2610B::tick(); + + bool writeNoteOn=false; + 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); + 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; + } + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn) { + writeNoteOn=true; + writeMask|=1<<(4+i); + opChan[i].keyOn=false; + } + } + if (writeNoteOn) { + immWrite(0x28,writeMask); + } +} + +void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { + if (ch<2) { + DivPlatformYM2610B::muteChannel(ch,mute); + return; + } + if (ch>5) { + DivPlatformYM2610B::muteChannel(ch-3,mute); + return; + } + isOpMuted[ch-2]=mute; + + int ordch=orderedOps[ch-2]; + DivInstrument* ins=parent->getIns(opChan[ch-2].ins); + unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch-2]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } +} + +void DivPlatformYM2610BExt::forceIns() { + DivPlatformYM2610B::forceIns(); + for (int i=0; i<4; i++) { + opChan[i].insChanged=true; + if (opChan[i].active) { + opChan[i].keyOn=true; + opChan[i].freqChanged=true; + } + } +} + + +void* DivPlatformYM2610BExt::getChanState(int ch) { + if (ch>=6) return &chan[ch-3]; + if (ch>=2) return &opChan[ch-2]; + return &chan[ch]; +} + +void DivPlatformYM2610BExt::reset() { + DivPlatformYM2610B::reset(); + + for (int i=0; i<4; i++) { + opChan[i]=DivPlatformYM2610BExt::OpChannel(); + opChan[i].vol=127; + } + + // channel 2 mode + immWrite(0x27,0x40); + extMode=true; +} + +bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) { + return (ch>8); +} + +void DivPlatformYM2610BExt::notifyInsChange(int ins) { + DivPlatformYM2610B::notifyInsChange(ins); + for (int i=0; i<4; i++) { + if (opChan[i].ins==ins) { + opChan[i].insChanged=true; + } + } +} + +int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2610B::init(parent,channels,sugRate,flags); + for (int i=0; i<4; i++) { + isOpMuted[i]=false; + } + + reset(); + return 19; +} + +void DivPlatformYM2610BExt::quit() { + DivPlatformYM2610B::quit(); +} + +DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { +} \ No newline at end of file diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h new file mode 100644 index 000000000..25ca59196 --- /dev/null +++ b/src/engine/platform/ym2610bext.h @@ -0,0 +1,51 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../dispatch.h" + +#include "ym2610b.h" + +class DivPlatformYM2610BExt: public DivPlatformYM2610B { + struct OpChannel { + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch; + unsigned char 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), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + }; + OpChannel opChan[4]; + bool isOpMuted[4]; + friend void putDispatchChan(void*,int,int); + public: + int dispatch(DivCommand c); + void* getChanState(int chan); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2610BExt(); +}; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 000ab69da..aff4bbf33 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -147,10 +147,6 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { if (return2) return 2; break; } - case DIV_CMD_SAMPLE_MODE: { - // ignored on extended channel 2 mode. - break; - } case DIV_CMD_LEGATO: { opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); opChan[ch].freqChanged=true; diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index b548a0ec1..82dad07d8 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -17,9 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "sound/ymfm/ymfm_opn.h" + static unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 }; +static unsigned short chanOffs_b[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 +}; static unsigned short opOffs[4]={ 0x00, 0x04, 0x08, 0x0c }; @@ -45,4 +50,13 @@ static int orderedOps[4]={ #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 9440540 \ No newline at end of file +#define CHIP_FREQBASE 9440540 + +class DivYM2610BInterface: public ymfm::ymfm_interface { + public: + DivEngine* parent; + int sampleBank; + uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); + void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); + DivYM2610BInterface(): parent(NULL), sampleBank(0) {} +}; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6c825cb71..099daadd6 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -262,6 +262,10 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case DIV_SYSTEM_YM2151: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: switch (effect) { case 0x10: // LFO or noise mode if (sysOfChan[ch]==DIV_SYSTEM_YM2151) { @@ -324,42 +328,58 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char dispatchCmd(DivCommand(DIV_CMD_FM_PM_DEPTH,ch,effectVal&127)); break; case 0x20: // Neo Geo PSG mode - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); } break; case 0x21: // Neo Geo PSG noise freq - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); } break; case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); } break; case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); } break; case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); } break; case 0x25: // UNOFFICIAL: Neo Geo PSG envelope slide up - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); } break; case 0x26: // UNOFFICIAL: Neo Geo PSG envelope slide down - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); } break; case 0x29: // auto-envelope - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); } break; diff --git a/src/engine/song.h b/src/engine/song.h index f4620eef9..ba1413e5f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -90,6 +90,7 @@ enum DivSystem { DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, + DIV_SYSTEM_YM2610B_EXT, DIV_SYSTEM_SEGAPCM_COMPAT }; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 54589e37d..85fe54b8e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xde: + return DIV_SYSTEM_YM2610B_EXT; case 0xe0: return DIV_SYSTEM_QSOUND; } @@ -256,6 +258,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_YM2610B_EXT: + return 0xde; case DIV_SYSTEM_QSOUND: return 0xe0; @@ -376,6 +380,7 @@ int DivEngine::getChannelCount(DivSystem sys) { return 4; case DIV_SYSTEM_SEGAPCM_COMPAT: return 5; + case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_QSOUND: return 19; } @@ -629,6 +634,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Atari Lynx"; case DIV_SYSTEM_SEGAPCM_COMPAT: return "SegaPCM (compatible 5-channel mode)"; + case DIV_SYSTEM_YM2610B_EXT: + return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; } @@ -752,6 +759,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Mikey"; case DIV_SYSTEM_SEGAPCM_COMPAT: return "SegaPCM (compatible 5-channel mode)"; + case DIV_SYSTEM_YM2610B_EXT: + return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; } @@ -822,6 +831,8 @@ bool DivEngine::isFMSystem(DivSystem sys) { sys==DIV_SYSTEM_YM2610_EXT || sys==DIV_SYSTEM_YM2610_FULL || sys==DIV_SYSTEM_YM2610_FULL_EXT || + sys==DIV_SYSTEM_YM2610B || + sys==DIV_SYSTEM_YM2610B_EXT || sys==DIV_SYSTEM_YMU759 || sys==DIV_SYSTEM_YM2151 || sys==DIV_SYSTEM_YM2612); @@ -834,7 +845,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[37][24]={ +const char* chanNames[38][24]={ {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) @@ -872,9 +883,10 @@ const char* chanNames[37][24]={ {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound + {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) }; -const char* chanShortNames[37][24]={ +const char* chanShortNames[38][24]={ {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) @@ -912,9 +924,10 @@ const char* chanShortNames[37][24]={ {"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op {"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound + {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[37][24]={ +const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -952,9 +965,10 @@ const int chanTypes[37][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {3, 3, 3, 3}, //Lynx + {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) }; -const DivInstrumentType chanPrefType[43][24]={ +const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -998,6 +1012,7 @@ const DivInstrumentType chanPrefType[43][24]={ {DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN}, // Swan {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) }; const char* DivEngine::getChannelName(int chan) { @@ -1115,6 +1130,9 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_YM2610B: return chanNames[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanNames[37][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: @@ -1251,6 +1269,9 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_YM2610B: return chanShortNames[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanShortNames[37][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: @@ -1385,6 +1406,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_YM2610B: return chanTypes[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanTypes[37][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: @@ -1519,6 +1543,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_YM2610B: return chanPrefType[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanPrefType[43][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: return chanPrefType[32][dispatchChanOfChan[chan]]; break; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 93a2bcf42..5c2a4d427 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -158,8 +158,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B_EXT: for (int i=0; i<2; i++) { // set SL and RR to highest w->writeC(isSecond?0xa8:0x58); w->writeC(0x81+i); @@ -386,8 +388,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B_EXT: switch (write.addr>>8) { case 0: // port 0 w->writeC(isSecond?0xa8:0x58); @@ -655,8 +659,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B_EXT: if (!hasOPNB) { hasOPNB=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -667,6 +673,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { hasOPNB|=0x40000000; howManyChips++; } + break; + if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag + hasOPNB|=0x80000000; + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 8b06f6dec..4a259145b 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -29,6 +29,8 @@ #include "../engine/platform/arcade.h" #include "../engine/platform/ym2610.h" #include "../engine/platform/ym2610ext.h" +#include "../engine/platform/ym2610b.h" +#include "../engine/platform/ym2610bext.h" #include "../engine/platform/ay.h" #include "../engine/platform/ay8930.h" #include "../engine/platform/tia.h" diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ee8d5a954..f0a9bc7ec 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4485,6 +4485,8 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_YM2610_EXT); sysAddOption(DIV_SYSTEM_YM2610_FULL); sysAddOption(DIV_SYSTEM_YM2610_FULL_EXT); + sysAddOption(DIV_SYSTEM_YM2610B); + sysAddOption(DIV_SYSTEM_YM2610B_EXT); sysAddOption(DIV_SYSTEM_AY8910); sysAddOption(DIV_SYSTEM_AMIGA); sysAddOption(DIV_SYSTEM_TIA); @@ -4716,6 +4718,8 @@ bool FurnaceGUI::loop() { case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YMU759: ImGui::Text("nothing to configure"); break; @@ -4749,6 +4753,8 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_YM2610_EXT); sysChangeOption(i,DIV_SYSTEM_YM2610_FULL); sysChangeOption(i,DIV_SYSTEM_YM2610_FULL_EXT); + sysChangeOption(i,DIV_SYSTEM_YM2610B); + sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT); sysChangeOption(i,DIV_SYSTEM_AY8910); sysChangeOption(i,DIV_SYSTEM_AMIGA); sysChangeOption(i,DIV_SYSTEM_TIA); From a132a28fcb19c05f24d6e0b346b587b9f889fd2b Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 25 Feb 2022 17:37:43 +0900 Subject: [PATCH 02/14] Fix VGM saving --- src/engine/vgmOps.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 7663914f8..fd5fa3ce4 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -673,10 +673,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { hasOPNB|=0x40000000; howManyChips++; } - break; - if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag + if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag hasOPNB|=0x80000000; - } + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: From e96cd77ba763c764fef45f8050ab6c297423a36e Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 25 Feb 2022 17:39:33 +0900 Subject: [PATCH 03/14] Remove unnecessary interface --- src/engine/platform/ym2610shared.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 82dad07d8..2f3e563ec 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -17,8 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "sound/ymfm/ymfm_opn.h" - static unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 }; @@ -51,12 +49,3 @@ static int orderedOps[4]={ #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 9440540 - -class DivYM2610BInterface: public ymfm::ymfm_interface { - public: - DivEngine* parent; - int sampleBank; - uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); - void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); - DivYM2610BInterface(): parent(NULL), sampleBank(0) {} -}; From ff743c92fda88e8955a79316dc9c2d34d2e0b6e4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Feb 2022 00:31:17 +0900 Subject: [PATCH 04/14] Fix build --- src/engine/platform/ym2610b.h | 4 ++++ src/engine/platform/ym2610shared.h | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 8b18acd15..892f68437 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -26,6 +26,10 @@ #include "ym2610.h" +static unsigned short chanOffs_b[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 +}; + class DivPlatformYM2610B: public DivDispatch { protected: struct Channel { diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 2f3e563ec..7891a14fb 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -20,9 +20,6 @@ static unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 }; -static unsigned short chanOffs_b[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 -}; static unsigned short opOffs[4]={ 0x00, 0x04, 0x08, 0x0c }; From b1a49dcdc5dcdc6808b775c63157afa28641d070 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Feb 2022 00:50:49 +0900 Subject: [PATCH 05/14] Fix actually --- src/engine/platform/ym2610b.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 892f68437..f995d5975 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -26,12 +26,12 @@ #include "ym2610.h" -static unsigned short chanOffs_b[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 -}; - class DivPlatformYM2610B: public DivDispatch { protected: + const unsigned short chanOffs_b[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; From d64ddaadeefe2d1b79257c4cae61e571dcf4caf8 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Feb 2022 01:12:37 +0900 Subject: [PATCH 06/14] Further fix build --- src/engine/platform/ym2610.h | 4 +++ src/engine/platform/ym2610b.cpp | 46 +++++++++++++++--------------- src/engine/platform/ym2610b.h | 2 +- src/engine/platform/ym2610bext.cpp | 20 ++++++------- src/engine/platform/ym2610shared.h | 3 -- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 317870f69..65456c720 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -35,6 +35,10 @@ class DivYM2610Interface: public ymfm::ymfm_interface { class DivPlatformYM2610: public DivDispatch { protected: + const unsigned short chanOffs[4]={ + 0x01, 0x02, 0x101, 0x102 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 5e755eeb5..839811920 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -250,7 +250,7 @@ void DivPlatformYM2610B::tick() { if (chan[i].std.hadVol) { chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs_b[i]|opOffs[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)); @@ -278,9 +278,9 @@ void DivPlatformYM2610B::tick() { if (chan[i].std.hadAlg) { chan[i].state.alg=chan[i].std.alg; - rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + 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_b[i]|opOffs[j]; + unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); @@ -295,18 +295,18 @@ void DivPlatformYM2610B::tick() { } if (chan[i].std.hadFb) { chan[i].state.fb=chan[i].std.fb; - rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } if (chan[i].std.hadFms) { chan[i].state.fms=chan[i].std.fms; - rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + 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.hadAms) { chan[i].state.ams=chan[i].std.ams; - rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + 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_b[i]|opOffs[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.hadAm) { @@ -409,8 +409,8 @@ void DivPlatformYM2610B::tick() { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq); - immWrite(chanOffs_b[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs_b[i]+ADDR_FREQ,freqt&0xff); + immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); + immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); chan[i].freqChanged=false; } if (chan[i].keyOn) { @@ -566,7 +566,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } for (int i=0; i<6; i++) { - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[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) { @@ -587,8 +587,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } } if (chan[c.chan].insChanged) { - rWrite(chanOffs_b[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); - rWrite(chanOffs_b[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)); + 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; @@ -658,7 +658,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } for (int i=0; i<4; i++) { - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[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)); @@ -699,7 +699,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } if (c.chan>5) break; - rWrite(chanOffs_b[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)); + 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: { @@ -783,12 +783,12 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { case DIV_CMD_FM_FB: { if (c.chan>5) break; chan[c.chan].state.fb=c.value&7; - rWrite(chanOffs_b[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + 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_b[c.chan]|opOffs[orderedOps[c.value]]; + 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)); @@ -796,7 +796,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } case DIV_CMD_FM_TL: { if (c.chan>5) break; - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[orderedOps[c.value]]; + 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]) { @@ -812,13 +812,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { 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_b[c.chan]|opOffs[i]; + 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_b[c.chan]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } break; @@ -920,13 +920,13 @@ void DivPlatformYM2610B::muteChannel(int ch, bool mute) { return; } // FM - rWrite(chanOffs_b[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); + 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_b[i]|opOffs[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)); @@ -940,8 +940,8 @@ void DivPlatformYM2610B::forceIns() { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } - rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + 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; diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index f995d5975..7ca766c22 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -28,7 +28,7 @@ class DivPlatformYM2610B: public DivDispatch { protected: - const unsigned short chanOffs_b[6]={ + const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 }; diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 5c3649164..f48fe19ab 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -37,7 +37,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins); - unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { @@ -56,8 +56,8 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { rWrite(baseAddr+0x90,op.ssgEnv&15); } if (opChan[ch].insChanged) { // TODO how does this work? - rWrite(chanOffs_b[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -77,7 +77,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; DivInstrument* ins=parent->getIns(opChan[ch].ins); - unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); @@ -110,7 +110,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? - rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -153,14 +153,14 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { // TODO - unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins); DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); break; } case DIV_CMD_FM_TL: { // TODO - unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins); if (isOutput[ins->fm.alg][c.value]) { rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); @@ -174,12 +174,12 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator op=ins->fm.op[i]; - unsigned short baseAddr=chanOffs_b[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); } } else { DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; - unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); } break; @@ -264,7 +264,7 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { int ordch=orderedOps[ch-2]; DivInstrument* ins=parent->getIns(opChan[ch-2].ins); - unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch-2]) { rWrite(baseAddr+0x40,127); diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 7891a14fb..c41d5520a 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -17,9 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -static unsigned short chanOffs[4]={ - 0x01, 0x02, 0x101, 0x102 -}; static unsigned short opOffs[4]={ 0x00, 0x04, 0x08, 0x0c }; From 34405de03c27cfc142a42df9a7590bb8914d0aca Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 27 Feb 2022 06:40:13 +0900 Subject: [PATCH 07/14] Fix build actually --- src/engine/platform/ym2610b.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 839811920..770454a8d 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -565,7 +565,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].state=ins->fm; } - for (int i=0; i<6; i++) { + 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]) { From e243a8558eca663a6f266522a8fd5a41b0c59c60 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 27 Feb 2022 06:47:31 +0900 Subject: [PATCH 08/14] Add YM2610B in documents --- papers/doc/7-systems/ym2610.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index dbe8b6a54..f557ea23d 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -4,6 +4,8 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive vid its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package! +YM2610B variant is YM2610 with 2 extra FM channels, it's mainly used at some 90s Taito Arcade hardwares. + # effects - `10xy`: set LFO parameters. From 2312ab19d248573036e1e713f5ced46f7c5f337a Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Feb 2022 03:04:04 +0900 Subject: [PATCH 09/14] Split YM2610B document --- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/ym2610.md | 2 -- papers/doc/7-systems/ym2610b.md | 57 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 papers/doc/7-systems/ym2610b.md diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 90a17ee2d..78aeb8cea 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -10,6 +10,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Commodore 64](c64.md) - [Arcade (YM2151/PCM)](arcade.md) - [Neo Geo/YM2610](ym2610.md) +- [Taito Arcade/YM2610B](ym2610b.md) - [AY-3-8910](ay8910.md) - [Amiga](amiga.md) - [Yamaha YM2612 standalone](ym2612.md) diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index f557ea23d..dbe8b6a54 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -4,8 +4,6 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive vid its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package! -YM2610B variant is YM2610 with 2 extra FM channels, it's mainly used at some 90s Taito Arcade hardwares. - # effects - `10xy`: set LFO parameters. diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md new file mode 100644 index 000000000..0910d1d3d --- /dev/null +++ b/papers/doc/7-systems/ym2610b.md @@ -0,0 +1,57 @@ +# Taito Arcade/Yamaha YM2610 + +YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares. + +# effects + +- `10xy`: set LFO parameters. + - `x` toggles the LFO. + - `y` sets its speed. +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. +- `15xx`: set operator 4 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4). + - `y` is the mutliplier. +- `18xx`: toggle extended channel 2 mode. + - 0 disables it and 1 enables it. + - only in extended channel 2 system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. +- `1Dxx`: set attack of operator 4. +- `20xx`: set SSG channel mode. `xx` may be one of the following: + - `00`: square + - `01`: noise + - `02`: square and noise + - `03`: nothing (apparently) + - `04`: envelope and square + - `05`: envelope and noise + - `06`: envelope and square and noise + - `07`: nothing +- `21xx`: set noise frequency. `xx` is a value between 00 and 1F. +- `22xy`: set envelope mode. + - `x` sets the envelope shape, which may be one of the following: + - `0: \___` decay + - `4: /___` attack once + - `8: \\\\` saw + - `9: \___` decay + - `A: \/\/` inverse obelisco + - `B: \¯¯¯` decay once + - `C: ////` inverse saw + - `D: /¯¯¯` attack + - `E: /\/\` obelisco + - `F: /___` attack once + - if `y` is 1 then the envelope will affect this channel. +- `23xx`: set envelope period low byte. +- `24xx`: set envelope period high byte. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable SSG auto-envelope mode. + - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. + - `x` is the numerator. + - `y` is the denominator. + - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file From d92b662851bc39ff3d2fe51e71eb7c6c0993dc39 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Feb 2022 03:05:31 +0900 Subject: [PATCH 10/14] Typo --- papers/doc/7-systems/ym2610b.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index 0910d1d3d..da4f542d4 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -1,4 +1,4 @@ -# Taito Arcade/Yamaha YM2610 +# Taito Arcade/Yamaha YM2610B YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares. From ac1c65fd62310e39f0444e87f60d8a8b96d03e84 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Feb 2022 04:45:55 +0900 Subject: [PATCH 11/14] Add register sheet for YM2610* --- src/engine/platform/ym2610.cpp | 212 ++++++++++++++++++++++++ src/engine/platform/ym2610.h | 1 + src/engine/platform/ym2610b.cpp | 276 ++++++++++++++++++++++++++++++++ src/engine/platform/ym2610b.h | 1 + 4 files changed, 490 insertions(+) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index e9e77d813..a6f29e4b8 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -32,6 +32,218 @@ static unsigned char konOffs[4]={ #define CHIP_DIVIDER 32 +const char* regCheatSheetYM2610[]={ + // 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-2) + "FM1_Op1_DT_MULT", "031", + "FM2_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "035", + "FM2_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "039", + "FM2_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03D", + "FM2_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "041", + "FM2_Op1_TL", "042", + "FM1_Op2_TL", "045", + "FM2_Op2_TL", "046", + "FM1_Op3_TL", "049", + "FM2_Op3_TL", "04A", + "FM1_Op4_TL", "04D", + "FM2_Op4_TL", "04E", + "FM1_Op1_KS_AR", "051", + "FM2_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "055", + "FM2_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "059", + "FM2_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05D", + "FM2_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "061", + "FM2_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "065", + "FM2_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "069", + "FM2_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06D", + "FM2_Op4_AM_DR", "06E", + "FM1_Op1_SR", "071", + "FM2_Op1_SR", "072", + "FM1_Op2_SR", "075", + "FM2_Op2_SR", "076", + "FM1_Op3_SR", "079", + "FM2_Op3_SR", "07A", + "FM1_Op4_SR", "07D", + "FM2_Op4_SR", "07E", + "FM1_Op1_SL_RR", "081", + "FM2_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "085", + "FM2_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "089", + "FM2_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08D", + "FM2_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "091", + "FM2_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "095", + "FM2_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "099", + "FM2_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09D", + "FM2_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A1", + "FM2_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A5", + "FM2_(Op1)FNum2", "0A6", + "FM2_Op2_FNum1", "0A8", + "FM2_Op3_FNum1", "0A9", + "FM2_Op4_FNum1", "0AA", + "FM2_Op2_FNum2", "0AC", + "FM2_Op3_FNum2", "0AD", + "FM2_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B1", + "FM2_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B5", + "FM2_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 3-4) + "FM3_Op1_DT_MULT", "131", + "FM4_Op1_DT_MULT", "132", + "FM3_Op2_DT_MULT", "135", + "FM4_Op2_DT_MULT", "136", + "FM3_Op3_DT_MULT", "139", + "FM4_Op3_DT_MULT", "13A", + "FM3_Op4_DT_MULT", "13D", + "FM4_Op4_DT_MULT", "13E", + "FM3_Op1_TL", "141", + "FM4_Op1_TL", "142", + "FM3_Op2_TL", "145", + "FM4_Op2_TL", "146", + "FM3_Op3_TL", "149", + "FM4_Op3_TL", "14A", + "FM3_Op4_TL", "14D", + "FM4_Op4_TL", "14E", + "FM3_Op1_KS_AR", "151", + "FM4_Op1_KS_AR", "152", + "FM3_Op2_KS_AR", "155", + "FM4_Op2_KS_AR", "156", + "FM3_Op3_KS_AR", "159", + "FM4_Op3_KS_AR", "15A", + "FM3_Op4_KS_AR", "15D", + "FM4_Op4_KS_AR", "15E", + "FM3_Op1_AM_DR", "161", + "FM4_Op1_AM_DR", "162", + "FM3_Op2_AM_DR", "165", + "FM4_Op2_AM_DR", "166", + "FM3_Op3_AM_DR", "169", + "FM4_Op3_AM_DR", "16A", + "FM3_Op4_AM_DR", "16D", + "FM4_Op4_AM_DR", "16E", + "FM3_Op1_SR", "171", + "FM4_Op1_SR", "172", + "FM3_Op2_SR", "175", + "FM4_Op2_SR", "176", + "FM3_Op3_SR", "179", + "FM4_Op3_SR", "17A", + "FM3_Op4_SR", "17D", + "FM4_Op4_SR", "17E", + "FM3_Op1_SL_RR", "181", + "FM4_Op1_SL_RR", "182", + "FM3_Op2_SL_RR", "185", + "FM4_Op2_SL_RR", "186", + "FM3_Op3_SL_RR", "189", + "FM4_Op3_SL_RR", "18A", + "FM3_Op4_SL_RR", "18D", + "FM4_Op4_SL_RR", "18E", + "FM3_Op1_SSG_EG", "191", + "FM4_Op1_SSG_EG", "192", + "FM3_Op2_SSG_EG", "195", + "FM4_Op2_SSG_EG", "196", + "FM3_Op3_SSG_EG", "199", + "FM4_Op3_SSG_EG", "19A", + "FM3_Op4_SSG_EG", "19D", + "FM4_Op4_SSG_EG", "19E", + "FM3_FNum1", "1A1", + "FM4_FNum1", "1A2", + "FM3_FNum2", "1A5", + "FM4_FNum2", "1A6", + "FM3_FB_ALG", "1B1", + "FM4_FB_ALG", "1B2", + "FM3_Pan_LFO", "1B5", + "FM4_Pan_LFO", "1B6", + NULL +}; + +const char** DivPlatformYM2610::getRegisterSheet() { + return regCheatSheetYM2610; +} + const char* DivPlatformYM2610::getEffectName(unsigned char effect) { switch (effect) { case 0x10: diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 65456c720..a0ee8b8d4 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -127,6 +127,7 @@ class DivPlatformYM2610: public DivDispatch { void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); + const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 770454a8d..edf9184d5 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -32,6 +32,282 @@ static unsigned char konOffs[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: diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 7ca766c22..ca43e36b4 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -120,6 +120,7 @@ class DivPlatformYM2610B: public DivDispatch { void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); + const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); From ddf341dfc095479b782ce12baa4f94e4322c21a8 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 1 Mar 2022 01:11:27 +0900 Subject: [PATCH 12/14] Add support YM2610* ADPCM-B Repeat flag, Add YM2610 ADPCM notes in GUI --- src/engine/platform/ym2610.cpp | 4 ++-- src/engine/platform/ym2610b.cpp | 4 ++-- src/gui/gui.cpp | 11 +++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index a6f29e4b8..dd2447da4 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -698,7 +698,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + 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); @@ -724,7 +724,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + 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; } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index edf9184d5..27bb2afbb 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -761,7 +761,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + 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); @@ -787,7 +787,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + 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; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 265624684..034afb4ac 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1309,18 +1309,25 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("notes:"); if (sample->loopStart>=0) { considerations=true; - ImGui::Text("- sample won't loop on Neo Geo ADPCM"); + ImGui::Text("- sample won't loop on Neo Geo ADPCM-A"); if (sample->loopStart&1) { ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); } + if (sample->loopStart>0) { + ImGui::Text("- sample loop start will be ignored on Neo Geo ADPCM-B"); + } } if (sample->samples&1) { considerations=true; ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga"); } + if (sample->samples&511) { + considerations=true; + ImGui::Text("- sample length will be aligned to 512 sample on Neo Geo ADPCM"); + } if (sample->samples>65535) { considerations=true; - ImGui::Text("- maximum sample length on Sega PCM is 65536 samples"); + ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); } if (sample->samples>2097151) { considerations=true; From 4e5b398994665f91d002557b673d8dafd6b8baa2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 1 Mar 2022 02:11:02 +0900 Subject: [PATCH 13/14] Fix notes --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 034afb4ac..ec566676b 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1323,7 +1323,7 @@ void FurnaceGUI::drawSampleEdit() { } if (sample->samples&511) { considerations=true; - ImGui::Text("- sample length will be aligned to 512 sample on Neo Geo ADPCM"); + ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); } if (sample->samples>65535) { considerations=true; From de8c79e3062ba05abf2a4f88f455969a256ca4f9 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 1 Mar 2022 02:50:26 +0900 Subject: [PATCH 14/14] Further informations --- papers/doc/7-systems/ym2610.md | 2 +- papers/doc/7-systems/ym2610b.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index dbe8b6a54..7eb66eb33 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -2,7 +2,7 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive video game console with the world's biggest cartridges because some people liked the system so much they wanted a home version of it. -its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package! +its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package! # effects diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index da4f542d4..2b2e5feaf 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -1,6 +1,6 @@ # Taito Arcade/Yamaha YM2610B -YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares. +YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares, it's backward compatible with non-B chip. # effects