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/esfm.cpp
src/engine/platform/powernoise.cpp src/engine/platform/powernoise.cpp
src/engine/platform/dave.cpp src/engine/platform/dave.cpp
src/engine/platform/gbadma.cpp
src/engine/platform/pcmdac.cpp src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp src/engine/platform/dummy.cpp

View file

@ -81,6 +81,7 @@
#include "platform/k053260.h" #include "platform/k053260.h"
#include "platform/ted.h" #include "platform/ted.h"
#include "platform/c140.h" #include "platform/c140.h"
#include "platform/gbadma.h"
#include "platform/pcmdac.h" #include "platform/pcmdac.h"
#include "platform/esfm.h" #include "platform/esfm.h"
#include "platform/powernoise.h" #include "platform/powernoise.h"
@ -644,6 +645,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
dispatch=new DivPlatformC140; dispatch=new DivPlatformC140;
((DivPlatformC140*)dispatch)->set219(true); ((DivPlatformC140*)dispatch)->set219(true);
break; break;
case DIV_SYSTEM_GBA_DMA:
dispatch=new DivPlatformGBADMA;
break;
case DIV_SYSTEM_PCM_DAC: case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC; dispatch=new DivPlatformPCMDAC;
break; break;

View file

@ -982,7 +982,8 @@ void DivEngine::delUnusedSamples() {
i->type==DIV_INS_GA20 || i->type==DIV_INS_GA20 ||
i->type==DIV_INS_K053260 || i->type==DIV_INS_K053260 ||
i->type==DIV_INS_C140 || 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) { if (i->amiga.initSample>=0 && i->amiga.initSample<song.sampleLen) {
isUsed[i->amiga.initSample]=true; isUsed[i->amiga.initSample]=true;
} }

View file

@ -1064,6 +1064,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
break; break;
case DIV_INS_DAVE: case DIV_INS_DAVE:
break; break;
case DIV_INS_GBA_DMA:
featureSM=true;
featureSL=true;
break;
case DIV_INS_MAX: case DIV_INS_MAX:
break; break;
case DIV_INS_NULL: case DIV_INS_NULL:

View file

@ -89,6 +89,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_POWERNOISE=56, DIV_INS_POWERNOISE=56,
DIV_INS_POWERNOISE_SLOPE=57, DIV_INS_POWERNOISE_SLOPE=57,
DIV_INS_DAVE=58, DIV_INS_DAVE=58,
DIV_INS_GBA_DMA=59,
DIV_INS_MAX, DIV_INS_MAX,
DIV_INS_NULL DIV_INS_NULL
}; };
@ -378,7 +379,7 @@ struct DivInstrumentSTD {
struct DivInstrumentGB { struct DivInstrumentGB {
unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; unsigned char envVol, envDir, envLen, soundLen, hwSeqLen;
bool softEnv, alwaysInit; bool softEnv, alwaysInit, doubleWave; // TODO file save/load of doubleWave
enum HWSeqCommands: unsigned char { enum HWSeqCommands: unsigned char {
DIV_GB_HWCMD_ENVELOPE=0, DIV_GB_HWCMD_ENVELOPE=0,
DIV_GB_HWCMD_SWEEP, DIV_GB_HWCMD_SWEEP,
@ -406,7 +407,8 @@ struct DivInstrumentGB {
soundLen(64), soundLen(64),
hwSeqLen(0), hwSeqLen(0),
softEnv(false), softEnv(false),
alwaysInit(false) { alwaysInit(false),
doubleWave(false) {
memset(hwSeq,0,256*sizeof(HWSeqCommandGB)); memset(hwSeq,0,256*sizeof(HWSeqCommandGB));
} }
}; };

View file

@ -64,13 +64,15 @@ const char** DivPlatformGB::getRegisterSheet() {
void DivPlatformGB::acquire(short** buf, size_t len) { void DivPlatformGB::acquire(short** buf, size_t len) {
for (size_t i=0; i<len; i++) { for (size_t i=0; i<len; i++) {
if (!writes.empty()) { for (int j=0; j<(1<<(outDepth-6)); j++) {
QueuedWrite& w=writes.front(); if (!writes.empty()) {
GB_apu_write(gb,w.addr,w.val); QueuedWrite& w=writes.front();
writes.pop(); 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[0][i]=gb->apu_output.final_sample.left;
buf[1][i]=gb->apu_output.final_sample.right; buf[1][i]=gb->apu_output.final_sample.right;
@ -81,17 +83,41 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
} }
void DivPlatformGB::updateWave() { void DivPlatformGB::updateWave() {
rWrite(0x1a,0); if (doubleWave) {
for (int i=0; i<16; i++) { rWrite(0x1a,0x40); // select 1 -> write to bank 0
int nibble1=ws.output[((i<<1)+antiClickWavePos)&31]; for (int i=0; i<16; i++) {
int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31]; int nibble1=ws.output[((i<<1)+antiClickWavePos)&63];
if (invertWave) { int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&63];
nibble1^=15; if (invertWave) {
nibble2^=15; 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]={ static unsigned char chanMuteMask[4]={
@ -112,6 +138,13 @@ static unsigned char gbVolMap[16]={
0x20, 0x20, 0x20, 0x20 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]={ static unsigned char noiseTable[256]={
0, 0,
0xf7, 0xf6, 0xf5, 0xf4, 0xf7, 0xf6, 0xf5, 0xf4,
@ -156,7 +189,7 @@ void DivPlatformGB::tick(bool sysTick) {
if (chan[i].outVol<0) chan[i].outVol=0; if (chan[i].outVol<0) chan[i].outVol=0;
if (i==2) { 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; chan[i].soundLen=64;
} else { } else {
chan[i].envLen=0; 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))); rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
} else if (!chan[i].softEnv) { } else if (!chan[i].softEnv) {
if (parent->song.waveDutyIsVol) { 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 (chan[i].keyOn) {
if (i==2) { // wave if (i==2) { // wave
rWrite(16+i*5,0x00); rWrite(16+i*5,0x00);
rWrite(16+i*5,0x80); rWrite(16+i*5,doubleWave?0xa0:0x80);
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]); rWrite(16+i*5+2,(extendWave?gbVolMapEx:gbVolMap)[chan[i].outVol]);
} else { } else {
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); 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)); 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].softEnv=ins->gb.softEnv;
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
if (c.chan==2) { if (c.chan==2) {
doubleWave=extendWave&&ins->gb.doubleWave;
if (chan[c.chan].wave<0) { if (chan[c.chan].wave<0) {
chan[c.chan].wave=0; chan[c.chan].wave=0;
ws.changeWave1(chan[c.chan].wave); 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].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) {
if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) { 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].vol=c.value;
chan[c.chan].outVol=c.value; chan[c.chan].outVol=c.value;
if (c.chan==2) { 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) { if (!chan[c.chan].softEnv) {
chan[c.chan].envVol=chan[c.chan].vol; chan[c.chan].envVol=chan[c.chan].vol;
@ -619,6 +657,8 @@ void DivPlatformGB::reset() {
antiClickPeriodCount=0; antiClickPeriodCount=0;
antiClickWavePos=0; antiClickWavePos=0;
doubleWave=false;
lastDoubleWave=false;
} }
int DivPlatformGB::getPortaFloor(int ch) { int DivPlatformGB::getPortaFloor(int ch) {
@ -665,6 +705,8 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformGB::setFlags(const DivConfig& flags) { void DivPlatformGB::setFlags(const DivConfig& flags) {
antiClickEnabled=!flags.getBool("noAntiClick",false); antiClickEnabled=!flags.getBool("noAntiClick",false);
extendWave=flags.getBool("extendWave",false);
outDepth=flags.getInt("dacDepth",9);
switch (flags.getInt("chipType",0)) { switch (flags.getInt("chipType",0)) {
case 0: case 0:
model=GB_MODEL_DMG_B; model=GB_MODEL_DMG_B;
@ -676,7 +718,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
model=GB_MODEL_CGB_E; model=GB_MODEL_CGB_E;
break; break;
case 3: case 3:
model=GB_MODEL_AGB; model=extendWave?GB_MODEL_AGB_NATIVE:GB_MODEL_AGB;
break; break;
} }
invertWave=flags.getBool("invertWave",true); invertWave=flags.getBool("invertWave",true);
@ -684,7 +726,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
chipClock=4194304; chipClock=4194304;
CHECK_CUSTOM_CLOCK; CHECK_CUSTOM_CLOCK;
rate=chipClock/16; rate=chipClock>>(outDepth-2);
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate; oscBuf[i]->rate=rate;
} }

View file

@ -59,6 +59,10 @@ class DivPlatformGB: public DivDispatch {
bool antiClickEnabled; bool antiClickEnabled;
bool invertWave; bool invertWave;
bool enoughAlready; bool enoughAlready;
bool extendWave;
bool doubleWave;
bool lastDoubleWave;
int outDepth;
unsigned char lastPan; unsigned char lastPan;
DivWaveSynth ws; DivWaveSynth ws;
struct QueuedWrite { 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]) { if (gb->apu.is_active[GB_WAVE]) {
uint8_t cycles_left = cycles; uint8_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) { 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; 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.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF;
gb->apu.wave_channel.current_sample_index++; 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.current_sample =
gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index]; gb->apu.wave_channel.wave_form[base + gb->apu.wave_channel.current_sample_index];
update_sample(gb, GB_WAVE, int8_t sample = gb->apu.wave_channel.force_3 ?
gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, (gb->apu.wave_channel.current_sample * 3) >> 2 :
cycles - cycles_left); 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; gb->apu.wave_channel.wave_form_just_read = true;
} }
if (cycles_left) { if (cycles_left) {
@ -738,8 +740,10 @@ void GB_apu_init(GB_gameboy_t *gb)
memset(&gb->apu, 0, sizeof(gb->apu)); memset(&gb->apu, 0, sizeof(gb->apu));
/* Restore the wave form */ /* Restore the wave form */
for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) { 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] = 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 + 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; 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, /* 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; gb->apu.is_active[GB_WAVE] = false;
update_sample(gb, GB_WAVE, 0, 0); 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; break;
case GB_IO_NR31: case GB_IO_NR31:
gb->apu.wave_channel.pulse_length = (0x100 - value); gb->apu.wave_channel.pulse_length = (0x100 - value);
break; break;
case GB_IO_NR32: case GB_IO_NR32:
gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3]; 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]) { 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; break;
case GB_IO_NR33: 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]) { if (!gb->apu.is_active[GB_WAVE]) {
gb->apu.is_active[GB_WAVE] = true; gb->apu.is_active[GB_WAVE] = true;
update_sample(gb, GB_WAVE, int8_t sample = gb->apu.wave_channel.force_3 ?
gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, (gb->apu.wave_channel.current_sample * 3) >> 2 :
0); 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.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3;
gb->apu.wave_channel.current_sample_index = 0; 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: default:
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { 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; uint8_t base = 0;
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; 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; gb->io_registers[reg] = value;

View file

@ -93,12 +93,15 @@ typedef struct
uint8_t shift; // NR32 uint8_t shift; // NR32
uint16_t sample_length; // NR33, NR34, in APU ticks uint16_t sample_length; // NR33, NR34, in APU ticks
bool length_enabled; // NR34 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) uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
uint8_t current_sample_index; uint8_t current_sample_index;
uint8_t current_sample; // Current sample before shifting. uint8_t current_sample; // Current sample before shifting.
int8_t wave_form[32]; int8_t wave_form[64];
bool wave_form_just_read; bool wave_form_just_read;
} wave_channel; } wave_channel;

View file

@ -114,6 +114,7 @@ typedef enum {
// GB_MODEL_CGB_D = 0x204, // GB_MODEL_CGB_D = 0x204,
GB_MODEL_CGB_E = 0x205, GB_MODEL_CGB_E = 0x205,
GB_MODEL_AGB = 0x206, GB_MODEL_AGB = 0x206,
GB_MODEL_AGB_NATIVE = 0x226,
} GB_model_t; } GB_model_t;
enum { enum {

View file

@ -118,6 +118,7 @@ enum DivSystem {
DIV_SYSTEM_T6W28, DIV_SYSTEM_T6W28,
DIV_SYSTEM_K007232, DIV_SYSTEM_K007232,
DIV_SYSTEM_GA20, DIV_SYSTEM_GA20,
DIV_SYSTEM_GBA_DMA,
DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PCM_DAC,
DIV_SYSTEM_PONG, DIV_SYSTEM_PONG,
DIV_SYSTEM_DUMMY, DIV_SYSTEM_DUMMY,

View file

@ -2002,6 +2002,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( sysDefs[DIV_SYSTEM_DAVE]=new DivSysDef(
"Dave", NULL, 0xd5, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0, "Dave", NULL, 0xd5, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this chip was featured in the Enterprise 128 computer. it is similar to POKEY, but with stereo output.", "this chip was featured in the Enterprise 128 computer. it is similar to POKEY, but with stereo output.",

View file

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

View file

@ -295,6 +295,7 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_POWERNOISE, GUI_COLOR_INSTR_POWERNOISE,
GUI_COLOR_INSTR_POWERNOISE_SLOPE, GUI_COLOR_INSTR_POWERNOISE_SLOPE,
GUI_COLOR_INSTR_DAVE, GUI_COLOR_INSTR_DAVE,
GUI_COLOR_INSTR_GBA_DMA,
GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_BG, 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 (noise)",ICON_FUR_NOISE,ICON_FUR_INS_POWERNOISE},
{"PowerNoise (slope)",ICON_FUR_SAW,ICON_FUR_INS_POWERNOISE_SAW}, {"PowerNoise (slope)",ICON_FUR_SAW,ICON_FUR_INS_POWERNOISE_SAW},
{"Dave",ICON_FA_BAR_CHART,ICON_FUR_INS_DAVE}, {"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} {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,"",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_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_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_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)), 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_TED,
DIV_SYSTEM_C140, DIV_SYSTEM_C140,
DIV_SYSTEM_C219, DIV_SYSTEM_C219,
DIV_SYSTEM_GBA_DMA,
DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PCM_DAC,
DIV_SYSTEM_ESFM, DIV_SYSTEM_ESFM,
DIV_SYSTEM_PONG, DIV_SYSTEM_PONG,
@ -1347,6 +1350,7 @@ const int chipsSample[]={
DIV_SYSTEM_K053260, DIV_SYSTEM_K053260,
DIV_SYSTEM_C140, DIV_SYSTEM_C140,
DIV_SYSTEM_C219, DIV_SYSTEM_C219,
DIV_SYSTEM_GBA_DMA,
0 // don't remove this last one! 0 // don't remove this last one!
}; };

View file

@ -2525,14 +2525,15 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) {
ImGui::EndCombo(); ImGui::EndCombo();
} }
// Wavetable // 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); 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) { if (ins->amiga.useWave) {
int len=ins->amiga.waveLen+1; int len=ins->amiga.waveLen+1;
int origLen=len; int origLen=len;
if (ImGui::InputInt("Width",&len,2,16)) { 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<16) len=16;
if (len>256) len=256; if (len>256) len=256;
if (len>origLen) { if (len>origLen) {
@ -5395,6 +5396,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) {
P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv)); P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv));
P(ImGui::Checkbox("Initialize envelope on every note",&ins->gb.alwaysInit)); 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); ImGui::BeginDisabled(ins->gb.softEnv);
if (ImGui::BeginTable("GBParams",2)) { if (ImGui::BeginTable("GBParams",2)) {
@ -6021,7 +6023,8 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_GA20 || ins->type==DIV_INS_GA20 ||
ins->type==DIV_INS_K053260 || ins->type==DIV_INS_K053260 ||
ins->type==DIV_INS_C140 || ins->type==DIV_INS_C140 ||
ins->type==DIV_INS_C219) { ins->type==DIV_INS_C219 ||
ins->type==DIV_INS_GBA_DMA) {
insTabSample(ins); insTabSample(ins);
} }
if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) {
@ -6456,6 +6459,7 @@ void FurnaceGUI::drawInsEdit() {
} }
if (ins->type==DIV_INS_GB || if (ins->type==DIV_INS_GB ||
(ins->type==DIV_INS_AMIGA && ins->amiga.useWave) || (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_X1_010 && !ins->amiga.useSample) ||
ins->type==DIV_INS_N163 || ins->type==DIV_INS_N163 ||
ins->type==DIV_INS_FDS || ins->type==DIV_INS_FDS ||
@ -6500,6 +6504,7 @@ void FurnaceGUI::drawInsEdit() {
wavePreviewHeight=255; wavePreviewHeight=255;
break; break;
case DIV_INS_AMIGA: case DIV_INS_AMIGA:
case DIV_INS_GBA_DMA:
wavePreviewLen=ins->amiga.waveLen+1; wavePreviewLen=ins->amiga.waveLen+1;
wavePreviewHeight=255; wavePreviewHeight=255;
break; break;
@ -6771,7 +6776,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_QSOUND) { if (ins->type==DIV_INS_QSOUND) {
volMax=16383; volMax=16383;
} }
if (ins->type==DIV_INS_POKEMINI) { if (ins->type==DIV_INS_POKEMINI || ins->type==DIV_INS_GBA_DMA) {
volMax=2; volMax=2;
} }
@ -6830,7 +6835,7 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_PET || ins->type==DIV_INS_SEGAPCM || 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_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_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; dutyMax=0;
} }
if (ins->type==DIV_INS_VBOY) { if (ins->type==DIV_INS_VBOY) {
@ -7045,7 +7050,8 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_VERA || ins->type==DIV_INS_VERA ||
ins->type==DIV_INS_ADPCMA || ins->type==DIV_INS_ADPCMA ||
ins->type==DIV_INS_ADPCMB || ins->type==DIV_INS_ADPCMB ||
ins->type==DIV_INS_ESFM) { ins->type==DIV_INS_ESFM ||
ins->type==DIV_INS_GBA_DMA) {
panMax=2; panMax=2;
panSingle=true; panSingle=true;
} }
@ -7201,7 +7207,8 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_ESFM || ins->type==DIV_INS_ESFM ||
ins->type==DIV_INS_POWERNOISE || ins->type==DIV_INS_POWERNOISE ||
ins->type==DIV_INS_POWERNOISE_SLOPE || 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)); macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
} }
if (ex1Max>0) { if (ex1Max>0) {

View file

@ -130,6 +130,16 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_GB, 1.0f, 0, "") 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( ENTRY(
"Neo Geo Pocket", { "Neo Geo Pocket", {
CH(DIV_SYSTEM_T6W28, 1.0f, 0, ""), CH(DIV_SYSTEM_T6W28, 1.0f, 0, ""),

View file

@ -379,6 +379,20 @@ void FurnaceGUI::drawSampleEdit() {
if (sample->samples>129024) { if (sample->samples>129024) {
SAMPLE_WARN(warnLength,"MSM6295: maximum bankswitched sample length is 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: default:
break; 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,"PowerNoise (noise)");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"PowerNoise (slope)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"PowerNoise (slope)");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_DAVE,"Dave"); 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"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown");
ImGui::TreePop(); 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 noAntiClick=flags.getBool("noAntiClick",false);
bool invertWave=flags.getBool("invertWave",true); bool invertWave=flags.getBool("invertWave",true);
bool enoughAlready=flags.getBool("enoughAlready",false); 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)) { if (ImGui::Checkbox("Disable anti-click",&noAntiClick)) {
altered=true; altered=true;
@ -352,6 +354,22 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
altered=true; altered=true;
} }
ImGui::Unindent(); 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:"); ImGui::Text("Wave channel orientation:");
if (chipType==3) { if (chipType==3) {
ImGui::Indent(); ImGui::Indent();
@ -381,11 +399,33 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
} }
if (altered) { if (altered) {
if (chipType!=3) {
extendWave=false;
dacDepth=6;
}
e->lockSave([&]() { e->lockSave([&]() {
flags.set("chipType",chipType); flags.set("chipType",chipType);
flags.set("noAntiClick",noAntiClick); flags.set("noAntiClick",noAntiClick);
flags.set("invertWave",invertWave); flags.set("invertWave",invertWave);
flags.set("enoughAlready",enoughAlready); 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; break;