mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-25 14:05:12 +00:00
commit
a8c4cd9a83
29 changed files with 1869 additions and 52 deletions
|
@ -722,6 +722,8 @@ 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/gbaminmod.cpp
|
||||||
src/engine/platform/nds.cpp
|
src/engine/platform/nds.cpp
|
||||||
src/engine/platform/pcmdac.cpp
|
src/engine/platform/pcmdac.cpp
|
||||||
src/engine/platform/dummy.cpp
|
src/engine/platform/dummy.cpp
|
||||||
|
|
|
@ -361,6 +361,7 @@ size | description
|
||||||
1 | sound length
|
1 | sound length
|
||||||
| - 64 is infinity
|
| - 64 is infinity
|
||||||
1 | flags
|
1 | flags
|
||||||
|
| - bit 2: double wave width for GBA (>=196)
|
||||||
| - bit 1: always init envelope
|
| - bit 1: always init envelope
|
||||||
| - bit 0: software envelope (zombie mode)
|
| - bit 0: software envelope (zombie mode)
|
||||||
1 | hardware sequence length
|
1 | hardware sequence length
|
||||||
|
|
|
@ -258,6 +258,8 @@ enum DivDispatchCmds {
|
||||||
DIV_CMD_DAVE_LOW_PASS,
|
DIV_CMD_DAVE_LOW_PASS,
|
||||||
DIV_CMD_DAVE_CLOCK_DIV,
|
DIV_CMD_DAVE_CLOCK_DIV,
|
||||||
|
|
||||||
|
DIV_CMD_MINMOD_ECHO,
|
||||||
|
|
||||||
DIV_CMD_MAX
|
DIV_CMD_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -476,6 +478,7 @@ enum DivMemoryWaveView: unsigned char {
|
||||||
DIV_MEMORY_WAVE_NONE=0,
|
DIV_MEMORY_WAVE_NONE=0,
|
||||||
DIV_MEMORY_WAVE_4BIT, // Namco 163
|
DIV_MEMORY_WAVE_4BIT, // Namco 163
|
||||||
DIV_MEMORY_WAVE_6BIT, // Virtual Boy
|
DIV_MEMORY_WAVE_6BIT, // Virtual Boy
|
||||||
|
DIV_MEMORY_WAVE_8BIT_SIGNED, // SCC
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DivMemoryComposition {
|
struct DivMemoryComposition {
|
||||||
|
|
|
@ -81,6 +81,8 @@
|
||||||
#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/gbaminmod.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"
|
||||||
|
@ -645,6 +647,12 @@ 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_GBA_MINMOD:
|
||||||
|
dispatch=new DivPlatformGBAMinMod;
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_PCM_DAC:
|
case DIV_SYSTEM_PCM_DAC:
|
||||||
dispatch=new DivPlatformPCMDAC;
|
dispatch=new DivPlatformPCMDAC;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -989,7 +989,9 @@ void DivEngine::delUnusedSamples() {
|
||||||
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_NDS) {
|
i->type==DIV_INS_NDS ||
|
||||||
|
i->type==DIV_INS_GBA_DMA ||
|
||||||
|
i->type==DIV_INS_GBA_MINMOD) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,8 @@ class DivWorkPool;
|
||||||
|
|
||||||
#define DIV_UNSTABLE
|
#define DIV_UNSTABLE
|
||||||
|
|
||||||
#define DIV_VERSION "dev195"
|
#define DIV_VERSION "dev196"
|
||||||
#define DIV_ENGINE_VERSION 195
|
#define DIV_ENGINE_VERSION 196
|
||||||
// for imports
|
// for imports
|
||||||
#define DIV_VERSION_MOD 0xff01
|
#define DIV_VERSION_MOD 0xff01
|
||||||
#define DIV_VERSION_FC 0xff02
|
#define DIV_VERSION_FC 0xff02
|
||||||
|
|
|
@ -83,7 +83,8 @@ bool DivInstrumentGB::operator==(const DivInstrumentGB& other) {
|
||||||
_C(soundLen) &&
|
_C(soundLen) &&
|
||||||
_C(hwSeqLen) &&
|
_C(hwSeqLen) &&
|
||||||
_C(softEnv) &&
|
_C(softEnv) &&
|
||||||
_C(alwaysInit)
|
_C(alwaysInit) &&
|
||||||
|
_C(doubleWave)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,6 +485,7 @@ void DivInstrument::writeFeatureGB(SafeWriter* w) {
|
||||||
w->writeC(gb.soundLen);
|
w->writeC(gb.soundLen);
|
||||||
|
|
||||||
w->writeC(
|
w->writeC(
|
||||||
|
(gb.doubleWave?4:0)|
|
||||||
(gb.alwaysInit?2:0)|
|
(gb.alwaysInit?2:0)|
|
||||||
(gb.softEnv?1:0)
|
(gb.softEnv?1:0)
|
||||||
);
|
);
|
||||||
|
@ -1102,6 +1104,14 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
|
||||||
featureSM=true;
|
featureSM=true;
|
||||||
if (amiga.useSample) featureSL=true;
|
if (amiga.useSample) featureSL=true;
|
||||||
break;
|
break;
|
||||||
|
case DIV_INS_GBA_DMA:
|
||||||
|
featureSM=true;
|
||||||
|
featureSL=true;
|
||||||
|
break;
|
||||||
|
case DIV_INS_GBA_MINMOD:
|
||||||
|
featureSM=true;
|
||||||
|
featureSL=true;
|
||||||
|
break;
|
||||||
case DIV_INS_MAX:
|
case DIV_INS_MAX:
|
||||||
break;
|
break;
|
||||||
case DIV_INS_NULL:
|
case DIV_INS_NULL:
|
||||||
|
@ -1625,6 +1635,7 @@ void DivInstrument::readFeatureGB(SafeReader& reader, short version) {
|
||||||
gb.soundLen=reader.readC();
|
gb.soundLen=reader.readC();
|
||||||
|
|
||||||
next=reader.readC();
|
next=reader.readC();
|
||||||
|
if (version>=196) gb.doubleWave=next&4;
|
||||||
gb.alwaysInit=next&2;
|
gb.alwaysInit=next&2;
|
||||||
gb.softEnv=next&1;
|
gb.softEnv=next&1;
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,8 @@ enum DivInstrumentType: unsigned short {
|
||||||
DIV_INS_POWERNOISE_SLOPE=57,
|
DIV_INS_POWERNOISE_SLOPE=57,
|
||||||
DIV_INS_DAVE=58,
|
DIV_INS_DAVE=58,
|
||||||
DIV_INS_NDS=59,
|
DIV_INS_NDS=59,
|
||||||
|
DIV_INS_GBA_DMA=60,
|
||||||
|
DIV_INS_GBA_MINMOD=61,
|
||||||
DIV_INS_MAX,
|
DIV_INS_MAX,
|
||||||
DIV_INS_NULL
|
DIV_INS_NULL
|
||||||
};
|
};
|
||||||
|
@ -382,7 +384,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;
|
||||||
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,
|
||||||
|
@ -410,7 +412,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));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -81,7 +81,30 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformGB::updateWave() {
|
void DivPlatformGB::updateWave() {
|
||||||
rWrite(0x1a,0);
|
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(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,model==GB_MODEL_AGB_NATIVE?0x40:0);
|
||||||
for (int i=0; i<16; i++) {
|
for (int i=0; i<16; i++) {
|
||||||
int nibble1=ws.output[((i<<1)+antiClickWavePos)&31];
|
int nibble1=ws.output[((i<<1)+antiClickWavePos)&31];
|
||||||
int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31];
|
int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31];
|
||||||
|
@ -92,6 +115,7 @@ void DivPlatformGB::updateWave() {
|
||||||
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
||||||
}
|
}
|
||||||
antiClickWavePos&=31;
|
antiClickWavePos&=31;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned char chanMuteMask[4]={
|
static unsigned char chanMuteMask[4]={
|
||||||
|
@ -112,6 +136,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 +187,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,(model==GB_MODEL_AGB_NATIVE?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 +219,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,(model==GB_MODEL_AGB_NATIVE?gbVolMapEx:gbVolMap)[(chan[i].std.duty.val&3)<<2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,8 +332,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,(model==GB_MODEL_AGB_NATIVE?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 +410,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=(model==GB_MODEL_AGB_NATIVE) && 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 +483,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,(model==GB_MODEL_AGB_NATIVE?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 +655,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) {
|
||||||
|
@ -630,7 +668,7 @@ int DivPlatformGB::getOutputCount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformGB::getDCOffRequired() {
|
bool DivPlatformGB::getDCOffRequired() {
|
||||||
return (model==GB_MODEL_AGB);
|
return (model==GB_MODEL_AGB_NATIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformGB::notifyInsChange(int ins) {
|
void DivPlatformGB::notifyInsChange(int ins) {
|
||||||
|
@ -676,7 +714,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=GB_MODEL_AGB_NATIVE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
invertWave=flags.getBool("invertWave",true);
|
invertWave=flags.getBool("invertWave",true);
|
||||||
|
|
|
@ -59,6 +59,8 @@ class DivPlatformGB: public DivDispatch {
|
||||||
bool antiClickEnabled;
|
bool antiClickEnabled;
|
||||||
bool invertWave;
|
bool invertWave;
|
||||||
bool enoughAlready;
|
bool enoughAlready;
|
||||||
|
bool doubleWave;
|
||||||
|
bool lastDoubleWave;
|
||||||
unsigned char lastPan;
|
unsigned char lastPan;
|
||||||
DivWaveSynth ws;
|
DivWaveSynth ws;
|
||||||
struct QueuedWrite {
|
struct QueuedWrite {
|
||||||
|
|
528
src/engine/platform/gbadma.cpp
Normal file
528
src/engine/platform/gbadma.cpp
Normal file
|
@ -0,0 +1,528 @@
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
chan[i].audDat=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=wtMem[i*256+chan[i].audPos];
|
||||||
|
}
|
||||||
|
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 if (sampleLoaded[chan[i].sample]) {
|
||||||
|
DivSample* s=parent->getSample(chan[i].sample);
|
||||||
|
if (s->samples>0) {
|
||||||
|
if (chan[i].audPos>=0) {
|
||||||
|
unsigned int pos=(sampleOff[chan[i].sample]+chan[i].audPos)&0x01ffffff;
|
||||||
|
chan[i].audDat=sampleMem[pos];
|
||||||
|
}
|
||||||
|
newSamp=true;
|
||||||
|
if (chan[i].audSub>=chan[i].freq) {
|
||||||
|
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 loopStart=s->loopStart&~3;
|
||||||
|
int loopPos=chan[i].audPos-loopStart;
|
||||||
|
chan[i].audPos=(loopPos%(s->loopEnd-s->loopStart))+loopStart;
|
||||||
|
}
|
||||||
|
} else if (chan[i].audPos>=(int)s->samples) {
|
||||||
|
chan[i].sample=-1;
|
||||||
|
}
|
||||||
|
chan[i].dmaCount&=15;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chan[i].sample=-1;
|
||||||
|
chan[i].audSub=0;
|
||||||
|
chan[i].audPos=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;
|
||||||
|
else if (chan[i].envVol>2) chan[i].envVol=2;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
if (chan[i].ws.tick()) {
|
||||||
|
updateWave(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
wtMemCompo.entries[c.chan].end=wtMemCompo.entries[c.chan].begin+chan[c.chan].audLen;
|
||||||
|
if (chan[c.chan].insChanged) {
|
||||||
|
if (chan[c.chan].wave<0) {
|
||||||
|
chan[c.chan].wave=0;
|
||||||
|
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=MIN(c.value,2);
|
||||||
|
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_CMD_MACRO_RESTART:
|
||||||
|
chan[c.chan].std.restart(c.value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformGBADMA::updateWave(int ch) {
|
||||||
|
int addr=ch*256;
|
||||||
|
for (unsigned int i=0; i<chan[ch].audLen; i++) {
|
||||||
|
wtMem[addr+i]=(signed char)(chan[ch].ws.output[i]-128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 || !chan[ch].active ||
|
||||||
|
chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) {
|
||||||
|
return DivSamplePos();
|
||||||
|
}
|
||||||
|
return DivSamplePos(
|
||||||
|
chan[ch].sample,
|
||||||
|
chan[ch].audPos,
|
||||||
|
chipClock/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);
|
||||||
|
updateWave(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformGBADMA::notifyInsDeletion(void* ins) {
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* DivPlatformGBADMA::getSampleMem(int index) {
|
||||||
|
return index == 0 ? sampleMem : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformGBADMA::getSampleMemCapacity(int index) {
|
||||||
|
return index == 0 ? 33554432 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformGBADMA::getSampleMemUsage(int index) {
|
||||||
|
return index == 0 ? sampleMemLen : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformGBADMA::isSampleLoaded(int index, int sample) {
|
||||||
|
if (index!=0) return false;
|
||||||
|
if (sample<0 || sample>255) return false;
|
||||||
|
return sampleLoaded[sample];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DivMemoryComposition* DivPlatformGBADMA::getMemCompo(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0: return &romMemCompo;
|
||||||
|
case 1: return &wtMemCompo;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformGBADMA::renderSamples(int sysID) {
|
||||||
|
size_t maxPos=getSampleMemCapacity();
|
||||||
|
memset(sampleMem,0,maxPos);
|
||||||
|
romMemCompo.entries.clear();
|
||||||
|
romMemCompo.capacity=maxPos;
|
||||||
|
|
||||||
|
size_t memPos=0;
|
||||||
|
for (int i=0; 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 (actualLength<length) {
|
||||||
|
logW("out of GBA DMA PCM memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sampleLoaded[i]=true;
|
||||||
|
romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos));
|
||||||
|
// pad to multiple of 16 bytes
|
||||||
|
memPos=(memPos+15)&~15;
|
||||||
|
}
|
||||||
|
sampleMemLen=memPos;
|
||||||
|
romMemCompo.used=sampleMemLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i),-1,i*256,i*256));
|
||||||
|
}
|
||||||
|
sampleMem=new signed char[getSampleMemCapacity()];
|
||||||
|
sampleMemLen=0;
|
||||||
|
romMemCompo=DivMemoryComposition();
|
||||||
|
romMemCompo.name="Sample ROM";
|
||||||
|
wtMemCompo=DivMemoryComposition();
|
||||||
|
wtMemCompo.name="Wavetable RAM";
|
||||||
|
wtMemCompo.used=256*2;
|
||||||
|
wtMemCompo.capacity=256*2;
|
||||||
|
wtMemCompo.memory=(unsigned char*)wtMem;
|
||||||
|
wtMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED;
|
||||||
|
setFlags(flags);
|
||||||
|
reset();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformGBADMA::quit() {
|
||||||
|
delete[] sampleMem;
|
||||||
|
for (int i=0; i<2; i++) {
|
||||||
|
delete oscBuf[i];
|
||||||
|
}
|
||||||
|
}
|
101
src/engine/platform/gbadma.h
Normal file
101
src/engine/platform/gbadma.h
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
unsigned int sampleOff[256];
|
||||||
|
bool sampleLoaded[256];
|
||||||
|
int outDepth;
|
||||||
|
|
||||||
|
signed char* sampleMem;
|
||||||
|
size_t sampleMemLen;
|
||||||
|
// maximum wavetable length is currently hardcoded to 256
|
||||||
|
signed char wtMem[256*2];
|
||||||
|
DivMemoryComposition romMemCompo;
|
||||||
|
DivMemoryComposition wtMemCompo;
|
||||||
|
|
||||||
|
friend void putDispatchChip(void*,int);
|
||||||
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
||||||
|
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);
|
||||||
|
const void* getSampleMem(int index = 0);
|
||||||
|
size_t getSampleMemCapacity(int index = 0);
|
||||||
|
size_t getSampleMemUsage(int index = 0);
|
||||||
|
bool isSampleLoaded(int index, int sample);
|
||||||
|
const DivMemoryComposition* getMemCompo(int index);
|
||||||
|
void renderSamples(int chipID);
|
||||||
|
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||||
|
void quit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateWave(int ch);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
781
src/engine/platform/gbaminmod.cpp
Normal file
781
src/engine/platform/gbaminmod.cpp
Normal file
|
@ -0,0 +1,781 @@
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
uint64_t address;
|
||||||
|
unsigned int freq, loopEnd, loopStart;
|
||||||
|
short volL, volR;
|
||||||
|
} chState[16];
|
||||||
|
for (int i=0; i<chanMax; i++) {
|
||||||
|
unsigned short* chReg=®Pool[i*16];
|
||||||
|
chState[i].address=chReg[0]|((uint64_t)chReg[1]<<16)|((uint64_t)chReg[2]<<32)|((uint64_t)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 (size_t j=mixBufOffset; j<4; j++) {
|
||||||
|
mixOut[0][j]=0;
|
||||||
|
mixOut[1][j]=0;
|
||||||
|
}
|
||||||
|
for (int i=0; i<chanMax; i++) {
|
||||||
|
for (size_t j=mixBufOffset; j<4; j++) {
|
||||||
|
unsigned int lastAddr=chState[i].address>>32;
|
||||||
|
chState[i].address+=((uint64_t)chState[i].freq)<<8;
|
||||||
|
unsigned int newAddr=chState[i].address>>32;
|
||||||
|
if (newAddr!=lastAddr) {
|
||||||
|
if (newAddr>=chState[i].loopEnd) {
|
||||||
|
newAddr=newAddr-chState[i].loopEnd+chState[i].loopStart;
|
||||||
|
chState[i].address=(chState[i].address&0xffffffff)|((uint64_t)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 (size_t 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);
|
||||||
|
mixMemCompo.entries[mixBufPage].end=mixMemCompo.entries[mixBufPage].begin+mixBufWritePos;
|
||||||
|
mixMemCompo.entries[mixBufPage+1].end=mixMemCompo.entries[mixBufPage+1].begin+mixBufWritePos;
|
||||||
|
mixBufPage=(mixBufPage+2)%(mixBufs*2);
|
||||||
|
memset(mixBuf[mixBufPage],0,sizeof(mixBuf[mixBufPage]));
|
||||||
|
memset(mixBuf[mixBufPage+1],0,sizeof(mixBuf[mixBufPage+1]));
|
||||||
|
// emulate buffer loss prevention and buffer copying
|
||||||
|
sampsRendered+=mixBufReadPos;
|
||||||
|
mixBufOffset=(4-(mixBufReadPos&3))&3;
|
||||||
|
mixBufReadPos=0;
|
||||||
|
mixBufWritePos=mixBufOffset;
|
||||||
|
for (size_t 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=(0x03000800ULL+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=®Pool[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 && updCyclesTotal>0) {
|
||||||
|
// assuming new sample, L!=R and lowest ROM access wait in all channels
|
||||||
|
// this gives 39.5 cycles/sample, rounded up to 40 for loops
|
||||||
|
maxCPU=(float)sampsRendered*chanMax*40/(float)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&0xf)!=0) {
|
||||||
|
// make sure echo channels' frequency can't be faster than the sample rate
|
||||||
|
if (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 (c.chan<chanMax) {
|
||||||
|
wtMemCompo.entries[c.chan].end=wtMemCompo.entries[c.chan].begin+chan[c.chan].wtLen;
|
||||||
|
}
|
||||||
|
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_CMD_MACRO_RESTART:
|
||||||
|
chan[c.chan].std.restart(c.value);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
DivSamplePos DivPlatformGBAMinMod::getSamplePos(int ch) {
|
||||||
|
if (ch>=chanMax ||
|
||||||
|
chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen ||
|
||||||
|
!chan[ch].active || (chan[ch].echo&0xf)!=0
|
||||||
|
) {
|
||||||
|
return DivSamplePos();
|
||||||
|
}
|
||||||
|
return DivSamplePos(
|
||||||
|
chan[ch].sample,
|
||||||
|
(((int)regPool[ch*16+2]|((int)regPool[ch*16+3]<<16))&0x01ffffff)-sampleOff[chan[ch].sample],
|
||||||
|
(int64_t)chan[ch].freq*chipClock/CHIP_FREQBASE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
updCycles=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];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DivMemoryComposition* DivPlatformGBAMinMod::getMemCompo(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0: return &romMemCompo;
|
||||||
|
case 1: return &wtMemCompo;
|
||||||
|
case 2: return &mixMemCompo;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformGBAMinMod::renderSamples(int sysID) {
|
||||||
|
size_t maxPos=getSampleMemCapacity();
|
||||||
|
memset(sampleMem,0,maxPos);
|
||||||
|
romMemCompo.entries.clear();
|
||||||
|
romMemCompo.capacity=maxPos;
|
||||||
|
|
||||||
|
// dummy zero-length samples are at pos 0 as the engine still outputs them
|
||||||
|
size_t memPos=1;
|
||||||
|
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;
|
||||||
|
romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos));
|
||||||
|
// if it's one-shot, add 16 silent samples for looping area
|
||||||
|
// this should be enough for most cases even though the
|
||||||
|
// frequency register can make position jump by up to 256 samples
|
||||||
|
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;
|
||||||
|
romMemCompo.used=sampleMemLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
wtMemCompo.used=256*chanMax;
|
||||||
|
mixMemCompo.used=2048*mixBufs;
|
||||||
|
wtMemCompo.entries.clear();
|
||||||
|
mixMemCompo.entries.clear();
|
||||||
|
for (int i=0; i<chanMax; i++) {
|
||||||
|
wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i),-1,i*256,i*256));
|
||||||
|
}
|
||||||
|
for (int i=0; i<(int)mixBufs; i++) {
|
||||||
|
mixMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_ECHO, fmt::sprintf("Buffer %d Left",i),-1,i*2048,i*2048));
|
||||||
|
mixMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_ECHO, fmt::sprintf("Buffer %d Right",i),-1,i*2048+1024,i*2048+1024));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
romMemCompo=DivMemoryComposition();
|
||||||
|
romMemCompo.name="Sample ROM";
|
||||||
|
wtMemCompo=DivMemoryComposition();
|
||||||
|
wtMemCompo.name="Wavetable RAM";
|
||||||
|
wtMemCompo.capacity=256*16;
|
||||||
|
wtMemCompo.memory=(unsigned char*)wtMem;
|
||||||
|
wtMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED;
|
||||||
|
mixMemCompo=DivMemoryComposition();
|
||||||
|
mixMemCompo.name="Mix/Echo Buffer";
|
||||||
|
mixMemCompo.capacity=2048*15;
|
||||||
|
mixMemCompo.memory=(unsigned char*)mixBuf;
|
||||||
|
mixMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED;
|
||||||
|
setFlags(flags);
|
||||||
|
reset();
|
||||||
|
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformGBAMinMod::quit() {
|
||||||
|
delete[] sampleMem;
|
||||||
|
for (int i=0; i<16; i++) {
|
||||||
|
delete oscBuf[i];
|
||||||
|
}
|
||||||
|
}
|
131
src/engine/platform/gbaminmod.h
Normal file
131
src/engine/platform/gbaminmod.h
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* 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 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
|
||||||
|
unsigned short regPool[16*16];
|
||||||
|
signed char wtMem[256*16];
|
||||||
|
DivMemoryComposition romMemCompo;
|
||||||
|
DivMemoryComposition mixMemCompo;
|
||||||
|
DivMemoryComposition wtMemCompo;
|
||||||
|
|
||||||
|
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);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
|
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);
|
||||||
|
const DivMemoryComposition* getMemCompo(int index);
|
||||||
|
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
|
|
@ -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) {
|
||||||
|
@ -740,6 +742,8 @@ void GB_apu_init(GB_gameboy_t *gb)
|
||||||
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,
|
||||||
|
@ -903,6 +907,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GB_MODEL_AGB:
|
case GB_MODEL_AGB:
|
||||||
|
case GB_MODEL_AGB_NATIVE:
|
||||||
/* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple
|
/* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple
|
||||||
pattern like the other revisions. */
|
pattern like the other revisions. */
|
||||||
/* For the most part, AGS seems to do:
|
/* For the most part, AGS seems to do:
|
||||||
|
@ -1160,14 +1165,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 +1224,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 +1427,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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -247,6 +247,8 @@ const char* cmdName[]={
|
||||||
"ESFM_MODIN",
|
"ESFM_MODIN",
|
||||||
"ESFM_ENV_DELAY",
|
"ESFM_ENV_DELAY",
|
||||||
|
|
||||||
|
"MACRO_RESTART",
|
||||||
|
|
||||||
"POWERNOISE_COUNTER_LOAD",
|
"POWERNOISE_COUNTER_LOAD",
|
||||||
"POWERNOISE_IO_WRITE",
|
"POWERNOISE_IO_WRITE",
|
||||||
|
|
||||||
|
@ -256,7 +258,7 @@ const char* cmdName[]={
|
||||||
"DAVE_LOW_PASS",
|
"DAVE_LOW_PASS",
|
||||||
"DAVE_CLOCK_DIV",
|
"DAVE_CLOCK_DIV",
|
||||||
|
|
||||||
"MACRO_RESTART",
|
"MINMOD_ECHO",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!");
|
static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!");
|
||||||
|
|
|
@ -136,6 +136,8 @@ enum DivSystem {
|
||||||
DIV_SYSTEM_POWERNOISE,
|
DIV_SYSTEM_POWERNOISE,
|
||||||
DIV_SYSTEM_DAVE,
|
DIV_SYSTEM_DAVE,
|
||||||
DIV_SYSTEM_NDS,
|
DIV_SYSTEM_NDS,
|
||||||
|
DIV_SYSTEM_GBA_DMA,
|
||||||
|
DIV_SYSTEM_GBA_MINMOD,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DivEffectType: unsigned short {
|
enum DivEffectType: unsigned short {
|
||||||
|
|
|
@ -1862,7 +1862,11 @@ void DivEngine::registerSystems() {
|
||||||
{"Sample"},
|
{"Sample"},
|
||||||
{"PCM"},
|
{"PCM"},
|
||||||
{DIV_CH_PCM},
|
{DIV_CH_PCM},
|
||||||
{DIV_INS_AMIGA}
|
{DIV_INS_AMIGA},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
sysDefs[DIV_SYSTEM_K007232]=new DivSysDef(
|
sysDefs[DIV_SYSTEM_K007232]=new DivSysDef(
|
||||||
|
@ -2021,6 +2025,36 @@ void DivEngine::registerSystems() {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sysDefs[DIV_SYSTEM_GBA_DMA]=new DivSysDef(
|
||||||
|
"Game Boy Advance DMA Sound", NULL, 0xd7, 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, 0xd8, 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_NDS]=new DivSysDef(
|
sysDefs[DIV_SYSTEM_NDS]=new DivSysDef(
|
||||||
"NDS", NULL, 0xd6, 0, 16, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_IMA_ADPCM)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 32, 32,
|
"NDS", NULL, 0xd6, 0, 16, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_IMA_ADPCM)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 32, 32,
|
||||||
"a handheld video game console with two screens. it uses a stylus.",
|
"a handheld video game console with two screens. it uses a stylus.",
|
||||||
|
|
|
@ -1542,7 +1542,9 @@ void FurnaceGUI::doAction(int what) {
|
||||||
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_NDS) {
|
i==DIV_INS_NDS ||
|
||||||
|
i==DIV_INS_GBA_DMA ||
|
||||||
|
i==DIV_INS_GBA_MINMOD) {
|
||||||
makeInsTypeList.push_back(i);
|
makeInsTypeList.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,6 +296,8 @@ enum FurnaceGUIColors {
|
||||||
GUI_COLOR_INSTR_POWERNOISE_SLOPE,
|
GUI_COLOR_INSTR_POWERNOISE_SLOPE,
|
||||||
GUI_COLOR_INSTR_DAVE,
|
GUI_COLOR_INSTR_DAVE,
|
||||||
GUI_COLOR_INSTR_NDS,
|
GUI_COLOR_INSTR_NDS,
|
||||||
|
GUI_COLOR_INSTR_GBA_DMA,
|
||||||
|
GUI_COLOR_INSTR_GBA_MINMOD,
|
||||||
GUI_COLOR_INSTR_UNKNOWN,
|
GUI_COLOR_INSTR_UNKNOWN,
|
||||||
|
|
||||||
GUI_COLOR_CHANNEL_BG,
|
GUI_COLOR_CHANNEL_BG,
|
||||||
|
|
|
@ -180,6 +180,8 @@ const char* insTypes[DIV_INS_MAX+1][3]={
|
||||||
{"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},
|
||||||
{"NDS",ICON_FA_BAR_CHART,ICON_FUR_INS_NDS},
|
{"NDS",ICON_FA_BAR_CHART,ICON_FUR_INS_NDS},
|
||||||
|
{"GBA DMA",ICON_FA_GAMEPAD,ICON_FUR_INS_GBA_DMA},
|
||||||
|
{"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD},
|
||||||
{NULL,ICON_FA_QUESTION,ICON_FA_QUESTION}
|
{NULL,ICON_FA_QUESTION,ICON_FA_QUESTION}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -994,6 +996,8 @@ 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_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_NDS,"",ImVec4(0.7f,0.7f,0.8f,1.0f)),
|
D(GUI_COLOR_INSTR_NDS,"",ImVec4(0.7f,0.7f,0.8f,1.0f)),
|
||||||
|
D(GUI_COLOR_INSTR_GBA_DMA,"",ImVec4(0.6f,0.4f,1.0f,1.0f)),
|
||||||
|
D(GUI_COLOR_INSTR_GBA_MINMOD,"",ImVec4(0.5f,0.45f,0.7f,1.0f)),
|
||||||
D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)),
|
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)),
|
||||||
|
@ -1231,6 +1235,8 @@ 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_GBA_MINMOD,
|
||||||
DIV_SYSTEM_PCM_DAC,
|
DIV_SYSTEM_PCM_DAC,
|
||||||
DIV_SYSTEM_ESFM,
|
DIV_SYSTEM_ESFM,
|
||||||
DIV_SYSTEM_PONG,
|
DIV_SYSTEM_PONG,
|
||||||
|
@ -1352,6 +1358,8 @@ const int chipsSample[]={
|
||||||
DIV_SYSTEM_C140,
|
DIV_SYSTEM_C140,
|
||||||
DIV_SYSTEM_C219,
|
DIV_SYSTEM_C219,
|
||||||
DIV_SYSTEM_NDS,
|
DIV_SYSTEM_NDS,
|
||||||
|
DIV_SYSTEM_GBA_DMA,
|
||||||
|
DIV_SYSTEM_GBA_MINMOD,
|
||||||
0 // don't remove this last one!
|
0 // don't remove this last one!
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -356,6 +356,10 @@ const char* es5506ControlModes[3]={
|
||||||
"pause", "reverse", NULL
|
"pause", "reverse", NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char* minModModeBits[3]={
|
||||||
|
"invert right", "invert left", NULL
|
||||||
|
};
|
||||||
|
|
||||||
const int orderedOps[4]={
|
const int orderedOps[4]={
|
||||||
0, 2, 1, 3
|
0, 2, 1, 3
|
||||||
};
|
};
|
||||||
|
@ -2526,14 +2530,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 || 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);
|
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) {
|
||||||
|
@ -5396,6 +5401,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)) {
|
||||||
|
@ -6023,7 +6029,9 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
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_NDS) {
|
ins->type==DIV_INS_NDS ||
|
||||||
|
ins->type==DIV_INS_GBA_DMA ||
|
||||||
|
ins->type==DIV_INS_GBA_MINMOD) {
|
||||||
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")) {
|
||||||
|
@ -6458,6 +6466,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 ||
|
||||||
|
@ -6467,7 +6476,8 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
ins->type==DIV_INS_SCC ||
|
ins->type==DIV_INS_SCC ||
|
||||||
ins->type==DIV_INS_SNES ||
|
ins->type==DIV_INS_SNES ||
|
||||||
ins->type==DIV_INS_NAMCO ||
|
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")) {
|
if (ImGui::BeginTabItem("Wavetable")) {
|
||||||
switch (ins->type) {
|
switch (ins->type) {
|
||||||
case DIV_INS_GB:
|
case DIV_INS_GB:
|
||||||
|
@ -6502,6 +6512,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;
|
||||||
|
@ -6509,6 +6520,10 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
wavePreviewLen=ins->amiga.waveLen+1;
|
wavePreviewLen=ins->amiga.waveLen+1;
|
||||||
wavePreviewHeight=15;
|
wavePreviewHeight=15;
|
||||||
break;
|
break;
|
||||||
|
case DIV_INS_GBA_MINMOD:
|
||||||
|
wavePreviewLen=ins->amiga.waveLen+1;
|
||||||
|
wavePreviewHeight=255;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
wavePreviewLen=32;
|
wavePreviewLen=32;
|
||||||
wavePreviewHeight=31;
|
wavePreviewHeight=31;
|
||||||
|
@ -6767,13 +6782,13 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
volMax=31;
|
volMax=31;
|
||||||
}
|
}
|
||||||
if (ins->type==DIV_INS_ADPCMB || ins->type==DIV_INS_YMZ280B || ins->type==DIV_INS_RF5C68 ||
|
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;
|
volMax=255;
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6832,7 +6847,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 || ins->type==DIV_INS_GBA_MINMOD) {
|
||||||
dutyMax=0;
|
dutyMax=0;
|
||||||
}
|
}
|
||||||
if (ins->type==DIV_INS_VBOY) {
|
if (ins->type==DIV_INS_VBOY) {
|
||||||
|
@ -7038,6 +7053,9 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
if (ins->type==DIV_INS_MIKEY) {
|
if (ins->type==DIV_INS_MIKEY) {
|
||||||
ex1Max=12;
|
ex1Max=12;
|
||||||
}
|
}
|
||||||
|
if (ins->type==DIV_INS_GBA_MINMOD) {
|
||||||
|
ex1Max=2;
|
||||||
|
}
|
||||||
|
|
||||||
int panMin=0;
|
int panMin=0;
|
||||||
int panMax=0;
|
int panMax=0;
|
||||||
|
@ -7052,7 +7070,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;
|
||||||
}
|
}
|
||||||
|
@ -7102,7 +7121,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
panMin=0;
|
panMin=0;
|
||||||
panMax=127;
|
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;
|
panMin=0;
|
||||||
panMax=255;
|
panMax=255;
|
||||||
}
|
}
|
||||||
|
@ -7214,7 +7233,8 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
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_NDS) {
|
ins->type==DIV_INS_NDS ||
|
||||||
|
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) {
|
||||||
|
@ -7252,6 +7272,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));
|
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) {
|
} 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));
|
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 {
|
} else {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,14 @@ void FurnaceGUI::drawMemory() {
|
||||||
dl->AddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_DATA]));
|
dl->AddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_DATA]));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DIV_MEMORY_WAVE_8BIT_SIGNED:
|
||||||
|
for (int k=0; k<(int)mc->capacity; k++) {
|
||||||
|
signed char val=(signed char)mc->memory[k];
|
||||||
|
ImVec2 pos1=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)k/(double)(mc->capacity),1.0f-((float)(val+129)/256.0f)));
|
||||||
|
ImVec2 pos2=ImLerp(dataRect.Min,dataRect.Max,ImVec2((double)(k+1)/(double)(mc->capacity),1.0f));
|
||||||
|
dl->AddRectFilled(pos1,pos2,ImGui::GetColorU32(uiColors[GUI_COLOR_MEMORY_DATA]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,18 @@ 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"),
|
||||||
|
CH(DIV_SYSTEM_GBA_DMA, 0.5f, 0, ""),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ENTRY(
|
||||||
|
"Game Boy Advance (with MinMod)", {
|
||||||
|
CH(DIV_SYSTEM_GB, 1.0f, 0, "chipType=3"),
|
||||||
|
CH(DIV_SYSTEM_GBA_MINMOD, 0.5f, 0, ""),
|
||||||
|
}
|
||||||
|
);
|
||||||
ENTRY(
|
ENTRY(
|
||||||
"Neo Geo Pocket", {
|
"Neo Geo Pocket", {
|
||||||
CH(DIV_SYSTEM_T6W28, 1.0f, 0, ""),
|
CH(DIV_SYSTEM_T6W28, 1.0f, 0, ""),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3552,6 +3552,8 @@ void FurnaceGUI::drawSettings() {
|
||||||
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_NDS,"NDS");
|
UI_COLOR_CONFIG(GUI_COLOR_INSTR_NDS,"NDS");
|
||||||
|
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");
|
UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown");
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "../engine/chipUtils.h"
|
#include "../engine/chipUtils.h"
|
||||||
|
#include "../engine/platform/gbaminmod.h"
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
#include "misc/cpp/imgui_stdlib.h"
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
@ -390,6 +391,78 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case DIV_SYSTEM_GBA_DMA: {
|
||||||
|
int dacDepth=flags.getInt("dacDepth",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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altered) {
|
||||||
|
e->lockSave([&]() {
|
||||||
|
flags.set("dacDepth",dacDepth);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
if (maxCPU>90) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]);
|
||||||
|
ImGui::Text("Max mixer CPU usage: %.0f%%", maxCPU);
|
||||||
|
if (maxCPU>90) ImGui::PopStyleColor();
|
||||||
|
FurnaceGUI::popWarningColor();
|
||||||
|
if (altered) {
|
||||||
|
e->lockSave([&]() {
|
||||||
|
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:
|
||||||
case DIV_SYSTEM_OPLL_DRUMS:
|
case DIV_SYSTEM_OPLL_DRUMS:
|
||||||
case DIV_SYSTEM_VRC7: {
|
case DIV_SYSTEM_VRC7: {
|
||||||
|
|
Loading…
Reference in a new issue