Add GBA MinMod driver support

This commit is contained in:
Natt Akuma 2024-03-16 14:59:02 +07:00
parent 0b1d2e24d7
commit 2b9dd1caff
17 changed files with 996 additions and 26 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 <math.h>
#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<chanMax; i++) {
unsigned short* chReg=&regPool[i*16];
chState[i].address=chReg[0]|((unsigned long long)chReg[1]<<16)|((unsigned long long)chReg[2]<<32)|((unsigned long long)chReg[3]<<48);
chState[i].freq=chReg[8]|((unsigned int)chReg[9]<<16);
chState[i].loopEnd=chReg[10]|((unsigned int)chReg[11]<<16);
chState[i].loopStart=chReg[12]|((unsigned int)chReg[13]<<16);
chState[i].volL=(short)chReg[14];
chState[i].volR=(short)chReg[15];
}
for (size_t h=0; h<len; h++) {
while (sampTimer>=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<chanMax; i++) {
for (int j=mixBufOffset; j<4; j++) {
unsigned int lastAddr=chState[i].address>>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 (newAddr<sizeof(wtMem)) {
newSamp=wtMem[newAddr];
}
break;
case 3: // echo
newAddr&=0x00007fff;
if (newAddr>0x800) {
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; i<chanMax; i++) {
oscBuf[i]->data[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; j<mixBufOffset; j++) {
mixOut[0][j]=mixOut[0][4-mixBufOffset+j];
mixOut[1][j]=mixOut[1][4-mixBufOffset+j];
mixBuf[mixBufPage][j]=mixOut[0][j];
mixBuf[mixBufPage+1][j]=mixOut[1][j];
}
// check for echo channels and give them proper addresses
for (int i=0; i<chanMax; i++) {
unsigned char echoDelay=MIN(chan[i].echo&0x0f,mixBufs-1);
if(echoDelay) {
echoDelay=echoDelay*2-((chan[i].echo&0x10)?0:1);
size_t echoPage=(mixBufPage+mixBufs*2-echoDelay)%(mixBufs*2);
chState[i].address=(0x03000800+echoPage*1024)<<32;
chState[i].loopStart=0-echoDelay;
chState[i].loopEnd=0-echoDelay;
}
}
updTimer-=updCycles;
updCyclesTotal+=updCycles;
// recalculate update timer from a new tick rate
float hz=parent->getCurHz();
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<<dacDepth;
sampTimer+=1<<dacDepth;
}
// write back changed cached channel registers
for (int i=0; i<chanMax; i++) {
unsigned short* chReg=&regPool[i*16];
chReg[0]=chState[i].address&0xffff;
chReg[1]=(chState[i].address>>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; i<chanMax; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
chan[i].volChangedL=true;
chan[i].volChangedR=true;
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_FREQUENCY(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].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<chan[ch].wtLen; i++) {
wtMem[addr+i]=(signed char)(chan[ch].ws.output[i]-128);
}
}
void DivPlatformGBAMinMod::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chan[ch].volChangedL=true;
chan[ch].volChangedR=true;
}
void DivPlatformGBAMinMod::forceIns() {
for (int i=0; i<chanMax; i++) {
chan[i].insChanged=true;
chan[i].volChangedL=true;
chan[i].volChangedR=true;
chan[i].sample=-1;
chan[i].active=false;
}
}
void* DivPlatformGBAMinMod::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformGBAMinMod::getChanMacroInt(int ch) {
return &chan[ch].std;
}
unsigned short DivPlatformGBAMinMod::getPan(int ch) {
return (chan[ch].chPanL<<8)|(chan[ch].chPanR);
}
DivDispatchOscBuffer* DivPlatformGBAMinMod::getOscBuffer(int ch) {
return oscBuf[ch];
}
void DivPlatformGBAMinMod::reset() {
resetMixer();
memset(regPool,0,sizeof(regPool));
memset(wtMem,0,sizeof(wtMem));
for (int i=0; i<16; i++) {
chan[i]=DivPlatformGBAMinMod::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,255);
}
}
void DivPlatformGBAMinMod::resetMixer() {
sampTimer=sampCycles;
updTimer=0;
mixBufReadPos=0;
mixBufWritePos=0;
mixBufPage=0;
mixBufOffset=0;
sampsRendered=0;
memset(mixBuf,0,sizeof(mixBuf));
memset(chanOut,0,sizeof(chanOut));
}
int DivPlatformGBAMinMod::getOutputCount() {
return 2;
}
void DivPlatformGBAMinMod::notifyInsChange(int ins) {
for (int i=0; i<16; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformGBAMinMod::notifyWaveChange(int wave) {
for (int i=0; i<16; i++) {
if (chan[i].useWave && chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}
}
void DivPlatformGBAMinMod::notifyInsDeletion(void* ins) {
for (int i=0; i<16; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformGBAMinMod::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformGBAMinMod::poke(std::vector<DivRegWrite>& 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; i<parent->song.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<length) {
logW("out of GBA MinMod PCM memory for sample %d!",i);
break;
}
sampleLoaded[i]=true;
}
sampleMemLen=memPos;
}
void DivPlatformGBAMinMod::setFlags(const DivConfig& flags) {
volScale=flags.getInt("volScale",4096);
mixBufs=flags.getInt("mixBufs",15);
dacDepth=flags.getInt("dacDepth",9);
chanMax=flags.getInt("channels",16);
rate=16777216>>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];
}
}

View file

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

View file

@ -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!");

View file

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

View file

@ -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<<DIV_SAMPLE_DEPTH_8BIT, 0, 256,
"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},
{},
{
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}},
}
);
sysDefs[DIV_SYSTEM_DAVE]=new DivSysDef(
"Dave", NULL, 0xd5, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
@ -2038,6 +2024,36 @@ void DivEngine::registerSystems() {
{0x16, {DIV_CMD_DAVE_CLOCK_DIV, "16xx: Set clock divider (0: /2; 1: /3)"}},
}
);
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, 256,
"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},
{},
{
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}},
}
);
sysDefs[DIV_SYSTEM_GBA_MINMOD]=new DivSysDef(
"Game Boy Advance MinMod", NULL, 0xff, 0, 16, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 256,
"additional PCM FIFO channels in Game Boy Advance driven by software mixing to provide up to sixteen sample channels",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD, DIV_INS_GBA_MINMOD},
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
{},
{
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}},
{0x11, {DIV_CMD_MINMOD_ECHO, "11xy: Set echo channel (x: left/right source; y: delay (0 disables))"}},
{0x12, {DIV_CMD_SNES_INVERT, "12xy: Toggle invert (x: left; y: right)"}},
}
);
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0, 0, 0,

View file

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

View file

@ -296,6 +296,7 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_POWERNOISE_SLOPE,
GUI_COLOR_INSTR_DAVE,
GUI_COLOR_INSTR_GBA_DMA,
GUI_COLOR_INSTR_GBA_MINMOD,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_BG,

View file

@ -179,7 +179,8 @@ 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_GAMEPAD,ICON_FA_QUESTION}, // TODO
{"GBA DMA",ICON_FA_GAMEPAD,ICON_FA_QUESTION}, // TODO
{"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FA_QUESTION}, // TODO
{NULL,ICON_FA_QUESTION,ICON_FA_QUESTION}
};
@ -994,6 +995,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
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_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)),
D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)),
@ -1232,6 +1234,7 @@ const int availableSystems[]={
DIV_SYSTEM_C140,
DIV_SYSTEM_C219,
DIV_SYSTEM_GBA_DMA,
DIV_SYSTEM_GBA_MINMOD,
DIV_SYSTEM_PCM_DAC,
DIV_SYSTEM_ESFM,
DIV_SYSTEM_PONG,
@ -1351,6 +1354,7 @@ const int chipsSample[]={
DIV_SYSTEM_C140,
DIV_SYSTEM_C219,
DIV_SYSTEM_GBA_DMA,
DIV_SYSTEM_GBA_MINMOD,
0 // don't remove this last one!
};

View file

@ -356,6 +356,10 @@ const char* es5506ControlModes[3]={
"pause", "reverse", NULL
};
const char* minModModeBits[3]={
"invert right", "invert left", NULL
};
const int orderedOps[4]={
0, 2, 1, 3
};
@ -2525,7 +2529,7 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) {
ImGui::EndCombo();
}
// Wavetable
if (ins->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]));
}

View file

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

View file

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

View file

@ -18,6 +18,7 @@
*/
#include "../engine/chipUtils.h"
#include "../engine/platform/gbaminmod.h"
#include "gui.h"
#include "misc/cpp/imgui_stdlib.h"
#include <imgui.h>
@ -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: {