mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-25 14:05:12 +00:00
Merge branch 'master' into minmod
This commit is contained in:
commit
11e492c897
29 changed files with 2369 additions and 401 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -15,3 +15,6 @@
|
|||
[submodule "extern/portaudio"]
|
||||
path = extern/portaudio
|
||||
url = https://github.com/PortAudio/portaudio.git
|
||||
[submodule "extern/adpcm-xq"]
|
||||
path = extern/adpcm-xq
|
||||
url = https://github.com/dbry/adpcm-xq.git
|
||||
|
|
|
@ -485,6 +485,8 @@ extern/adpcm/yma_codec.c
|
|||
extern/adpcm/ymb_codec.c
|
||||
extern/adpcm/ymz_codec.c
|
||||
|
||||
extern/adpcm-xq/adpcm-lib.c
|
||||
|
||||
extern/opn/ym3438.c
|
||||
extern/Nuked-PSG/ympsg.c
|
||||
extern/opm/opm.c
|
||||
|
@ -609,6 +611,8 @@ src/engine/platform/sound/c140_c219.c
|
|||
|
||||
src/engine/platform/sound/dave/dave.cpp
|
||||
|
||||
src/engine/platform/sound/nds.cpp
|
||||
|
||||
src/engine/platform/oplAInterface.cpp
|
||||
src/engine/platform/ym2608Interface.cpp
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
@ -620,6 +624,7 @@ src/engine/fileOps/ftm.cpp
|
|||
src/engine/fileOps/fur.cpp
|
||||
src/engine/fileOps/mod.cpp
|
||||
src/engine/fileOps/s3m.cpp
|
||||
src/engine/fileOps/text.cpp
|
||||
|
||||
src/engine/blip_buf.c
|
||||
src/engine/brrUtils.c
|
||||
|
@ -719,6 +724,7 @@ src/engine/platform/powernoise.cpp
|
|||
src/engine/platform/dave.cpp
|
||||
src/engine/platform/gbadma.cpp
|
||||
src/engine/platform/gbaminmod.cpp
|
||||
src/engine/platform/nds.cpp
|
||||
src/engine/platform/pcmdac.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
|
||||
|
|
BIN
demos/specs2/rastaline_dub.fur
Normal file
BIN
demos/specs2/rastaline_dub.fur
Normal file
Binary file not shown.
1
extern/adpcm-xq
vendored
Submodule
1
extern/adpcm-xq
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 6220fed7655e86a29702b45dbc641a028ed5a4bf
|
|
@ -564,9 +564,13 @@ size | description
|
|||
| - 4: QSound ADPCM
|
||||
| - 5: ADPCM-A
|
||||
| - 6: ADPCM-B
|
||||
| - 7: K05 ADPCM
|
||||
| - 8: 8-bit PCM
|
||||
| - 9: BRR (SNES)
|
||||
| - 10: VOX
|
||||
| - 11: 8-bit μ-law PCM
|
||||
| - 12: C219 PCM
|
||||
| - 13: IMA ADPCM
|
||||
| - 16: 16-bit PCM
|
||||
1 | loop direction (>=123) or reserved
|
||||
| - 0: forward
|
||||
|
|
|
@ -124,6 +124,8 @@ the following instrument types are available:
|
|||
- 55: ESFM
|
||||
- 56: PowerNoise (noise)
|
||||
- 57: PowerNoise (slope)
|
||||
- 58: Dave
|
||||
- 59: NDS
|
||||
|
||||
the following feature codes are recognized:
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
#include "platform/esfm.h"
|
||||
#include "platform/powernoise.h"
|
||||
#include "platform/dave.h"
|
||||
#include "platform/nds.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
#include "song.h"
|
||||
|
@ -669,6 +670,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_DAVE:
|
||||
dispatch=new DivPlatformDave;
|
||||
break;
|
||||
case DIV_SYSTEM_NDS:
|
||||
dispatch=new DivPlatformNDS;
|
||||
break;
|
||||
case DIV_SYSTEM_DUMMY:
|
||||
dispatch=new DivPlatformDummy;
|
||||
break;
|
||||
|
|
|
@ -989,6 +989,7 @@ void DivEngine::delUnusedSamples() {
|
|||
i->type==DIV_INS_K053260 ||
|
||||
i->type==DIV_INS_C140 ||
|
||||
i->type==DIV_INS_C219 ||
|
||||
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) {
|
||||
|
|
|
@ -1626,355 +1626,3 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
saveLock.unlock();
|
||||
return w;
|
||||
}
|
||||
|
||||
static const char* trueFalse[2]={
|
||||
"no", "yes"
|
||||
};
|
||||
|
||||
static const char* gbEnvDir[2]={
|
||||
"down", "up"
|
||||
};
|
||||
|
||||
static const char* notes[12]={
|
||||
"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"
|
||||
};
|
||||
|
||||
static const char* notesNegative[12]={
|
||||
"c_", "c+", "d_", "d+", "e_", "f_", "f+", "g_", "g+", "a_", "a+", "b_"
|
||||
};
|
||||
|
||||
static const char* sampleLoopModes[4]={
|
||||
"forward", "backward", "ping-pong", "invalid"
|
||||
};
|
||||
|
||||
void writeTextMacro(SafeWriter* w, DivInstrumentMacro& m, const char* name, bool& wroteMacroHeader) {
|
||||
if ((m.open&6)==0 && m.len<1) return;
|
||||
if (!wroteMacroHeader) {
|
||||
w->writeText("- macros:\n");
|
||||
wroteMacroHeader=true;
|
||||
}
|
||||
w->writeText(fmt::sprintf(" - %s:",name));
|
||||
int len=m.len;
|
||||
switch (m.open&6) {
|
||||
case 2:
|
||||
len=16;
|
||||
w->writeText(" [ADSR]");
|
||||
break;
|
||||
case 4:
|
||||
len=16;
|
||||
w->writeText(" [LFO]");
|
||||
break;
|
||||
}
|
||||
if (m.mode) {
|
||||
w->writeText(fmt::sprintf(" [MODE %d]",m.mode));
|
||||
}
|
||||
if (m.delay>0) {
|
||||
w->writeText(fmt::sprintf(" [DELAY %d]",m.delay));
|
||||
}
|
||||
if (m.speed>1) {
|
||||
w->writeText(fmt::sprintf(" [SPEED %d]",m.speed));
|
||||
}
|
||||
for (int i=0; i<len; i++) {
|
||||
if (i==m.loop) {
|
||||
w->writeText(" |");
|
||||
}
|
||||
if (i==m.rel) {
|
||||
w->writeText(" /");
|
||||
}
|
||||
w->writeText(fmt::sprintf(" %d",m.val[i]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
SafeWriter* DivEngine::saveText(bool separatePatterns) {
|
||||
saveLock.lock();
|
||||
|
||||
SafeWriter* w=new SafeWriter;
|
||||
w->init();
|
||||
|
||||
w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION));
|
||||
w->writeText(fmt::sprintf("- name: %s\n",song.name));
|
||||
w->writeText(fmt::sprintf("- author: %s\n",song.author));
|
||||
w->writeText(fmt::sprintf("- album: %s\n",song.category));
|
||||
w->writeText(fmt::sprintf("- system: %s\n",song.systemName));
|
||||
w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning));
|
||||
|
||||
w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen));
|
||||
w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen));
|
||||
w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen));
|
||||
|
||||
w->writeText("# Sound Chips\n\n");
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
w->writeText(fmt::sprintf("- %s\n",getSystemName(song.system[i])));
|
||||
w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i]));
|
||||
w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i]));
|
||||
w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i]));
|
||||
w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i]));
|
||||
|
||||
String sysFlags=song.systemFlags[i].toString();
|
||||
|
||||
if (!sysFlags.empty()) {
|
||||
w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags));
|
||||
}
|
||||
}
|
||||
|
||||
if (!song.notes.empty()) {
|
||||
w->writeText("\n# Song Comments\n\n");
|
||||
w->writeText(song.notes);
|
||||
}
|
||||
|
||||
w->writeText("\n# Instruments\n\n");
|
||||
|
||||
for (int i=0; i<song.insLen; i++) {
|
||||
DivInstrument* ins=song.ins[i];
|
||||
|
||||
w->writeText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name));
|
||||
|
||||
w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type));
|
||||
|
||||
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) {
|
||||
w->writeText("- FM parameters:\n");
|
||||
w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg));
|
||||
w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb));
|
||||
w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms));
|
||||
w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams));
|
||||
w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2));
|
||||
w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2));
|
||||
w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops));
|
||||
w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset));
|
||||
w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0]));
|
||||
w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq));
|
||||
w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq));
|
||||
w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq));
|
||||
|
||||
for (int j=0; j<ins->fm.ops; j++) {
|
||||
DivInstrumentFM::Operator& op=ins->fm.op[j];
|
||||
|
||||
w->writeText(fmt::sprintf(" - operator %d:\n",j));
|
||||
w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0]));
|
||||
w->writeText(fmt::sprintf(" - AM: %d\n",op.am));
|
||||
w->writeText(fmt::sprintf(" - AR: %d\n",op.ar));
|
||||
w->writeText(fmt::sprintf(" - DR: %d\n",op.dr));
|
||||
w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult));
|
||||
w->writeText(fmt::sprintf(" - RR: %d\n",op.rr));
|
||||
w->writeText(fmt::sprintf(" - SL: %d\n",op.sl));
|
||||
w->writeText(fmt::sprintf(" - TL: %d\n",op.tl));
|
||||
w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2));
|
||||
w->writeText(fmt::sprintf(" - RS: %d\n",op.rs));
|
||||
w->writeText(fmt::sprintf(" - DT: %d\n",op.dt));
|
||||
w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r));
|
||||
w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv));
|
||||
w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam));
|
||||
w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb));
|
||||
w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt));
|
||||
w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl));
|
||||
w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus));
|
||||
w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib));
|
||||
w->writeText(fmt::sprintf(" - WS: %d\n",op.ws));
|
||||
w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr));
|
||||
w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs));
|
||||
}
|
||||
}
|
||||
|
||||
if (ins->type==DIV_INS_ESFM) {
|
||||
w->writeText("- ESFM parameters:\n");
|
||||
w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise));
|
||||
|
||||
for (int j=0; j<ins->fm.ops; j++) {
|
||||
DivInstrumentESFM::Operator& opE=ins->esfm.op[j];
|
||||
|
||||
w->writeText(fmt::sprintf(" - operator %d:\n",j));
|
||||
w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay));
|
||||
w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl));
|
||||
w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn));
|
||||
w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0]));
|
||||
w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0]));
|
||||
w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct));
|
||||
w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt));
|
||||
w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0]));
|
||||
}
|
||||
}
|
||||
|
||||
if (ins->type==DIV_INS_GB) {
|
||||
w->writeText("- Game Boy parameters:\n");
|
||||
w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol));
|
||||
w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0]));
|
||||
w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen));
|
||||
w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen));
|
||||
w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0]));
|
||||
w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0]));
|
||||
if (ins->gb.hwSeqLen>0) {
|
||||
w->writeText(" - hardware sequence:\n");
|
||||
for (int j=0; j<ins->gb.hwSeqLen; j++) {
|
||||
w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool header=false;
|
||||
writeTextMacro(w,ins->std.volMacro,"vol",header);
|
||||
writeTextMacro(w,ins->std.arpMacro,"arp",header);
|
||||
writeTextMacro(w,ins->std.dutyMacro,"duty",header);
|
||||
writeTextMacro(w,ins->std.waveMacro,"wave",header);
|
||||
writeTextMacro(w,ins->std.pitchMacro,"pitch",header);
|
||||
writeTextMacro(w,ins->std.panLMacro,"panL",header);
|
||||
writeTextMacro(w,ins->std.panRMacro,"panR",header);
|
||||
writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header);
|
||||
writeTextMacro(w,ins->std.ex1Macro,"ex1",header);
|
||||
writeTextMacro(w,ins->std.ex2Macro,"ex2",header);
|
||||
writeTextMacro(w,ins->std.ex3Macro,"ex3",header);
|
||||
writeTextMacro(w,ins->std.ex4Macro,"ex4",header);
|
||||
writeTextMacro(w,ins->std.ex5Macro,"ex5",header);
|
||||
writeTextMacro(w,ins->std.ex6Macro,"ex6",header);
|
||||
writeTextMacro(w,ins->std.ex7Macro,"ex7",header);
|
||||
writeTextMacro(w,ins->std.ex8Macro,"ex8",header);
|
||||
writeTextMacro(w,ins->std.algMacro,"alg",header);
|
||||
writeTextMacro(w,ins->std.fbMacro,"fb",header);
|
||||
writeTextMacro(w,ins->std.fmsMacro,"fms",header);
|
||||
writeTextMacro(w,ins->std.amsMacro,"ams",header);
|
||||
|
||||
// TODO: the rest
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
w->writeText("\n# Wavetables\n\n");
|
||||
|
||||
for (int i=0; i<song.waveLen; i++) {
|
||||
DivWavetable* wave=song.wave[i];
|
||||
|
||||
w->writeText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1));
|
||||
for (int j=0; j<=wave->len; j++) {
|
||||
w->writeText(fmt::sprintf(" %d",wave->data[j]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
w->writeText("\n# Samples\n\n");
|
||||
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* sample=song.sample[i];
|
||||
|
||||
w->writeText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name));
|
||||
|
||||
w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth));
|
||||
w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen()));
|
||||
w->writeText(fmt::sprintf("- samples: %d\n",sample->samples));
|
||||
w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate));
|
||||
w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate));
|
||||
w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0]));
|
||||
if (sample->loop) {
|
||||
w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart));
|
||||
w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd));
|
||||
w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3]));
|
||||
}
|
||||
w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0]));
|
||||
w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0]));
|
||||
|
||||
// TODO' render matrix
|
||||
unsigned char* buf=(unsigned char*)sample->getCurBuf();
|
||||
unsigned int bufLen=sample->getCurBufLen();
|
||||
w->writeText("\n```");
|
||||
for (unsigned int i=0; i<bufLen; i++) {
|
||||
if ((i&15)==0) w->writeText(fmt::sprintf("\n%.8X:",i));
|
||||
w->writeText(fmt::sprintf(" %.2X",buf[i]));
|
||||
}
|
||||
w->writeText("\n```\n\n");
|
||||
}
|
||||
|
||||
w->writeText("\n# Subsongs\n\n");
|
||||
|
||||
for (size_t i=0; i<song.subsong.size(); i++) {
|
||||
DivSubSong* s=song.subsong[i];
|
||||
w->writeText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name));
|
||||
|
||||
w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz));
|
||||
w->writeText(fmt::sprintf("- speeds:"));
|
||||
for (int j=0; j<s->speeds.len; j++) {
|
||||
w->writeText(fmt::sprintf(" %d",s->speeds.val[j]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD));
|
||||
w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase));
|
||||
w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen));
|
||||
w->writeText(fmt::sprintf("\norders:\n```\n"));
|
||||
|
||||
for (int j=0; j<s->ordersLen; j++) {
|
||||
w->writeText(fmt::sprintf("%.2X |",j));
|
||||
for (int k=0; k<chans; k++) {
|
||||
w->writeText(fmt::sprintf(" %.2X",s->orders.ord[k][j]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
}
|
||||
w->writeText("```\n\n## Patterns\n\n");
|
||||
|
||||
if (separatePatterns) {
|
||||
w->writeText("TODO: separate patterns\n\n");
|
||||
} else {
|
||||
for (int j=0; j<s->ordersLen; j++) {
|
||||
w->writeText(fmt::sprintf("----- ORDER %.2X\n",j));
|
||||
|
||||
for (int k=0; k<s->patLen; k++) {
|
||||
w->writeText(fmt::sprintf("%.2X ",k));
|
||||
|
||||
for (int l=0; l<chans; l++) {
|
||||
DivPattern* p=s->pat[l].getPattern(s->orders.ord[l][j],false);
|
||||
|
||||
int note=p->data[k][0];
|
||||
int octave=p->data[k][1];
|
||||
|
||||
if (note==0 && octave==0) {
|
||||
w->writeText("|... ");
|
||||
} else if (note==100) {
|
||||
w->writeText("|OFF ");
|
||||
} else if (note==101) {
|
||||
w->writeText("|=== ");
|
||||
} else if (note==102) {
|
||||
w->writeText("|REL ");
|
||||
} else if ((octave>9 && octave<250) || note>12) {
|
||||
w->writeText("|??? ");
|
||||
} else {
|
||||
if (octave>=128) octave-=256;
|
||||
if (note>11) {
|
||||
note-=12;
|
||||
octave++;
|
||||
}
|
||||
w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave));
|
||||
}
|
||||
|
||||
if (p->data[k][2]==-1) {
|
||||
w->writeText(".. ");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff));
|
||||
}
|
||||
|
||||
if (p->data[k][3]==-1) {
|
||||
w->writeText("..");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff));
|
||||
}
|
||||
|
||||
for (int m=0; m<s->pat[l].effectCols; m++) {
|
||||
if (p->data[k][4+(m<<1)]==-1) {
|
||||
w->writeText(" ..");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff));
|
||||
}
|
||||
if (p->data[k][5+(m<<1)]==-1) {
|
||||
w->writeText("..");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w->writeText("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
saveLock.unlock();
|
||||
return w;
|
||||
}
|
||||
|
|
372
src/engine/fileOps/text.cpp
Normal file
372
src/engine/fileOps/text.cpp
Normal file
|
@ -0,0 +1,372 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
|
||||
|
||||
static const char* trueFalse[2]={
|
||||
"no", "yes"
|
||||
};
|
||||
|
||||
static const char* gbEnvDir[2]={
|
||||
"down", "up"
|
||||
};
|
||||
|
||||
static const char* notes[12]={
|
||||
"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"
|
||||
};
|
||||
|
||||
static const char* notesNegative[12]={
|
||||
"c_", "c+", "d_", "d+", "e_", "f_", "f+", "g_", "g+", "a_", "a+", "b_"
|
||||
};
|
||||
|
||||
static const char* sampleLoopModes[4]={
|
||||
"forward", "backward", "ping-pong", "invalid"
|
||||
};
|
||||
|
||||
void writeTextMacro(SafeWriter* w, DivInstrumentMacro& m, const char* name, bool& wroteMacroHeader) {
|
||||
if ((m.open&6)==0 && m.len<1) return;
|
||||
if (!wroteMacroHeader) {
|
||||
w->writeText("- macros:\n");
|
||||
wroteMacroHeader=true;
|
||||
}
|
||||
w->writeText(fmt::sprintf(" - %s:",name));
|
||||
int len=m.len;
|
||||
switch (m.open&6) {
|
||||
case 2:
|
||||
len=16;
|
||||
w->writeText(" [ADSR]");
|
||||
break;
|
||||
case 4:
|
||||
len=16;
|
||||
w->writeText(" [LFO]");
|
||||
break;
|
||||
}
|
||||
if (m.mode) {
|
||||
w->writeText(fmt::sprintf(" [MODE %d]",m.mode));
|
||||
}
|
||||
if (m.delay>0) {
|
||||
w->writeText(fmt::sprintf(" [DELAY %d]",m.delay));
|
||||
}
|
||||
if (m.speed>1) {
|
||||
w->writeText(fmt::sprintf(" [SPEED %d]",m.speed));
|
||||
}
|
||||
for (int i=0; i<len; i++) {
|
||||
if (i==m.loop) {
|
||||
w->writeText(" |");
|
||||
}
|
||||
if (i==m.rel) {
|
||||
w->writeText(" /");
|
||||
}
|
||||
w->writeText(fmt::sprintf(" %d",m.val[i]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
SafeWriter* DivEngine::saveText(bool separatePatterns) {
|
||||
saveLock.lock();
|
||||
|
||||
SafeWriter* w=new SafeWriter;
|
||||
w->init();
|
||||
|
||||
w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION));
|
||||
w->writeText(fmt::sprintf("- name: %s\n",song.name));
|
||||
w->writeText(fmt::sprintf("- author: %s\n",song.author));
|
||||
w->writeText(fmt::sprintf("- album: %s\n",song.category));
|
||||
w->writeText(fmt::sprintf("- system: %s\n",song.systemName));
|
||||
w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning));
|
||||
|
||||
w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen));
|
||||
w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen));
|
||||
w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen));
|
||||
|
||||
w->writeText("# Sound Chips\n\n");
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
w->writeText(fmt::sprintf("- %s\n",getSystemName(song.system[i])));
|
||||
w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i]));
|
||||
w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i]));
|
||||
w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i]));
|
||||
w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i]));
|
||||
|
||||
String sysFlags=song.systemFlags[i].toString();
|
||||
|
||||
if (!sysFlags.empty()) {
|
||||
w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags));
|
||||
}
|
||||
}
|
||||
|
||||
if (!song.notes.empty()) {
|
||||
w->writeText("\n# Song Comments\n\n");
|
||||
w->writeText(song.notes);
|
||||
}
|
||||
|
||||
w->writeText("\n# Instruments\n\n");
|
||||
|
||||
for (int i=0; i<song.insLen; i++) {
|
||||
DivInstrument* ins=song.ins[i];
|
||||
|
||||
w->writeText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name));
|
||||
|
||||
w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type));
|
||||
|
||||
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) {
|
||||
w->writeText("- FM parameters:\n");
|
||||
w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg));
|
||||
w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb));
|
||||
w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms));
|
||||
w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams));
|
||||
w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2));
|
||||
w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2));
|
||||
w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops));
|
||||
w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset));
|
||||
w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0]));
|
||||
w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq));
|
||||
w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq));
|
||||
w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq));
|
||||
|
||||
for (int j=0; j<ins->fm.ops; j++) {
|
||||
DivInstrumentFM::Operator& op=ins->fm.op[j];
|
||||
|
||||
w->writeText(fmt::sprintf(" - operator %d:\n",j));
|
||||
w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0]));
|
||||
w->writeText(fmt::sprintf(" - AM: %d\n",op.am));
|
||||
w->writeText(fmt::sprintf(" - AR: %d\n",op.ar));
|
||||
w->writeText(fmt::sprintf(" - DR: %d\n",op.dr));
|
||||
w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult));
|
||||
w->writeText(fmt::sprintf(" - RR: %d\n",op.rr));
|
||||
w->writeText(fmt::sprintf(" - SL: %d\n",op.sl));
|
||||
w->writeText(fmt::sprintf(" - TL: %d\n",op.tl));
|
||||
w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2));
|
||||
w->writeText(fmt::sprintf(" - RS: %d\n",op.rs));
|
||||
w->writeText(fmt::sprintf(" - DT: %d\n",op.dt));
|
||||
w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r));
|
||||
w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv));
|
||||
w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam));
|
||||
w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb));
|
||||
w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt));
|
||||
w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl));
|
||||
w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus));
|
||||
w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib));
|
||||
w->writeText(fmt::sprintf(" - WS: %d\n",op.ws));
|
||||
w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr));
|
||||
w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs));
|
||||
}
|
||||
}
|
||||
|
||||
if (ins->type==DIV_INS_ESFM) {
|
||||
w->writeText("- ESFM parameters:\n");
|
||||
w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise));
|
||||
|
||||
for (int j=0; j<ins->fm.ops; j++) {
|
||||
DivInstrumentESFM::Operator& opE=ins->esfm.op[j];
|
||||
|
||||
w->writeText(fmt::sprintf(" - operator %d:\n",j));
|
||||
w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay));
|
||||
w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl));
|
||||
w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn));
|
||||
w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0]));
|
||||
w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0]));
|
||||
w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct));
|
||||
w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt));
|
||||
w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0]));
|
||||
}
|
||||
}
|
||||
|
||||
if (ins->type==DIV_INS_GB) {
|
||||
w->writeText("- Game Boy parameters:\n");
|
||||
w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol));
|
||||
w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0]));
|
||||
w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen));
|
||||
w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen));
|
||||
w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0]));
|
||||
w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0]));
|
||||
if (ins->gb.hwSeqLen>0) {
|
||||
w->writeText(" - hardware sequence:\n");
|
||||
for (int j=0; j<ins->gb.hwSeqLen; j++) {
|
||||
w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool header=false;
|
||||
writeTextMacro(w,ins->std.volMacro,"vol",header);
|
||||
writeTextMacro(w,ins->std.arpMacro,"arp",header);
|
||||
writeTextMacro(w,ins->std.dutyMacro,"duty",header);
|
||||
writeTextMacro(w,ins->std.waveMacro,"wave",header);
|
||||
writeTextMacro(w,ins->std.pitchMacro,"pitch",header);
|
||||
writeTextMacro(w,ins->std.panLMacro,"panL",header);
|
||||
writeTextMacro(w,ins->std.panRMacro,"panR",header);
|
||||
writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header);
|
||||
writeTextMacro(w,ins->std.ex1Macro,"ex1",header);
|
||||
writeTextMacro(w,ins->std.ex2Macro,"ex2",header);
|
||||
writeTextMacro(w,ins->std.ex3Macro,"ex3",header);
|
||||
writeTextMacro(w,ins->std.ex4Macro,"ex4",header);
|
||||
writeTextMacro(w,ins->std.ex5Macro,"ex5",header);
|
||||
writeTextMacro(w,ins->std.ex6Macro,"ex6",header);
|
||||
writeTextMacro(w,ins->std.ex7Macro,"ex7",header);
|
||||
writeTextMacro(w,ins->std.ex8Macro,"ex8",header);
|
||||
writeTextMacro(w,ins->std.algMacro,"alg",header);
|
||||
writeTextMacro(w,ins->std.fbMacro,"fb",header);
|
||||
writeTextMacro(w,ins->std.fmsMacro,"fms",header);
|
||||
writeTextMacro(w,ins->std.amsMacro,"ams",header);
|
||||
|
||||
// TODO: the rest
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
w->writeText("\n# Wavetables\n\n");
|
||||
|
||||
for (int i=0; i<song.waveLen; i++) {
|
||||
DivWavetable* wave=song.wave[i];
|
||||
|
||||
w->writeText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1));
|
||||
for (int j=0; j<=wave->len; j++) {
|
||||
w->writeText(fmt::sprintf(" %d",wave->data[j]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
w->writeText("\n# Samples\n\n");
|
||||
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* sample=song.sample[i];
|
||||
|
||||
w->writeText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name));
|
||||
|
||||
w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth));
|
||||
w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen()));
|
||||
w->writeText(fmt::sprintf("- samples: %d\n",sample->samples));
|
||||
w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate));
|
||||
w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate));
|
||||
w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0]));
|
||||
if (sample->loop) {
|
||||
w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart));
|
||||
w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd));
|
||||
w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3]));
|
||||
}
|
||||
w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0]));
|
||||
w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0]));
|
||||
|
||||
// TODO' render matrix
|
||||
unsigned char* buf=(unsigned char*)sample->getCurBuf();
|
||||
unsigned int bufLen=sample->getCurBufLen();
|
||||
w->writeText("\n```");
|
||||
for (unsigned int i=0; i<bufLen; i++) {
|
||||
if ((i&15)==0) w->writeText(fmt::sprintf("\n%.8X:",i));
|
||||
w->writeText(fmt::sprintf(" %.2X",buf[i]));
|
||||
}
|
||||
w->writeText("\n```\n\n");
|
||||
}
|
||||
|
||||
w->writeText("\n# Subsongs\n\n");
|
||||
|
||||
for (size_t i=0; i<song.subsong.size(); i++) {
|
||||
DivSubSong* s=song.subsong[i];
|
||||
w->writeText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name));
|
||||
|
||||
w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz));
|
||||
w->writeText(fmt::sprintf("- speeds:"));
|
||||
for (int j=0; j<s->speeds.len; j++) {
|
||||
w->writeText(fmt::sprintf(" %d",s->speeds.val[j]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD));
|
||||
w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase));
|
||||
w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen));
|
||||
w->writeText(fmt::sprintf("\norders:\n```\n"));
|
||||
|
||||
for (int j=0; j<s->ordersLen; j++) {
|
||||
w->writeText(fmt::sprintf("%.2X |",j));
|
||||
for (int k=0; k<chans; k++) {
|
||||
w->writeText(fmt::sprintf(" %.2X",s->orders.ord[k][j]));
|
||||
}
|
||||
w->writeText("\n");
|
||||
}
|
||||
w->writeText("```\n\n## Patterns\n\n");
|
||||
|
||||
if (separatePatterns) {
|
||||
w->writeText("TODO: separate patterns\n\n");
|
||||
} else {
|
||||
for (int j=0; j<s->ordersLen; j++) {
|
||||
w->writeText(fmt::sprintf("----- ORDER %.2X\n",j));
|
||||
|
||||
for (int k=0; k<s->patLen; k++) {
|
||||
w->writeText(fmt::sprintf("%.2X ",k));
|
||||
|
||||
for (int l=0; l<chans; l++) {
|
||||
DivPattern* p=s->pat[l].getPattern(s->orders.ord[l][j],false);
|
||||
|
||||
int note=p->data[k][0];
|
||||
int octave=p->data[k][1];
|
||||
|
||||
if (note==0 && octave==0) {
|
||||
w->writeText("|... ");
|
||||
} else if (note==100) {
|
||||
w->writeText("|OFF ");
|
||||
} else if (note==101) {
|
||||
w->writeText("|=== ");
|
||||
} else if (note==102) {
|
||||
w->writeText("|REL ");
|
||||
} else if ((octave>9 && octave<250) || note>12) {
|
||||
w->writeText("|??? ");
|
||||
} else {
|
||||
if (octave>=128) octave-=256;
|
||||
if (note>11) {
|
||||
note-=12;
|
||||
octave++;
|
||||
}
|
||||
w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave));
|
||||
}
|
||||
|
||||
if (p->data[k][2]==-1) {
|
||||
w->writeText(".. ");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff));
|
||||
}
|
||||
|
||||
if (p->data[k][3]==-1) {
|
||||
w->writeText("..");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff));
|
||||
}
|
||||
|
||||
for (int m=0; m<s->pat[l].effectCols; m++) {
|
||||
if (p->data[k][4+(m<<1)]==-1) {
|
||||
w->writeText(" ..");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff));
|
||||
}
|
||||
if (p->data[k][5+(m<<1)]==-1) {
|
||||
w->writeText("..");
|
||||
} else {
|
||||
w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w->writeText("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
saveLock.unlock();
|
||||
return w;
|
||||
}
|
594
src/engine/platform/nds.cpp
Normal file
594
src/engine/platform/nds.cpp
Normal file
|
@ -0,0 +1,594 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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 "nds.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define CHIP_DIVIDER 32
|
||||
#define CLOCK_DIVIDER 512 // for match to output rate
|
||||
|
||||
#define rRead8(a) (nds.read8(a))
|
||||
#define rWrite8(a,v) {if(!skipRegisterWrites) {nds.write8((a),(v)); regPool[(a)]=(v); if(dumpWrites) addWrite((a),(v)); }}
|
||||
#define rWrite16(a,v) { \
|
||||
if(!skipRegisterWrites) { \
|
||||
nds.write16((a)>>1,(v)); \
|
||||
regPool[(a)+0]=(v)&0xff; \
|
||||
regPool[(a)+1]=((v)>>8)&0xff; \
|
||||
if(dumpWrites) addWrite((a)+0,(v)&0xff); \
|
||||
if(dumpWrites) addWrite((a)+1,((v)>>8)&0xff); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define rWrite32(a,v) { \
|
||||
if(!skipRegisterWrites) { \
|
||||
nds.write32((a)>>2,(v)); \
|
||||
regPool[(a)+0]=(v)&0xff; \
|
||||
regPool[(a)+1]=((v)>>8)&0xff; \
|
||||
regPool[(a)+2]=((v)>>16)&0xff; \
|
||||
regPool[(a)+3]=((v)>>24)&0xff; \
|
||||
if(dumpWrites) addWrite((a)+0,(v)&0xff); \
|
||||
if(dumpWrites) addWrite((a)+1,((v)>>8)&0xff); \
|
||||
if(dumpWrites) addWrite((a)+2,((v)>>16)&0xff); \
|
||||
if(dumpWrites) addWrite((a)+3,((v)>>24)&0xff); \
|
||||
} \
|
||||
}
|
||||
|
||||
const char* regCheatSheetNDS[]={
|
||||
"CHx_Control", "000+x*10",
|
||||
"CHx_Start", "004+x*10",
|
||||
"CHx_Freq", "008+x*10",
|
||||
"CHx_LoopStart", "00A+x*10",
|
||||
"CHx_Length", "00C+x*10",
|
||||
"Control", "100",
|
||||
"Bias", "104",
|
||||
"CAPx_Control", "108+x*1",
|
||||
"CAPx_Dest", "110+x*8",
|
||||
"CAPx_Length", "114+x*8",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformNDS::getRegisterSheet() {
|
||||
return regCheatSheetNDS;
|
||||
}
|
||||
|
||||
void DivPlatformNDS::acquire(short** buf, size_t len) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
nds.tick(CLOCK_DIVIDER);
|
||||
int lout=((nds.loutput()-0x200)<<5); // scale to 16 bit
|
||||
int rout=((nds.routput()-0x200)<<5); // scale to 16 bit
|
||||
if (lout>32767) lout=32767;
|
||||
if (lout<-32768) lout=-32768;
|
||||
if (rout>32767) rout=32767;
|
||||
if (rout<-32768) rout=-32768;
|
||||
buf[0][i]=lout;
|
||||
buf[1][i]=rout;
|
||||
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(nds.chan_lout(i)+nds.chan_rout(i))>>1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 DivPlatformNDS::read_byte(u32 addr) {
|
||||
if (addr<getSampleMemCapacity()) {
|
||||
return sampleMem[addr];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DivPlatformNDS::write_byte(u32 addr, u8 data) {
|
||||
if (addr<getSampleMemCapacity()) {
|
||||
sampleMem[addr]=data;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNDS::tick(bool sysTick) {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=((chan[i].vol&0x7f)*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
|
||||
writeOutVol(i);
|
||||
}
|
||||
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].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 ((i>=8) && (i<14)) {
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
if ((!chan[i].pcm)) { // pulse
|
||||
rWrite8(0x03+i*16,(rRead8(0x03+i*16)&0xe8)|(chan[i].duty&7));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.panL.had) { // panning
|
||||
chan[i].panning=0x40+chan[i].std.panL.val;
|
||||
rWrite8(0x02+i*16,chan[i].panning);
|
||||
}
|
||||
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 ((rRead8(0x03+i*16)&0x80)==0)
|
||||
chan[i].busy=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].setPos) {
|
||||
// force keyon
|
||||
chan[i].keyOn=true;
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
unsigned char ctrl=0;
|
||||
if (chan[i].pcm || i<8) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
switch (s->depth) {
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM: ctrl=0x40; break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: ctrl=0x00; break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT: ctrl=0x20; break;
|
||||
default: break;
|
||||
}
|
||||
double off=(s->centerRate>=1)?(8363.0/(double)s->centerRate):1.0;
|
||||
chan[i].freq=0x10000-(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));
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if ((!chan[i].keyOn) && ((rRead8(0x03+i*16)&0x80)==0))
|
||||
chan[i].busy=false;
|
||||
ctrl|=(chan[i].busy?0x80:0)|((s->isLoopable())?0x08:0x10);
|
||||
if (chan[i].keyOn) {
|
||||
unsigned int start=0;
|
||||
int loopStart=0;
|
||||
int loopEnd=0;
|
||||
int end=0;
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
start=sampleOff[chan[i].sample];
|
||||
end=s->getCurBufLen()/4;
|
||||
}
|
||||
if (chan[i].audPos>0) {
|
||||
switch (s->depth) {
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM: start+=chan[i].audPos/2; end-=(chan[i].audPos/8); break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: start+=chan[i].audPos; end-=(chan[i].audPos/4); break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT: start+=chan[i].audPos*2; end-=(chan[i].audPos/2); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (s->isLoopable()) {
|
||||
if (chan[i].audPos>0) {
|
||||
switch (s->depth) {
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM:
|
||||
loopStart=(s->loopStart-chan[i].audPos)/8;
|
||||
loopEnd=(s->loopEnd-s->loopStart)/8;
|
||||
if (chan[i].audPos>(unsigned int)s->loopStart) {
|
||||
loopStart=0;
|
||||
loopEnd-=(chan[i].audPos-s->loopStart)/8;
|
||||
}
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
loopStart=(s->loopStart-chan[i].audPos)/4;
|
||||
loopEnd=(s->loopEnd-s->loopStart)/4;
|
||||
if (chan[i].audPos>(unsigned int)s->loopStart) {
|
||||
loopStart=0;
|
||||
loopEnd-=(chan[i].audPos-s->loopStart)/4;
|
||||
}
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
loopStart=(s->loopStart-chan[i].audPos)/2;
|
||||
loopEnd=(s->loopEnd-s->loopStart)/2;
|
||||
if (chan[i].audPos>(unsigned int)s->loopStart) {
|
||||
loopStart=0;
|
||||
loopEnd-=(chan[i].audPos-s->loopStart)/2;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
switch (s->depth) {
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM: loopStart=s->loopStart/8; loopEnd=(s->loopEnd-s->loopStart)/8; break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: loopStart=s->loopStart/4; loopEnd=(s->loopEnd-s->loopStart)/4; break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT: loopStart=s->loopStart/2; loopEnd=(s->loopEnd-s->loopStart)/2; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
loopEnd=CLAMP(loopEnd,0,0x3fffff);
|
||||
loopStart=CLAMP(loopStart,0,0xffff);
|
||||
rWrite16(0x0a+i*16,loopStart);
|
||||
rWrite32(0x0c+i*16,loopEnd);
|
||||
} else {
|
||||
end=CLAMP(end,0,0x3fffff);
|
||||
rWrite16(0x0a+i*16,0);
|
||||
rWrite32(0x0c+i*16,end&0x3fffff);
|
||||
}
|
||||
rWrite8(0x03+i*16,ctrl&~0x80); // force keyoff first
|
||||
rWrite32(0x04+i*16,start&0x7fffffc);
|
||||
}
|
||||
} else {
|
||||
chan[i].freq=0x10000-(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,8));
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
ctrl=(chan[i].active?0xe8:0)|(chan[i].duty&7);
|
||||
rWrite8(0x03+i*16,ctrl&~0x80); // force keyoff first
|
||||
}
|
||||
if (!chan[i].std.vol.had) {
|
||||
chan[i].outVol=chan[i].vol;
|
||||
writeOutVol(i);
|
||||
}
|
||||
chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) {
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
if (chan[i].freqChanged) {
|
||||
rWrite16(0x08+i*16,chan[i].freq&0xffff);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
rWrite8(0x03+i*16,ctrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformNDS::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_NDS);
|
||||
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample || (c.chan<8)) {
|
||||
chan[c.chan].pcm=true;
|
||||
}
|
||||
if (chan[c.chan].pcm || (c.chan<8)) {
|
||||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
chan[c.chan].sampleNote=c.value;
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
|
||||
}
|
||||
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].sample=-1;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].macroVolMul=127;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].busy=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].busy=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_ADPCMA_GLOBAL_VOLUME: {
|
||||
if (globalVolume!=(c.value&0x7f)) {
|
||||
globalVolume=c.value&0x7f;
|
||||
rWrite32(0x100,0x8000|globalVolume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
writeOutVol(c.chan);
|
||||
}
|
||||
}
|
||||
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_PANNING:
|
||||
chan[c.chan].panning=MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,127),127);
|
||||
rWrite8(0x02+c.chan*16,chan[c.chan].panning);
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta);
|
||||
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_STD_NOISE_MODE:
|
||||
if ((c.chan>=8) && (c.chan<14) && (!chan[c.chan].pcm)) { // pulse
|
||||
chan[c.chan].duty=c.value;
|
||||
rWrite8(0x03+c.chan*16,(rRead8(0x03+c.chan*16)&0xe8)|(chan[c.chan].duty&7));
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+chan[c.chan].sampleNoteDelta+((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_NDS));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
if (chan[c.chan].pcm || (c.chan<8)) {
|
||||
chan[c.chan].audPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 127;
|
||||
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 DivPlatformNDS::writeOutVol(int ch) {
|
||||
unsigned char val=isMuted[ch]?0:chan[ch].outVol;
|
||||
rWrite8(0x00+ch*16,val);
|
||||
}
|
||||
|
||||
void DivPlatformNDS::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
writeOutVol(ch);
|
||||
}
|
||||
|
||||
void DivPlatformNDS::forceIns() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
|
||||
rWrite8(0x02+i*16,chan[i].panning);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformNDS::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivMacroInt* DivPlatformNDS::getChanMacroInt(int ch) {
|
||||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformNDS::getPan(int ch) {
|
||||
return parent->convertPanLinearToSplit(chan[ch].panning,8,127);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformNDS::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
void DivPlatformNDS::reset() {
|
||||
memset(regPool,0,288);
|
||||
nds.reset();
|
||||
globalVolume=0x7f;
|
||||
rWrite32(0x100,0x8000|globalVolume); // enable keyon
|
||||
rWrite32(0x104,0x200); // initialize bias
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i]=DivPlatformNDS::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
rWrite32(0x00+i*16,0x40007f);
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformNDS::getOutputCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
void DivPlatformNDS::notifyInsChange(int ins) {
|
||||
for (int i=0; i<16; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNDS::notifyWaveChange(int wave) {
|
||||
// TODO when wavetables are added
|
||||
// TODO they probably won't be added unless the samples reside in RAM
|
||||
}
|
||||
|
||||
void DivPlatformNDS::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNDS::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite8(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformNDS::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite8(i.addr,i.val);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformNDS::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformNDS::getRegisterPoolSize() {
|
||||
return 288;
|
||||
}
|
||||
|
||||
float DivPlatformNDS::getPostAmp() {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const void* DivPlatformNDS::getSampleMem(int index) {
|
||||
return index == 0 ? sampleMem : NULL;
|
||||
}
|
||||
|
||||
size_t DivPlatformNDS::getSampleMemCapacity(int index) {
|
||||
return index == 0 ? (isDSi?16777216:4194304) : 0;
|
||||
}
|
||||
|
||||
size_t DivPlatformNDS::getSampleMemUsage(int index) {
|
||||
return index == 0 ? sampleMemLen : 0;
|
||||
}
|
||||
|
||||
bool DivPlatformNDS::isSampleLoaded(int index, int sample) {
|
||||
if (index!=0) return false;
|
||||
if (sample<0 || sample>255) return false;
|
||||
return sampleLoaded[sample];
|
||||
}
|
||||
|
||||
const DivMemoryComposition* DivPlatformNDS::getMemCompo(int index) {
|
||||
if (index!=0) return NULL;
|
||||
return &memCompo;
|
||||
}
|
||||
|
||||
void DivPlatformNDS::renderSamples(int sysID) {
|
||||
memset(sampleMem,0,16777216);
|
||||
memset(sampleOff,0,256*sizeof(unsigned int));
|
||||
memset(sampleLoaded,0,256*sizeof(bool));
|
||||
|
||||
memCompo=DivMemoryComposition();
|
||||
memCompo.name="Main Memory";
|
||||
|
||||
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=MIN(16777212,s->getCurBufLen());
|
||||
unsigned char* src=(unsigned char*)s->getCurBuf();
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length);
|
||||
if (actualLength>0) {
|
||||
memcpy(&sampleMem[memPos],src,actualLength);
|
||||
sampleOff[i]=memPos;
|
||||
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength));
|
||||
memPos+=actualLength;
|
||||
}
|
||||
if (actualLength<length) {
|
||||
logW("out of NDS PCM memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
// align memPos to int
|
||||
memPos=(memPos+3)&~3;
|
||||
sampleLoaded[i]=true;
|
||||
}
|
||||
sampleMemLen=memPos;
|
||||
|
||||
memCompo.capacity=(isDSi?16777216:4194304);
|
||||
memCompo.used=sampleMemLen;
|
||||
}
|
||||
|
||||
void DivPlatformNDS::setFlags(const DivConfig& flags) {
|
||||
isDSi=flags.getBool("chipType",0);
|
||||
chipClock=33513982;
|
||||
rate=chipClock/2/CLOCK_DIVIDER;
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformNDS::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 unsigned char[16777216];
|
||||
sampleMemLen=0;
|
||||
nds.reset();
|
||||
setFlags(flags);
|
||||
reset();
|
||||
|
||||
return 16;
|
||||
}
|
||||
|
||||
void DivPlatformNDS::quit() {
|
||||
delete[] sampleMem;
|
||||
for (int i=0; i<16; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
104
src/engine/platform/nds.h
Normal file
104
src/engine/platform/nds.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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 _NDS_H
|
||||
#define _NDS_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "sound/nds.hpp"
|
||||
|
||||
using namespace nds_sound_emu;
|
||||
|
||||
class DivPlatformNDS: public DivDispatch, public nds_sound_intf {
|
||||
struct Channel: public SharedChannel<int> {
|
||||
unsigned int audPos;
|
||||
int sample, wave;
|
||||
int panning, duty;
|
||||
bool setPos, pcm, busy;
|
||||
int macroVolMul;
|
||||
Channel():
|
||||
SharedChannel<int>(127),
|
||||
audPos(0),
|
||||
sample(-1),
|
||||
wave(-1),
|
||||
panning(64),
|
||||
duty(0),
|
||||
setPos(false),
|
||||
pcm(false),
|
||||
busy(false),
|
||||
macroVolMul(64) {}
|
||||
};
|
||||
Channel chan[16];
|
||||
DivDispatchOscBuffer* oscBuf[16];
|
||||
bool isMuted[16];
|
||||
bool isDSi;
|
||||
int globalVolume;
|
||||
unsigned int sampleOff[256];
|
||||
bool sampleLoaded[256];
|
||||
|
||||
unsigned char* sampleMem;
|
||||
size_t sampleMemLen;
|
||||
nds_sound_t nds;
|
||||
DivMemoryComposition memCompo;
|
||||
unsigned char regPool[288];
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
virtual u8 read_byte(u32 addr) override;
|
||||
virtual void write_byte(u32 addr, u8 data) override;
|
||||
|
||||
virtual void acquire(short** buf, size_t len) override;
|
||||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
virtual unsigned short getPan(int chan) override;
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
virtual void reset() override;
|
||||
virtual void forceIns() override;
|
||||
virtual void tick(bool sysTick=true) override;
|
||||
virtual void muteChannel(int ch, bool mute) override;
|
||||
virtual float getPostAmp() override;
|
||||
virtual int getOutputCount() override;
|
||||
virtual void notifyInsChange(int ins) override;
|
||||
virtual void notifyWaveChange(int wave) override;
|
||||
virtual void notifyInsDeletion(void* ins) override;
|
||||
virtual void poke(unsigned int addr, unsigned short val) override;
|
||||
virtual void poke(std::vector<DivRegWrite>& wlist) override;
|
||||
virtual const char** getRegisterSheet() override;
|
||||
virtual const void* getSampleMem(int index = 0) override;
|
||||
virtual size_t getSampleMemCapacity(int index = 0) override;
|
||||
virtual size_t getSampleMemUsage(int index = 0) override;
|
||||
virtual bool isSampleLoaded(int index, int sample) override;
|
||||
virtual const DivMemoryComposition* getMemCompo(int index) override;
|
||||
virtual void renderSamples(int chipID) override;
|
||||
virtual void setFlags(const DivConfig& flags) override;
|
||||
virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override;
|
||||
virtual void quit() override;
|
||||
DivPlatformNDS():
|
||||
DivDispatch(),
|
||||
nds_sound_intf(),
|
||||
nds(*this) {}
|
||||
private:
|
||||
void writeOutVol(int ch);
|
||||
};
|
||||
|
||||
#endif
|
634
src/engine/platform/sound/nds.cpp
Normal file
634
src/engine/platform/sound/nds.cpp
Normal file
|
@ -0,0 +1,634 @@
|
|||
/*
|
||||
|
||||
============================================================================
|
||||
|
||||
NDS sound emulator
|
||||
by cam900
|
||||
|
||||
This file is licensed under zlib license.
|
||||
|
||||
============================================================================
|
||||
|
||||
zlib License
|
||||
|
||||
(C) 2024-present cam900 and contributors
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
============================================================================
|
||||
TODO:
|
||||
- needs to further verifications from real hardware
|
||||
|
||||
Tech info: https://problemkaputt.de/gbatek.htm
|
||||
|
||||
*/
|
||||
|
||||
#include "nds.hpp"
|
||||
|
||||
namespace nds_sound_emu
|
||||
{
|
||||
void nds_sound_t::reset()
|
||||
{
|
||||
for (channel_t &elem : m_channel)
|
||||
elem.reset();
|
||||
for (capture_t &elem : m_capture)
|
||||
elem.reset();
|
||||
|
||||
m_control = 0;
|
||||
m_bias = 0;
|
||||
m_loutput = 0;
|
||||
m_routput = 0;
|
||||
}
|
||||
|
||||
void nds_sound_t::tick(s32 cycle)
|
||||
{
|
||||
m_loutput = m_routput = (m_bias & 0x3ff);
|
||||
if (!enable())
|
||||
return;
|
||||
|
||||
// mix outputs
|
||||
s32 lmix = 0, rmix = 0;
|
||||
for (u8 i = 0; i < 16; i++)
|
||||
{
|
||||
channel_t &channel = m_channel[i];
|
||||
channel.update(cycle);
|
||||
// bypass mixer
|
||||
if (((i == 1) && (mix_ch1())) || ((i == 3) && (mix_ch3())))
|
||||
continue;
|
||||
|
||||
lmix += channel.loutput();
|
||||
rmix += channel.routput();
|
||||
}
|
||||
|
||||
// send mixer output to capture
|
||||
m_capture[0].update(lmix, cycle);
|
||||
m_capture[1].update(rmix, cycle);
|
||||
|
||||
// select left/right output source
|
||||
switch (lout_from())
|
||||
{
|
||||
case 0: // left mixer
|
||||
break;
|
||||
case 1: // channel 1
|
||||
lmix = m_channel[1].loutput();
|
||||
break;
|
||||
case 2: // channel 3
|
||||
lmix = m_channel[3].loutput();
|
||||
break;
|
||||
case 3: // channel 1 + 3
|
||||
lmix = m_channel[1].loutput() + m_channel[3].loutput();
|
||||
break;
|
||||
}
|
||||
|
||||
switch (rout_from())
|
||||
{
|
||||
case 0: // right mixer
|
||||
break;
|
||||
case 1: // channel 1
|
||||
rmix = m_channel[1].routput();
|
||||
break;
|
||||
case 2: // channel 3
|
||||
rmix = m_channel[3].routput();
|
||||
break;
|
||||
case 3: // channel 1 + 3
|
||||
rmix = m_channel[1].routput() + m_channel[3].routput();
|
||||
break;
|
||||
}
|
||||
|
||||
// adjust master volume
|
||||
lmix = (lmix * mvol()) >> 13;
|
||||
rmix = (rmix * mvol()) >> 13;
|
||||
|
||||
// add bias and clip output
|
||||
m_loutput = clamp<s32>((lmix + (m_bias & 0x3ff)), 0, 0x3ff);
|
||||
m_routput = clamp<s32>((rmix + (m_bias & 0x3ff)), 0, 0x3ff);
|
||||
}
|
||||
|
||||
u8 nds_sound_t::read8(u32 addr)
|
||||
{
|
||||
return bitfield(read32(addr >> 2), bitfield(addr, 0, 2) << 3, 8);
|
||||
}
|
||||
|
||||
u16 nds_sound_t::read16(u32 addr)
|
||||
{
|
||||
return bitfield(read32(addr >> 1), bitfield(addr, 0) << 4, 16);
|
||||
}
|
||||
|
||||
u32 nds_sound_t::read32(u32 addr)
|
||||
{
|
||||
addr <<= 2; // word address
|
||||
|
||||
switch (addr & 0x100)
|
||||
{
|
||||
case 0x000:
|
||||
if ((addr & 0xc) == 0)
|
||||
return m_channel[bitfield(addr, 4, 4)].control();
|
||||
break;
|
||||
case 0x100:
|
||||
switch (addr & 0xff)
|
||||
{
|
||||
case 0x00:
|
||||
return m_control;
|
||||
case 0x04:
|
||||
return m_bias;
|
||||
case 0x08:
|
||||
return m_capture[0].control() | (m_capture[1].control() << 8);
|
||||
case 0x10:
|
||||
case 0x18:
|
||||
return m_capture[bitfield(addr, 3)].dstaddr();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nds_sound_t::write8(u32 addr, u8 data)
|
||||
{
|
||||
const u8 bit = bitfield(addr, 0, 2);
|
||||
const u32 in = u32(data) << (bit << 3);
|
||||
const u32 in_mask = 0xff << (bit << 3);
|
||||
write32(addr >> 2, in, in_mask);
|
||||
}
|
||||
|
||||
void nds_sound_t::write16(u32 addr, u16 data, u16 mask)
|
||||
{
|
||||
const u8 bit = bitfield(addr, 0);
|
||||
const u32 in = u32(data) << (bit << 4);
|
||||
const u32 in_mask = u32(mask) << (bit << 4);
|
||||
write32(addr >> 1, in, in_mask);
|
||||
}
|
||||
|
||||
void nds_sound_t::write32(u32 addr, u32 data, u32 mask)
|
||||
{
|
||||
addr <<= 2; // word address
|
||||
|
||||
switch (addr & 0x100)
|
||||
{
|
||||
case 0x000:
|
||||
m_channel[bitfield(addr, 4, 4)].write(bitfield(addr, 2, 2), data, mask);
|
||||
break;
|
||||
case 0x100:
|
||||
switch (addr & 0xff)
|
||||
{
|
||||
case 0x00:
|
||||
m_control = (m_control & ~mask) | (data & mask);
|
||||
break;
|
||||
case 0x04:
|
||||
mask &= 0x3ff;
|
||||
m_bias = (m_bias & ~mask) | (data & mask);
|
||||
break;
|
||||
case 0x08:
|
||||
if (bitfield(mask, 0, 8))
|
||||
m_capture[0].control_w(data & 0xff);
|
||||
if (bitfield(mask, 8, 8))
|
||||
m_capture[1].control_w((data >> 8) & 0xff);
|
||||
break;
|
||||
case 0x10:
|
||||
case 0x14:
|
||||
case 0x18:
|
||||
case 0x1c:
|
||||
m_capture[bitfield(addr, 3)].addrlen_w(bitfield(addr, 2), data, mask);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// channels
|
||||
void nds_sound_t::channel_t::reset()
|
||||
{
|
||||
m_control = 0;
|
||||
m_sourceaddr = 0;
|
||||
m_freq = 0;
|
||||
m_loopstart = 0;
|
||||
m_length = 0;
|
||||
|
||||
m_playing = false;
|
||||
m_adpcm_out = 0;
|
||||
m_adpcm_index = 0;
|
||||
m_prev_adpcm_out = 0;
|
||||
m_prev_adpcm_index = 0;
|
||||
m_cur_addr = 0;
|
||||
m_cur_state = 0;
|
||||
m_cur_bitaddr = 0;
|
||||
m_delay = 0;
|
||||
m_sample = 0;
|
||||
m_lfsr = 0x7fff;
|
||||
m_lfsr_out = 0x7fff;
|
||||
m_counter = 0x10000;
|
||||
m_output = 0;
|
||||
m_loutput = 0;
|
||||
m_routput = 0;
|
||||
}
|
||||
|
||||
void nds_sound_t::channel_t::write(u32 offset, u32 data, u32 mask)
|
||||
{
|
||||
const u32 old = m_control;
|
||||
switch (offset & 3)
|
||||
{
|
||||
case 0: // Control/Status
|
||||
m_control = (m_control & ~mask) | (data & mask);
|
||||
if (bitfield(old ^ m_control, 31))
|
||||
{
|
||||
if (busy())
|
||||
keyon();
|
||||
else if (!busy())
|
||||
keyoff();
|
||||
}
|
||||
// reset hold flag
|
||||
if (!m_playing && !hold())
|
||||
{
|
||||
m_sample = m_lfsr_out = 0;
|
||||
m_output = m_loutput = m_routput = 0;
|
||||
}
|
||||
break;
|
||||
case 1: // Source address
|
||||
mask &= 0x7ffffff;
|
||||
m_sourceaddr = (m_sourceaddr & ~mask) | (data & mask);
|
||||
break;
|
||||
case 2: // Frequency, Loopstart
|
||||
if (bitfield(mask, 0, 16))
|
||||
m_freq = (m_freq & bitfield(~mask, 0, 16)) | (bitfield(data & mask, 0, 16));
|
||||
if (bitfield(mask, 16, 16))
|
||||
m_loopstart = (m_loopstart & bitfield(~mask, 16, 16)) | (bitfield(data & mask, 16, 16));
|
||||
break;
|
||||
case 3: // Length
|
||||
mask &= 0x3fffff;
|
||||
m_length = (m_length & ~mask) | (data & mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void nds_sound_t::channel_t::keyon()
|
||||
{
|
||||
if (!m_playing)
|
||||
{
|
||||
m_playing = true;
|
||||
m_delay = format() == 2 ? 11 : 3; // 3 (11 for ADPCM) delay for playing sample
|
||||
m_cur_bitaddr = m_cur_addr = 0;
|
||||
m_cur_state = (format() == 2) ? STATE_ADPCM_LOAD : ((m_loopstart == 0) ? STATE_POST_LOOP : STATE_PRE_LOOP);
|
||||
m_counter = 0x10000;
|
||||
m_sample = 0;
|
||||
m_lfsr_out = 0x7fff;
|
||||
m_lfsr = 0x7fff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void nds_sound_t::channel_t::keyoff()
|
||||
{
|
||||
if (m_playing)
|
||||
{
|
||||
if (busy())
|
||||
m_control &= ~(1 << 31);
|
||||
if (!hold())
|
||||
{
|
||||
m_sample = m_lfsr_out = 0;
|
||||
m_output = m_loutput = m_routput = 0;
|
||||
}
|
||||
|
||||
m_playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
void nds_sound_t::channel_t::update(s32 cycle)
|
||||
{
|
||||
if (m_playing)
|
||||
{
|
||||
// get output
|
||||
fetch();
|
||||
m_counter -= cycle;
|
||||
while (m_counter <= m_freq)
|
||||
{
|
||||
// advance
|
||||
advance();
|
||||
m_counter += 0x10000 - m_freq;
|
||||
}
|
||||
m_output = (m_sample * volume()) >> (7 + voldiv());
|
||||
m_loutput = (m_output * lvol()) >> 7;
|
||||
m_routput = (m_output * rvol()) >> 7;
|
||||
}
|
||||
}
|
||||
|
||||
void nds_sound_t::channel_t::fetch()
|
||||
{
|
||||
if (m_playing)
|
||||
{
|
||||
// fetch samples
|
||||
switch (format())
|
||||
{
|
||||
case 0: // PCM8
|
||||
m_sample = s16(m_host.m_intf.read_byte(addr()) << 8);
|
||||
break;
|
||||
case 1: // PCM16
|
||||
m_sample = m_host.m_intf.read_word(addr());
|
||||
break;
|
||||
case 2: // ADPCM
|
||||
m_sample = m_cur_state == STATE_ADPCM_LOAD ? 0 : m_adpcm_out;
|
||||
break;
|
||||
case 3: // PSG or Noise
|
||||
m_sample = 0;
|
||||
if (m_psg) // psg
|
||||
m_sample = (duty() == 7) ? -0x8000 : ((m_cur_bitaddr < s32(u32(7) - duty())) ? -0x8000 : 0x7fff);
|
||||
else if (m_noise) // noise
|
||||
m_sample = m_lfsr_out;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// apply delay
|
||||
if (format() != 3 && m_delay > 0)
|
||||
m_sample = 0;
|
||||
}
|
||||
|
||||
void nds_sound_t::channel_t::advance()
|
||||
{
|
||||
if (m_playing)
|
||||
{
|
||||
// advance bit address
|
||||
switch (format())
|
||||
{
|
||||
case 0: // PCM8
|
||||
m_cur_bitaddr += 8;
|
||||
break;
|
||||
case 1: // PCM16
|
||||
m_cur_bitaddr += 16;
|
||||
break;
|
||||
case 2: // ADPCM
|
||||
if (m_cur_state == STATE_ADPCM_LOAD) // load ADPCM data
|
||||
{
|
||||
if (m_cur_bitaddr == 0)
|
||||
m_prev_adpcm_out = m_adpcm_out = m_host.m_intf.read_word(addr());
|
||||
if (m_cur_bitaddr == 16)
|
||||
m_prev_adpcm_index = m_adpcm_index = clamp<s32>(m_host.m_intf.read_byte(addr()) & 0x7f, 0, 88);
|
||||
}
|
||||
else // decode ADPCM
|
||||
{
|
||||
const u8 input = bitfield(m_host.m_intf.read_byte(addr()), m_cur_bitaddr & 4, 4);
|
||||
s32 diff = ((bitfield(input, 0, 3) * 2 + 1) * m_host.adpcm_diff_table[m_adpcm_index] / 8);
|
||||
if (bitfield(input, 3)) diff = -diff;
|
||||
m_adpcm_out = clamp<s32>(m_adpcm_out + diff, -0x8000, 0x7fff);
|
||||
m_adpcm_index = clamp<s32>(m_adpcm_index + m_host.adpcm_index_table[bitfield(input, 0, 3)], 0, 88);
|
||||
}
|
||||
m_cur_bitaddr += 4;
|
||||
break;
|
||||
case 3: // PSG or Noise
|
||||
if (m_psg) // psg
|
||||
m_cur_bitaddr = (m_cur_bitaddr + 1) & 7;
|
||||
else if (m_noise) // noise
|
||||
{
|
||||
if (bitfield(m_lfsr, 1))
|
||||
{
|
||||
m_lfsr = (m_lfsr >> 1) ^ 0x6000;
|
||||
m_lfsr_out = -0x8000;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lfsr >>= 1;
|
||||
m_lfsr_out = 0x7fff;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// address update
|
||||
if (format() != 3)
|
||||
{
|
||||
// adjust delay
|
||||
m_delay--;
|
||||
|
||||
// update address, loop
|
||||
while (m_cur_bitaddr >= 32)
|
||||
{
|
||||
// already loaded?
|
||||
if (format() == 2 && m_cur_state == STATE_ADPCM_LOAD)
|
||||
{
|
||||
m_cur_state = m_loopstart == 0 ? STATE_POST_LOOP : STATE_PRE_LOOP;
|
||||
}
|
||||
m_cur_addr++;
|
||||
if (m_cur_state == STATE_PRE_LOOP && m_cur_addr >= m_loopstart)
|
||||
{
|
||||
m_cur_state = STATE_POST_LOOP;
|
||||
m_cur_addr = 0;
|
||||
if (format() == 2)
|
||||
{
|
||||
m_prev_adpcm_out = m_adpcm_out;
|
||||
m_prev_adpcm_index = m_adpcm_index;
|
||||
}
|
||||
}
|
||||
else if (m_cur_state == STATE_POST_LOOP && m_cur_addr >= m_length)
|
||||
{
|
||||
switch (repeat())
|
||||
{
|
||||
case 0: // manual; not correct?
|
||||
case 2: // one-shot
|
||||
case 3: // prohibited
|
||||
keyoff();
|
||||
break;
|
||||
case 1: // loop infinitely
|
||||
if (format() == 2)
|
||||
{
|
||||
if (m_loopstart == 0) // reload ADPCM
|
||||
{
|
||||
m_cur_state = STATE_ADPCM_LOAD;
|
||||
}
|
||||
else // restore
|
||||
{
|
||||
m_adpcm_out = m_prev_adpcm_out;
|
||||
m_adpcm_index = m_prev_adpcm_index;
|
||||
}
|
||||
}
|
||||
m_cur_addr = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_cur_bitaddr -= 32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// capture
|
||||
void nds_sound_t::capture_t::reset()
|
||||
{
|
||||
m_control = 0;
|
||||
m_dstaddr = 0;
|
||||
m_length = 0;
|
||||
|
||||
m_counter = 0x10000;
|
||||
m_cur_addr = 0;
|
||||
m_cur_waddr = 0;
|
||||
m_cur_bitaddr = 0;
|
||||
m_enable = false;
|
||||
}
|
||||
|
||||
void nds_sound_t::capture_t::control_w(u8 data)
|
||||
{
|
||||
const u8 old = m_control;
|
||||
m_control = data;
|
||||
if (bitfield(old ^ m_control, 7))
|
||||
{
|
||||
if (busy())
|
||||
capture_on();
|
||||
else if (!busy())
|
||||
capture_off();
|
||||
}
|
||||
}
|
||||
|
||||
void nds_sound_t::capture_t::addrlen_w(u32 offset, u32 data, u32 mask)
|
||||
{
|
||||
switch (offset & 1)
|
||||
{
|
||||
case 0: // Destination Address
|
||||
mask &= 0x7ffffff;
|
||||
m_dstaddr = (m_dstaddr & ~mask) | (data & mask);
|
||||
break;
|
||||
case 1: // Buffer Length
|
||||
mask &= 0xffff;
|
||||
m_length = (m_length & ~mask) | (data & mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void nds_sound_t::capture_t::update(s32 mix, s32 cycle)
|
||||
{
|
||||
if (m_enable)
|
||||
{
|
||||
s32 inval = 0;
|
||||
// get inputs
|
||||
// TODO: hardware bugs aren't emulated, add mode behavior not verified
|
||||
if (addmode())
|
||||
inval = get_source() ? m_input.output() + m_output.output() : mix;
|
||||
else
|
||||
inval = get_source() ? m_input.output() : mix;
|
||||
|
||||
// clip output
|
||||
inval = clamp<s32>(inval, -0x8000, 0x7fff);
|
||||
|
||||
// update counter
|
||||
m_counter -= cycle;
|
||||
while (m_counter <= m_output.freq())
|
||||
{
|
||||
// write to memory; TODO: verify write behavior
|
||||
if (format()) // 8 bit output
|
||||
{
|
||||
m_fifo[m_fifo_head & 7].write_byte(m_cur_bitaddr & 0x18, (inval >> 8) & 0xff);
|
||||
m_cur_bitaddr += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fifo[m_fifo_head & 7].write_word(m_cur_bitaddr & 0x10, inval & 0xffff);
|
||||
m_cur_bitaddr += 16;
|
||||
}
|
||||
|
||||
// update address
|
||||
while (m_cur_bitaddr >= 32)
|
||||
{
|
||||
// clear FIFO empty flag
|
||||
m_fifo_empty = false;
|
||||
|
||||
// advance FIFO head position
|
||||
m_fifo_head = (m_fifo_head + 1) & 7;
|
||||
if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask()))
|
||||
m_fifo_full = true;
|
||||
|
||||
// update loop
|
||||
if (++m_cur_addr >= m_length)
|
||||
{
|
||||
if (repeat())
|
||||
m_cur_addr = 0;
|
||||
else
|
||||
capture_off();
|
||||
}
|
||||
|
||||
if (m_fifo_full)
|
||||
{
|
||||
// execute FIFO
|
||||
fifo_write();
|
||||
|
||||
// check repeat
|
||||
if (m_cur_waddr >= m_length && repeat())
|
||||
m_cur_waddr = 0;
|
||||
}
|
||||
|
||||
m_cur_bitaddr -= 32;
|
||||
}
|
||||
m_counter += 0x10000 - m_output.freq();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool nds_sound_t::capture_t::fifo_write()
|
||||
{
|
||||
if (m_fifo_empty)
|
||||
return true;
|
||||
|
||||
// clear FIFO full flag
|
||||
m_fifo_full = false;
|
||||
|
||||
// write FIFO data to memory
|
||||
m_host.m_intf.write_dword(waddr(), m_fifo[m_fifo_tail].data());
|
||||
m_cur_waddr++;
|
||||
|
||||
// advance FIFO tail position
|
||||
m_fifo_tail = (m_fifo_tail + 1) & 7;
|
||||
if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask()))
|
||||
m_fifo_empty = true;
|
||||
|
||||
return m_fifo_empty;
|
||||
}
|
||||
|
||||
void nds_sound_t::capture_t::capture_on()
|
||||
{
|
||||
if (!m_enable)
|
||||
{
|
||||
m_enable = true;
|
||||
|
||||
// reset address
|
||||
m_cur_bitaddr = 0;
|
||||
m_cur_addr = m_cur_waddr = 0;
|
||||
m_counter = 0x10000;
|
||||
|
||||
// reset FIFO
|
||||
m_fifo_head = m_fifo_tail = 0;
|
||||
m_fifo_empty = true;
|
||||
m_fifo_full = false;
|
||||
}
|
||||
}
|
||||
|
||||
void nds_sound_t::capture_t::capture_off()
|
||||
{
|
||||
if (m_enable)
|
||||
{
|
||||
// flush FIFO
|
||||
while (m_cur_waddr < m_length)
|
||||
{
|
||||
if (fifo_write())
|
||||
break;
|
||||
}
|
||||
|
||||
m_enable = false;
|
||||
if (busy())
|
||||
m_control &= ~(1 << 7);
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace nds_sound_emu
|
415
src/engine/platform/sound/nds.hpp
Normal file
415
src/engine/platform/sound/nds.hpp
Normal file
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
|
||||
============================================================================
|
||||
|
||||
NDS sound emulator
|
||||
by cam900
|
||||
|
||||
This file is licensed under zlib license.
|
||||
|
||||
============================================================================
|
||||
|
||||
zlib License
|
||||
|
||||
(C) 2024-present cam900 and contributors
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
============================================================================
|
||||
TODO:
|
||||
- needs to further verifications from real hardware
|
||||
|
||||
Tech info: https://problemkaputt.de/gbatek.htm
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NDS_SOUND_EMU_H
|
||||
#define NDS_SOUND_EMU_H
|
||||
|
||||
namespace nds_sound_emu
|
||||
{
|
||||
using u8 = unsigned char;
|
||||
using u16 = unsigned short;
|
||||
using u32 = unsigned int;
|
||||
using u64 = unsigned long long;
|
||||
using s8 = signed char;
|
||||
using s16 = signed short;
|
||||
using s32 = signed int;
|
||||
using s64 = signed long long;
|
||||
|
||||
template<typename T>
|
||||
static const inline T bitfield(const T in, const u8 pos)
|
||||
{
|
||||
return (in >> pos) & 1;
|
||||
} // bitfield
|
||||
|
||||
template<typename T>
|
||||
static const inline T bitfield(const T in, const u8 pos, const u8 len)
|
||||
{
|
||||
return (in >> pos) & ((1 << len) - 1);
|
||||
} // bitfield
|
||||
|
||||
template<typename T>
|
||||
static const inline T clamp(const T in, const T min, const T max)
|
||||
{
|
||||
return (in < min) ? min : ((in > max) ? max : in);
|
||||
} // clamp
|
||||
|
||||
class nds_sound_intf
|
||||
{
|
||||
public:
|
||||
nds_sound_intf()
|
||||
{
|
||||
}
|
||||
|
||||
virtual u8 read_byte(u32 addr) { return 0; }
|
||||
inline u16 read_word(u32 addr) { return read_byte(addr) | (u16(read_byte(addr + 1)) << 8); }
|
||||
inline u32 read_dword(u32 addr) { return read_word(addr) | (u16(read_word(addr + 2)) << 16); }
|
||||
|
||||
virtual void write_byte(u32 addr, u8 data) {}
|
||||
inline void write_word(u32 addr, u16 data)
|
||||
{
|
||||
write_byte(addr, data & 0xff);
|
||||
write_byte(addr + 1, data >> 8);
|
||||
}
|
||||
inline void write_dword(u32 addr, u32 data)
|
||||
{
|
||||
write_word(addr, data & 0xffff);
|
||||
write_word(addr + 2, data >> 16);
|
||||
}
|
||||
};
|
||||
|
||||
class nds_sound_t
|
||||
{
|
||||
public:
|
||||
nds_sound_t(nds_sound_intf &intf)
|
||||
: m_intf(intf)
|
||||
, m_channel{
|
||||
channel_t(*this, false, false), channel_t(*this, false, false),
|
||||
channel_t(*this, false, false), channel_t(*this, false, false),
|
||||
channel_t(*this, false, false), channel_t(*this, false, false),
|
||||
channel_t(*this, false, false), channel_t(*this, false, false),
|
||||
channel_t(*this, true, false), channel_t(*this, true, false),
|
||||
channel_t(*this, true, false), channel_t(*this, true, false),
|
||||
channel_t(*this, true, false), channel_t(*this, true, false),
|
||||
channel_t(*this, false, true), channel_t(*this, false, true)
|
||||
}
|
||||
, m_capture{
|
||||
capture_t(*this, m_channel[0], m_channel[1]),
|
||||
capture_t(*this, m_channel[2], m_channel[3])
|
||||
}
|
||||
, m_control(0)
|
||||
, m_bias(0)
|
||||
, m_loutput(0)
|
||||
, m_routput(0)
|
||||
{
|
||||
}
|
||||
|
||||
void reset();
|
||||
void tick(s32 cycle);
|
||||
|
||||
// host accesses
|
||||
u32 read32(u32 addr);
|
||||
void write32(u32 addr, u32 data, u32 mask = ~0);
|
||||
|
||||
u16 read16(u32 addr);
|
||||
void write16(u32 addr, u16 data, u16 mask = ~0);
|
||||
|
||||
u8 read8(u32 addr);
|
||||
void write8(u32 addr, u8 data);
|
||||
|
||||
s32 loutput() { return m_loutput; }
|
||||
s32 routput() { return m_routput; }
|
||||
|
||||
// for debug
|
||||
s32 chan_lout(u8 ch) { return m_channel[ch].loutput(); }
|
||||
s32 chan_rout(u8 ch) { return m_channel[ch].routput(); }
|
||||
|
||||
private:
|
||||
// ADPCM tables
|
||||
s8 adpcm_index_table[8] =
|
||||
{
|
||||
-1, -1, -1, -1, 2, 4, 6, 8
|
||||
};
|
||||
|
||||
u16 adpcm_diff_table[89] =
|
||||
{
|
||||
0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x0010,
|
||||
0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001c, 0x001f, 0x0022, 0x0025,
|
||||
0x0029, 0x002d, 0x0032, 0x0037, 0x003c, 0x0042, 0x0049, 0x0050, 0x0058,
|
||||
0x0061, 0x006b, 0x0076, 0x0082, 0x008f, 0x009d, 0x00ad, 0x00be, 0x00d1,
|
||||
0x00e6, 0x00fd, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01c1, 0x01ee,
|
||||
0x0220, 0x0256, 0x0292, 0x02d4, 0x031c, 0x036c, 0x03c3, 0x0424, 0x048e,
|
||||
0x0502, 0x0583, 0x0610, 0x06ab, 0x0756, 0x0812, 0x08e0, 0x09c3, 0x0abd,
|
||||
0x0bd0, 0x0cff, 0x0e4c, 0x0fba, 0x114c, 0x1307, 0x14ee, 0x1706, 0x1954,
|
||||
0x1bdc, 0x1ea5, 0x21b6, 0x2515, 0x28ca, 0x2cdf, 0x315b, 0x364b, 0x3bb9,
|
||||
0x41b2, 0x4844, 0x4f7e, 0x5771, 0x602f, 0x69ce, 0x7462, 0x7fff
|
||||
};
|
||||
|
||||
// structs
|
||||
enum
|
||||
{
|
||||
STATE_ADPCM_LOAD = 0,
|
||||
STATE_PRE_LOOP,
|
||||
STATE_POST_LOOP
|
||||
};
|
||||
|
||||
class channel_t
|
||||
{
|
||||
public:
|
||||
channel_t(nds_sound_t &host, bool psg, bool noise)
|
||||
: m_host(host)
|
||||
|
||||
, m_psg(psg)
|
||||
, m_noise(noise)
|
||||
|
||||
, m_control(0)
|
||||
, m_sourceaddr(0)
|
||||
, m_freq(0)
|
||||
, m_loopstart(0)
|
||||
, m_length(0)
|
||||
, m_playing(false)
|
||||
, m_adpcm_out(0)
|
||||
, m_adpcm_index(0)
|
||||
, m_prev_adpcm_out(0)
|
||||
, m_prev_adpcm_index(0)
|
||||
, m_cur_addr(0)
|
||||
, m_cur_state(0)
|
||||
, m_cur_bitaddr(0)
|
||||
, m_delay(0)
|
||||
, m_sample(0)
|
||||
, m_lfsr(0x7fff)
|
||||
, m_lfsr_out(0x7fff)
|
||||
, m_counter(0x10000)
|
||||
, m_output(0)
|
||||
, m_loutput(0)
|
||||
, m_routput(0)
|
||||
{
|
||||
}
|
||||
|
||||
void reset();
|
||||
void write(u32 offset, u32 data, u32 mask = ~0);
|
||||
|
||||
void update(s32 cycle);
|
||||
|
||||
// getters
|
||||
// control word
|
||||
u32 control() const { return m_control; }
|
||||
u32 freq() const { return m_freq; }
|
||||
|
||||
// outputs
|
||||
s32 output() const { return m_output; }
|
||||
s32 loutput() const { return m_loutput; }
|
||||
s32 routput() const { return m_routput; }
|
||||
|
||||
private:
|
||||
// inline constants
|
||||
const u8 m_voldiv_shift[4] = {0, 1, 2, 4};
|
||||
|
||||
// control bits
|
||||
s32 volume() const { return bitfield(m_control, 0, 7); } // global volume
|
||||
u32 voldiv() const { return m_voldiv_shift[bitfield(m_control, 8, 2)]; } // volume shift
|
||||
bool hold() const { return bitfield(m_control, 15); } // hold bit
|
||||
u32 pan() const { return bitfield(m_control, 16, 7); } // panning (0...127, 0 = left, 127 = right, 64 = half)
|
||||
u32 duty() const { return bitfield(m_control, 24, 3); } // PSG duty
|
||||
u32 repeat() const { return bitfield(m_control, 27, 2); } // Repeat mode (Manual, Loop infinitely, One-shot)
|
||||
u32 format() const { return bitfield(m_control, 29, 2); } // Sound Format (PCM8, PCM16, ADPCM, PSG/Noise when exists)
|
||||
bool busy() const { return bitfield(m_control, 31); } // Busy flag
|
||||
|
||||
// calculated values
|
||||
s32 lvol() const { return (pan() == 0x7f) ? 0 : 128 - pan(); } // calculated left volume
|
||||
s32 rvol() const { return (pan() == 0x7f) ? 128 : pan(); } // calculated right volume
|
||||
|
||||
// calculated address
|
||||
u32 addr() const { return (m_sourceaddr & ~3) + (m_cur_bitaddr >> 3) + (m_cur_state == STATE_POST_LOOP ? ((m_loopstart + m_cur_addr) << 2) : (m_cur_addr << 2)); }
|
||||
|
||||
void keyon();
|
||||
void keyoff();
|
||||
void fetch();
|
||||
void advance();
|
||||
|
||||
// interfaces
|
||||
nds_sound_t &m_host; // host device
|
||||
|
||||
// configuration
|
||||
bool m_psg = false; // PSG Enable
|
||||
bool m_noise = false; // Noise Enable
|
||||
|
||||
// registers
|
||||
u32 m_control = 0; // Control
|
||||
u32 m_sourceaddr = 0; // Source Address
|
||||
u16 m_freq = 0; // Frequency
|
||||
u16 m_loopstart = 0; // Loop Start
|
||||
u32 m_length = 0; // Length
|
||||
|
||||
// internal states
|
||||
bool m_playing = false; // playing flag
|
||||
s32 m_adpcm_out = 0; // current ADPCM sample value
|
||||
s32 m_adpcm_index = 0; // current ADPCM step
|
||||
s32 m_prev_adpcm_out = 0; // previous ADPCM sample value
|
||||
s32 m_prev_adpcm_index = 0; // previous ADPCM step
|
||||
u32 m_cur_addr = 0; // current address
|
||||
s32 m_cur_state = 0; // current state
|
||||
s32 m_cur_bitaddr = 0; // bit address
|
||||
s32 m_delay = 0; // delay
|
||||
s16 m_sample = 0; // current sample
|
||||
u32 m_lfsr = 0x7fff; // noise LFSR
|
||||
s16 m_lfsr_out = 0x7fff; // LFSR output
|
||||
s32 m_counter = 0x10000; // clock counter
|
||||
s32 m_output = 0; // current output
|
||||
s32 m_loutput = 0; // current left output
|
||||
s32 m_routput = 0; // current right output
|
||||
};
|
||||
|
||||
class capture_t
|
||||
{
|
||||
public:
|
||||
capture_t(nds_sound_t &host, channel_t &input, channel_t &output)
|
||||
: m_host(host)
|
||||
, m_input(input)
|
||||
, m_output(output)
|
||||
|
||||
, m_control(0)
|
||||
, m_dstaddr(0)
|
||||
, m_length(0)
|
||||
|
||||
, m_counter(0x10000)
|
||||
, m_cur_addr(0)
|
||||
, m_cur_waddr(0)
|
||||
, m_cur_bitaddr(0)
|
||||
, m_enable(0)
|
||||
|
||||
, m_fifo{
|
||||
fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t(),
|
||||
fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t()
|
||||
}
|
||||
, m_fifo_head(0)
|
||||
, m_fifo_tail(0)
|
||||
, m_fifo_empty(true)
|
||||
, m_fifo_full(false)
|
||||
{
|
||||
}
|
||||
|
||||
void reset();
|
||||
void update(s32 mix, s32 cycle);
|
||||
|
||||
void control_w(u8 data);
|
||||
void addrlen_w(u32 offset, u32 data, u32 mask = ~0);
|
||||
|
||||
// getters
|
||||
u32 control() const { return m_control; }
|
||||
u32 dstaddr() const { return m_dstaddr; }
|
||||
|
||||
private:
|
||||
// inline constants
|
||||
// control bits
|
||||
bool addmode() const { return bitfield(m_control, 0); } // Add mode (add channel 1/3 output with channel 0/2)
|
||||
bool get_source() const { return bitfield(m_control, 1); } // Select source (left or right mixer, channel 0/2)
|
||||
bool repeat() const { return bitfield(m_control, 2); } // repeat flag
|
||||
bool format() const { return bitfield(m_control, 3); } // store format (PCM16, PCM8)
|
||||
bool busy() const { return bitfield(m_control, 7); } // busy flag
|
||||
|
||||
// FIFO offset mask
|
||||
u32 fifo_mask() const { return format() ? 7 : 3; }
|
||||
|
||||
// calculated address
|
||||
u32 waddr() const { return (m_dstaddr & ~3) + (m_cur_waddr << 2); }
|
||||
|
||||
void capture_on();
|
||||
void capture_off();
|
||||
bool fifo_write();
|
||||
|
||||
// interfaces
|
||||
nds_sound_t &m_host; // host device
|
||||
channel_t &m_input; // Input channel
|
||||
channel_t &m_output; // Output channel
|
||||
|
||||
// registers
|
||||
u8 m_control = 0; // Control
|
||||
u32 m_dstaddr = 0; // Destination Address
|
||||
u32 m_length = 0; // Buffer Length
|
||||
|
||||
// internal states
|
||||
u32 m_counter = 0x10000; // clock counter
|
||||
u32 m_cur_addr = 0; // current address
|
||||
u32 m_cur_waddr = 0; // current write address
|
||||
s32 m_cur_bitaddr = 0; // bit address
|
||||
bool m_enable = false; // capture enable
|
||||
|
||||
// FIFO
|
||||
class fifo_data_t
|
||||
{
|
||||
public:
|
||||
fifo_data_t()
|
||||
: m_data(0)
|
||||
{
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_data = 0;
|
||||
}
|
||||
|
||||
// accessors
|
||||
void write_byte(const u8 bit, const u8 data)
|
||||
{
|
||||
u32 input = u32(data) << bit;
|
||||
u32 mask = (0xff << bit);
|
||||
m_data = (m_data & ~mask) | (input & mask);
|
||||
}
|
||||
|
||||
void write_word(const u8 bit, const u16 data)
|
||||
{
|
||||
u32 input = u32(data) << bit;
|
||||
u32 mask = (0xffff << bit);
|
||||
m_data = (m_data & ~mask) | (input & mask);
|
||||
}
|
||||
|
||||
// getters
|
||||
u32 data() const { return m_data; }
|
||||
|
||||
private:
|
||||
u32 m_data = 0;
|
||||
};
|
||||
|
||||
fifo_data_t m_fifo[8]; // FIFO (8 word, for 16 sample delay)
|
||||
u32 m_fifo_head = 0; // FIFO head
|
||||
u32 m_fifo_tail = 0; // FIFO tail
|
||||
bool m_fifo_empty = true; // FIFO empty flag
|
||||
bool m_fifo_full = false; // FIFO full flag
|
||||
};
|
||||
|
||||
nds_sound_intf &m_intf; // memory interface
|
||||
|
||||
channel_t m_channel[16]; // 16 channels
|
||||
capture_t m_capture[2]; // 2 capture channels
|
||||
|
||||
inline u8 mvol() const { return bitfield(m_control, 0, 7); } // master volume
|
||||
inline u8 lout_from() const { return bitfield(m_control, 8, 2); } // left output source (mixer, channel 1, channel 3, channel 1+3)
|
||||
inline u8 rout_from() const { return bitfield(m_control, 10, 2); } // right output source (mixer, channel 1, channel 3, channel 1+3)
|
||||
inline bool mix_ch1() const { return bitfield(m_control, 12); } // mix/bypass channel 1
|
||||
inline bool mix_ch3() const { return bitfield(m_control, 13); } // mix/bypass channel 3
|
||||
inline bool enable() const { return bitfield(m_control, 15); } // global enable
|
||||
|
||||
u32 m_control = 0; // global control
|
||||
u32 m_bias = 0; // output bias
|
||||
s32 m_loutput = 0; // left output
|
||||
s32 m_routput = 0; // right output
|
||||
};
|
||||
}; // namespace nds_sound_emu
|
||||
|
||||
#endif // NDS_SOUND_EMU_H
|
|
@ -35,6 +35,7 @@ extern "C" {
|
|||
#include "../../extern/adpcm/ymb_codec.h"
|
||||
#include "../../extern/adpcm/ymz_codec.h"
|
||||
}
|
||||
#include "../../extern/adpcm-xq/adpcm-lib.h"
|
||||
#include "brrUtils.h"
|
||||
|
||||
DivSampleHistory::~DivSampleHistory() {
|
||||
|
@ -279,6 +280,9 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) {
|
|||
case DIV_SAMPLE_DEPTH_C219:
|
||||
off=offset;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM:
|
||||
off=(offset+1)/2;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
off=offset*2;
|
||||
break;
|
||||
|
@ -338,6 +342,10 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) {
|
|||
off=offset;
|
||||
len=length;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM:
|
||||
off=(offset+1)/2;
|
||||
len=(length+1)/2;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
off=offset*2;
|
||||
len=length*2;
|
||||
|
@ -396,6 +404,9 @@ int DivSample::getEndPosition(DivSampleDepth depth) {
|
|||
case DIV_SAMPLE_DEPTH_C219:
|
||||
off=lengthC219;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM:
|
||||
off=lengthIMA;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
off=length16;
|
||||
break;
|
||||
|
@ -587,6 +598,12 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
|
|||
dataC219=new unsigned char[(count+4095)&(~0xfff)];
|
||||
memset(dataC219,0,(count+4095)&(~0xfff));
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM
|
||||
if (dataIMA!=NULL) delete[] dataIMA;
|
||||
lengthIMA=4+((count+1)/2);
|
||||
dataIMA=new unsigned char[lengthIMA];
|
||||
memset(dataIMA,0,lengthIMA);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT: // 16-bit
|
||||
if (data16!=NULL) delete[] data16;
|
||||
length16=count*2;
|
||||
|
@ -1271,6 +1288,9 @@ void DivSample::render(unsigned int formatMask) {
|
|||
if (dataC219[i]&0x80) data16[i]=-data16[i];
|
||||
}
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM
|
||||
if (adpcm_decode_block(data16,dataIMA,lengthIMA,1)==0) logE("oh crap!");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@ -1442,6 +1462,23 @@ void DivSample::render(unsigned int formatMask) {
|
|||
dataC219[i]=x|(negate?0x80:0);
|
||||
}
|
||||
}
|
||||
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_IMA_ADPCM)) { // IMA ADPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_IMA_ADPCM,samples)) return;
|
||||
int delta[2];
|
||||
delta[0]=0;
|
||||
delta[1]=0;
|
||||
|
||||
void* codec=adpcm_create_context(1,4,NOISE_SHAPING_OFF,delta);
|
||||
if (codec==NULL) {
|
||||
logE("oh no IMA encoder could not be created!");
|
||||
} else {
|
||||
size_t whyPointer=0;
|
||||
adpcm_encode_block(codec,dataIMA,&whyPointer,data16,samples);
|
||||
if (whyPointer!=lengthIMA) logW("IMA length mismatch! %d -> %d!=%d",(int)samples,(int)whyPointer,(int)lengthIMA);
|
||||
|
||||
adpcm_free_context(codec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* DivSample::getCurBuf() {
|
||||
|
@ -1470,6 +1507,8 @@ void* DivSample::getCurBuf() {
|
|||
return dataMuLaw;
|
||||
case DIV_SAMPLE_DEPTH_C219:
|
||||
return dataC219;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM:
|
||||
return dataIMA;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
return data16;
|
||||
default:
|
||||
|
@ -1504,6 +1543,8 @@ unsigned int DivSample::getCurBufLen() {
|
|||
return lengthMuLaw;
|
||||
case DIV_SAMPLE_DEPTH_C219:
|
||||
return lengthC219;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM:
|
||||
return lengthIMA;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
return length16;
|
||||
default:
|
||||
|
@ -1616,4 +1657,5 @@ DivSample::~DivSample() {
|
|||
if (dataVOX) delete[] dataVOX;
|
||||
if (dataMuLaw) delete[] dataMuLaw;
|
||||
if (dataC219) delete[] dataC219;
|
||||
if (dataIMA) delete[] dataIMA;
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ enum DivSampleDepth: unsigned char {
|
|||
DIV_SAMPLE_DEPTH_VOX=10,
|
||||
DIV_SAMPLE_DEPTH_MULAW=11,
|
||||
DIV_SAMPLE_DEPTH_C219=12,
|
||||
DIV_SAMPLE_DEPTH_IMA_ADPCM=13,
|
||||
DIV_SAMPLE_DEPTH_16BIT=16,
|
||||
DIV_SAMPLE_DEPTH_MAX // boundary for sample depth
|
||||
};
|
||||
|
@ -114,6 +115,7 @@ struct DivSample {
|
|||
// - 10: VOX ADPCM
|
||||
// - 11: 8-bit µ-law PCM
|
||||
// - 12: C219 "µ-law" PCM
|
||||
// - 13: IMA ADPCM
|
||||
// - 16: 16-bit PCM
|
||||
DivSampleDepth depth;
|
||||
bool loop, brrEmphasis, dither;
|
||||
|
@ -139,8 +141,9 @@ struct DivSample {
|
|||
unsigned char* dataVOX; // 10
|
||||
unsigned char* dataMuLaw; // 11
|
||||
unsigned char* dataC219; // 12
|
||||
unsigned char* dataIMA; // 13
|
||||
|
||||
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219;
|
||||
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219, lengthIMA;
|
||||
|
||||
unsigned int samples;
|
||||
|
||||
|
@ -349,6 +352,7 @@ struct DivSample {
|
|||
dataVOX(NULL),
|
||||
dataMuLaw(NULL),
|
||||
dataC219(NULL),
|
||||
dataIMA(NULL),
|
||||
length8(0),
|
||||
length16(0),
|
||||
length1(0),
|
||||
|
@ -362,6 +366,7 @@ struct DivSample {
|
|||
lengthVOX(0),
|
||||
lengthMuLaw(0),
|
||||
lengthC219(0),
|
||||
lengthIMA(0),
|
||||
samples(0) {
|
||||
for (int i=0; i<DIV_MAX_CHIPS; i++) {
|
||||
for (int j=0; j<DIV_MAX_SAMPLE_TYPE; j++) {
|
||||
|
|
|
@ -135,6 +135,7 @@ enum DivSystem {
|
|||
DIV_SYSTEM_ESFM,
|
||||
DIV_SYSTEM_POWERNOISE,
|
||||
DIV_SYSTEM_DAVE,
|
||||
DIV_SYSTEM_NDS,
|
||||
DIV_SYSTEM_GBA_DMA,
|
||||
DIV_SYSTEM_GBA_MINMOD,
|
||||
};
|
||||
|
|
|
@ -2055,6 +2055,20 @@ void DivEngine::registerSystems() {
|
|||
}
|
||||
);
|
||||
|
||||
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,
|
||||
"a handheld video game console with two screens. it uses a stylus.",
|
||||
{"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_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE},
|
||||
{DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS, DIV_INS_NDS},
|
||||
{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},
|
||||
{
|
||||
{0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle (pulse: 0 to 7)"}},
|
||||
{0x1f, {DIV_CMD_ADPCMA_GLOBAL_VOLUME, "1Fxx: Set global volume (0 to 7F)"}},
|
||||
}
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
|
||||
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0, 0, 0,
|
||||
"this is a system designed for testing purposes.",
|
||||
|
|
|
@ -214,6 +214,7 @@ const char* aboutLine[]={
|
|||
"FFTW by Matteo Frigo and Steven G. Johnson",
|
||||
"backward-cpp by Google",
|
||||
"adpcm by superctr",
|
||||
"adpcm-xq by David Bryant",
|
||||
"Nuked-OPL3/OPLL/OPM/OPN2/PSG by nukeykt",
|
||||
"YM3812-LLE, YMF262-LLE and YMF276-LLE by nukeykt",
|
||||
"ESFMu (modified version) by Kagamiin~",
|
||||
|
@ -253,6 +254,8 @@ const char* aboutLine[]={
|
|||
"D65010G031 emulator (modified version) by cam900",
|
||||
"Namco C140/C219 emulator (modified version) by cam900",
|
||||
"PowerNoise emulator by scratchminer",
|
||||
"ep128emu by Istvan Varga",
|
||||
"NDS sound emulator by cam900",
|
||||
"",
|
||||
"greetings to:",
|
||||
"NEOART Costa Rica",
|
||||
|
|
|
@ -1014,7 +1014,8 @@ void FurnaceGUI::doAction(int what) {
|
|||
i==DIV_INS_GA20 ||
|
||||
i==DIV_INS_K053260 ||
|
||||
i==DIV_INS_C140 ||
|
||||
i==DIV_INS_C219) {
|
||||
i==DIV_INS_C219 ||
|
||||
i==DIV_INS_NDS) {
|
||||
makeInsTypeList.push_back(i);
|
||||
}
|
||||
}
|
||||
|
@ -1541,6 +1542,7 @@ void FurnaceGUI::doAction(int what) {
|
|||
i==DIV_INS_K053260 ||
|
||||
i==DIV_INS_C140 ||
|
||||
i==DIV_INS_C219 ||
|
||||
i==DIV_INS_NDS ||
|
||||
i==DIV_INS_GBA_DMA ||
|
||||
i==DIV_INS_GBA_MINMOD) {
|
||||
makeInsTypeList.push_back(i);
|
||||
|
|
|
@ -251,6 +251,33 @@ void FurnaceGUI::drawExportCommand(bool onWindow) {
|
|||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::drawExportDMF(bool onWindow) {
|
||||
exitDisabledTimer=1;
|
||||
|
||||
ImGui::Text(
|
||||
"export in DefleMask module format.\n"
|
||||
"only do it if you really, really need to, or are downgrading an existing .dmf."
|
||||
);
|
||||
|
||||
ImGui::Text("format version:");
|
||||
ImGui::RadioButton("1.1.3 and higher",&dmfExportVersion,0);
|
||||
ImGui::RadioButton("1.0/legacy (0.12)",&dmfExportVersion,1);
|
||||
|
||||
if (onWindow) {
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup();
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) {
|
||||
if (dmfExportVersion==1) {
|
||||
openFileDialog(GUI_FILE_SAVE_DMF_LEGACY);
|
||||
} else {
|
||||
openFileDialog(GUI_FILE_SAVE_DMF);
|
||||
}
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::drawExport() {
|
||||
if (settings.exportOptionsLayout==1 || curExportType==GUI_EXPORT_NONE) {
|
||||
if (ImGui::BeginTabBar("ExportTypes")) {
|
||||
|
@ -290,6 +317,10 @@ void FurnaceGUI::drawExport() {
|
|||
drawExportCommand(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("DMF")) {
|
||||
drawExportDMF(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
} else switch (curExportType) {
|
||||
|
@ -311,6 +342,9 @@ void FurnaceGUI::drawExport() {
|
|||
case GUI_EXPORT_CMD_STREAM:
|
||||
drawExportCommand(true);
|
||||
break;
|
||||
case GUI_EXPORT_DMF:
|
||||
drawExportDMF(true);
|
||||
break;
|
||||
default:
|
||||
ImGui::Text("congratulations! you've unlocked a secret panel.");
|
||||
if (ImGui::Button("Toggle hidden systems")) {
|
||||
|
|
|
@ -1667,7 +1667,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
|||
case GUI_FILE_SAVE_DMF:
|
||||
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
"Save File",
|
||||
"Export DMF",
|
||||
{"DefleMask 1.1.3 module", "*.dmf"},
|
||||
workingDirSong,
|
||||
dpiScale
|
||||
|
@ -1676,7 +1676,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
|||
case GUI_FILE_SAVE_DMF_LEGACY:
|
||||
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
"Save File",
|
||||
"Export DMF",
|
||||
{"DefleMask 1.0/legacy module", "*.dmf"},
|
||||
workingDirSong,
|
||||
dpiScale
|
||||
|
@ -4156,12 +4156,6 @@ bool FurnaceGUI::loop() {
|
|||
if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) {
|
||||
openFileDialog(GUI_FILE_SAVE);
|
||||
}
|
||||
if (ImGui::MenuItem("save as .dmf (1.1.3+)...")) {
|
||||
openFileDialog(GUI_FILE_SAVE_DMF);
|
||||
}
|
||||
if (ImGui::MenuItem("save as .dmf (1.0/legacy)...")) {
|
||||
openFileDialog(GUI_FILE_SAVE_DMF_LEGACY);
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (settings.exportOptionsLayout==0) {
|
||||
if (ImGui::BeginMenu("export audio...")) {
|
||||
|
@ -4200,6 +4194,10 @@ bool FurnaceGUI::loop() {
|
|||
drawExportCommand();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("export .dmf...")) {
|
||||
drawExportDMF();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
} else if (settings.exportOptionsLayout==2) {
|
||||
if (ImGui::MenuItem("export audio...")) {
|
||||
curExportType=GUI_EXPORT_AUDIO;
|
||||
|
@ -4237,6 +4235,10 @@ bool FurnaceGUI::loop() {
|
|||
curExportType=GUI_EXPORT_CMD_STREAM;
|
||||
displayExport=true;
|
||||
}
|
||||
if (ImGui::MenuItem("export .dmf...")) {
|
||||
curExportType=GUI_EXPORT_DMF;
|
||||
displayExport=true;
|
||||
}
|
||||
} else {
|
||||
if (ImGui::MenuItem("export...",BIND_FOR(GUI_ACTION_EXPORT))) {
|
||||
displayExport=true;
|
||||
|
@ -4358,7 +4360,7 @@ bool FurnaceGUI::loop() {
|
|||
if (ImGui::MenuItem("lock layout",NULL,lockLayout)) {
|
||||
lockLayout=!lockLayout;
|
||||
}
|
||||
if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) {
|
||||
if (ImGui::MenuItem("pattern visualizer",NULL,fancyPattern)) {
|
||||
fancyPattern=!fancyPattern;
|
||||
e->enableCommandStream(fancyPattern);
|
||||
e->getCommandStream(cmdStream);
|
||||
|
@ -4379,41 +4381,58 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu(settings.capitalMenuBar?"Window":"window")) {
|
||||
if (ImGui::BeginMenu("song")) {
|
||||
if (ImGui::MenuItem("song comments", BIND_FOR(GUI_ACTION_WINDOW_NOTES), notesOpen)) notesOpen = !notesOpen;
|
||||
if (ImGui::MenuItem("song information", BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO), songInfoOpen)) songInfoOpen = !songInfoOpen;
|
||||
if (ImGui::MenuItem("subsongs", BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS), subSongsOpen)) subSongsOpen = !subSongsOpen;
|
||||
if (ImGui::MenuItem("speed",BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen;
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen;
|
||||
if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen;
|
||||
if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen;
|
||||
if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen;
|
||||
if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen;
|
||||
if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen;
|
||||
if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("assets")) {
|
||||
if (settings.unifiedDataView) {
|
||||
if (ImGui::MenuItem("assets", BIND_FOR(GUI_ACTION_WINDOW_INS_LIST), insListOpen)) insListOpen = !insListOpen;
|
||||
} else {
|
||||
if (ImGui::MenuItem("instruments", BIND_FOR(GUI_ACTION_WINDOW_INS_LIST), insListOpen)) insListOpen = !insListOpen;
|
||||
if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen;
|
||||
if (ImGui::MenuItem("samples", BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST), sampleListOpen)) sampleListOpen = !sampleListOpen;
|
||||
if (ImGui::MenuItem("wavetables", BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST), waveListOpen)) waveListOpen = !waveListOpen;
|
||||
}
|
||||
if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen;
|
||||
if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen;
|
||||
if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen;
|
||||
if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen;
|
||||
if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen;
|
||||
if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen;
|
||||
if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen;
|
||||
if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen;
|
||||
if (ImGui::MenuItem("song comments",BIND_FOR(GUI_ACTION_WINDOW_NOTES),notesOpen)) notesOpen=!notesOpen;
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("instrument editor", BIND_FOR(GUI_ACTION_WINDOW_INS_EDIT), insEditOpen)) insEditOpen = !insEditOpen;
|
||||
if (ImGui::MenuItem("wavetable editor",BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT),waveEditOpen)) waveEditOpen=!waveEditOpen;
|
||||
if (ImGui::MenuItem("sample editor", BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_EDIT), sampleEditOpen)) sampleEditOpen = !sampleEditOpen;
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen;
|
||||
if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen;
|
||||
if (ImGui::MenuItem("wavetable editor", BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT), waveEditOpen)) waveEditOpen = !waveEditOpen;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("visualizers")) {
|
||||
if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen;
|
||||
if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen;
|
||||
if (ImGui::MenuItem("oscilloscope (X-Y)",BIND_FOR(GUI_ACTION_WINDOW_XY_OSC),xyOscOpen)) xyOscOpen=!xyOscOpen;
|
||||
if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("tempo")) {
|
||||
if (ImGui::MenuItem("clock",BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen;
|
||||
if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen;
|
||||
if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen;
|
||||
if (ImGui::MenuItem("speed",BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("debug")) {
|
||||
if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen;
|
||||
if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen;
|
||||
if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen;
|
||||
if (ImGui::MenuItem("memory composition",BIND_FOR(GUI_ACTION_WINDOW_MEMORY),memoryOpen)) memoryOpen=!memoryOpen;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen;
|
||||
if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen;
|
||||
if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen;
|
||||
if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen;
|
||||
|
||||
ImGui::EndMenu();
|
||||
|
@ -4422,7 +4441,6 @@ bool FurnaceGUI::loop() {
|
|||
if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen;
|
||||
if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen;
|
||||
if (ImGui::MenuItem("inspector")) inspectorOpen=!inspectorOpen;
|
||||
if (ImGui::MenuItem("shader editor")) shaderEditor=!shaderEditor;
|
||||
if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset();
|
||||
if (ImGui::MenuItem("about...",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) {
|
||||
aboutOpen=true;
|
||||
|
@ -7798,6 +7816,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
curTutorial(-1),
|
||||
curTutorialStep(0),
|
||||
audioExportType(0),
|
||||
dmfExportVersion(0),
|
||||
curExportType(GUI_EXPORT_NONE) {
|
||||
// value keys
|
||||
valueKeys[SDLK_0]=0;
|
||||
|
|
|
@ -589,7 +589,8 @@ enum FurnaceGUIExportTypes {
|
|||
GUI_EXPORT_ZSM,
|
||||
GUI_EXPORT_CMD_STREAM,
|
||||
GUI_EXPORT_AMIGA_VAL,
|
||||
GUI_EXPORT_TEXT
|
||||
GUI_EXPORT_TEXT,
|
||||
GUI_EXPORT_DMF
|
||||
};
|
||||
|
||||
enum FurnaceGUIFMAlgs {
|
||||
|
@ -2465,6 +2466,7 @@ class FurnaceGUI {
|
|||
|
||||
// export options
|
||||
int audioExportType;
|
||||
int dmfExportVersion;
|
||||
FurnaceGUIExportTypes curExportType;
|
||||
|
||||
void drawExportAudio(bool onWindow=false);
|
||||
|
@ -2473,6 +2475,7 @@ class FurnaceGUI {
|
|||
void drawExportAmigaVal(bool onWindow=false);
|
||||
void drawExportText(bool onWindow=false);
|
||||
void drawExportCommand(bool onWindow=false);
|
||||
void drawExportDMF(bool onWindow=false);
|
||||
|
||||
void drawSSGEnv(unsigned char type, const ImVec2& size);
|
||||
void drawWaveform(unsigned char type, bool opz, const ImVec2& size);
|
||||
|
|
|
@ -205,7 +205,7 @@ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={
|
|||
"VOX",
|
||||
"8-bit µ-law PCM",
|
||||
"C219 PCM",
|
||||
NULL,
|
||||
"IMA ADPCM",
|
||||
NULL,
|
||||
NULL,
|
||||
"16-bit PCM"
|
||||
|
@ -1242,6 +1242,7 @@ const int availableSystems[]={
|
|||
DIV_SYSTEM_PONG,
|
||||
DIV_SYSTEM_POWERNOISE,
|
||||
DIV_SYSTEM_DAVE,
|
||||
DIV_SYSTEM_NDS,
|
||||
0 // don't remove this last one!
|
||||
};
|
||||
|
||||
|
@ -1333,6 +1334,7 @@ const int chipsSpecial[]={
|
|||
DIV_SYSTEM_SM8521,
|
||||
DIV_SYSTEM_POWERNOISE,
|
||||
DIV_SYSTEM_DAVE,
|
||||
DIV_SYSTEM_NDS,
|
||||
0 // don't remove this last one!
|
||||
};
|
||||
|
||||
|
@ -1355,6 +1357,7 @@ const int chipsSample[]={
|
|||
DIV_SYSTEM_K053260,
|
||||
DIV_SYSTEM_C140,
|
||||
DIV_SYSTEM_C219,
|
||||
DIV_SYSTEM_NDS,
|
||||
DIV_SYSTEM_GBA_DMA,
|
||||
DIV_SYSTEM_GBA_MINMOD,
|
||||
0 // don't remove this last one!
|
||||
|
|
|
@ -2505,7 +2505,8 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) {
|
|||
ins->type==DIV_INS_AY ||
|
||||
ins->type==DIV_INS_AY8930 ||
|
||||
ins->type==DIV_INS_VRC6 ||
|
||||
ins->type==DIV_INS_SU) {
|
||||
ins->type==DIV_INS_SU ||
|
||||
ins->type==DIV_INS_NDS) {
|
||||
P(ImGui::Checkbox("Use sample",&ins->amiga.useSample));
|
||||
if (ins->type==DIV_INS_X1_010) {
|
||||
if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER
|
||||
|
@ -6028,6 +6029,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ins->type==DIV_INS_K053260 ||
|
||||
ins->type==DIV_INS_C140 ||
|
||||
ins->type==DIV_INS_C219 ||
|
||||
ins->type==DIV_INS_NDS ||
|
||||
ins->type==DIV_INS_GBA_DMA ||
|
||||
ins->type==DIV_INS_GBA_MINMOD) {
|
||||
insTabSample(ins);
|
||||
|
@ -6751,7 +6753,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_MIKEY ||
|
||||
ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ ||
|
||||
ins->type==DIV_INS_OPM || ins->type==DIV_INS_SNES || ins->type==DIV_INS_MSM5232 ||
|
||||
ins->type==DIV_INS_K053260) {
|
||||
ins->type==DIV_INS_K053260 || ins->type==DIV_INS_NDS) {
|
||||
volMax=127;
|
||||
}
|
||||
if (ins->type==DIV_INS_GB) {
|
||||
|
@ -6936,6 +6938,10 @@ void FurnaceGUI::drawInsEdit() {
|
|||
dutyLabel="Noise Freq";
|
||||
dutyMax=3;
|
||||
}
|
||||
if (ins->type==DIV_INS_NDS) {
|
||||
dutyLabel="Duty";
|
||||
dutyMax=ins->amiga.useSample?0:7;
|
||||
}
|
||||
|
||||
const char* waveLabel="Waveform";
|
||||
int waveMax=(ins->type==DIV_INS_VERA)?3:(MAX(1,e->song.waveLen-1));
|
||||
|
@ -6974,6 +6980,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_POWERNOISE_SLOPE) waveMax=0;
|
||||
if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7;
|
||||
if (ins->type==DIV_INS_DAVE) waveMax=4;
|
||||
if (ins->type==DIV_INS_NDS) waveMax=0;
|
||||
if (ins->type==DIV_INS_PET) {
|
||||
waveMax=8;
|
||||
waveBitMode=true;
|
||||
|
@ -7130,6 +7137,11 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_DAVE) {
|
||||
panMax=63;
|
||||
}
|
||||
if (ins->type==DIV_INS_NDS) {
|
||||
panMin=-64;
|
||||
panMax=63;
|
||||
panSingleNoBit=true;
|
||||
}
|
||||
|
||||
if (volMax>0) {
|
||||
macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
|
||||
|
@ -7221,6 +7233,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ins->type==DIV_INS_POWERNOISE ||
|
||||
ins->type==DIV_INS_POWERNOISE_SLOPE ||
|
||||
ins->type==DIV_INS_DAVE ||
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -286,6 +286,11 @@ void FurnaceGUI::initSystemPresets() {
|
|||
CH(DIV_SYSTEM_PV1000, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"NDS", {
|
||||
CH(DIV_SYSTEM_NDS, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
CATEGORY_END;
|
||||
|
||||
CATEGORY_BEGIN("Computers","let's get to work on chiptune today.");
|
||||
|
@ -2723,6 +2728,11 @@ void FurnaceGUI::initSystemPresets() {
|
|||
CH(DIV_SYSTEM_C219, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"NDS", {
|
||||
CH(DIV_SYSTEM_NDS, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
CATEGORY_END;
|
||||
|
||||
CATEGORY_BEGIN("Wavetable","chips which use user-specified waveforms to generate sound.");
|
||||
|
@ -2893,6 +2903,11 @@ void FurnaceGUI::initSystemPresets() {
|
|||
},
|
||||
"tickRate=50"
|
||||
);
|
||||
ENTRY(
|
||||
"NDS", {
|
||||
CH(DIV_SYSTEM_NDS, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
CATEGORY_END;
|
||||
|
||||
CATEGORY_BEGIN("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program.");
|
||||
|
|
|
@ -3551,6 +3551,7 @@ void FurnaceGUI::drawSettings() {
|
|||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE,"PowerNoise (noise)");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_POWERNOISE_SLOPE,"PowerNoise (slope)");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_DAVE,"Dave");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_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");
|
||||
|
|
|
@ -2413,6 +2413,28 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_NDS: {
|
||||
int chipType=flags.getInt("chipType",0);
|
||||
|
||||
ImGui::Text("Model:");
|
||||
ImGui::Indent();
|
||||
if (ImGui::RadioButton("DS (4MB RAM)",chipType==0)) {
|
||||
chipType=0;
|
||||
altered=true;
|
||||
}
|
||||
if (ImGui::RadioButton("DSi (16MB RAM)",chipType==1)) {
|
||||
chipType=1;
|
||||
altered=true;
|
||||
}
|
||||
ImGui::Unindent();
|
||||
|
||||
if (altered) {
|
||||
e->lockSave([&]() {
|
||||
flags.set("chipType",chipType);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_SWAN:
|
||||
case DIV_SYSTEM_BUBSYS_WSG:
|
||||
case DIV_SYSTEM_PET:
|
||||
|
|
|
@ -215,6 +215,7 @@ TAParamResult pVersion(String) {
|
|||
printf("- ESFMu (modified version) by Kagamiin~ (LGPLv2.1)\n");
|
||||
printf("- ymfm by Aaron Giles (BSD 3-clause)\n");
|
||||
printf("- adpcm by superctr (public domain)\n");
|
||||
printf("- adpcm-xq by David Bryant (BSD 3-clause)\n");
|
||||
printf("- MAME SN76496 emulation core by Nicola Salmoria (BSD 3-clause)\n");
|
||||
printf("- MAME AY-3-8910 emulation core by Couriersud (BSD 3-clause)\n");
|
||||
printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n");
|
||||
|
@ -248,6 +249,8 @@ TAParamResult pVersion(String) {
|
|||
printf("- D65010G031 emulator (modified version) by cam900 (zlib license)\n");
|
||||
printf("- C140/C219 emulator (modified version) by cam900 (zlib license)\n");
|
||||
printf("- PowerNoise emulator by scratchminer (MIT)\n");
|
||||
printf("- ep128emu by Istvan Varga (GPLv2)\n");
|
||||
printf("- NDS sound emulator by cam900 (zlib license)\n");
|
||||
return TA_PARAM_QUIT;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue