WIP add GBA system

This commit is contained in:
Natt Akuma 2024-01-10 11:17:36 +07:00
parent 892ee12d91
commit f3705fb435
22 changed files with 748 additions and 49 deletions

View file

@ -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

View file

@ -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;

View file

@ -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.initSample<song.sampleLen) {
isUsed[i->amiga.initSample]=true;
}

View file

@ -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:

View file

@ -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));
}
};

View file

@ -64,13 +64,15 @@ const char** DivPlatformGB::getRegisterSheet() {
void DivPlatformGB::acquire(short** buf, size_t len) {
for (size_t i=0; i<len; i++) {
if (!writes.empty()) {
QueuedWrite& w=writes.front();
GB_apu_write(gb,w.addr,w.val);
writes.pop();
}
for (int j=0; j<(1<<(outDepth-6)); j++) {
if (!writes.empty()) {
QueuedWrite& w=writes.front();
GB_apu_write(gb,w.addr,w.val);
writes.pop();
}
GB_advance_cycles(gb,16);
GB_advance_cycles(gb,16);
}
buf[0][i]=gb->apu_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<DivRegWrite>& 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;
}

View file

@ -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 {

View file

@ -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 <math.h>
#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<len; h++) {
// internal mixing is always 10-bit
for (int i=0; i<2; i++) {
bool newSamp=h==0;
if (chan[i].active && (chan[i].useWave || (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen))) {
chan[i].audSub+=(1<<outDepth);
if (chan[i].useWave) {
if (chan[i].audPos<(int)chan[i].audLen) {
chan[i].audDat=chan[i].ws.output[chan[i].audPos]-0x80;
} 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 (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].sample<parent->song.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];
}
}

View file

@ -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<int> {
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<int>(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

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

@ -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,

View file

@ -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<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"additional PCM FIFO channels in Game Boy Advance driven directly by its DMA hardware.",
{"PCM 1", "PCM 2"},
{"P1", "P2"},
{DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_GBA_DMA, DIV_INS_GBA_DMA},
{DIV_INS_AMIGA, DIV_INS_AMIGA}
);
sysDefs[DIV_SYSTEM_DAVE]=new DivSysDef(
"Dave", NULL, 0xd5, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,

View file

@ -1540,7 +1540,8 @@ void FurnaceGUI::doAction(int what) {
i==DIV_INS_GA20 ||
i==DIV_INS_K053260 ||
i==DIV_INS_C140 ||
i==DIV_INS_C219) {
i==DIV_INS_C219 ||
i==DIV_INS_GBA_DMA) {
makeInsTypeList.push_back(i);
}
}

View file

@ -295,6 +295,7 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_POWERNOISE,
GUI_COLOR_INSTR_POWERNOISE_SLOPE,
GUI_COLOR_INSTR_DAVE,
GUI_COLOR_INSTR_GBA_DMA,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_BG,

View file

@ -179,6 +179,7 @@ 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},
{"Game Boy Advance DMA",ICON_FA_VOLUME_UP,ICON_FA_QUESTION}, // TODO
{NULL,ICON_FA_QUESTION,ICON_FA_QUESTION}
};
@ -992,6 +993,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_GBA_DMA,"",ImVec4(0.6f,0.4f,1.0f,1.0f)),
D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)),
D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)),
@ -1229,6 +1231,7 @@ const int availableSystems[]={
DIV_SYSTEM_TED,
DIV_SYSTEM_C140,
DIV_SYSTEM_C219,
DIV_SYSTEM_GBA_DMA,
DIV_SYSTEM_PCM_DAC,
DIV_SYSTEM_ESFM,
DIV_SYSTEM_PONG,
@ -1347,6 +1350,7 @@ const int chipsSample[]={
DIV_SYSTEM_K053260,
DIV_SYSTEM_C140,
DIV_SYSTEM_C219,
DIV_SYSTEM_GBA_DMA,
0 // don't remove this last one!
};

View file

@ -2525,14 +2525,15 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) {
ImGui::EndCombo();
}
// Wavetable
if (ins->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) {

View file

@ -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, ""),

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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;