From f3705fb43567c79ae7b2c8de93b5363bf07fe1c1 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 10 Jan 2024 11:17:36 +0700 Subject: [PATCH] WIP add GBA system --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/engine.cpp | 3 +- src/engine/instrument.cpp | 4 + src/engine/instrument.h | 6 +- src/engine/platform/gb.cpp | 88 ++++-- src/engine/platform/gb.h | 4 + src/engine/platform/gbadma.cpp | 445 +++++++++++++++++++++++++++++ src/engine/platform/gbadma.h | 83 ++++++ src/engine/platform/sound/gb/apu.c | 46 ++- src/engine/platform/sound/gb/apu.h | 5 +- src/engine/platform/sound/gb/gb.h | 1 + src/engine/song.h | 1 + src/engine/sysDef.cpp | 10 + src/gui/doAction.cpp | 3 +- src/gui/gui.h | 1 + src/gui/guiConst.cpp | 4 + src/gui/insEdit.cpp | 23 +- src/gui/presets.cpp | 10 + src/gui/sampleEdit.cpp | 14 + src/gui/settings.cpp | 1 + src/gui/sysConf.cpp | 40 +++ 22 files changed, 748 insertions(+), 49 deletions(-) create mode 100644 src/engine/platform/gbadma.cpp create mode 100644 src/engine/platform/gbadma.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 581e1c2de..87c684e02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -717,6 +717,7 @@ src/engine/platform/c140.cpp src/engine/platform/esfm.cpp src/engine/platform/powernoise.cpp src/engine/platform/dave.cpp +src/engine/platform/gbadma.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 7146e90c6..420876a8e 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -81,6 +81,7 @@ #include "platform/k053260.h" #include "platform/ted.h" #include "platform/c140.h" +#include "platform/gbadma.h" #include "platform/pcmdac.h" #include "platform/esfm.h" #include "platform/powernoise.h" @@ -644,6 +645,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformC140; ((DivPlatformC140*)dispatch)->set219(true); break; + case DIV_SYSTEM_GBA_DMA: + dispatch=new DivPlatformGBADMA; + break; case DIV_SYSTEM_PCM_DAC: dispatch=new DivPlatformPCMDAC; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index c34a20e47..fabb26ac3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -982,7 +982,8 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_GA20 || i->type==DIV_INS_K053260 || i->type==DIV_INS_C140 || - i->type==DIV_INS_C219) { + i->type==DIV_INS_C219 || + i->type==DIV_INS_GBA_DMA) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 8ad21b100..e21f406e7 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1064,6 +1064,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo break; case DIV_INS_DAVE: break; + case DIV_INS_GBA_DMA: + featureSM=true; + featureSL=true; + break; case DIV_INS_MAX: break; case DIV_INS_NULL: diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 714adeea5..5e1822e46 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -89,6 +89,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_POWERNOISE=56, DIV_INS_POWERNOISE_SLOPE=57, DIV_INS_DAVE=58, + DIV_INS_GBA_DMA=59, DIV_INS_MAX, DIV_INS_NULL }; @@ -378,7 +379,7 @@ struct DivInstrumentSTD { struct DivInstrumentGB { unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; - bool softEnv, alwaysInit; + bool softEnv, alwaysInit, doubleWave; // TODO file save/load of doubleWave enum HWSeqCommands: unsigned char { DIV_GB_HWCMD_ENVELOPE=0, DIV_GB_HWCMD_SWEEP, @@ -406,7 +407,8 @@ struct DivInstrumentGB { soundLen(64), hwSeqLen(0), softEnv(false), - alwaysInit(false) { + alwaysInit(false), + doubleWave(false) { memset(hwSeq,0,256*sizeof(HWSeqCommandGB)); } }; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 3ae02dcf9..2ca894633 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -64,13 +64,15 @@ const char** DivPlatformGB::getRegisterSheet() { void DivPlatformGB::acquire(short** buf, size_t len) { for (size_t i=0; iapu_output.final_sample.left; buf[1][i]=gb->apu_output.final_sample.right; @@ -81,17 +83,41 @@ void DivPlatformGB::acquire(short** buf, size_t len) { } void DivPlatformGB::updateWave() { - rWrite(0x1a,0); - for (int i=0; i<16; i++) { - int nibble1=ws.output[((i<<1)+antiClickWavePos)&31]; - int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31]; - if (invertWave) { - nibble1^=15; - nibble2^=15; + if (doubleWave) { + rWrite(0x1a,0x40); // select 1 -> write to bank 0 + for (int i=0; i<16; i++) { + int nibble1=ws.output[((i<<1)+antiClickWavePos)&63]; + int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&63]; + if (invertWave) { + nibble1^=15; + nibble2^=15; + } + rWrite(0x30+i,(nibble1<<4)|nibble2); } - rWrite(0x30+i,(nibble1<<4)|nibble2); + rWrite(0x1a,0); // select 0 -> write to bank 1 + for (int i=0; i<16; i++) { + int nibble1=ws.output[((32+(i<<1))+antiClickWavePos)&63]; + int nibble2=ws.output[((33+(i<<1))+antiClickWavePos)&63]; + if (invertWave) { + nibble1^=15; + nibble2^=15; + } + rWrite(0x30+i,(nibble1<<4)|nibble2); + } + antiClickWavePos&=63; + } else { + rWrite(0x1a,extendWave?0x40:0); + for (int i=0; i<16; i++) { + int nibble1=ws.output[((i<<1)+antiClickWavePos)&31]; + int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31]; + if (invertWave) { + nibble1^=15; + nibble2^=15; + } + rWrite(0x30+i,(nibble1<<4)|nibble2); + } + antiClickWavePos&=31; } - antiClickWavePos&=31; } static unsigned char chanMuteMask[4]={ @@ -112,6 +138,13 @@ static unsigned char gbVolMap[16]={ 0x20, 0x20, 0x20, 0x20 }; +static unsigned char gbVolMapEx[16]={ + 0x00, 0x00, 0x00, 0x00, + 0x60, 0x60, 0x60, 0x60, + 0x40, 0x40, 0x40, 0x40, + 0xa0, 0xa0, 0x20, 0x20 +}; + static unsigned char noiseTable[256]={ 0, 0xf7, 0xf6, 0xf5, 0xf4, @@ -156,7 +189,7 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].outVol<0) chan[i].outVol=0; if (i==2) { - rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); + rWrite(16+i*5+2,(extendWave?gbVolMapEx:gbVolMap)[chan[i].outVol]); chan[i].soundLen=64; } else { chan[i].envLen=0; @@ -188,7 +221,7 @@ void DivPlatformGB::tick(bool sysTick) { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); } else if (!chan[i].softEnv) { if (parent->song.waveDutyIsVol) { - rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); + rWrite(16+i*5+2,(extendWave?gbVolMapEx:gbVolMap)[(chan[i].std.duty.val&3)<<2]); } } } @@ -301,8 +334,8 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].keyOn) { if (i==2) { // wave rWrite(16+i*5,0x00); - rWrite(16+i*5,0x80); - rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); + rWrite(16+i*5,doubleWave?0xa0:0x80); + rWrite(16+i*5+2,(extendWave?gbVolMapEx:gbVolMap)[chan[i].outVol]); } else { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); @@ -379,11 +412,16 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].softEnv=ins->gb.softEnv; chan[c.chan].macroInit(ins); if (c.chan==2) { + doubleWave=extendWave&&ins->gb.doubleWave; if (chan[c.chan].wave<0) { chan[c.chan].wave=0; ws.changeWave1(chan[c.chan].wave); } - ws.init(ins,32,15,chan[c.chan].insChanged); + ws.init(ins,doubleWave?64:32,15,chan[c.chan].insChanged); + if (doubleWave!=lastDoubleWave) { + ws.changeWave1(chan[c.chan].wave); + lastDoubleWave=doubleWave; + } } if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) { if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) { @@ -447,7 +485,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].vol=c.value; chan[c.chan].outVol=c.value; if (c.chan==2) { - rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]); + rWrite(16+c.chan*5+2,(extendWave?gbVolMapEx:gbVolMap)[chan[c.chan].outVol]); } if (!chan[c.chan].softEnv) { chan[c.chan].envVol=chan[c.chan].vol; @@ -619,6 +657,8 @@ void DivPlatformGB::reset() { antiClickPeriodCount=0; antiClickWavePos=0; + doubleWave=false; + lastDoubleWave=false; } int DivPlatformGB::getPortaFloor(int ch) { @@ -665,6 +705,8 @@ void DivPlatformGB::poke(std::vector& wlist) { void DivPlatformGB::setFlags(const DivConfig& flags) { antiClickEnabled=!flags.getBool("noAntiClick",false); + extendWave=flags.getBool("extendWave",false); + outDepth=flags.getInt("dacDepth",9); switch (flags.getInt("chipType",0)) { case 0: model=GB_MODEL_DMG_B; @@ -676,7 +718,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { model=GB_MODEL_CGB_E; break; case 3: - model=GB_MODEL_AGB; + model=extendWave?GB_MODEL_AGB_NATIVE:GB_MODEL_AGB; break; } invertWave=flags.getBool("invertWave",true); @@ -684,7 +726,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { chipClock=4194304; CHECK_CUSTOM_CLOCK; - rate=chipClock/16; + rate=chipClock>>(outDepth-2); for (int i=0; i<4; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 6fe094020..5bd445b7f 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -59,6 +59,10 @@ class DivPlatformGB: public DivDispatch { bool antiClickEnabled; bool invertWave; bool enoughAlready; + bool extendWave; + bool doubleWave; + bool lastDoubleWave; + int outDepth; unsigned char lastPan; DivWaveSynth ws; struct QueuedWrite { diff --git a/src/engine/platform/gbadma.cpp b/src/engine/platform/gbadma.cpp new file mode 100644 index 000000000..da4e95403 --- /dev/null +++ b/src/engine/platform/gbadma.cpp @@ -0,0 +1,445 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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. + */ + +#define _USE_MATH_DEFINES +#include "gbadma.h" +#include "../engine.h" +#include "../filter.h" +#include + +#define CHIP_DIVIDER 16 + +void DivPlatformGBADMA::acquire(short** buf, size_t len) { + // HLE for now + int outL[2]={0,0}; + int outR[2]={0,0}; + for (size_t h=0; h=0 && chan[i].samplesong.sampleLen))) { + chan[i].audSub+=(1<=chan[i].freq) { + int posInc=chan[i].audSub/chan[i].freq; + chan[i].audSub-=chan[i].freq*posInc; + chan[i].audPos+=posInc; + chan[i].dmaCount+=posInc; + if (chan[i].dmaCount>=16 && chan[i].audPos>=(int)chan[i].audLen) { + chan[i].audPos%=chan[i].audLen; + } + chan[i].dmaCount&=15; + } + } else { + DivSample* s=parent->getSample(chan[i].sample); + if (s->samples>0) { + if (chan[i].audPos>=0 && chan[i].audPos<(int)s->samples) { + chan[i].audDat=s->data8[chan[i].audPos]; + } else { + chan[i].audDat=0; + } + newSamp=true; + if (chan[i].audSub>=chan[i].freq) { + int posInc=chan[i].audSub/chan[i].freq; + chan[i].audSub-=chan[i].freq*posInc; + chan[i].audPos+=posInc; + chan[i].dmaCount+=posInc; + if (s->isLoopable()) { + if (chan[i].dmaCount>=16 && chan[i].audPos>=s->loopEnd) { + int loopPos=chan[i].audPos-s->loopStart; + chan[i].audPos=(loopPos%(s->loopEnd-s->loopStart))+s->loopStart; + } + } else if (chan[i].audPos>=(int)s->samples) { + chan[i].sample=-1; + } + chan[i].dmaCount&=15; + } + } else { + chan[i].sample=-1; + chan[i].audSub=0; + chan[i].audPos=0; + } + } + } else { + chan[i].audDat=0; + } + if (!isMuted[i] && newSamp) { + int out=chan[i].audDat*(chan[i].vol*chan[i].envVol/2)<<1; + outL[i]=(chan[i].pan&2)?out:0; + outR[i]=(chan[i].pan&1)?out:0; + } + oscBuf[i]->data[oscBuf[i]->needle++]=(short)((outL[i]+outR[i])<<5); + } + int l=outL[0]+outL[1]; + int r=outR[0]+outR[1]; + l=(l>>(10-outDepth))<<(16-outDepth); + r=(r>>(10-outDepth))<<(16-outDepth); + if (l<-32768) l=-32768; + if (l>32767) l=32767; + if (r<-32768) r=-32768; + if (r>32767) r=32767; + buf[0][h]=(short)l; + buf[1][h]=(short)r; + } +} + +void DivPlatformGBADMA::tick(bool sysTick) { + for (int i=0; i<2; i++) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].envVol=chan[i].std.vol.val; + if (ins->type==DIV_INS_AMIGA) chan[i].envVol/=32; + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].useWave && chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + if (chan[i].useWave && chan[i].active) { + chan[i].ws.tick(); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (ins->type==DIV_INS_AMIGA) { + if (chan[0].std.panL.had) { + chan[0].pan=(chan[0].pan&~2)|(chan[0].std.panL.val>0?2:0); + } + if (chan[0].std.panR.had) { + chan[0].pan=(chan[0].pan&~1)|(chan[0].std.panR.val>0?1:0); + } + } else { + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&3; + } + } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + chan[i].audPos=0; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + double off=1.0; + if (!chan[i].useWave && chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].sample); + off=(s->centerRate>=1)?(8363.0/(double)s->centerRate):1.0; + } + chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + + // emulate prescaler rounding + if (chan[i].freq<65536) { + if (chan[i].freq<1) chan[i].freq=1; + } else if (chan[i].freq<65536*64) { + chan[i].freq=chan[i].freq&~63; + } else if (chan[i].freq<65536*256) { + chan[i].freq=chan[i].freq&~255; + } else { + chan[i].freq=chan[i].freq&~1024; + if (chan[i].freq>65536*1024) chan[i].freq=65536*1024; + } + if (chan[i].keyOn) { + if (!chan[i].std.vol.had) { + chan[i].envVol=2; + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + chan[i].freqChanged=false; + } + } +} + +int DivPlatformGBADMA::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + if (ins->amiga.useWave) { + chan[c.chan].useWave=true; + chan[c.chan].audLen=ins->amiga.waveLen+1; + if (chan[c.chan].insChanged) { + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.setWidth(chan[c.chan].audLen); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + } + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } + chan[c.chan].useWave=false; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } + if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + if (chan[c.chan].setPos) { + chan[c.chan].setPos=false; + } else { + chan[c.chan].audPos=0; + } + chan[c.chan].audSub=0; + chan[c.chan].audDat=0; + chan[c.chan].dmaCount=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].envVol=2; + } + if (chan[c.chan].useWave) { + chan[c.chan].ws.init(ins,chan[c.chan].audLen,255,chan[c.chan].insChanged); + } + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].sample=-1; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].envVol=2; + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PANNING: + chan[c.chan].pan=0; + chan[c.chan].pan|=(c.value>0)?2:0; + chan[c.chan].pan|=(c.value2>0)?1:0; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + if (!chan[c.chan].useWave) break; + chan[c.chan].wave=c.value; + chan[c.chan].keyOn=true; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + break; + case DIV_CMD_NOTE_PORTA: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + chan[c.chan].sample=ins->amiga.getSample(c.value2); + 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; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + } + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + if (chan[c.chan].useWave) break; + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + break; + case DIV_CMD_GET_VOLMAX: + return 2; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformGBADMA::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformGBADMA::forceIns() { + for (int i=0; i<2; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + chan[i].audPos=0; + chan[i].sample=-1; + } +} + +void* DivPlatformGBADMA::getChanState(int ch) { + return &chan; +} + +DivDispatchOscBuffer* DivPlatformGBADMA::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +void DivPlatformGBADMA::reset() { + for (int i=0; i<2; i++) { + chan[i]=DivPlatformGBADMA::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255); + chan[i].audDat=0; + } +} + +int DivPlatformGBADMA::getOutputCount() { + return 2; +} + +DivMacroInt* DivPlatformGBADMA::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +unsigned short DivPlatformGBADMA::getPan(int ch) { + return ((chan[ch].pan&2)<<7)|(chan[ch].pan&1); +} + +DivSamplePos DivPlatformGBADMA::getSamplePos(int ch) { + if (ch>=2) return DivSamplePos(); + return DivSamplePos( + chan[ch].sample, + chan[ch].audPos, + chan[ch].freq + ); +} + +void DivPlatformGBADMA::notifyInsChange(int ins) { + for (int i=0; i<2; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformGBADMA::notifyWaveChange(int wave) { + for (int i=0; i<2; i++) { + if (chan[i].useWave && chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + } + } +} + +void DivPlatformGBADMA::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformGBADMA::setFlags(const DivConfig& flags) { + outDepth=flags.getInt("dacDepth",9); + chipClock=1<<24; + CHECK_CUSTOM_CLOCK; + rate=chipClock>>outDepth; + for (int i=0; i<2; i++) { + oscBuf[i]->rate=rate; + } +} + +int DivPlatformGBADMA::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<2; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 2; +} + +void DivPlatformGBADMA::quit() { + for (int i=0; i<2; i++) { + delete oscBuf[i]; + } +} diff --git a/src/engine/platform/gbadma.h b/src/engine/platform/gbadma.h new file mode 100644 index 000000000..dd739b1d5 --- /dev/null +++ b/src/engine/platform/gbadma.h @@ -0,0 +1,83 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 _GBA_DMA_H +#define _GBA_DMA_H + +#include "../dispatch.h" +#include "../waveSynth.h" + +class DivPlatformGBADMA: public DivDispatch { + struct Channel: public SharedChannel { + unsigned int audLoc; + unsigned short audLen; + int audDat; + int audPos; + int audSub; + int dmaCount; + int sample, wave; + int pan; + bool useWave, setPos; + int envVol; + DivWaveSynth ws; + Channel(): + SharedChannel(2), + audLoc(0), + audLen(0), + audDat(0), + audPos(0), + audSub(0), + dmaCount(0), + sample(-1), + wave(-1), + pan(3), + useWave(false), + setPos(false), + envVol(2) {} + }; + Channel chan[2]; + DivDispatchOscBuffer* oscBuf[2]; + bool isMuted[2]; + int outDepth; + + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short** buf, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); + DivSamplePos getSamplePos(int ch); + void setFlags(const DivConfig& flags); + void notifyInsChange(int ins); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); +}; + +#endif diff --git a/src/engine/platform/sound/gb/apu.c b/src/engine/platform/sound/gb/apu.c index 8b29f72de..e4be5e99a 100644 --- a/src/engine/platform/sound/gb/apu.c +++ b/src/engine/platform/sound/gb/apu.c @@ -668,15 +668,17 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu.is_active[GB_WAVE]) { uint8_t cycles_left = cycles; while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { + uint8_t base = (!gb->apu.wave_channel.double_length && gb->apu.wave_channel.bank_select) ? 32 : 0; cycles_left -= gb->apu.wave_channel.sample_countdown + 1; gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF; gb->apu.wave_channel.current_sample_index++; - gb->apu.wave_channel.current_sample_index &= 0x1F; + gb->apu.wave_channel.current_sample_index &= gb->apu.wave_channel.double_length ? 0x3F : 0x1F; gb->apu.wave_channel.current_sample = - gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; - update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, - cycles - cycles_left); + gb->apu.wave_channel.wave_form[base + gb->apu.wave_channel.current_sample_index]; + int8_t sample = gb->apu.wave_channel.force_3 ? + (gb->apu.wave_channel.current_sample * 3) >> 2 : + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift; + update_sample(gb, GB_WAVE, sample, cycles - cycles_left); gb->apu.wave_channel.wave_form_just_read = true; } if (cycles_left) { @@ -738,8 +740,10 @@ void GB_apu_init(GB_gameboy_t *gb) memset(&gb->apu, 0, sizeof(gb->apu)); /* Restore the wave form */ for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) { - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4; - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 32] = gb->io_registers[reg] >> 4; + gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 33] = gb->io_registers[reg] & 0xF; } gb->apu.lf_div = 1; /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, @@ -1160,14 +1164,24 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.is_active[GB_WAVE] = false; update_sample(gb, GB_WAVE, 0, 0); } + if (gb->model==GB_MODEL_AGB_NATIVE) { + gb->apu.wave_channel.bank_select = value & 0x40; + gb->apu.wave_channel.double_length = value & 0x20; + } break; case GB_IO_NR31: gb->apu.wave_channel.pulse_length = (0x100 - value); break; case GB_IO_NR32: gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; + if (gb->model==GB_MODEL_AGB_NATIVE) { + gb->apu.wave_channel.force_3 = value & 0x80; + } if (gb->apu.is_active[GB_WAVE]) { - update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0); + int8_t sample = gb->apu.wave_channel.force_3 ? + (gb->apu.wave_channel.current_sample * 3) >> 2 : + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift; + update_sample(gb, GB_WAVE, sample, 0); } break; case GB_IO_NR33: @@ -1209,9 +1223,10 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (!gb->apu.is_active[GB_WAVE]) { gb->apu.is_active[GB_WAVE] = true; - update_sample(gb, GB_WAVE, - gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, - 0); + int8_t sample = gb->apu.wave_channel.force_3 ? + (gb->apu.wave_channel.current_sample * 3) >> 2 : + gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift; + update_sample(gb, GB_WAVE, sample, 0); } gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3; gb->apu.wave_channel.current_sample_index = 0; @@ -1411,8 +1426,13 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) default: if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; - gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; + uint8_t base = 0; + if (gb->model == GB_MODEL_AGB_NATIVE && + (!gb->apu.global_enable || !gb->apu.wave_channel.bank_select)) { + base = 32; + } + gb->apu.wave_channel.wave_form[base + (reg - GB_IO_WAV_START) * 2] = value >> 4; + gb->apu.wave_channel.wave_form[base + (reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; } } gb->io_registers[reg] = value; diff --git a/src/engine/platform/sound/gb/apu.h b/src/engine/platform/sound/gb/apu.h index 3c07b46fe..6ba168f71 100644 --- a/src/engine/platform/sound/gb/apu.h +++ b/src/engine/platform/sound/gb/apu.h @@ -93,12 +93,15 @@ typedef struct uint8_t shift; // NR32 uint16_t sample_length; // NR33, NR34, in APU ticks bool length_enabled; // NR34 + bool double_length; // NR30 + bool bank_select; // NR30 + bool force_3; // NR32 uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF) uint8_t current_sample_index; uint8_t current_sample; // Current sample before shifting. - int8_t wave_form[32]; + int8_t wave_form[64]; bool wave_form_just_read; } wave_channel; diff --git a/src/engine/platform/sound/gb/gb.h b/src/engine/platform/sound/gb/gb.h index ca3650852..7adb27599 100644 --- a/src/engine/platform/sound/gb/gb.h +++ b/src/engine/platform/sound/gb/gb.h @@ -114,6 +114,7 @@ typedef enum { // GB_MODEL_CGB_D = 0x204, GB_MODEL_CGB_E = 0x205, GB_MODEL_AGB = 0x206, + GB_MODEL_AGB_NATIVE = 0x226, } GB_model_t; enum { diff --git a/src/engine/song.h b/src/engine/song.h index 86674ee52..4756499bf 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -118,6 +118,7 @@ enum DivSystem { DIV_SYSTEM_T6W28, DIV_SYSTEM_K007232, DIV_SYSTEM_GA20, + DIV_SYSTEM_GBA_DMA, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, DIV_SYSTEM_DUMMY, diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index e33b17b1c..9680012f3 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2001,6 +2001,16 @@ void DivEngine::registerSystems() { }, {} ); + + sysDefs[DIV_SYSTEM_GBA_DMA]=new DivSysDef( + "Game Boy Advance DMA Sound", NULL, 0xfe, 0, 2, false, true, 0, false, 1U<type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES) { + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES || ins->type==DIV_INS_GBA_DMA) { + const char* useWaveText=ins->type==DIV_INS_AMIGA?"Use wavetable (Amiga/Generic DAC only)":"Use wavetable"; ImGui::BeginDisabled(ins->amiga.useNoteMap); - P(ImGui::Checkbox("Use wavetable (Amiga/SNES/Generic DAC only)",&ins->amiga.useWave)); + P(ImGui::Checkbox(useWaveText,&ins->amiga.useWave)); if (ins->amiga.useWave) { int len=ins->amiga.waveLen+1; int origLen=len; if (ImGui::InputInt("Width",&len,2,16)) { - if (ins->type==DIV_INS_SNES) { + if (ins->type==DIV_INS_SNES || ins->type==DIV_INS_GBA_DMA) { if (len<16) len=16; if (len>256) len=256; if (len>origLen) { @@ -5395,6 +5396,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv)); P(ImGui::Checkbox("Initialize envelope on every note",&ins->gb.alwaysInit)); + P(ImGui::Checkbox("Double wave length (GBA only)",&ins->gb.doubleWave)); ImGui::BeginDisabled(ins->gb.softEnv); if (ImGui::BeginTable("GBParams",2)) { @@ -6021,7 +6023,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_GA20 || ins->type==DIV_INS_K053260 || ins->type==DIV_INS_C140 || - ins->type==DIV_INS_C219) { + ins->type==DIV_INS_C219 || + ins->type==DIV_INS_GBA_DMA) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6456,6 +6459,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_GB || (ins->type==DIV_INS_AMIGA && ins->amiga.useWave) || + (ins->type==DIV_INS_GBA_DMA && ins->amiga.useWave) || (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) || ins->type==DIV_INS_N163 || ins->type==DIV_INS_FDS || @@ -6500,6 +6504,7 @@ void FurnaceGUI::drawInsEdit() { wavePreviewHeight=255; break; case DIV_INS_AMIGA: + case DIV_INS_GBA_DMA: wavePreviewLen=ins->amiga.waveLen+1; wavePreviewHeight=255; break; @@ -6771,7 +6776,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_QSOUND) { volMax=16383; } - if (ins->type==DIV_INS_POKEMINI) { + if (ins->type==DIV_INS_POKEMINI || ins->type==DIV_INS_GBA_DMA) { volMax=2; } @@ -6830,7 +6835,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_PET || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 || ins->type==DIV_INS_SM8521 || ins->type==DIV_INS_PV1000 || ins->type==DIV_INS_K053260 || - ins->type==DIV_INS_C140) { + ins->type==DIV_INS_C140 || ins->type==DIV_INS_GBA_DMA) { dutyMax=0; } if (ins->type==DIV_INS_VBOY) { @@ -7045,7 +7050,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_VERA || ins->type==DIV_INS_ADPCMA || ins->type==DIV_INS_ADPCMB || - ins->type==DIV_INS_ESFM) { + ins->type==DIV_INS_ESFM || + ins->type==DIV_INS_GBA_DMA) { panMax=2; panSingle=true; } @@ -7201,7 +7207,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_ESFM || ins->type==DIV_INS_POWERNOISE || ins->type==DIV_INS_POWERNOISE_SLOPE || - ins->type==DIV_INS_DAVE) { + ins->type==DIV_INS_DAVE || + ins->type==DIV_INS_GBA_DMA) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 18a0ebdc9..2cf952ba0 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -130,6 +130,16 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_GB, 1.0f, 0, "") } ); + ENTRY( + "Game Boy Advance (no software mixing)", { + CH(DIV_SYSTEM_GB, 1.0f, 0, + "chipType=3\n" + "extendWave=true\n" + "dacDepth=9\n" + ), + CH(DIV_SYSTEM_GBA_DMA, 0.5f, 0, "dacDepth=9"), + } + ); ENTRY( "Neo Geo Pocket", { CH(DIV_SYSTEM_T6W28, 1.0f, 0, ""), diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 6a3a666ee..a3c70b820 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -379,6 +379,20 @@ void FurnaceGUI::drawSampleEdit() { if (sample->samples>129024) { SAMPLE_WARN(warnLength,"MSM6295: maximum bankswitched sample length is 129024"); } + break; + case DIV_SYSTEM_GBA_DMA: + if (sample->loop) { + if (sample->loopStart&3) { + SAMPLE_WARN(warnLoopStart,"GBA DMA: loop start must be a multiple of 4"); + } + if ((sample->loopEnd-sample->loopStart)&15) { + SAMPLE_WARN(warnLoopEnd,"GBA DMA: loop length must be a multiple of 16"); + } + } + if (sample->samples&15) { + SAMPLE_WARN(warnLength,"GBA DMA: sample length will be padded to multiple of 16"); + } + break; default: break; } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 8fdd1733f..566f0c513 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3551,6 +3551,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE,"PowerNoise (noise)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"PowerNoise (slope)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_DAVE,"Dave"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_DMA,"GBA DMA"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 18bc3d967..ad8b391b4 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -329,6 +329,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl bool noAntiClick=flags.getBool("noAntiClick",false); bool invertWave=flags.getBool("invertWave",true); bool enoughAlready=flags.getBool("enoughAlready",false); + bool extendWave=flags.getBool("extendWave",false); + int dacDepth=flags.getInt("dacDepth",6); if (ImGui::Checkbox("Disable anti-click",&noAntiClick)) { altered=true; @@ -352,6 +354,22 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl altered=true; } ImGui::Unindent(); + ImGui::Text("Game Boy Advance:"); + ImGui::Indent(); + ImGui::BeginDisabled(chipType!=3); + if (ImGui::Checkbox("Wave channel extension",&extendWave)) { + altered=true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("note: not supported by the VGM format!\nallows wave channel to have double length and 75%% volume"); + } + if (CWSliderInt("DAC bit depth (reduces output rate)",&dacDepth,6,9)) { + if (dacDepth<6) dacDepth=6; + if (dacDepth>9) dacDepth=9; + altered=true; + } + ImGui::EndDisabled(); + ImGui::Unindent(); ImGui::Text("Wave channel orientation:"); if (chipType==3) { ImGui::Indent(); @@ -381,11 +399,33 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } if (altered) { + if (chipType!=3) { + extendWave=false; + dacDepth=6; + } e->lockSave([&]() { flags.set("chipType",chipType); flags.set("noAntiClick",noAntiClick); flags.set("invertWave",invertWave); flags.set("enoughAlready",enoughAlready); + flags.set("extendWave",extendWave); + flags.set("dacDepth",dacDepth); + }); + } + break; + } + case DIV_SYSTEM_GBA_DMA: { + int dacDepth=flags.getInt("dacDepth",6); + + if (CWSliderInt("DAC bit depth (reduces output rate)",&dacDepth,6,9)) { + if (dacDepth<6) dacDepth=6; + if (dacDepth>9) dacDepth=9; + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("dacDepth",dacDepth); }); } break;