From f3705fb43567c79ae7b2c8de93b5363bf07fe1c1 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 10 Jan 2024 11:17:36 +0700 Subject: [PATCH 01/19] 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; From 3cd835098158d46c8fc069d67aa4298f087db360 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Fri, 12 Jan 2024 21:28:07 +0700 Subject: [PATCH 02/19] Define wave height and change icon for GBA DMA --- src/engine/sysDef.cpp | 2 +- src/gui/guiConst.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 9680012f3..7f87af69b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2003,7 +2003,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_GBA_DMA]=new DivSysDef( - "Game Boy Advance DMA Sound", NULL, 0xfe, 0, 2, false, true, 0, false, 1U< Date: Fri, 12 Jan 2024 22:00:12 +0700 Subject: [PATCH 03/19] Add set waveform commands to PCM DAC and GBA DMA --- src/engine/sysDef.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 7f87af69b..3de964f8b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1862,7 +1862,11 @@ void DivEngine::registerSystems() { {"Sample"}, {"PCM"}, {DIV_CH_PCM}, - {DIV_INS_AMIGA} + {DIV_INS_AMIGA}, + {}, + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + } ); sysDefs[DIV_SYSTEM_K007232]=new DivSysDef( @@ -2009,7 +2013,11 @@ void DivEngine::registerSystems() { {"P1", "P2"}, {DIV_CH_PCM, DIV_CH_PCM}, {DIV_INS_GBA_DMA, DIV_INS_GBA_DMA}, - {DIV_INS_AMIGA, DIV_INS_AMIGA} + {DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + } ); sysDefs[DIV_SYSTEM_DAVE]=new DivSysDef( From 0b1d2e24d75f8e6e84c24e6e350b70232699344c Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 13 Jan 2024 00:17:45 +0700 Subject: [PATCH 04/19] Change default GBA DMA DAC bit depth to 9 --- src/gui/sysConf.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index ad8b391b4..17d9e40d9 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -415,9 +415,10 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl break; } case DIV_SYSTEM_GBA_DMA: { - int dacDepth=flags.getInt("dacDepth",6); + int dacDepth=flags.getInt("dacDepth",9); - if (CWSliderInt("DAC bit depth (reduces output rate)",&dacDepth,6,9)) { + ImGui::Text("DAC bit depth (reduces output rate):"); + if (CWSliderInt("##DACDepth",&dacDepth,6,9)) { if (dacDepth<6) dacDepth=6; if (dacDepth>9) dacDepth=9; altered=true; From 2b9dd1cafffc5de0b1d4b83e2c0531c1f2c45826 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 16 Mar 2024 14:59:02 +0700 Subject: [PATCH 05/19] Add GBA MinMod driver support --- CMakeLists.txt | 1 + src/engine/dispatch.h | 4 + src/engine/dispatchContainer.cpp | 4 + src/engine/engine.cpp | 3 +- src/engine/instrument.h | 1 + src/engine/platform/gbaminmod.cpp | 725 ++++++++++++++++++++++++++++++ src/engine/platform/gbaminmod.h | 126 ++++++ src/engine/playback.cpp | 3 + src/engine/song.h | 3 +- src/engine/sysDef.cpp | 44 +- src/gui/doAction.cpp | 3 +- src/gui/gui.h | 1 + src/gui/guiConst.cpp | 6 +- src/gui/insEdit.cpp | 27 +- src/gui/presets.cpp | 12 +- src/gui/settings.cpp | 1 + src/gui/sysConf.cpp | 58 ++- 17 files changed, 996 insertions(+), 26 deletions(-) create mode 100644 src/engine/platform/gbaminmod.cpp create mode 100644 src/engine/platform/gbaminmod.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 87c684e02..4059c694f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -718,6 +718,7 @@ src/engine/platform/esfm.cpp src/engine/platform/powernoise.cpp src/engine/platform/dave.cpp src/engine/platform/gbadma.cpp +src/engine/platform/gbaminmod.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 06157e841..079a49022 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -251,6 +251,10 @@ enum DivDispatchCmds { DIV_CMD_POWERNOISE_COUNTER_LOAD, // (which, val) DIV_CMD_POWERNOISE_IO_WRITE, // (port, value) + + DIV_CMD_MINMOD_ECHO, + + DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_DAVE_HIGH_PASS, DIV_CMD_DAVE_RING_MOD, diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 420876a8e..2840ce740 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -82,6 +82,7 @@ #include "platform/ted.h" #include "platform/c140.h" #include "platform/gbadma.h" +#include "platform/gbaminmod.h" #include "platform/pcmdac.h" #include "platform/esfm.h" #include "platform/powernoise.h" @@ -648,6 +649,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_GBA_DMA: dispatch=new DivPlatformGBADMA; break; + case DIV_SYSTEM_GBA_MINMOD: + dispatch=new DivPlatformGBAMinMod; + break; case DIV_SYSTEM_PCM_DAC: dispatch=new DivPlatformPCMDAC; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index fabb26ac3..fe7b5f41e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -983,7 +983,8 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_K053260 || i->type==DIV_INS_C140 || i->type==DIV_INS_C219 || - i->type==DIV_INS_GBA_DMA) { + i->type==DIV_INS_GBA_DMA || + i->type==DIV_INS_GBA_MINMOD) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 5e1822e46..90f0e2309 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -90,6 +90,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_POWERNOISE_SLOPE=57, DIV_INS_DAVE=58, DIV_INS_GBA_DMA=59, + DIV_INS_GBA_MINMOD=60, DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp new file mode 100644 index 000000000..118882687 --- /dev/null +++ b/src/engine/platform/gbaminmod.cpp @@ -0,0 +1,725 @@ +/** + * 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. + */ + +#include "gbaminmod.h" +#include "../engine.h" +#include "../../ta-log.h" +#include + +#define CHIP_FREQBASE 16777216 + +#define rWrite(a,v) {regPool[a]=v;} + +const char* regCheatSheetMinMod[]={ + "CHx_Counter", "x0", + "CHx_Address", "x2", + "CHx_LastLeft", "x4", + "CHx_LastRight", "x6", + "CHx_Freq", "x8", + "CHx_LoopEnd", "xA", + "CHx_LoopStart", "xC", + "CHx_VolumeLeft", "xE", + "CHx_VolumeRight", "xF", + NULL +}; + +const char** DivPlatformGBAMinMod::getRegisterSheet() { + return regCheatSheetMinMod; +} + +void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { + short sampL, sampR; + size_t sampPos=mixBufReadPos&3; + bool newSamp=true; + // cache channel registers that might change + struct { + unsigned long long address; + unsigned int freq, loopEnd, loopStart; + short volL, volR; + } chState[16]; + for (int i=0; i=sampCycles) { + // the driver generates 4 samples at a time and can be start-offset + sampPos=mixBufReadPos&3; + if (sampPos==mixBufOffset) { + for (int j=mixBufOffset; j<4; j++) { + mixOut[0][j]=0; + mixOut[1][j]=0; + } + for (int i=0; i>32; + chState[i].address+=((unsigned long long)chState[i].freq)<<8; + unsigned int newAddr=chState[i].address>>32; + if (newAddr!=lastAddr) { + if (newAddr>=chState[i].loopEnd) { + newAddr=newAddr-chState[i].loopEnd+chState[i].loopStart; + chState[i].address=(chState[i].address&0xffffffff)|((unsigned long long)newAddr<<32); + } + int newSamp=0; + switch (newAddr>>24) { + case 2: // wavetable + newAddr&=0x0003ffff; + if (newAddr0x800) { + newSamp=mixBuf[(newAddr-0x800)/1024][newAddr&1023]; + } + break; + case 8: // sample + case 9: + case 10: + case 11: + case 12: + newSamp=sampleMem[newAddr&0x01ffffff]; + break; + } + chanOut[i][0]=newSamp*chState[i].volL; + chanOut[i][1]=newSamp*chState[i].volR; + } + int outL=chanOut[i][0]; + int outR=chanOut[i][1]; + int outA=(chan[i].invertL==chan[i].invertR)?outL+outR:outL-outR; + mixOut[0][j]+=(unsigned char)(outL>>15); + mixOut[1][j]+=(unsigned char)(outR>>15); + oscOut[i][j]=volScale>0?outA*64/volScale:0; + } + } + for (int j=mixBufOffset; j<4; j++) { + mixBuf[mixBufPage][mixBufWritePos]=mixOut[0][j]; + mixBuf[mixBufPage+1][mixBufWritePos]=mixOut[1][j]; + mixBufWritePos++; + } + mixBufOffset=0; + } + newSamp=true; + mixBufReadPos++; + sampTimer-=sampCycles; + } + if (newSamp) { + // assuming max PCM FIFO volume + sampL=((short)mixOut[0][sampPos]<<8)&(0xff80<<(9-dacDepth)); + sampR=((short)mixOut[1][sampPos]<<8)&(0xff80<<(9-dacDepth)); + newSamp=false; + } + buf[0][h]=sampL; + buf[1][h]=sampR; + for (int i=0; idata[oscBuf[i]->needle++]=oscOut[i][sampPos]; + } + for (int i=chanMax; i<16; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=0; + } + while (updTimer>=updCycles) { + // flip buffer + // logV("ut=%d,pg=%d,w=%d,r=%d,sc=%d,st=%d",updTimer,mixBufPage,mixBufWritePos,mixBufReadPos,sampCycles,sampTimer); + mixBufPage=(mixBufPage+2)%(mixBufs*2); + memset(mixBuf[mixBufPage],0,sizeof(mixBuf[mixBufPage])); + memset(mixBuf[mixBufPage+1],0,sizeof(mixBuf[mixBufPage+1])); + // emulate buffer loss prevention and buffer copying + sampsRendered+=mixBufReadPos; + mixBufOffset=(4-(mixBufReadPos&3))&3; + mixBufReadPos=0; + mixBufWritePos=mixBufOffset; + for (int j=0; jgetCurHz(); + float updCyclesNew=(hz>=1)?(16777216.f/hz):1; + // the maximum buffer size in the default multi-rate config is 1024 samples + // (so 15 left/right buffers + mixer code fit the entire 32k of internal RAM) + // if the driver determines that the current tick rate is too low, it will + // internally double the rate until the resulting buffer size fits + while (true) { + updCycles=floorf(updCyclesNew); + // emulate prescaler rounding + if (updCycles>=65536*256) { + updCycles&=~1024; + } else if (updCycles>=65536*64) { + updCycles&=~256; + } else if (updCycles>=65536) { + updCycles&=~64; + } + unsigned int bufSize=(updCycles/sampCycles+3)&~3; + if (bufSize<1024 || updCyclesNew<1) { + break; + } + updCyclesNew/=2; + } + } + updTimer+=1<>16)&0xffff; + chReg[2]=(chState[i].address>>32)&0xffff; + chReg[3]=(chState[i].address>>48)&0xffff; + chReg[4]=(chanOut[i][0]>>7)&0xff00; + chReg[5]=0; + chReg[6]=(chanOut[i][1]>>7)&0xff00; + chReg[7]=0; + chReg[10]=chState[i].loopEnd&0xffff; + chReg[11]=(chState[i].loopEnd>>16)&0xffff; + chReg[12]=chState[i].loopStart&0xffff; + chReg[13]=(chState[i].loopStart>>16)&0xffff; + } +} + +void DivPlatformGBAMinMod::tick(bool sysTick) { + // collect stats for display in chip config + // logV("rendered=%d,updTot=%d",sampsRendered,updCyclesTotal); + if (sysTick && sampsRendered>0) { + // assuming new sample, L!=R and lowest ROM access wait in all channels + // this gives 39.5 cycles/sample, rounded up to 40 for loops + maxCPU=(float)sampsRendered*chanMax*40/updCyclesTotal; + } + sampsRendered=0; + updCyclesTotal=0; + + for (int i=0; icalcArp(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].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 (chan[i].std.panL.had) { + chan[i].chPanL=(255*(chan[i].std.panL.val&255))/chan[i].macroPanMul; + chan[i].volChangedL=true; + } + + if (chan[i].std.panR.had) { + chan[i].chPanR=(255*(chan[i].std.panR.val&255))/chan[i].macroPanMul; + chan[i].volChangedR=true; + } + if (chan[i].std.phaseReset.had) { + if ((chan[i].std.phaseReset.val==1) && chan[i].active) { + chan[i].audPos=0; + chan[i].setPos=true; + } + } + if (chan[i].std.ex1.had) { + if (chan[i].invertL!=(bool)(chan[i].std.ex1.val&16)) { + chan[i].invertL=chan[i].std.ex1.val&2; + chan[i].volChangedL=true; + } + if (chan[i].invertR!=(bool)(chan[i].std.ex1.val&8)) { + chan[i].invertR=chan[i].std.ex1.val&1; + chan[i].volChangedR=true; + } + } + if (chan[i].setPos) { + // force keyon + chan[i].keyOn=true; + chan[i].setPos=false; + } else { + chan[i].audPos=0; + } + if (chan[i].useWave && chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivSample* s=parent->getSample(chan[i].sample); + double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)); + if (chan[i].keyOn) { + unsigned int start, end, loop; + if (chan[i].echo!=0) { + // make sure echo channels' frequency can't be faster than the sample rate + if (chan[i].echo!=0 && chan[i].freq>CHIP_FREQBASE) { + chan[i].freq=CHIP_FREQBASE; + } + // this is only to match the HLE implementation + // the actual engine will handle mid-frame echo switch differently + start=loop=0x08000000; + end=0x08000001; + } else if (chan[i].useWave) { + start=(i*256)|0x02000000; + end=start+chan[i].wtLen; + loop=start; + } else { + size_t maxPos=getSampleMemCapacity(); + start=sampleOff[chan[i].sample]; + if (s->isLoopable()) { + end=MIN(start+MAX(s->length8,1),maxPos); + loop=start+s->loopStart; + } else { + end=MIN(start+s->length8+16,maxPos); + loop=MIN(start+s->length8,maxPos); + } + if (chan[i].audPos>0) { + start=start+MIN(chan[i].audPos,end); + } + start|=0x08000000; + end|=0x08000000; + loop|=0x08000000; + } + rWrite(2+i*16,start&0xffff); + rWrite(3+i*16,start>>16); + rWrite(10+i*16,end&0xffff); + rWrite(11+i*16,end>>16); + rWrite(12+i*16,loop&0xffff); + rWrite(13+i*16,loop>>16); + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + } + chan[i].volChangedL=true; + chan[i].volChangedR=true; + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].volChangedL=true; + chan[i].volChangedR=true; + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + rWrite(8+i*16,chan[i].freq&0xffff); + rWrite(9+i*16,chan[i].freq>>16); + chan[i].freqChanged=false; + } + } + // don't scale echo channels + if (chan[i].volChangedL) { + int out=chan[i].outVol*chan[i].chPanL; + if ((chan[i].echo&0xf)==0) out=(out*volScale)>>16; + else out=out>>1; + if (chan[i].invertL) out=-out; + rWrite(14+i*16,(isMuted[i] || !chan[i].active)?0:out); + chan[i].volChangedL=false; + } + if (chan[i].volChangedR) { + int out=chan[i].outVol*chan[i].chPanR; + if ((chan[i].echo&0xf)==0) out=(out*volScale)>>16; + else out=out>>1; + if (chan[i].invertR) out=-out; + rWrite(15+i*16,(isMuted[i] || !chan[i].active)?0:out); + chan[i].volChangedR=false; + } + } +} + +int DivPlatformGBAMinMod::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; + chan[c.chan].macroPanMul=ins->type==DIV_INS_AMIGA?127:255; + if (ins->amiga.useWave) { + chan[c.chan].useWave=true; + chan[c.chan].wtLen=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].wtLen); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,chan[c.chan].wtLen,255,chan[c.chan].insChanged); + } 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 (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value)); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + 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; + } + break; + case DIV_CMD_VOLUME: + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + chan[c.chan].volChangedL=true; + chan[c.chan].volChangedR=true; + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_SNES_INVERT: + chan[c.chan].invertL=(c.value>>4); + chan[c.chan].invertR=c.value&15; + chan[c.chan].volChangedL=true; + chan[c.chan].volChangedR=true; + break; + case DIV_CMD_PANNING: + chan[c.chan].chPanL=c.value; + chan[c.chan].chPanR=c.value2; + chan[c.chan].volChangedL=true; + chan[c.chan].volChangedR=true; + 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].ws.changeWave1(chan[c.chan].wave); + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(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)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_POS: + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + break; + case DIV_CMD_MINMOD_ECHO: + chan[c.chan].echo=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 255; + 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 DivPlatformGBAMinMod::updateWave(int ch) { + int addr=ch*256; + for (unsigned int i=0; i& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +unsigned char* DivPlatformGBAMinMod::getRegisterPool() { + return (unsigned char*)regPool; +} + +int DivPlatformGBAMinMod::getRegisterPoolSize() { + return 256; +} + +int DivPlatformGBAMinMod::getRegisterPoolDepth() { + return 16; +} + +const void* DivPlatformGBAMinMod::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformGBAMinMod::getSampleMemCapacity(int index) { + return index == 0 ? 33554432 : 0; +} + +size_t DivPlatformGBAMinMod::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +bool DivPlatformGBAMinMod::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +void DivPlatformGBAMinMod::renderSamples(int sysID) { + size_t maxPos=getSampleMemCapacity(); + memset(sampleMem,0,maxPos); + + // dummy zero-length samples are at pos 0 as the engine still outputs them + size_t memPos=1; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOff[i]=0; + continue; + } + int length=s->length8; + int actualLength=MIN((int)(maxPos-memPos),length); + if (actualLength>0) { + sampleOff[i]=memPos; + memcpy(&sampleMem[memPos],s->data8,actualLength); + memPos+=actualLength; + // if it's one-shot, add 16 silent samples for looping area + // this should be enough for most cases even though the + // frequency register can make position jump by up to 256 samples + if (!s->isLoopable()) { + int oneShotLen=MIN((int)maxPos-memPos,16); + memset(&sampleMem[memPos],0,oneShotLen); + memPos+=oneShotLen; + } + } + if (actualLength>dacDepth; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + sampCycles=16777216/flags.getInt("sampRate",21845); + chipClock=16777216/sampCycles; + resetMixer(); +} + +int DivPlatformGBAMinMod::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + sampleMem=new signed char[getSampleMemCapacity()]; + sampleMemLen=0; + setFlags(flags); + reset(); + + return 16; +} + +void DivPlatformGBAMinMod::quit() { + delete[] sampleMem; + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } +} diff --git a/src/engine/platform/gbaminmod.h b/src/engine/platform/gbaminmod.h new file mode 100644 index 000000000..0b7190bd5 --- /dev/null +++ b/src/engine/platform/gbaminmod.h @@ -0,0 +1,126 @@ +/** + * 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_MINMOD_H +#define _GBA_MINMOD_H + +#include "../dispatch.h" +#include "../waveSynth.h" + +class DivPlatformGBAMinMod: public DivDispatch { + struct Channel: public SharedChannel { + unsigned char echo; + unsigned int audPos, wtLen; + int sample, wave; + bool useWave, setPos, volChangedL, volChangedR, invertL, invertR; + int chPanL, chPanR; + int macroVolMul; + int macroPanMul; + DivWaveSynth ws; + Channel(): + SharedChannel(255), + echo(0), + audPos(0), + wtLen(1), + sample(-1), + wave(-1), + useWave(false), + setPos(false), + volChangedL(false), + volChangedR(false), + invertL(false), + invertR(false), + chPanL(255), + chPanR(255), + macroVolMul(256), + macroPanMul(127) {} + }; + Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + unsigned int sampleOff[256]; + bool sampleLoaded[256]; + int volScale; + unsigned char chanMax; + + // emulator part + unsigned int mixBufs; + unsigned int mixBufSize; + unsigned int dacDepth; + unsigned int sampCycles; + unsigned int sampTimer; + unsigned int updCycles; + unsigned int updTimer; + unsigned int updCyclesTotal; + unsigned int sampsRendered; + signed char mixBuf[15*2][1024]; + unsigned char mixOut[2][4]; + short oscOut[16][4]; + int chanOut[16][2]; + size_t mixBufPage; + size_t mixBufReadPos; + size_t mixBufWritePos; + size_t mixBufOffset; + + signed char* sampleMem; + size_t sampleMemLen; + // maximum wavetable length is currently hardcoded to 256 + signed char wtMem[256*16]; + unsigned short regPool[16*16]; + 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); + DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + int getRegisterPoolDepth(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + void notifyInsChange(int ins); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + size_t getSampleMemUsage(int index = 0); + bool isSampleLoaded(int index, int sample); + void renderSamples(int chipID); + void setFlags(const DivConfig& flags); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + + float maxCPU; + private: + void updateWave(int ch); + // emulator part + void resetMixer(); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 49341ce14..c8249b1b9 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -257,6 +257,9 @@ const char* cmdName[]={ "DAVE_CLOCK_DIV", "MACRO_RESTART", + "MINMOD_ECHO", + + "ALWAYS_SET_VOLUME" }; static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); diff --git a/src/engine/song.h b/src/engine/song.h index 4756499bf..3088be22f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -118,7 +118,6 @@ 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, @@ -136,6 +135,8 @@ enum DivSystem { DIV_SYSTEM_ESFM, DIV_SYSTEM_POWERNOISE, DIV_SYSTEM_DAVE, + DIV_SYSTEM_GBA_DMA, + DIV_SYSTEM_GBA_MINMOD, }; enum DivEffectType: unsigned short { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3de964f8b..1cdcdfb70 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2005,20 +2005,6 @@ 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 || ins->type==DIV_INS_GBA_DMA) { + if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES || ins->type==DIV_INS_GBA_DMA || ins->type==DIV_INS_GBA_MINMOD) { const char* useWaveText=ins->type==DIV_INS_AMIGA?"Use wavetable (Amiga/Generic DAC only)":"Use wavetable"; ImGui::BeginDisabled(ins->amiga.useNoteMap); P(ImGui::Checkbox(useWaveText,&ins->amiga.useWave)); @@ -6024,7 +6028,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_K053260 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219 || - ins->type==DIV_INS_GBA_DMA) { + ins->type==DIV_INS_GBA_DMA || + ins->type==DIV_INS_GBA_MINMOD) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6469,7 +6474,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SCC || ins->type==DIV_INS_SNES || ins->type==DIV_INS_NAMCO || - ins->type==DIV_INS_SM8521) { + ins->type==DIV_INS_SM8521 || + (ins->type==DIV_INS_GBA_MINMOD && ins->amiga.useWave)) { if (ImGui::BeginTabItem("Wavetable")) { switch (ins->type) { case DIV_INS_GB: @@ -6512,6 +6518,10 @@ void FurnaceGUI::drawInsEdit() { wavePreviewLen=ins->amiga.waveLen+1; wavePreviewHeight=15; break; + case DIV_INS_GBA_MINMOD: + wavePreviewLen=ins->amiga.waveLen+1; + wavePreviewHeight=255; + break; default: wavePreviewLen=32; wavePreviewHeight=31; @@ -6770,7 +6780,7 @@ void FurnaceGUI::drawInsEdit() { volMax=31; } if (ins->type==DIV_INS_ADPCMB || ins->type==DIV_INS_YMZ280B || ins->type==DIV_INS_RF5C68 || - ins->type==DIV_INS_GA20 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219) { + ins->type==DIV_INS_GA20 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219 || ins->type==DIV_INS_GBA_MINMOD) { volMax=255; } if (ins->type==DIV_INS_QSOUND) { @@ -6835,7 +6845,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_GBA_DMA) { + ins->type==DIV_INS_C140 || ins->type==DIV_INS_GBA_DMA || ins->type==DIV_INS_GBA_MINMOD) { dutyMax=0; } if (ins->type==DIV_INS_VBOY) { @@ -7036,6 +7046,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_MIKEY) { ex1Max=12; } + if (ins->type==DIV_INS_GBA_MINMOD) { + ex1Max=2; + } int panMin=0; int panMax=0; @@ -7101,7 +7114,7 @@ void FurnaceGUI::drawInsEdit() { panMin=0; panMax=127; } - if (ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219) { + if (ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219 || ins->type==DIV_INS_GBA_MINMOD) { panMin=0; panMax=255; } @@ -7246,6 +7259,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,daveControlBits)); } else if (ins->type==DIV_INS_MIKEY) { macroList.push_back(FurnaceGUIMacroDesc("Load LFSR",&ins->std.ex1Macro,0,12,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + } else if (ins->type==DIV_INS_GBA_MINMOD) { + macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex1Macro,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,minModModeBits)); } else { macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 2cf952ba0..b098a98f0 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -137,7 +137,17 @@ void FurnaceGUI::initSystemPresets() { "extendWave=true\n" "dacDepth=9\n" ), - CH(DIV_SYSTEM_GBA_DMA, 0.5f, 0, "dacDepth=9"), + CH(DIV_SYSTEM_GBA_DMA, 0.5f, 0, ""), + } + ); + ENTRY( + "Game Boy Advance (with MinMod)", { + CH(DIV_SYSTEM_GB, 1.0f, 0, + "chipType=3\n" + "extendWave=true\n" + "dacDepth=9\n" + ), + CH(DIV_SYSTEM_GBA_MINMOD, 0.5f, 0, ""), } ); ENTRY( diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 566f0c513..44f61e5f6 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3552,6 +3552,7 @@ void FurnaceGUI::drawSettings() { 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_GBA_MINMOD,"GBA MinMod"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 17d9e40d9..fba0a0f3a 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -18,6 +18,7 @@ */ #include "../engine/chipUtils.h" +#include "../engine/platform/gbaminmod.h" #include "gui.h" #include "misc/cpp/imgui_stdlib.h" #include @@ -363,7 +364,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl 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)) { + ImGui::Text("DAC bit depth (reduces output rate):"); + if (CWSliderInt("##DACDepth",&dacDepth,6,9)) { if (dacDepth<6) dacDepth=6; if (dacDepth>9) dacDepth=9; altered=true; @@ -431,6 +433,60 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } + case DIV_SYSTEM_GBA_MINMOD: { + supportsCustomRate=false; + int volScale=flags.getInt("volScale",4096); + int mixBufs=flags.getInt("mixBufs",15); + int dacDepth=flags.getInt("dacDepth",9); + int channels=flags.getInt("channels",16); + int sampRate=flags.getInt("sampRate",21845); + ImGui::Text("Volume scale:"); + if (CWSliderInt("##VolScale",&volScale,0,32768)) { + if (volScale<0) volScale=0; + if (volScale>32768) volScale=32768; + altered=true; + } rightClickable + ImGui::Text("Mix buffers (allows longer echo delay):"); + if (CWSliderInt("##MixBufs",&mixBufs,2,15)) { + if (mixBufs<2) mixBufs=2; + if (mixBufs>16) mixBufs=16; + altered=true; + } rightClickable + ImGui::Text("DAC bit depth (reduces output rate):"); + if (CWSliderInt("##DACDepth",&dacDepth,6,9)) { + if (dacDepth<6) dacDepth=6; + if (dacDepth>9) dacDepth=9; + altered=true; + } rightClickable + ImGui::Text("Channel limit:"); + if (CWSliderInt("##Channels",&channels,1,16)) { + if (channels<1) channels=1; + if (channels>16) channels=16; + altered=true; + } rightClickable + ImGui::Text("Sample rate:"); + if (CWSliderInt("##SampRate",&sampRate,256,65536)) { + if (sampRate<1) sampRate=21845; + if (sampRate>65536) sampRate=65536; + altered=true; + } rightClickable + DivPlatformGBAMinMod* dispatch=(DivPlatformGBAMinMod*)e->getDispatch(chan); + float maxCPU=dispatch->maxCPU*100; + ImGui::Text("Actual sample rate: %d Hz", dispatch->chipClock); + FurnaceGUI::pushWarningColor(maxCPU>90); + ImGui::Text("Max mixer CPU usage: %.0f%%", maxCPU); + FurnaceGUI::popWarningColor(); + if (altered) { + e->lockSave([&]() { + flags.set("volScale",volScale); + flags.set("mixBufs",mixBufs); + flags.set("dacDepth",dacDepth); + flags.set("channels",channels); + flags.set("sampRate",sampRate); + }); + } + break; + } case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: { From 67253245abfe499a3a822ca1945e3ed1b13f2e3e Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 16 Mar 2024 19:59:55 +0700 Subject: [PATCH 06/19] GBA DMA: implement sample memory --- src/engine/platform/gbadma.cpp | 86 ++++++++++++++++++++++++++++------ src/engine/platform/gbadma.h | 14 ++++++ 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/engine/platform/gbadma.cpp b/src/engine/platform/gbadma.cpp index da4e95403..9a0ac2fab 100644 --- a/src/engine/platform/gbadma.cpp +++ b/src/engine/platform/gbadma.cpp @@ -33,13 +33,12 @@ void DivPlatformGBADMA::acquire(short** buf, size_t len) { // internal mixing is always 10-bit for (int i=0; i<2; i++) { bool newSamp=h==0; + chan[i].audDat=0; if (chan[i].active && (chan[i].useWave || (chan[i].sample>=0 && chan[i].samplesong.sampleLen))) { chan[i].audSub+=(1<=chan[i].freq) { @@ -52,13 +51,12 @@ void DivPlatformGBADMA::acquire(short** buf, size_t len) { } chan[i].dmaCount&=15; } - } else { + } else if (sampleLoaded[chan[i].sample]) { 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; + if (chan[i].audPos>=0) { + unsigned int pos=(sampleOff[chan[i].sample]+chan[i].audPos)&0x01ffffff; + chan[i].audDat=sampleMem[pos]; } newSamp=true; if (chan[i].audSub>=chan[i].freq) { @@ -68,8 +66,9 @@ void DivPlatformGBADMA::acquire(short** buf, size_t len) { 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; + int loopStart=s->loopStart&~3; + int loopPos=chan[i].audPos-loopStart; + chan[i].audPos=(loopPos%(s->loopEnd-s->loopStart))+loopStart; } } else if (chan[i].audPos>=(int)s->samples) { chan[i].sample=-1; @@ -82,8 +81,6 @@ void DivPlatformGBADMA::acquire(short** buf, size_t len) { 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; @@ -112,6 +109,7 @@ void DivPlatformGBADMA::tick(bool sysTick) { if (chan[i].std.vol.had) { chan[i].envVol=chan[i].std.vol.val; if (ins->type==DIV_INS_AMIGA) chan[i].envVol/=32; + else if (chan[i].envVol>2) chan[i].envVol=2; } if (NEW_ARP_STRAT) { chan[i].handleArp(); @@ -129,7 +127,9 @@ void DivPlatformGBADMA::tick(bool sysTick) { } } if (chan[i].useWave && chan[i].active) { - chan[i].ws.tick(); + if (chan[i].ws.tick()) { + updateWave(i); + } } if (chan[i].std.pitch.had) { if (chan[i].std.pitch.mode) { @@ -257,7 +257,7 @@ int DivPlatformGBADMA::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { - chan[c.chan].vol=c.value; + chan[c.chan].vol=MIN(c.value,2); if (!chan[c.chan].std.vol.has) { chan[c.chan].envVol=2; } @@ -341,6 +341,13 @@ int DivPlatformGBADMA::dispatch(DivCommand c) { return 1; } +void DivPlatformGBADMA::updateWave(int ch) { + int addr=ch*256; + for (unsigned int i=0; i255) return false; + return sampleLoaded[sample]; +} + +void DivPlatformGBADMA::renderSamples(int sysID) { + size_t maxPos=getSampleMemCapacity(); + memset(sampleMem,0,maxPos); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOff[i]=0; + continue; + } + int length=s->length8; + int actualLength=MIN((int)(maxPos-memPos),length); + if (actualLength>0) { + sampleOff[i]=memPos; + memcpy(&sampleMem[memPos],s->data8,actualLength); + memPos+=actualLength; + } + if (actualLength Date: Sat, 16 Mar 2024 23:16:40 +0700 Subject: [PATCH 07/19] GBA: implement memory composition and status --- src/engine/dispatch.h | 1 + src/engine/platform/gbadma.cpp | 29 +++++++++++++-- src/engine/platform/gbadma.h | 6 +++- src/engine/platform/gbaminmod.cpp | 60 +++++++++++++++++++++++++++++-- src/engine/platform/gbaminmod.h | 8 ++++- src/gui/memory.cpp | 8 +++++ 6 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 079a49022..dee8efe75 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -480,6 +480,7 @@ enum DivMemoryWaveView: unsigned char { DIV_MEMORY_WAVE_NONE=0, DIV_MEMORY_WAVE_4BIT, // Namco 163 DIV_MEMORY_WAVE_6BIT, // Virtual Boy + DIV_MEMORY_WAVE_8BIT_SIGNED, // SCC }; struct DivMemoryComposition { diff --git a/src/engine/platform/gbadma.cpp b/src/engine/platform/gbadma.cpp index 9a0ac2fab..af61fa543 100644 --- a/src/engine/platform/gbadma.cpp +++ b/src/engine/platform/gbadma.cpp @@ -195,6 +195,7 @@ int DivPlatformGBADMA::dispatch(DivCommand c) { if (ins->amiga.useWave) { chan[c.chan].useWave=true; chan[c.chan].audLen=ins->amiga.waveLen+1; + wtMemCompo.entries[c.chan].end=wtMemCompo.entries[c.chan].begin+chan[c.chan].audLen; if (chan[c.chan].insChanged) { if (chan[c.chan].wave<0) { chan[c.chan].wave=0; @@ -392,11 +393,14 @@ unsigned short DivPlatformGBADMA::getPan(int ch) { } DivSamplePos DivPlatformGBADMA::getSamplePos(int ch) { - if (ch>=2) return DivSamplePos(); + if (ch>=2 || !chan[ch].active || + chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) { + return DivSamplePos(); + } return DivSamplePos( chan[ch].sample, chan[ch].audPos, - chan[ch].freq + chipClock/chan[ch].freq ); } @@ -441,9 +445,19 @@ bool DivPlatformGBADMA::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformGBADMA::getMemCompo(int index) { + switch (index) { + case 0: return &romMemCompo; + case 1: return &wtMemCompo; + } + return NULL; +} + void DivPlatformGBADMA::renderSamples(int sysID) { size_t maxPos=getSampleMemCapacity(); memset(sampleMem,0,maxPos); + romMemCompo.entries.clear(); + romMemCompo.capacity=maxPos; size_t memPos=0; for (int i=0; isong.sampleLen; i++) { @@ -466,8 +480,10 @@ void DivPlatformGBADMA::renderSamples(int sysID) { sampleLoaded[i]=true; // pad to multiple of 16 bytes memPos=(memPos+15)&~15; + romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos)); } sampleMemLen=memPos; + romMemCompo.used=sampleMemLen; } void DivPlatformGBADMA::setFlags(const DivConfig& flags) { @@ -484,9 +500,18 @@ int DivPlatformGBADMA::init(DivEngine* p, int channels, int sugRate, const DivCo parent=p; dumpWrites=false; skipRegisterWrites=false; + romMemCompo=DivMemoryComposition(); + romMemCompo.name="Sample ROM"; + wtMemCompo=DivMemoryComposition(); + wtMemCompo.name="Wavetable RAM"; + wtMemCompo.used=256*2; + wtMemCompo.capacity=256*2; + wtMemCompo.memory=(unsigned char*)wtMem; + wtMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED; for (int i=0; i<2; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; + wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i),-1,i*256,i*256)); } sampleMem=new signed char[getSampleMemCapacity()]; sampleMemLen=0; diff --git a/src/engine/platform/gbadma.h b/src/engine/platform/gbadma.h index aff18d3df..8ea38e545 100644 --- a/src/engine/platform/gbadma.h +++ b/src/engine/platform/gbadma.h @@ -62,6 +62,9 @@ class DivPlatformGBADMA: public DivDispatch { size_t sampleMemLen; // maximum wavetable length is currently hardcoded to 256 signed char wtMem[256*2]; + DivMemoryComposition romMemCompo; + DivMemoryComposition wtMemCompo; + friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); @@ -86,10 +89,11 @@ class DivPlatformGBADMA: public DivDispatch { size_t getSampleMemCapacity(int index = 0); size_t getSampleMemUsage(int index = 0); bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); - + private: void updateWave(int ch); }; diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp index 118882687..4d3605195 100644 --- a/src/engine/platform/gbaminmod.cpp +++ b/src/engine/platform/gbaminmod.cpp @@ -142,6 +142,8 @@ void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { while (updTimer>=updCycles) { // flip buffer // logV("ut=%d,pg=%d,w=%d,r=%d,sc=%d,st=%d",updTimer,mixBufPage,mixBufWritePos,mixBufReadPos,sampCycles,sampTimer); + mixMemCompo.entries[mixBufPage].end=mixMemCompo.entries[mixBufPage].begin+mixBufWritePos; + mixMemCompo.entries[mixBufPage+1].end=mixMemCompo.entries[mixBufPage+1].begin+mixBufWritePos; mixBufPage=(mixBufPage+2)%(mixBufs*2); memset(mixBuf[mixBufPage],0,sizeof(mixBuf[mixBufPage])); memset(mixBuf[mixBufPage+1],0,sizeof(mixBuf[mixBufPage+1])); @@ -298,9 +300,9 @@ void DivPlatformGBAMinMod::tick(bool sysTick) { chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)); if (chan[i].keyOn) { unsigned int start, end, loop; - if (chan[i].echo!=0) { + if ((chan[i].echo&0xf)!=0) { // make sure echo channels' frequency can't be faster than the sample rate - if (chan[i].echo!=0 && chan[i].freq>CHIP_FREQBASE) { + if (chan[i].freq>CHIP_FREQBASE) { chan[i].freq=CHIP_FREQBASE; } // this is only to match the HLE implementation @@ -381,6 +383,9 @@ int DivPlatformGBAMinMod::dispatch(DivCommand c) { if (ins->amiga.useWave) { chan[c.chan].useWave=true; chan[c.chan].wtLen=ins->amiga.waveLen+1; + if (c.chan=chanMax || + chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen || + !chan[ch].active || (chan[ch].echo&0xf)!=0 + ) { + return DivSamplePos(); + } + return DivSamplePos( + chan[ch].sample, + (((int)regPool[ch*16+2]|((int)regPool[ch*16+3]<<16))&0x01ffffff)-sampleOff[chan[ch].sample], + (long long)chan[ch].freq*chipClock/CHIP_FREQBASE + ); +} + DivDispatchOscBuffer* DivPlatformGBAMinMod::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -577,6 +596,7 @@ void DivPlatformGBAMinMod::reset() { void DivPlatformGBAMinMod::resetMixer() { sampTimer=sampCycles; updTimer=0; + updCycles=0; mixBufReadPos=0; mixBufWritePos=0; mixBufPage=0; @@ -651,9 +671,20 @@ bool DivPlatformGBAMinMod::isSampleLoaded(int index, int sample) { return sampleLoaded[sample]; } +const DivMemoryComposition* DivPlatformGBAMinMod::getMemCompo(int index) { + switch (index) { + case 0: return &romMemCompo; + case 1: return &wtMemCompo; + case 2: return &mixMemCompo; + } + return NULL; +} + void DivPlatformGBAMinMod::renderSamples(int sysID) { size_t maxPos=getSampleMemCapacity(); memset(sampleMem,0,maxPos); + romMemCompo.entries.clear(); + romMemCompo.capacity=maxPos; // dummy zero-length samples are at pos 0 as the engine still outputs them size_t memPos=1; @@ -677,6 +708,7 @@ void DivPlatformGBAMinMod::renderSamples(int sysID) { memset(&sampleMem[memPos],0,oneShotLen); memPos+=oneShotLen; } + romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos)); } if (actualLengthAddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_DATA])); } break; + case DIV_MEMORY_WAVE_8BIT_SIGNED: + for (int k=0; kcapacity; k++) { + signed char val=(signed char)mc->memory[k]; + ImVec2 pos1=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)k/(double)(mc->capacity),1.0f-((float)(val+129)/256.0f))); + ImVec2 pos2=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)(k+1)/(double)(mc->capacity),1.0f)); + dl->AddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_DATA])); + } + break; default: break; } From 7fe7c67b4b5c0be310b876959b054c49bc5473fa Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 17 Mar 2024 05:17:22 +0700 Subject: [PATCH 08/19] Fix CI build --- src/engine/instrument.cpp | 4 ++++ src/engine/platform/gbaminmod.cpp | 12 ++++++------ src/engine/platform/gbaminmod.h | 1 - src/gui/memory.cpp | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index e21f406e7..d2b5e6acc 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1068,6 +1068,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo featureSM=true; featureSL=true; break; + case DIV_INS_GBA_MINMOD: + featureSM=true; + featureSL=true; + break; case DIV_INS_MAX: break; case DIV_INS_NULL: diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp index 4d3605195..1fb66ddd2 100644 --- a/src/engine/platform/gbaminmod.cpp +++ b/src/engine/platform/gbaminmod.cpp @@ -67,12 +67,12 @@ void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { // the driver generates 4 samples at a time and can be start-offset sampPos=mixBufReadPos&3; if (sampPos==mixBufOffset) { - for (int j=mixBufOffset; j<4; j++) { + for (size_t j=mixBufOffset; j<4; j++) { mixOut[0][j]=0; mixOut[1][j]=0; } for (int i=0; i>32; chState[i].address+=((unsigned long long)chState[i].freq)<<8; unsigned int newAddr=chState[i].address>>32; @@ -114,7 +114,7 @@ void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { oscOut[i][j]=volScale>0?outA*64/volScale:0; } } - for (int j=mixBufOffset; j<4; j++) { + for (size_t j=mixBufOffset; j<4; j++) { mixBuf[mixBufPage][mixBufWritePos]=mixOut[0][j]; mixBuf[mixBufPage+1][mixBufWritePos]=mixOut[1][j]; mixBufWritePos++; @@ -152,7 +152,7 @@ void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { mixBufOffset=(4-(mixBufReadPos&3))&3; mixBufReadPos=0; mixBufWritePos=mixBufOffset; - for (int j=0; jcapacity; k++) { + for (int k=0; k<(int)mc->capacity; k++) { signed char val=(signed char)mc->memory[k]; ImVec2 pos1=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)k/(double)(mc->capacity),1.0f-((float)(val+129)/256.0f))); ImVec2 pos2=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)(k+1)/(double)(mc->capacity),1.0f)); From 3cf8d1c501a0bb31c1484e455c107b97e877cf46 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 17 Mar 2024 05:21:53 +0700 Subject: [PATCH 09/19] GBA: Allocate system IDs --- src/engine/instrument.h | 5 +++-- src/engine/sysDef.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 90f0e2309..a470bea6d 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -89,8 +89,9 @@ enum DivInstrumentType: unsigned short { DIV_INS_POWERNOISE=56, DIV_INS_POWERNOISE_SLOPE=57, DIV_INS_DAVE=58, - DIV_INS_GBA_DMA=59, - DIV_INS_GBA_MINMOD=60, + DIV_INS_NDS=59, + DIV_INS_GBA_DMA=60, + DIV_INS_GBA_MINMOD=61, DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 1cdcdfb70..edb9419ac 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2026,7 +2026,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_GBA_DMA]=new DivSysDef( - "Game Boy Advance DMA Sound", NULL, 0xfe, 0, 2, false, true, 0, false, 1U< Date: Sun, 17 Mar 2024 15:30:45 +0700 Subject: [PATCH 10/19] Fix CI build --- src/engine/instrument.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index d2b5e6acc..c506419ad 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_NDS: + featureSM=true; + if (amiga.useSample) featureSL=true; + break; case DIV_INS_GBA_DMA: featureSM=true; featureSL=true; From cebe47d992173bbb89cd0cf859a043f0d0982297 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 17 Mar 2024 19:48:36 +0700 Subject: [PATCH 11/19] GBA: update memory composition view --- src/engine/platform/gbadma.cpp | 16 ++++++++-------- src/engine/platform/gbaminmod.cpp | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/engine/platform/gbadma.cpp b/src/engine/platform/gbadma.cpp index af61fa543..cf28a6c62 100644 --- a/src/engine/platform/gbadma.cpp +++ b/src/engine/platform/gbadma.cpp @@ -478,9 +478,9 @@ void DivPlatformGBADMA::renderSamples(int sysID) { break; } sampleLoaded[i]=true; + romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos)); // pad to multiple of 16 bytes memPos=(memPos+15)&~15; - romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos)); } sampleMemLen=memPos; romMemCompo.used=sampleMemLen; @@ -500,6 +500,13 @@ int DivPlatformGBADMA::init(DivEngine* p, int channels, int sugRate, const DivCo parent=p; dumpWrites=false; skipRegisterWrites=false; + for (int i=0; i<2; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i),-1,i*256,i*256)); + } + sampleMem=new signed char[getSampleMemCapacity()]; + sampleMemLen=0; romMemCompo=DivMemoryComposition(); romMemCompo.name="Sample ROM"; wtMemCompo=DivMemoryComposition(); @@ -508,13 +515,6 @@ int DivPlatformGBADMA::init(DivEngine* p, int channels, int sugRate, const DivCo wtMemCompo.capacity=256*2; wtMemCompo.memory=(unsigned char*)wtMem; wtMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED; - for (int i=0; i<2; i++) { - isMuted[i]=false; - oscBuf[i]=new DivDispatchOscBuffer; - wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i),-1,i*256,i*256)); - } - sampleMem=new signed char[getSampleMemCapacity()]; - sampleMemLen=0; setFlags(flags); reset(); return 2; diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp index 1fb66ddd2..14b7717ec 100644 --- a/src/engine/platform/gbaminmod.cpp +++ b/src/engine/platform/gbaminmod.cpp @@ -700,6 +700,7 @@ void DivPlatformGBAMinMod::renderSamples(int sysID) { sampleOff[i]=memPos; memcpy(&sampleMem[memPos],s->data8,actualLength); memPos+=actualLength; + romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos)); // if it's one-shot, add 16 silent samples for looping area // this should be enough for most cases even though the // frequency register can make position jump by up to 256 samples @@ -708,7 +709,6 @@ void DivPlatformGBAMinMod::renderSamples(int sysID) { memset(&sampleMem[memPos],0,oneShotLen); memPos+=oneShotLen; } - romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos)); } if (actualLength Date: Sun, 17 Mar 2024 20:21:59 +0700 Subject: [PATCH 12/19] GBA: fix instrument colors --- src/gui/guiConst.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index e91b4af3f..49f5cf34c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -179,8 +179,9 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"PowerNoise (noise)",ICON_FUR_NOISE,ICON_FUR_INS_POWERNOISE}, {"PowerNoise (slope)",ICON_FUR_SAW,ICON_FUR_INS_POWERNOISE_SAW}, {"Dave",ICON_FA_BAR_CHART,ICON_FUR_INS_DAVE}, - {"GBA DMA",ICON_FA_GAMEPAD,ICON_FA_QUESTION}, // TODO - {"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FA_QUESTION}, // TODO + {"NDS",ICON_FA_BAR_CHART,ICON_FUR_INS_NDS}, + {"GBA DMA",ICON_FA_GAMEPAD,ICON_FUR_INS_GBA_DMA}, + {"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -994,6 +995,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_POWERNOISE,"",ImVec4(1.0f,1.0f,0.8f,1.0f)), D(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"",ImVec4(1.0f,0.6f,0.3f,1.0f)), D(GUI_COLOR_INSTR_DAVE,"",ImVec4(0.7f,0.7f,0.8f,1.0f)), + D(GUI_COLOR_INSTR_NDS,"",ImVec4(0.7f,0.7f,0.8f,1.0f)), D(GUI_COLOR_INSTR_GBA_DMA,"",ImVec4(0.6f,0.4f,1.0f,1.0f)), D(GUI_COLOR_INSTR_GBA_MINMOD,"",ImVec4(0.5f,0.45f,0.7f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), From 804598d8daf4b95ba0e52b9e4fdfda7686fe4343 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 17 Mar 2024 20:57:52 +0700 Subject: [PATCH 13/19] I forgor --- src/gui/gui.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.h b/src/gui/gui.h index da476fc3a..90ffd64fb 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -295,6 +295,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_POWERNOISE, GUI_COLOR_INSTR_POWERNOISE_SLOPE, GUI_COLOR_INSTR_DAVE, + GUI_COLOR_INSTR_NDS, GUI_COLOR_INSTR_GBA_DMA, GUI_COLOR_INSTR_GBA_MINMOD, GUI_COLOR_INSTR_UNKNOWN, From 0a498dc4c4ecea19449fc936fde8a903c17f9040 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 15:57:41 -0500 Subject: [PATCH 14/19] GBA: update to latest dispatch spec remove ALWAYS_SET_VOLUME implement macro restart --- src/engine/dispatch.h | 6 ++---- src/engine/platform/gbadma.cpp | 4 ++-- src/engine/platform/gbaminmod.cpp | 4 ++-- src/engine/playback.cpp | 5 ++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index dee8efe75..12238cfe2 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -252,16 +252,14 @@ enum DivDispatchCmds { DIV_CMD_POWERNOISE_COUNTER_LOAD, // (which, val) DIV_CMD_POWERNOISE_IO_WRITE, // (port, value) - DIV_CMD_MINMOD_ECHO, - - DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol - DIV_CMD_DAVE_HIGH_PASS, DIV_CMD_DAVE_RING_MOD, DIV_CMD_DAVE_SWAP_COUNTERS, DIV_CMD_DAVE_LOW_PASS, DIV_CMD_DAVE_CLOCK_DIV, + DIV_CMD_MINMOD_ECHO, + DIV_CMD_MAX }; diff --git a/src/engine/platform/gbadma.cpp b/src/engine/platform/gbadma.cpp index cf28a6c62..79e8b7880 100644 --- a/src/engine/platform/gbadma.cpp +++ b/src/engine/platform/gbadma.cpp @@ -333,8 +333,8 @@ int DivPlatformGBADMA::dispatch(DivCommand c) { case DIV_CMD_MACRO_ON: chan[c.chan].std.mask(c.value,false); break; - case DIV_ALWAYS_SET_VOLUME: - return 1; + case DIV_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); break; default: break; diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp index 14b7717ec..2ad6713b4 100644 --- a/src/engine/platform/gbaminmod.cpp +++ b/src/engine/platform/gbaminmod.cpp @@ -519,8 +519,8 @@ int DivPlatformGBAMinMod::dispatch(DivCommand c) { case DIV_CMD_MACRO_ON: chan[c.chan].std.mask(c.value,false); break; - case DIV_ALWAYS_SET_VOLUME: - return 1; + case DIV_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); break; default: break; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 978274076..1e7d823c2 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -247,6 +247,8 @@ const char* cmdName[]={ "ESFM_MODIN", "ESFM_ENV_DELAY", + "MACRO_RESTART", + "POWERNOISE_COUNTER_LOAD", "POWERNOISE_IO_WRITE", @@ -256,10 +258,7 @@ const char* cmdName[]={ "DAVE_LOW_PASS", "DAVE_CLOCK_DIV", - "MACRO_RESTART", "MINMOD_ECHO", - - "ALWAYS_SET_VOLUME" }; static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); From 60586a0d1510f067f862de388d30232bfe5d611c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 16:11:22 -0500 Subject: [PATCH 15/19] Game Boy: save doubleWave flag TODO: TAG DEV196 ON MERGE --- papers/newIns.md | 1 + src/engine/engine.h | 4 ++-- src/engine/instrument.cpp | 5 ++++- src/engine/instrument.h | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/papers/newIns.md b/papers/newIns.md index f37a54deb..e5eee2d14 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -361,6 +361,7 @@ size | description 1 | sound length | - 64 is infinity 1 | flags + | - bit 2: double wave width for GBA (>=196) | - bit 1: always init envelope | - bit 0: software envelope (zombie mode) 1 | hardware sequence length diff --git a/src/engine/engine.h b/src/engine/engine.h index 601d750e4..d224cfd65 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -54,8 +54,8 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "dev195" -#define DIV_ENGINE_VERSION 195 +#define DIV_VERSION "dev196" +#define DIV_ENGINE_VERSION 196 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index eddeeb807..6225765aa 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -83,7 +83,8 @@ bool DivInstrumentGB::operator==(const DivInstrumentGB& other) { _C(soundLen) && _C(hwSeqLen) && _C(softEnv) && - _C(alwaysInit) + _C(alwaysInit) && + _C(doubleWave) ); } @@ -484,6 +485,7 @@ void DivInstrument::writeFeatureGB(SafeWriter* w) { w->writeC(gb.soundLen); w->writeC( + (gb.doubleWave?4:0)| (gb.alwaysInit?2:0)| (gb.softEnv?1:0) ); @@ -1633,6 +1635,7 @@ void DivInstrument::readFeatureGB(SafeReader& reader, short version) { gb.soundLen=reader.readC(); next=reader.readC(); + if (version>=196) gb.doubleWave=next&4; gb.alwaysInit=next&2; gb.softEnv=next&1; diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 45b02c727..21de82121 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -384,7 +384,7 @@ struct DivInstrumentSTD { struct DivInstrumentGB { unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; - bool softEnv, alwaysInit, doubleWave; // TODO file save/load of doubleWave + bool softEnv, alwaysInit, doubleWave; enum HWSeqCommands: unsigned char { DIV_GB_HWCMD_ENVELOPE=0, DIV_GB_HWCMD_SWEEP, From 572d826fb120e9715b4e40619d7c918c6495839c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 18:16:19 -0500 Subject: [PATCH 16/19] fix warning --- src/engine/platform/sound/gb/apu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/sound/gb/apu.c b/src/engine/platform/sound/gb/apu.c index e4be5e99a..0a8a84ca4 100644 --- a/src/engine/platform/sound/gb/apu.c +++ b/src/engine/platform/sound/gb/apu.c @@ -907,6 +907,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) } break; case GB_MODEL_AGB: + case GB_MODEL_AGB_NATIVE: /* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple pattern like the other revisions. */ /* For the most part, AGS seems to do: From 1bdbd640ecd9829ac2a99c036c25e8ae0857e02e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Mar 2024 18:29:21 -0500 Subject: [PATCH 17/19] long long -> int64_t --- src/engine/platform/gbaminmod.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp index 2ad6713b4..a1ad0efe0 100644 --- a/src/engine/platform/gbaminmod.cpp +++ b/src/engine/platform/gbaminmod.cpp @@ -49,13 +49,13 @@ void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { bool newSamp=true; // cache channel registers that might change struct { - unsigned long long address; + uint64_t address; unsigned int freq, loopEnd, loopStart; short volL, volR; } chState[16]; for (int i=0; i>32; - chState[i].address+=((unsigned long long)chState[i].freq)<<8; + chState[i].address+=((uint64_t)chState[i].freq)<<8; unsigned int newAddr=chState[i].address>>32; if (newAddr!=lastAddr) { if (newAddr>=chState[i].loopEnd) { newAddr=newAddr-chState[i].loopEnd+chState[i].loopStart; - chState[i].address=(chState[i].address&0xffffffff)|((unsigned long long)newAddr<<32); + chState[i].address=(chState[i].address&0xffffffff)|((uint64_t)newAddr<<32); } int newSamp=0; switch (newAddr>>24) { @@ -573,7 +573,7 @@ DivSamplePos DivPlatformGBAMinMod::getSamplePos(int ch) { return DivSamplePos( chan[ch].sample, (((int)regPool[ch*16+2]|((int)regPool[ch*16+3]<<16))&0x01ffffff)-sampleOff[chan[ch].sample], - (long long)chan[ch].freq*chipClock/CHIP_FREQBASE + (int64_t)chan[ch].freq*chipClock/CHIP_FREQBASE ); } From 21eed3e51248ccb735d72022a6569026be52b722 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 18 Mar 2024 11:09:56 +0700 Subject: [PATCH 18/19] GB: Remove GBA-only system configs --- src/engine/platform/gb.cpp | 34 +++++++++++++++------------------- src/engine/platform/gb.h | 2 -- src/gui/presets.cpp | 12 ++---------- src/gui/sysConf.cpp | 25 ------------------------- 4 files changed, 17 insertions(+), 56 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 2ca894633..e942951ff 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -64,15 +64,13 @@ 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; @@ -106,7 +104,7 @@ void DivPlatformGB::updateWave() { } antiClickWavePos&=63; } else { - rWrite(0x1a,extendWave?0x40:0); + rWrite(0x1a,model==GB_MODEL_AGB_NATIVE?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]; @@ -189,7 +187,7 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].outVol<0) chan[i].outVol=0; if (i==2) { - rWrite(16+i*5+2,(extendWave?gbVolMapEx:gbVolMap)[chan[i].outVol]); + rWrite(16+i*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[chan[i].outVol]); chan[i].soundLen=64; } else { chan[i].envLen=0; @@ -221,7 +219,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,(extendWave?gbVolMapEx:gbVolMap)[(chan[i].std.duty.val&3)<<2]); + rWrite(16+i*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[(chan[i].std.duty.val&3)<<2]); } } } @@ -335,7 +333,7 @@ void DivPlatformGB::tick(bool sysTick) { if (i==2) { // wave rWrite(16+i*5,0x00); rWrite(16+i*5,doubleWave?0xa0:0x80); - rWrite(16+i*5+2,(extendWave?gbVolMapEx:gbVolMap)[chan[i].outVol]); + rWrite(16+i*5+2,(model==GB_MODEL_AGB_NATIVE?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)); @@ -412,7 +410,7 @@ 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; + doubleWave=(model==GB_MODEL_AGB_NATIVE) && ins->gb.doubleWave; if (chan[c.chan].wave<0) { chan[c.chan].wave=0; ws.changeWave1(chan[c.chan].wave); @@ -485,7 +483,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,(extendWave?gbVolMapEx:gbVolMap)[chan[c.chan].outVol]); + rWrite(16+c.chan*5+2,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[chan[c.chan].outVol]); } if (!chan[c.chan].softEnv) { chan[c.chan].envVol=chan[c.chan].vol; @@ -670,7 +668,7 @@ int DivPlatformGB::getOutputCount() { } bool DivPlatformGB::getDCOffRequired() { - return (model==GB_MODEL_AGB); + return (model==GB_MODEL_AGB_NATIVE); } void DivPlatformGB::notifyInsChange(int ins) { @@ -705,8 +703,6 @@ 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; @@ -718,7 +714,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { model=GB_MODEL_CGB_E; break; case 3: - model=extendWave?GB_MODEL_AGB_NATIVE:GB_MODEL_AGB; + model=GB_MODEL_AGB_NATIVE; break; } invertWave=flags.getBool("invertWave",true); @@ -726,7 +722,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { chipClock=4194304; CHECK_CUSTOM_CLOCK; - rate=chipClock>>(outDepth-2); + rate=chipClock/16; 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 5bd445b7f..fa0484dff 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -59,10 +59,8 @@ 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/gui/presets.cpp b/src/gui/presets.cpp index 9cbf192e4..4c96204d1 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -132,21 +132,13 @@ void FurnaceGUI::initSystemPresets() { ); 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_GB, 1.0f, 0, "chipType=3"), CH(DIV_SYSTEM_GBA_DMA, 0.5f, 0, ""), } ); ENTRY( "Game Boy Advance (with MinMod)", { - CH(DIV_SYSTEM_GB, 1.0f, 0, - "chipType=3\n" - "extendWave=true\n" - "dacDepth=9\n" - ), + CH(DIV_SYSTEM_GB, 1.0f, 0, "chipType=3"), CH(DIV_SYSTEM_GBA_MINMOD, 0.5f, 0, ""), } ); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index d6a2b043b..b62fe7f2a 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -330,8 +330,6 @@ 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; @@ -355,23 +353,6 @@ 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"); - } - ImGui::Text("DAC bit depth (reduces output rate):"); - if (CWSliderInt("##DACDepth",&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(); @@ -401,17 +382,11 @@ 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; From 01db0f7e542ee560bd1c27a0e8db0d915d76d689 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 18 Mar 2024 12:22:38 +0700 Subject: [PATCH 19/19] MinMod: Fix CPU usage warning --- src/engine/platform/gbaminmod.cpp | 4 ++-- src/gui/sysConf.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/gbaminmod.cpp b/src/engine/platform/gbaminmod.cpp index a1ad0efe0..3e573811d 100644 --- a/src/engine/platform/gbaminmod.cpp +++ b/src/engine/platform/gbaminmod.cpp @@ -219,10 +219,10 @@ void DivPlatformGBAMinMod::acquire(short** buf, size_t len) { void DivPlatformGBAMinMod::tick(bool sysTick) { // collect stats for display in chip config // logV("rendered=%d,updTot=%d",sampsRendered,updCyclesTotal); - if (sysTick && sampsRendered>0) { + if (sysTick && updCyclesTotal>0) { // assuming new sample, L!=R and lowest ROM access wait in all channels // this gives 39.5 cycles/sample, rounded up to 40 for loops - maxCPU=(float)sampsRendered*chanMax*40/updCyclesTotal; + maxCPU=(float)sampsRendered*chanMax*40/(float)updCyclesTotal; } sampsRendered=0; updCyclesTotal=0; diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index b62fe7f2a..b1c996113 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -448,8 +448,9 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl DivPlatformGBAMinMod* dispatch=(DivPlatformGBAMinMod*)e->getDispatch(chan); float maxCPU=dispatch->maxCPU*100; ImGui::Text("Actual sample rate: %d Hz", dispatch->chipClock); - FurnaceGUI::pushWarningColor(maxCPU>90); + if (maxCPU>90) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]); ImGui::Text("Max mixer CPU usage: %.0f%%", maxCPU); + if (maxCPU>90) ImGui::PopStyleColor(); FurnaceGUI::popWarningColor(); if (altered) { e->lockSave([&]() {