From 708c0e359ae46ea1eb7c786e80672a6b5b3c1afd Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Thu, 9 Mar 2023 21:39:32 +0100 Subject: [PATCH 01/64] n163: macro removal part 2 --- src/gui/insEdit.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6237a4392..8badcf2f9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -271,9 +271,9 @@ const char* x1_010EnvBits[8]={ "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; -const char* n163UpdateBits[8]={ +/*const char* n163UpdateBits[8]={ "now", "every waveform changed", NULL -}; +};*/ const char* suControlBits[5]={ "ring mod", "low pass", "high pass", "band pass", NULL @@ -5149,10 +5149,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=63; } - if (ins->type==DIV_INS_N163) { + /*if (ins->type==DIV_INS_N163) { dutyLabel="Waveform pos."; dutyMax=255; - } + }*/ if (ins->type==DIV_INS_VRC6) { dutyLabel="Duty"; dutyMax=ins->amiga.useSample?0:7; @@ -5432,8 +5432,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,saaEnvBits)); } else if (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) { macroList.push_back(FurnaceGUIMacroDesc("Envelope Mode",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,x1_010EnvBits)); - } else if (ins->type==DIV_INS_N163) { - macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + /*} else if (ins->type==DIV_INS_N163) { + macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));*/ } else if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SU) { @@ -5455,8 +5455,8 @@ void FurnaceGUI::drawInsEdit() { if (ex2Max>0) { if (ins->type==DIV_INS_C64) { macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER])); - } else if (ins->type==DIV_INS_N163) { - macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits)); + /*} else if (ins->type==DIV_INS_N163) { + macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits));*/ } else if (ins->type==DIV_INS_FDS) { macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SU) { From c2761e4f417b8c0dfc323c9bedfbc445b3b5bb57 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Fri, 10 Mar 2023 15:42:58 +0100 Subject: [PATCH 02/64] fix the legendary chiptune moment it still distorts on the first key on, but not later on --- src/gui/insEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8badcf2f9..32dd44cc4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4420,11 +4420,11 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER if (ins->n163.wavePos<0) ins->n163.wavePos=0; - if (ins->n163.wavePos>255) ins->n163.wavePos=255; + if (ins->n163.wavePos>236) ins->n163.wavePos=236; } if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER if (ins->n163.waveLen<0) ins->n163.waveLen=0; - if (ins->n163.waveLen>252) ins->n163.waveLen=252; + if (ins->n163.waveLen>240) ins->n163.waveLen=240; ins->n163.waveLen&=0xfc; } From 9b92b118c624ae420c85b18140292cc8d347e876 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Fri, 10 Mar 2023 22:54:09 +0100 Subject: [PATCH 03/64] fix this for real sorry for indentation --- src/gui/insEdit.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 32dd44cc4..f6813fd32 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4423,9 +4423,14 @@ void FurnaceGUI::drawInsEdit() { if (ins->n163.wavePos>236) ins->n163.wavePos=236; } if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER + int n163origLen = ins->n163.waveLen; if (ins->n163.waveLen<0) ins->n163.waveLen=0; if (ins->n163.waveLen>240) ins->n163.waveLen=240; - ins->n163.waveLen&=0xfc; + if (ins->n163.waveLen > n163origLen) { + ins->n163.waveLen = ((ins->n163.waveLen+3)&~2); + } + else ins->n163.waveLen = (ins->n163.waveLen & (~2)); + //ins->n163.waveLen&=0xfc; } bool preLoad=ins->n163.waveMode&0x1; From b289d4ac1d193b647942e626b81827c454ea8ea3 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Fri, 10 Mar 2023 23:33:50 +0100 Subject: [PATCH 04/64] fix offset beyond that point, i can't improve --- src/gui/insEdit.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f6813fd32..e3a580c2e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4420,7 +4420,10 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER if (ins->n163.wavePos<0) ins->n163.wavePos=0; - if (ins->n163.wavePos>236) ins->n163.wavePos=236; + if (ins->n163.wavePos>240) ins->n163.wavePos=240; + if (ins->n163.wavePos + ins->n163.waveLen > 240) { + ins->n163.wavePos -= ins->n163.waveLen; + } } if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER int n163origLen = ins->n163.waveLen; From c33d5876226986a926900e6ecd15cd98b26d19eb Mon Sep 17 00:00:00 2001 From: freq-mod Date: Sun, 12 Mar 2023 13:06:34 +0100 Subject: [PATCH 05/64] Revert "fix offset" This reverts commit b289d4ac1d193b647942e626b81827c454ea8ea3. --- src/gui/insEdit.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e3a580c2e..f6813fd32 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4420,10 +4420,7 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER if (ins->n163.wavePos<0) ins->n163.wavePos=0; - if (ins->n163.wavePos>240) ins->n163.wavePos=240; - if (ins->n163.wavePos + ins->n163.waveLen > 240) { - ins->n163.wavePos -= ins->n163.waveLen; - } + if (ins->n163.wavePos>236) ins->n163.wavePos=236; } if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER int n163origLen = ins->n163.waveLen; From 0db4dc01791caf2c0011c82a8f6521fae3271467 Mon Sep 17 00:00:00 2001 From: freq-mod Date: Sun, 12 Mar 2023 13:07:14 +0100 Subject: [PATCH 06/64] Revert "fix this for real" This reverts commit 9b92b118c624ae420c85b18140292cc8d347e876. --- src/gui/insEdit.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f6813fd32..32dd44cc4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4423,14 +4423,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->n163.wavePos>236) ins->n163.wavePos=236; } if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER - int n163origLen = ins->n163.waveLen; if (ins->n163.waveLen<0) ins->n163.waveLen=0; if (ins->n163.waveLen>240) ins->n163.waveLen=240; - if (ins->n163.waveLen > n163origLen) { - ins->n163.waveLen = ((ins->n163.waveLen+3)&~2); - } - else ins->n163.waveLen = (ins->n163.waveLen & (~2)); - //ins->n163.waveLen&=0xfc; + ins->n163.waveLen&=0xfc; } bool preLoad=ins->n163.waveMode&0x1; From f29867a655e31a94e4f9b3c6da63dcacb95c6511 Mon Sep 17 00:00:00 2001 From: freq-mod Date: Sun, 12 Mar 2023 13:07:21 +0100 Subject: [PATCH 07/64] Revert "fix the legendary chiptune moment" This reverts commit c2761e4f417b8c0dfc323c9bedfbc445b3b5bb57. --- src/gui/insEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 32dd44cc4..8badcf2f9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4420,11 +4420,11 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER if (ins->n163.wavePos<0) ins->n163.wavePos=0; - if (ins->n163.wavePos>236) ins->n163.wavePos=236; + if (ins->n163.wavePos>255) ins->n163.wavePos=255; } if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER if (ins->n163.waveLen<0) ins->n163.waveLen=0; - if (ins->n163.waveLen>240) ins->n163.waveLen=240; + if (ins->n163.waveLen>252) ins->n163.waveLen=252; ins->n163.waveLen&=0xfc; } From bc95fb0181a927113643e37f23696af817c8078c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 12 Mar 2023 16:13:00 -0500 Subject: [PATCH 08/64] the unsorted dir has blank name --- src/engine/engine.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 04469b63f..14d90aa62 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1588,7 +1588,7 @@ void DivEngine::checkAssetDir(std::vector& dir, size_t entries) { // get unsorted directory DivAssetDir* unsortedDir=NULL; for (DivAssetDir& i: dir) { - if (i.name=="Unsorted") { + if (i.name.empty()) { unsortedDir=&i; break; } @@ -1596,7 +1596,7 @@ void DivEngine::checkAssetDir(std::vector& dir, size_t entries) { // create unsorted directory if it doesn't exist if (unsortedDir==NULL) { - dir.push_back(DivAssetDir("Unsorted")); + dir.push_back(DivAssetDir("")); unsortedDir=&(*dir.rbegin()); } @@ -2498,6 +2498,10 @@ void DivEngine::recalcChans() { if (isInsTypePossible[i]) possibleInsTypes.push_back((DivInstrumentType)i); } + checkAssetDir(song.insDir,song.ins.size()); + checkAssetDir(song.waveDir,song.wave.size()); + checkAssetDir(song.sampleDir,song.sample.size()); + hasLoadedSomething=true; } From 854698cd752d8dc641b4067383758c3c0361c1d0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 12 Mar 2023 19:11:05 -0500 Subject: [PATCH 09/64] S3M import? no, it's not there yet --- src/engine/engine.h | 2 + src/engine/fileOps.cpp | 227 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) diff --git a/src/engine/engine.h b/src/engine/engine.h index f1e5e91f8..0ff399bf8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -52,6 +52,7 @@ // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 +#define DIV_VERSION_S3M 0xff03 // "Namco C163" #define DIV_C163_DEFAULT_NAME "Namco 163" @@ -457,6 +458,7 @@ class DivEngine { bool loadDMF(unsigned char* file, size_t len); bool loadFur(unsigned char* file, size_t len); bool loadMod(unsigned char* file, size_t len); + bool loadS3M(unsigned char* file, size_t len); bool loadFTM(unsigned char* file, size_t len); bool loadFC(unsigned char* file, size_t len); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index c1ce34897..4db523133 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -30,6 +30,7 @@ #define DIV_FTM_MAGIC "FamiTracker Module" #define DIV_FC13_MAGIC "SMOD" #define DIV_FC14_MAGIC "FC14" +#define DIV_S3M_MAGIC "SCRM" struct InflateBlock { unsigned char* buf; @@ -3226,6 +3227,232 @@ void generateFCPresetWave(int index, DivWavetable* wave) { } } +bool DivEngine::loadS3M(unsigned char* file, size_t len) { + struct InvalidHeaderException {}; + bool success=false; + char magic[4]={0,0,0,0}; + SafeReader reader=SafeReader(file,len); + warnings=""; + + unsigned char chanSettings[32]; + unsigned char ord[256]; + unsigned short insPtr[256]; + unsigned short panPtr[256]; + unsigned char chanPan[16]; + unsigned char defVol[256]; + + try { + DivSong ds; + ds.version=DIV_VERSION_S3M; + ds.linearPitch=0; + ds.pitchMacroIsLinear=false; + ds.noSlidesOnFirstTick=true; + ds.rowResetsArpPos=true; + ds.ignoreJumpAtEnd=false; + + // load here + if (!reader.seek(0x2c,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + reader.read(magic,4); + + if (memcmp(magic,DIV_S3M_MAGIC,4)!=0) { + logW("the magic isn't complete"); + throw EndOfFileException(&reader,reader.tell()); + } + + if (!reader.seek(0,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + + ds.name=reader.readString(28); + + reader.readC(); // 0x1a + if (reader.readC()!=16) { + logW("type is wrong!"); + } + reader.readS(); // x + + unsigned short ordersLen=reader.readS(); + ds.insLen=reader.readS(); + + if (ds.insLen<0 || ds.insLen>256) { + logE("invalid instrument count!"); + lastError="invalid instrument count!"; + delete[] file; + return false; + } + + unsigned short patCount=reader.readS(); + + unsigned short flags=reader.readS(); + unsigned short version=reader.readS(); + bool signedSamples=(reader.readS()==1); + + if ((flags&64) || version==0x1300) { + ds.noSlidesOnFirstTick=false; + } + + reader.readI(); // "SCRM" + + unsigned char globalVol=reader.readC(); + + ds.subsong[0]->speeds.val[0]=(unsigned char)reader.readC(); + ds.subsong[0]->hz=((double)reader.readC())/2.5; + ds.subsong[0]->customTempo=true; + + unsigned char masterVol=reader.readC(); + + reader.readC(); // UC + bool defaultPan=(((unsigned char)reader.readC())==252); + + reader.readS(); // reserved + reader.readI(); + reader.readI(); // the last 2 bytes is Special. we don't read that. + + reader.read(chanSettings,32); + + for (int i=0; i=32) continue; + if ((chanSettings[i]&127)>=16) { + hasFM=true; + } else { + hasPCM=true; + } + + if (hasFM && hasPCM) break; + } + + ds.systemName="PC"; + if (hasPCM) { + ds.system[ds.systemLen]=DIV_SYSTEM_ES5506; + ds.systemVol[ds.systemLen]=1.0f; + ds.systemPan[ds.systemLen]=0; + ds.systemLen++; + } + if (hasFM) { + ds.system[ds.systemLen]=DIV_SYSTEM_OPL2; + ds.systemVol[ds.systemLen]=1.0f; + ds.systemPan[ds.systemLen]=0; + ds.systemLen++; + } + + // load instruments/samples + for (int i=0; itype=DIV_INS_ES5506; + } else if (memcmp(magic,"SCRI",4)==0) { + ins->type=DIV_INS_OPL; + } else { + ins->type=DIV_INS_ES5506; + ds.ins.push_back(ins); + continue; + } + + if (!reader.seek(insPtr[i]*16,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete ins; + delete[] file; + return false; + } + + String dosName=reader.readString(13); + + if (ins->type==DIV_INS_ES5506) { + unsigned int memSeg=0; + memSeg=(unsigned char)reader.readC(); + memSeg|=((unsigned short)reader.readS())<<8; + + unsigned int length=reader.readI(); + + DivSample* s=new DivSample; + s->depth=DIV_SAMPLE_DEPTH_8BIT; + s->init(length); + + s->loopStart=reader.readI(); + s->loopEnd=reader.readI(); + defVol[i]=reader.readC(); + + reader.readC(); // x + } else { + + } + + ds.ins.push_back(ins); + } + + if (active) quitDispatch(); + BUSY_BEGIN_SOFT; + saveLock.lock(); + song.unload(); + song=ds; + changeSong(0); + recalcChans(); + saveLock.unlock(); + BUSY_END; + if (active) { + initDispatch(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; + } + success=true; + } catch (EndOfFileException& e) { + //logE("premature end of file!"); + lastError="incomplete file"; + } catch (InvalidHeaderException& e) { + //logE("invalid header!"); + lastError="invalid header!"; + } + return success; +} + bool DivEngine::loadFC(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; From d73c2346c495b54394c5b080b66cadf972ce44bd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 12 Mar 2023 20:28:13 -0500 Subject: [PATCH 10/64] fix --- src/engine/fileOps.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4db523133..390fd1dbd 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -3237,7 +3237,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { unsigned char chanSettings[32]; unsigned char ord[256]; unsigned short insPtr[256]; - unsigned short panPtr[256]; + unsigned short patPtr[256]; unsigned char chanPan[16]; unsigned char defVol[256]; @@ -3309,6 +3309,10 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { unsigned char masterVol=reader.readC(); + logV("masterVol: %d",masterVol); + logV("signedSamples: %d",signedSamples); + logV("globalVol: %d",globalVol); + reader.readC(); // UC bool defaultPan=(((unsigned char)reader.readC())==252); @@ -3324,11 +3328,11 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { // should be even if (ordersLen&1) reader.readC(); - for (int i=0: i Date: Mon, 13 Mar 2023 03:12:03 -0500 Subject: [PATCH 11/64] prepare to add some code --- src/asm/68k/amigatest/README.md | 49 +++++++++++++++++++++++++++++++++ src/asm/68k/amigatest/player.s | 32 +++++++++++++++++++++ src/gui/gui.h | 2 ++ src/gui/settings.cpp | 6 +++- 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/asm/68k/amigatest/README.md create mode 100644 src/asm/68k/amigatest/player.s diff --git a/src/asm/68k/amigatest/README.md b/src/asm/68k/amigatest/README.md new file mode 100644 index 000000000..fe941ea50 --- /dev/null +++ b/src/asm/68k/amigatest/README.md @@ -0,0 +1,49 @@ +# Amiga verification export format + +"ROM" export format exclusively for verifying whether the Amiga emulation in Furnace is correct. + +do not assume this is the actual ROM export! it is nothing more than a register dump kind of thing... + +# process + +enable the setting in Furnace to unlock the export option by adding this to furnace.cfg: + +``` +iCannotWait=true +``` + +go to file > export Amiga validation data... + +put sample.bin and seq.bin in this directory. + +compile with vasm: + +``` +vasmm68k_mot -Fhunkexe -kick1hunks player.s +``` + +run a.out on Amiga. it should play the exported song. + +# sequence format + +## 00-0F: global + +- 00: do nothing +- 01: next tick +- 02 xx: wait +- 03 xxxx: wait +- 06 xxxx: write to DMACON +- 0a xxxx: write to INTENA +- 0e xxxx: write to ADKCON + +## 10-1F: per-channel (10, 20, 30, 40) + +- 10 xxxxxx yyyy zzzzzz wwww: set loc/len + - x: loc + - y: len + - z: loc after interrupt + - w: len after interrupt +- 12 xxxx yy: initialize wavetable (xxxx: pos; yy: length) +- 16 xxxx: set period +- 18 xx: set volume +- 1a xxxx: set data diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s new file mode 100644 index 000000000..b382a77f6 --- /dev/null +++ b/src/asm/68k/amigatest/player.s @@ -0,0 +1,32 @@ +; Furnace validation player code +; this is NOT the ROM export you're looking for! + +; incomplete! + +VPOSR = $dff004 +COLOR00 = $dff180 + +cseg + move.l #0,d0 + +main: + jsr waitVBlank + + move.w curColor,d0 + move.w d0,COLOR00 + addi.w #1,d0 + move.w d0,curColor + + jmp main + +waitVBlank: + move.l (VPOSR),d0 + and.l #$1ff00,d0 + cmp.l #$12c00,d0 + bne waitVBlank + rts + +data_c + +curColor: + dc.w 0 diff --git a/src/gui/gui.h b/src/gui/gui.h index 5b4cc3221..a42c47429 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1369,6 +1369,7 @@ class FurnaceGUI { int oneDigitEffects; int disableFadeIn; int alwaysPlayIntro; + int iCannotWait; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1509,6 +1510,7 @@ class FurnaceGUI { oneDigitEffects(0), disableFadeIn(0), alwaysPlayIntro(0), + iCannotWait(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index e457c99c3..c39d03e0a 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2416,6 +2416,7 @@ void FurnaceGUI::drawSettings() { // "Debug" - toggles mobile UI // "Nice Amiga cover of the song!" - enables hidden systems (YMU759/SoundUnit/Dummy) // "42 63" - enables all instrument types + // "????" - enables stuff if (ImGui::BeginTabItem("Cheat Codes")) { ImVec2 settingsViewSize=ImGui::GetContentRegionAvail(); settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y; @@ -2619,6 +2620,7 @@ void FurnaceGUI::syncSettings() { settings.disableFadeIn=e->getConfInt("disableFadeIn",0); settings.alwaysPlayIntro=e->getConfInt("alwaysPlayIntro",0); settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1); + settings.iCannotWait=e->getConfInt("iCannotWait",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2734,6 +2736,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.disableFadeIn,0,1); clampSetting(settings.alwaysPlayIntro,0,3); clampSetting(settings.cursorFollowsOrder,0,1); + clampSetting(settings.iCannotWait,0,1); if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; @@ -2943,7 +2946,8 @@ void FurnaceGUI::commitSettings() { e->setConf("oneDigitEffects",settings.oneDigitEffects); e->setConf("disableFadeIn",settings.disableFadeIn); e->setConf("alwaysPlayIntro",settings.alwaysPlayIntro); - e->setConf("cursorFollowsOrder", settings.cursorFollowsOrder); + e->setConf("cursorFollowsOrder",settings.cursorFollowsOrder); + e->setConf("iCannotWait",settings.iCannotWait); // colors for (int i=0; i Date: Mon, 13 Mar 2023 03:18:52 -0500 Subject: [PATCH 12/64] really fix build --- src/engine/fileOps.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 390fd1dbd..41e2f6ffc 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -3322,18 +3322,24 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { reader.read(chanSettings,32); + logD("reading orders..."); for (int i=0; iloopStart=reader.readI(); s->loopEnd=reader.readI(); defVol[i]=reader.readC(); + + logV("defVol: %d",defVol[i]); reader.readC(); // x } else { From 6663fc274d17b1d1c053253b591120a2c3a2b4b9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 13 Mar 2023 04:20:54 -0500 Subject: [PATCH 13/64] prepare to add ROM export framework --- CMakeLists.txt | 3 ++ src/asm/68k/amigatest/README.md | 4 +- src/engine/engine.h | 3 +- src/engine/export.h | 63 +++++++++++++++++++++++++++ src/engine/export/abstract.cpp | 26 +++++++++++ src/engine/export/amigaValidation.cpp | 0 src/engine/export/amigaValidation.h | 0 src/engine/platform/amiga.cpp | 4 ++ src/gui/gui.cpp | 44 ++++++++++++++----- 9 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 src/engine/export.h create mode 100644 src/engine/export/abstract.cpp create mode 100644 src/engine/export/amigaValidation.cpp create mode 100644 src/engine/export/amigaValidation.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 906bacffc..fa8f92c13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -488,6 +488,7 @@ src/engine/waveSynth.cpp src/engine/vgmOps.cpp src/engine/zsmOps.cpp src/engine/zsm.cpp + src/engine/platform/abstract.cpp src/engine/platform/genesis.cpp src/engine/platform/genesisext.cpp @@ -550,6 +551,8 @@ src/engine/platform/sm8521.cpp src/engine/platform/pv1000.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp + +src/engine/export/abstract.cpp ) if (USE_SNDFILE) diff --git a/src/asm/68k/amigatest/README.md b/src/asm/68k/amigatest/README.md index fe941ea50..7cb5b0cdb 100644 --- a/src/asm/68k/amigatest/README.md +++ b/src/asm/68k/amigatest/README.md @@ -9,12 +9,12 @@ do not assume this is the actual ROM export! it is nothing more than a register enable the setting in Furnace to unlock the export option by adding this to furnace.cfg: ``` -iCannotWait=true +iCannotWait=1 ``` go to file > export Amiga validation data... -put sample.bin and seq.bin in this directory. +put sample.bin, seq.bin and wave.bin in this directory. compile with vasm: diff --git a/src/engine/engine.h b/src/engine/engine.h index 0ff399bf8..7cbc1bd05 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -23,6 +23,7 @@ #include "instrument.h" #include "song.h" #include "dispatch.h" +#include "export.h" #include "dataErrors.h" #include "safeWriter.h" #include "../audio/taAudio.h" @@ -531,7 +532,7 @@ class DivEngine { SafeWriter* saveFur(bool notPrimary=false); // build a ROM file (TODO). // specify system to build ROM for. - SafeWriter* buildROM(int sys); + std::vector buildROM(int sys); // dump to VGM. // set trailingTicks to: // - 0 to add one tick of trailing diff --git a/src/engine/export.h b/src/engine/export.h new file mode 100644 index 000000000..82ba790d0 --- /dev/null +++ b/src/engine/export.h @@ -0,0 +1,63 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _EXPORT_H +#define _EXPORT_H + +#include "song.h" +#include + +class DivEngine; + +enum DivROMExportOptions { + DIV_ROM_ABSTRACT=0, + DIV_ROM_AMIGA_VALIDATION, + + DIV_ROM_MAX +}; + +struct DivROMExportOutput { + String name; + SafeWriter* data; + + DivROMExportOutput(String n, SafeWriter* d): + name(n), + data(d) {} + DivROMExportOutput(): + name(""), + data(NULL) {} +}; + +class DivROMExport { + const char* name; + const char* author; + const char* description; + DivSystem requisites[32]; + int requisitesLen; + bool multiOutput; + + public: + virtual std::vector go(DivEngine* e); + DivROMExport(const char* n, const char* auth, const char* desc): + name(n), + author(auth), + description(desc) {} +}; + +#endif diff --git a/src/engine/export/abstract.cpp b/src/engine/export/abstract.cpp new file mode 100644 index 000000000..e60e5b0fb --- /dev/null +++ b/src/engine/export/abstract.cpp @@ -0,0 +1,26 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../export.h" +#include "../../ta-log.h" + +std::vector go(DivEngine* e) { + logW("what's this? the null ROM export?"); + return std::vector(); +} \ No newline at end of file diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/engine/export/amigaValidation.h b/src/engine/export/amigaValidation.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index a83921adf..2a5e9729b 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -213,6 +213,10 @@ void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) { //logV("%.3x = %.4x",addr,val); regPool[addr>>1]=val; + if (!skipRegisterWrites && dumpWrites) { + addWrite(addr,val); + } + switch (addr&0x1fe) { case 0x96: { // DMACON if (val&32768) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2c96ceeb8..010e9f16c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3668,18 +3668,38 @@ bool FurnaceGUI::loop() { } if (numZSMCompat > 0) { if (ImGui::BeginMenu("export ZSM...")) { - ImGui::Text("Commander X16 Zsound Music File"); - if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { - if (zsmExportTickRate<1) zsmExportTickRate=1; - if (zsmExportTickRate>44100) zsmExportTickRate=44100; - } - ImGui::Checkbox("loop",&zsmExportLoop); - ImGui::SameLine(); - if (ImGui::Button("Begin Export")) { - openFileDialog(GUI_FILE_EXPORT_ZSM); - ImGui::CloseCurrentPopup(); - } - ImGui::EndMenu(); + ImGui::Text("Commander X16 Zsound Music File"); + if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { + if (zsmExportTickRate<1) zsmExportTickRate=1; + if (zsmExportTickRate>44100) zsmExportTickRate=44100; + } + ImGui::Checkbox("loop",&zsmExportLoop); + ImGui::SameLine(); + if (ImGui::Button("Begin Export")) { + openFileDialog(GUI_FILE_EXPORT_ZSM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::BeginMenu("export Amiga validation data...")) { + ImGui::Text( + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." + ); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); } } if (ImGui::BeginMenu("export command stream...")) { From 07ed76a63b620661966b51af30eb702d016dfb63 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 13 Mar 2023 14:17:05 -0500 Subject: [PATCH 14/64] add ROM export framework, part 1 --- CMakeLists.txt | 2 ++ src/engine/engine.cpp | 4 +++ src/engine/engine.h | 8 +++++- src/engine/export.cpp | 37 +++++++++++++++++++++++++++ src/engine/export.h | 24 ++++++++++++----- src/engine/export/abstract.cpp | 4 +-- src/engine/export/amigaValidation.cpp | 27 +++++++++++++++++++ src/engine/export/amigaValidation.h | 26 +++++++++++++++++++ 8 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 src/engine/export.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fa8f92c13..9dd1418c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -473,6 +473,7 @@ src/engine/config.cpp src/engine/configEngine.cpp src/engine/dispatchContainer.cpp src/engine/engine.cpp +src/engine/export.cpp src/engine/fileOps.cpp src/engine/fileOpsIns.cpp src/engine/filter.cpp @@ -553,6 +554,7 @@ src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp src/engine/export/abstract.cpp +src/engine/export/amigaValidation.cpp ) if (USE_SNDFILE) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 14d90aa62..d03b83b57 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2580,6 +2580,10 @@ int DivEngine::divToFileRate(int drate) { return 4; } +void DivEngine::testFunction() { + logI("it works!"); +} + int DivEngine::getEffectiveSampleRate(int rate) { if (rate<1) return 0; switch (song.system[0]) { diff --git a/src/engine/engine.h b/src/engine/engine.h index 7cbc1bd05..e5deb7ace 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -456,6 +456,8 @@ class DivEngine { void reset(); void playSub(bool preserveDrift, int goalRow=0); + void testFunction(); + bool loadDMF(unsigned char* file, size_t len); bool loadFur(unsigned char* file, size_t len); bool loadMod(unsigned char* file, size_t len); @@ -496,6 +498,10 @@ class DivEngine { // check whether an asset directory is complete void checkAssetDir(std::vector& dir, size_t entries); + // add every export method here + friend class DivROMExport; + friend class DivExportAmigaValidation; + public: DivSong song; DivOrders* curOrders; @@ -532,7 +538,7 @@ class DivEngine { SafeWriter* saveFur(bool notPrimary=false); // build a ROM file (TODO). // specify system to build ROM for. - std::vector buildROM(int sys); + std::vector buildROM(DivROMExportOptions sys); // dump to VGM. // set trailingTicks to: // - 0 to add one tick of trailing diff --git a/src/engine/export.cpp b/src/engine/export.cpp new file mode 100644 index 000000000..141736d9e --- /dev/null +++ b/src/engine/export.cpp @@ -0,0 +1,37 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "engine.h" + +#include "export/amigaValidation.h" + +std::vector DivEngine::buildROM(DivROMExportOptions sys) { + DivROMExport* exporter=NULL; + switch (sys) { + case DIV_ROM_AMIGA_VALIDATION: + exporter=new DivExportAmigaValidation; + break; + default: + exporter=new DivROMExport; + break; + } + std::vector ret=exporter->go(this); + delete exporter; + return ret; +} diff --git a/src/engine/export.h b/src/engine/export.h index 82ba790d0..07354fdb3 100644 --- a/src/engine/export.h +++ b/src/engine/export.h @@ -21,6 +21,7 @@ #define _EXPORT_H #include "song.h" +#include #include class DivEngine; @@ -45,6 +46,12 @@ struct DivROMExportOutput { }; class DivROMExport { + public: + virtual std::vector go(DivEngine* e); + virtual ~DivROMExport() {} +}; + +struct DivROMExportDef { const char* name; const char* author; const char* description; @@ -52,12 +59,17 @@ class DivROMExport { int requisitesLen; bool multiOutput; - public: - virtual std::vector go(DivEngine* e); - DivROMExport(const char* n, const char* auth, const char* desc): - name(n), - author(auth), - description(desc) {} + DivROMExportDef(const char* n, const char* a, const char* d, std::initializer_list req, bool multiOut): + name(n), + author(a), + description(d), + multiOutput(multiOut) { + requisitesLen=0; + memset(requisites,0,32*sizeof(DivSystem)); + for (DivSystem i: req) { + requisites[requisitesLen++]=i; + } + } }; #endif diff --git a/src/engine/export/abstract.cpp b/src/engine/export/abstract.cpp index e60e5b0fb..ce07519c0 100644 --- a/src/engine/export/abstract.cpp +++ b/src/engine/export/abstract.cpp @@ -20,7 +20,7 @@ #include "../export.h" #include "../../ta-log.h" -std::vector go(DivEngine* e) { +std::vector DivROMExport::go(DivEngine* e) { logW("what's this? the null ROM export?"); return std::vector(); -} \ No newline at end of file +} diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index e69de29bb..7552350e1 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -0,0 +1,27 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "amigaValidation.h" +#include "../engine.h" + +std::vector DivExportAmigaValidation::go(DivEngine* e) { + e->testFunction(); + + return std::vector(); +} diff --git a/src/engine/export/amigaValidation.h b/src/engine/export/amigaValidation.h index e69de29bb..94d5eabbe 100644 --- a/src/engine/export/amigaValidation.h +++ b/src/engine/export/amigaValidation.h @@ -0,0 +1,26 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../export.h" + +class DivExportAmigaValidation: public DivROMExport { + public: + std::vector go(DivEngine* e); + ~DivExportAmigaValidation() {} +}; From ced4fd8ee1775874f52335240b4509476f8dd772 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 13 Mar 2023 20:01:01 -0500 Subject: [PATCH 15/64] more --- src/asm/68k/amigatest/Makefile | 4 + src/asm/68k/amigatest/README.md | 35 ++++--- src/asm/68k/amigatest/player.s | 38 +++++++- src/engine/engine.h | 4 + src/engine/export/amigaValidation.cpp | 130 +++++++++++++++++++++++++- src/engine/platform/amiga.cpp | 20 ++++ src/gui/editControls.cpp | 36 +++++++ src/gui/gui.cpp | 1 + 8 files changed, 247 insertions(+), 21 deletions(-) create mode 100644 src/asm/68k/amigatest/Makefile diff --git a/src/asm/68k/amigatest/Makefile b/src/asm/68k/amigatest/Makefile new file mode 100644 index 000000000..19f1cdc15 --- /dev/null +++ b/src/asm/68k/amigatest/Makefile @@ -0,0 +1,4 @@ +all: player + +player: player.s + vasmm68k_mot -Fhunkexe -kick1hunks -o player player.s diff --git a/src/asm/68k/amigatest/README.md b/src/asm/68k/amigatest/README.md index 7cb5b0cdb..20ae10b84 100644 --- a/src/asm/68k/amigatest/README.md +++ b/src/asm/68k/amigatest/README.md @@ -26,24 +26,23 @@ run a.out on Amiga. it should play the exported song. # sequence format -## 00-0F: global +## 00-0F: per-channel (00, 10, 20, 30) -- 00: do nothing -- 01: next tick -- 02 xx: wait -- 03 xxxx: wait -- 06 xxxx: write to DMACON -- 0a xxxx: write to INTENA -- 0e xxxx: write to ADKCON - -## 10-1F: per-channel (10, 20, 30, 40) - -- 10 xxxxxx yyyy zzzzzz wwww: set loc/len +- 00 xxxxxx yyyy: set loc/len - x: loc - y: len - - z: loc after interrupt - - w: len after interrupt -- 12 xxxx yy: initialize wavetable (xxxx: pos; yy: length) -- 16 xxxx: set period -- 18 xx: set volume -- 1a xxxx: set data +- 01 xxxx yy: initialize wavetable (xxxx: pos; yy: length) +- 06 xxxx: set period +- 08 xx: set volume +- 0a xxxx: set data + +## F0-FF: global + +- f0: do nothing +- f1: next tick +- f2 xx: wait +- f3 xxxx: wait +- f6 xxxx: write to DMACON +- fa xxxx: write to INTENA +- fe xxxx: write to ADKCON +- ff: end of song diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index b382a77f6..3b36284e4 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -5,9 +5,34 @@ VPOSR = $dff004 COLOR00 = $dff180 +DMACONR = $dff002 +DMACON = $dff096 +AUD0LCH = $dff0a0 +AUD0LCL = $dff0a2 +AUD0LEN = $dff0a4 +AUD0PER = $dff0a6 +AUD0VOL = $dff0a8 +AUD0DAT = $dff0aa cseg - move.l #0,d0 + move.w #15,d0 + move.w d0,DMACON + +testDMACon: + move.w DMACON,d0 + btst #0,d0 + bne testDMACon + + lea sampleData(pc),a0 + move.l a0,AUD0LCH + move.w #$2000,d0 + move.w d0,AUD0LEN + move.w #$a0,d0 + move.w d0,AUD0PER + move.w #$40,d0 + move.w d0,AUD0VOL + move.l #$8201,d0 + move.w d0,DMACON main: jsr waitVBlank @@ -30,3 +55,14 @@ data_c curColor: dc.w 0 + +sampleData: + incbin "sample.bin" + +data_f + +sequence: + incbin "seq.bin" + +wavetable: + incbin "wave.bin" diff --git a/src/engine/engine.h b/src/engine/engine.h index e5deb7ace..7b84f5da2 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -48,6 +48,10 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; +#define EXTERN_BUSY_BEGIN e->softLocked=false; e->isBusy.lock(); +#define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock(); +#define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; + #define DIV_VERSION "dev145" #define DIV_ENGINE_VERSION 145 // for imports diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index 7552350e1..02811d753 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -19,9 +19,135 @@ #include "amigaValidation.h" #include "../engine.h" +#include "../platform/amiga.h" std::vector DivExportAmigaValidation::go(DivEngine* e) { - e->testFunction(); + std::vector ret; + DivPlatformAmiga* amiga=(DivPlatformAmiga*)e->getDispatch(0); - return std::vector(); + e->stop(); + e->repeatPattern=false; + e->setOrder(0); + EXTERN_BUSY_BEGIN_SOFT; + + // determine loop point + int loopOrder=0; + int loopRow=0; + int loopEnd=0; + e->walkSong(loopOrder,loopRow,loopEnd); + + e->curOrder=0; + e->freelance=false; + e->playing=false; + e->extValuePresent=false; + e->remainingLoops=-1; + + // play the song ourselves + bool done=false; + + // sample.bin + SafeWriter* sample=new SafeWriter; + sample->init(); + for (int i=0; i<256; i++) { + sample->writeI(0); + } + sample->write(&((const unsigned char*)amiga->getSampleMem(0))[0x400],amiga->getSampleMemUsage(0)-0x400); + if (sample->tell()&1) sample->writeC(0); + + // wave.bin + SafeWriter* wave=new SafeWriter; + wave->init(); + for (int i=0; i<32; i++) { + sample->writeC(i<<3); + } + + // seq.bin + SafeWriter* seq=new SafeWriter; + seq->init(); + + amiga->toggleRegisterDump(true); + + // write song data + e->playSub(false); + size_t songTick=0; + size_t lastTick=0; + bool writeLoop=false; + int loopPos=-1; + for (int i=0; ichans; i++) { + e->chan[i].wentThroughNote=false; + e->chan[i].goneThroughNote=false; + } + while (!done) { + if (loopPos==-1) { + if (loopOrder==e->curOrder && loopRow==e->curRow && e->ticks==1) { + writeLoop=true; + } + } + if (e->nextTick(false,true)) { + done=true; + amiga->getRegisterWrites().clear(); + break; + } + // get register writes + std::vector& writes=amiga->getRegisterWrites(); + for (DivRegWrite& j: writes) { + if (lastTick!=songTick) { + int delta=songTick-lastTick; + if (delta==1) { + seq->writeC(0xf1); + } else if (delta<256) { + seq->writeC(0xf2); + seq->writeC(delta); + } else if (delta<65536) { + seq->writeC(0xf3); + seq->writeS_BE(delta); + } + lastTick=songTick; + } + if (j.addr>=0x200) { // direct loc/len change + if (j.addr&4) { // len + seq->writeS_BE(j.val); + } else { // loc + seq->writeC((j.addr&3)<<4); + seq->writeC(j.val>>16); + seq->writeC(j.val>>8); + seq->writeC(j.val); + } + } else if (j.addr<0xa0) { + // don't write INTENA + if ((j.addr&15)!=10) { + seq->writeC(0xf0|(j.addr&15)); + seq->writeS_BE(j.val); + } + } else if ((j.addr&15)!=0 && (j.addr&15)!=2 && (j.addr&15)!=4) { + seq->writeC(j.addr-0xa0); + if ((j.addr&15)==8) { + seq->writeC(j.val); + } else { + seq->writeS_BE(j.val); + } + } + } + writes.clear(); + + songTick++; + } + // end of song + seq->writeC(0xff); + + amiga->toggleRegisterDump(false); + + e->remainingLoops=-1; + e->playing=false; + e->freelance=false; + e->extValuePresent=false; + + EXTERN_BUSY_END; + + // finish + ret.push_back(DivROMExportOutput("sample.bin",sample)); + ret.push_back(DivROMExportOutput("wave.bin",wave)); + ret.push_back(DivROMExportOutput("seq.bin",seq)); + + return ret; } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 2a5e9729b..ac67460b3 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -419,6 +419,10 @@ void DivPlatformAmiga::tick(bool sysTick) { chWrite(i,0,0); chWrite(i,2,i<<8); chWrite(i,4,chan[i].audLen); + if (dumpWrites) { + addWrite(0x200+i,i<<8); + addWrite(0x204+i,chan[i].audLen); + } rWrite(0x96,0x8000|(1<=0 && chan[i].samplesong.sampleLen) { @@ -436,10 +440,18 @@ void DivPlatformAmiga::tick(bool sysTick) { chWrite(i,0,0); chWrite(i,2,0x400); chWrite(i,4,1); + if (dumpWrites) { + addWrite(0x200+i,0x400); + addWrite(0x204+i,1); + } } else { chWrite(i,0,start>>16); chWrite(i,2,start); chWrite(i,4,len); + if (dumpWrites) { + addWrite(0x200+i,start); + addWrite(0x204+i,len); + } } rWrite(0x96,0x8000|(1< // 0: all directions @@ -575,6 +577,40 @@ void FurnaceGUI::drawMobileControls() { if (ImGui::Button("Switch to Desktop Mode")) { toggleMobileUI(!mobileUI); } + + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + + if (numAmiga) { + ImGui::Text( + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." + ); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + } + } + break; } } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 010e9f16c..ff530fa89 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3697,6 +3697,7 @@ bool FurnaceGUI::loop() { ImGui::SameLine(); ImGui::InputText("##AVDPath",&workingDirROMExport); if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); From 5a8a29f5f298812975fd609d8c5c56095f050cd8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 14 Mar 2023 01:09:53 -0500 Subject: [PATCH 16/64] Please enter the commit message for your changes. --- src/engine/export/amigaValidation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index 02811d753..2ec7326be 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -71,7 +71,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { e->playSub(false); size_t songTick=0; size_t lastTick=0; - bool writeLoop=false; + //bool writeLoop=false; int loopPos=-1; for (int i=0; ichans; i++) { e->chan[i].wentThroughNote=false; @@ -80,7 +80,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { while (!done) { if (loopPos==-1) { if (loopOrder==e->curOrder && loopRow==e->curRow && e->ticks==1) { - writeLoop=true; + //writeLoop=true; } } if (e->nextTick(false,true)) { From e5ab3413cb49edc7aba4d71c683c01419cf1ce2d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 14 Mar 2023 01:27:45 -0500 Subject: [PATCH 17/64] oh wow --- src/engine/dispatch.h | 6 +++--- src/engine/vgmOps.cpp | 4 ++-- src/gui/gui.cpp | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 6cb1b4e99..9a87cc874 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -280,15 +280,15 @@ struct DivRegWrite { * - 0xffffffff: reset */ unsigned int addr; - unsigned short val; - DivRegWrite(unsigned int a, unsigned short v): + unsigned int val; + DivRegWrite(unsigned int a, unsigned int v): addr(a), val(v) {} }; struct DivDelayedWrite { int time; DivRegWrite write; - DivDelayedWrite(int t, unsigned int a, unsigned short v): + DivDelayedWrite(int t, unsigned int a, unsigned int v): time(t), write(a,v) {} }; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index fdcd80720..01833d26f 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -583,8 +583,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID); switch (write.addr&0xff) { case 0: // play sample - if (write.val out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); From 4a225c9c64799be3265f5c7504ef71cd5075aee9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 14 Mar 2023 04:19:13 -0500 Subject: [PATCH 18/64] asdf --- src/asm/68k/amigatest/Makefile | 2 +- src/asm/68k/amigatest/player.s | 2 +- src/engine/export/amigaValidation.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/asm/68k/amigatest/Makefile b/src/asm/68k/amigatest/Makefile index 19f1cdc15..105ef8f9f 100644 --- a/src/asm/68k/amigatest/Makefile +++ b/src/asm/68k/amigatest/Makefile @@ -1,4 +1,4 @@ all: player -player: player.s +player: player.s sample.bin seq.bin wave.bin vasmm68k_mot -Fhunkexe -kick1hunks -o player player.s diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index 3b36284e4..f3c6e984c 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -14,7 +14,7 @@ AUD0PER = $dff0a6 AUD0VOL = $dff0a8 AUD0DAT = $dff0aa -cseg +code_c move.w #15,d0 move.w d0,DMACON diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index 2ec7326be..ac3e044be 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -58,7 +58,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { SafeWriter* wave=new SafeWriter; wave->init(); for (int i=0; i<32; i++) { - sample->writeC(i<<3); + wave->writeC(i<<3); } // seq.bin From ed3b0610d348977abcaa05c5e2ed2d22898f469d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 14 Mar 2023 23:04:57 -0500 Subject: [PATCH 19/64] GUI: remember extraChannelButtons' state --- src/gui/gui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2fc1144bb..95be83891 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5470,6 +5470,7 @@ bool FurnaceGUI::init() { waveSigned=e->getConfBool("waveSigned",false); waveGenVisible=e->getConfBool("waveGenVisible",false); waveEditStyle=e->getConfInt("waveEditStyle",0); + extraChannelButtons=e->getConfInt("extraChannelButtons",0); lockLayout=e->getConfBool("lockLayout",false); #ifdef IS_MOBILE fullScreen=true; @@ -5884,6 +5885,7 @@ void FurnaceGUI::commitState() { e->setConf("waveSigned",waveSigned); e->setConf("waveGenVisible",waveGenVisible); e->setConf("waveEditStyle",waveEditStyle); + e->setConf("extraChannelButtons",extraChannelButtons); e->setConf("lockLayout",lockLayout); e->setConf("fullScreen",fullScreen); e->setConf("mobileUI",mobileUI); From 557f77c3c6ce06d93bddacc3d0241d091bfec33a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 15 Mar 2023 02:13:10 -0500 Subject: [PATCH 20/64] asdfgklj --- src/asm/68k/amigatest/player.s | 79 +++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index f3c6e984c..49a2e2b93 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -5,43 +5,47 @@ VPOSR = $dff004 COLOR00 = $dff180 -DMACONR = $dff002 -DMACON = $dff096 -AUD0LCH = $dff0a0 -AUD0LCL = $dff0a2 -AUD0LEN = $dff0a4 -AUD0PER = $dff0a6 -AUD0VOL = $dff0a8 -AUD0DAT = $dff0aa + +chipBase=$dff000 + +DMACONR = $02 +DMACON = $96 +AUD0LCH = $a0 +AUD0LCL = $a2 +AUD0LEN = $a4 +AUD0PER = $a6 +AUD0VOL = $a8 +AUD0DAT = $aa code_c - move.w #15,d0 - move.w d0,DMACON +start: + lea chipBase,a0 + + move.w #15,DMACON(a0) testDMACon: - move.w DMACON,d0 + move.w DMACONR(a0),d0 btst #0,d0 bne testDMACon - lea sampleData(pc),a0 - move.l a0,AUD0LCH + lea sampleData(pc),a1 + move.l a1,AUD0LCH(a0) move.w #$2000,d0 - move.w d0,AUD0LEN - move.w #$a0,d0 - move.w d0,AUD0PER - move.w #$40,d0 - move.w d0,AUD0VOL - move.l #$8201,d0 - move.w d0,DMACON + move.w d0,AUD0LEN(a0) + move.w #$a0,AUD0PER(a0) + move.w #$40,AUD0VOL(a0) + move.w #$8201,DMACON(a0) main: jsr waitVBlank - + move.w curColor,d0 move.w d0,COLOR00 addi.w #1,d0 move.w d0,curColor + jsr nextTick + jmp main waitVBlank: @@ -51,11 +55,44 @@ waitVBlank: bne waitVBlank rts +nextTick: + lea state(pc),a4 + move.w (a4),d0 + subi.w #1,d0 + bmi nextTick1 + move.w d0,(a4) + rts +nextTick1: + move.l seqAddr(pc),a2 + ; get next command + move.b (a2)+,d0 + +testSpecial: + cmp.b #$f0,d0 + blt testChannel + + cmp.b #$ + +testChannel: + cmp.b #$40,d0 + bge nextTickPost + +nextTickPost: + lea seqAddr(pc),a3 + move.l a2,(a3) + bra nextTick1 + data_c curColor: dc.w 0 +state: + dc.w 0 ; ticks + +seqAddr: + dc.l sequence + sampleData: incbin "sample.bin" From 32298f6ab35df2c17e830cf7211d6b5ae8475b88 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 15 Mar 2023 04:23:47 -0500 Subject: [PATCH 21/64] nothing --- src/asm/68k/amigatest/README.md | 1 - src/asm/68k/amigatest/player.s | 129 ++++++++++++++++++++++++++------ 2 files changed, 107 insertions(+), 23 deletions(-) diff --git a/src/asm/68k/amigatest/README.md b/src/asm/68k/amigatest/README.md index 20ae10b84..22022982e 100644 --- a/src/asm/68k/amigatest/README.md +++ b/src/asm/68k/amigatest/README.md @@ -43,6 +43,5 @@ run a.out on Amiga. it should play the exported song. - f2 xx: wait - f3 xxxx: wait - f6 xxxx: write to DMACON -- fa xxxx: write to INTENA - fe xxxx: write to ADKCON - ff: end of song diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index 49a2e2b93..82745e2c9 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -10,6 +10,8 @@ chipBase=$dff000 DMACONR = $02 DMACON = $96 +ADKCON = $9e +AUDBASE = $a0 AUD0LCH = $a0 AUD0LCL = $a2 AUD0LEN = $a4 @@ -18,24 +20,6 @@ AUD0VOL = $a8 AUD0DAT = $aa code_c -start: - lea chipBase,a0 - - move.w #15,DMACON(a0) - -testDMACon: - move.w DMACONR(a0),d0 - btst #0,d0 - bne testDMACon - - lea sampleData(pc),a1 - move.l a1,AUD0LCH(a0) - move.w #$2000,d0 - move.w d0,AUD0LEN(a0) - move.w #$a0,AUD0PER(a0) - move.w #$40,AUD0VOL(a0) - move.w #$8201,DMACON(a0) - main: jsr waitVBlank @@ -71,16 +55,117 @@ testSpecial: cmp.b #$f0,d0 blt testChannel - cmp.b #$ +testF1: + ; f1 - next tick + cmp.b #$f1,d0 + bne testF2 + ; end of tick + move.w #0,(a4) + bra endTick +testF2: + ; f2 - wait (char) + cmp.b #$f2,d0 + bne testF3 + move.b (a2)+,d0 + andi.w #$ff,d0 + move.w d0,(a4) + bra endTick +testF3: + ; f3 - wait (short) + cmp.b #$f3,d0 + bne testF6 + clr.w d2 + move.b (a2)+,d2 + rol.w #8,d2 + or.b (a2)+,d2 + move.w d2,(a4) + bra endTick +testF6: + ; f6 - write DMACON + cmp.b #$f6,d0 + bne testFE + clr.w d2 + move.b (a2)+,d2 + rol.w #8,d2 + or.b (a2)+,d2 + move.w d2,chipBase+DMACON + bra nextTick1 +testFE: + ; fe - write ADKCON + cmp.b #$fe,d0 + bne testFF + clr.w d2 + move.b (a2)+,d2 + rol.w #8,d2 + or.b (a2)+,d2 + move.w d2,chipBase+ADKCON + bra nextTick1 +testFF: + ; ff - end of song + cmp.b #$ff,d0 + bne testOther + bra nextTick1 +testOther: + ; something else + bra nextTick1 testChannel: cmp.b #$40,d0 - bge nextTickPost + bge invalidCmd + ; process channel + move.b d0,d1 + andi.b #15,d0 + ; check for 0 + bne chanNotZero + ; write loc/len + clr.l d2 + move.b (a2)+,d2 + rol.w #8,d2 + or.b (a2)+,d2 + rol.w #8,d2 + or.b (a2)+,d2 + add.l sampleData(pc),d2 + lea chipBase,a0 + or.b d1,d0 + addi.b #AUDBASE,d0 + andi.w #$ff,d0 + adda.w d0,a0 + move.l d2,(a0) ; location + clr.w d2 + move.b (a2)+,d2 + rol.w #8,d2 + or.b (a2)+,d2 + adda.w #4,a0 + move.l d2,(a0) ; length + bra nextTick1 +chanNotZero: + ; check for 8 (VOL) + cmp.b #8,d0 + bne chanOther + ; write volume + clr.w d2 + move.b (a2)+,d2 + bra chanWrite +chanOther: + ; get value and write + clr.w d2 + move.b (a2)+,d2 + rol.w #8,d2 + or.b (a2)+,d2 +chanWrite: + lea chipBase,a0 + or.b d1,d0 + addi.b #AUDBASE,d0 + andi.w #$ff,d0 + adda.w d0,a0 + move.w d2,(a0) +invalidCmd: + bra nextTick1 -nextTickPost: +endTick: lea seqAddr(pc),a3 move.l a2,(a3) - bra nextTick1 + rts data_c From 4b708e98cf96905e4e6501f7475f7d219a22ed98 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 15 Mar 2023 18:16:47 -0500 Subject: [PATCH 22/64] Amiga: validation "export" only for testing purposes! wavetables are missing but I might add that at some point --- .gitignore | 2 + src/asm/68k/amigatest/Makefile | 2 +- src/asm/68k/amigatest/player.s | 129 +++++++++++++++++++------- src/engine/export/amigaValidation.cpp | 6 +- 4 files changed, 99 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 25bd6d0e0..8b45bb970 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ android/app/.cxx/ CMakeSettings.json CMakePresets.json extern/imgui_patched/examples/ +src/asm/68k/amigatest/*.bin +src/asm/68k/amigatest/player diff --git a/src/asm/68k/amigatest/Makefile b/src/asm/68k/amigatest/Makefile index 105ef8f9f..967d5d501 100644 --- a/src/asm/68k/amigatest/Makefile +++ b/src/asm/68k/amigatest/Makefile @@ -1,4 +1,4 @@ all: player player: player.s sample.bin seq.bin wave.bin - vasmm68k_mot -Fhunkexe -kick1hunks -o player player.s + vasmm68k_mot -Fhunkexe -kick1hunks -nosym -o player player.s diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index 82745e2c9..185b0730a 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -4,6 +4,7 @@ ; incomplete! VPOSR = $dff004 +VHPOSR = $dff006 COLOR00 = $dff180 chipBase=$dff000 @@ -18,42 +19,70 @@ AUD0LEN = $a4 AUD0PER = $a6 AUD0VOL = $a8 AUD0DAT = $aa +AUD1VOL = $b8 +AUD2VOL = $c8 +AUD3VOL = $d8 code_c +init: + lea chipBase,a0 + move.w #15,DMACON(a0) +waitCon: + move.w DMACONR(a0),d0 + andi.w #15,d0 + bne waitCon + + move.w #$8200,DMACON(a0) + move.w #$40,AUD0VOL(a0) + move.w #$40,AUD1VOL(a0) + move.w #$40,AUD2VOL(a0) + move.w #$40,AUD3VOL(a0) + lea seqAddr(pc),a0 + lea sequence(pc),a1 + move.l a1,(a0) main: - jsr waitVBlank + bsr waitVBlank - move.w curColor,d0 - move.w d0,COLOR00 - addi.w #1,d0 - move.w d0,curColor + ;move.w curColor,d0 + ;move.w d0,COLOR00 + ;addi.w #1,d0 + ;move.w d0,curColor - jsr nextTick + bsr nextTick - jmp main + bra main waitVBlank: move.l (VPOSR),d0 and.l #$1ff00,d0 - cmp.l #$12c00,d0 + cmp.l #$8c00,d0 bne waitVBlank +waitVBlank2: + move.l (VPOSR),d0 + and.l #$1ff00,d0 + cmp.l #$8d00,d0 + bne waitVBlank2 rts nextTick: lea state(pc),a4 move.w (a4),d0 subi.w #1,d0 - bmi nextTick1 + bmi nextTick0 move.w d0,(a4) rts -nextTick1: +nextTick0: move.l seqAddr(pc),a2 +nextTick1: ; get next command + clr.w d0 move.b (a2)+,d0 + move.w #$0ff,d4 + move.w d4,COLOR00 testSpecial: - cmp.b #$f0,d0 - blt testChannel + cmp.w #$f0,d0 + bmi testChannel testF1: ; f1 - next tick @@ -76,7 +105,7 @@ testF3: bne testF6 clr.w d2 move.b (a2)+,d2 - rol.w #8,d2 + lsl.w #8,d2 or.b (a2)+,d2 move.w d2,(a4) bra endTick @@ -84,11 +113,23 @@ testF6: ; f6 - write DMACON cmp.b #$f6,d0 bne testFE + move.w #$f00,d4 + move.w d4,COLOR00 clr.w d2 move.b (a2)+,d2 - rol.w #8,d2 + lsl.w #8,d2 or.b (a2)+,d2 move.w d2,chipBase+DMACON + ; wait for DMACON to be done + move.b (VHPOSR),d0 +dmaConWait: + cmp.b (VHPOSR),d0 + beq dmaConWait + ; wait for DMACON to be done -2 + move.b (VHPOSR),d0 +dmaConWait1: + cmp.b (VHPOSR),d0 + beq dmaConWait1 bra nextTick1 testFE: ; fe - write ADKCON @@ -96,7 +137,7 @@ testFE: bne testFF clr.w d2 move.b (a2)+,d2 - rol.w #8,d2 + lsl.w #8,d2 or.b (a2)+,d2 move.w d2,chipBase+ADKCON bra nextTick1 @@ -104,7 +145,10 @@ testFF: ; ff - end of song cmp.b #$ff,d0 bne testOther - bra nextTick1 +theEnd: + move.w #$fff,d4 + move.w d4,COLOR00 + bra theEnd testOther: ; something else bra nextTick1 @@ -118,25 +162,27 @@ testChannel: ; check for 0 bne chanNotZero ; write loc/len + move.w #$f0f,d4 + move.w d4,COLOR00 clr.l d2 move.b (a2)+,d2 - rol.w #8,d2 + lsl.l #8,d2 or.b (a2)+,d2 - rol.w #8,d2 + lsl.l #8,d2 or.b (a2)+,d2 - add.l sampleData(pc),d2 + lea sampleData(pc),a0 + add.l a0,d2 lea chipBase,a0 - or.b d1,d0 - addi.b #AUDBASE,d0 - andi.w #$ff,d0 - adda.w d0,a0 - move.l d2,(a0) ; location + move.b d1,d0 + andi.l #$ff,d0 + addi.l #$a0,d0 + adda.l d0,a0 + move.l d2,(a0)+ ; location clr.w d2 move.b (a2)+,d2 - rol.w #8,d2 + lsl.w #8,d2 or.b (a2)+,d2 - adda.w #4,a0 - move.l d2,(a0) ; length + move.w d2,(a0) ; length bra nextTick1 chanNotZero: ; check for 8 (VOL) @@ -150,14 +196,16 @@ chanOther: ; get value and write clr.w d2 move.b (a2)+,d2 - rol.w #8,d2 + lsl.w #8,d2 or.b (a2)+,d2 chanWrite: + move.w #$ff0,d4 + move.w d4,COLOR00 lea chipBase,a0 or.b d1,d0 addi.b #AUDBASE,d0 - andi.w #$ff,d0 - adda.w d0,a0 + andi.l #$ff,d0 + adda.l d0,a0 move.w d2,(a0) invalidCmd: bra nextTick1 @@ -165,9 +213,12 @@ invalidCmd: endTick: lea seqAddr(pc),a3 move.l a2,(a3) + move.w #$000,d4 + move.w d4,COLOR00 rts data_c + cnop 0,4 curColor: dc.w 0 @@ -175,16 +226,22 @@ curColor: state: dc.w 0 ; ticks + cnop 0,4 + seqAddr: - dc.l sequence - -sampleData: - incbin "sample.bin" - -data_f + dc.l 0 + + cnop 0,4 sequence: incbin "seq.bin" + cnop 0,4 + +sampleData: + incbin "sample.bin" + +;data_f + wavetable: incbin "wave.bin" diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index ac3e044be..385c8e3df 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -97,10 +97,10 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { seq->writeC(0xf1); } else if (delta<256) { seq->writeC(0xf2); - seq->writeC(delta); - } else if (delta<65536) { + seq->writeC(delta-1); + } else if (delta<32768) { seq->writeC(0xf3); - seq->writeS_BE(delta); + seq->writeS_BE(delta-1); } lastTick=songTick; } From 3ff3a9952a63ad02f0218d3de25e3d4c73cf8c84 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 15 Mar 2023 18:45:32 -0500 Subject: [PATCH 23/64] Amiga: DMACON coalescing --- src/asm/68k/amigatest/player.s | 2 +- src/engine/platform/amiga.cpp | 45 +++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index 185b0730a..9234b17bd 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -170,7 +170,7 @@ testChannel: or.b (a2)+,d2 lsl.l #8,d2 or.b (a2)+,d2 - lea sampleData(pc),a0 + lea sampleData,a0 add.l a0,d2 lea chipBase,a0 move.b d1,d0 diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index ac67460b3..83d9edbe2 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -404,6 +404,29 @@ void DivPlatformAmiga::tick(bool sysTick) { chan[i].keyOn=true; } } + } + + unsigned short dmaOff=0; + unsigned short dmaOn=0; + for (int i=0; i<4; i++) { + if (chan[i].keyOn || chan[i].keyOff) { + chWrite(i,6,1); + dmaOff|=1<=0 && chan[i].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); @@ -413,7 +436,6 @@ void DivPlatformAmiga::tick(bool sysTick) { chWrite(i,6,chan[i].freq); if (chan[i].keyOn) { - rWrite(0x96,1<=0 && chan[i].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[i].sample); @@ -454,7 +476,7 @@ void DivPlatformAmiga::tick(bool sysTick) { } } - rWrite(0x96,0x8000|(1<isLoopable()) { int loopPos=(sampleOff[chan[i].sample]+s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))&(~1); int loopEnd=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))>>1; @@ -466,10 +488,6 @@ void DivPlatformAmiga::tick(bool sysTick) { chan[i].irLocL=0x400; chan[i].irLen=1; } - if (dumpWrites) { - addWrite(0x200+i,(chan[i].irLocH<<16)|chan[i].irLocL); - addWrite(0x204+i,chan[i].irLen); - } rWrite(0x9a,0x8000|(128< Date: Wed, 15 Mar 2023 18:51:42 -0500 Subject: [PATCH 24/64] Amiga: DMACON coalescing fix --- src/engine/platform/amiga.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 83d9edbe2..e07fa80a7 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -415,7 +415,7 @@ void DivPlatformAmiga::tick(bool sysTick) { } } - rWrite(0x96,dmaOff); + if (dmaOff) rWrite(0x96,dmaOff); for (int i=0; i<4; i++) { double off=1.0; @@ -506,7 +506,7 @@ void DivPlatformAmiga::tick(bool sysTick) { } } - rWrite(0x96,0x8000|dmaOn); + if (dmaOn) rWrite(0x96,0x8000|dmaOn); for (int i=0; i<4; i++) { if ((dmaOn&(1< Date: Wed, 15 Mar 2023 22:08:18 -0500 Subject: [PATCH 25/64] GUI: AY-3-8914 VGM disclaimer --- src/gui/sysConf.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 654f075b4..3650d1b96 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -621,6 +621,9 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo chipType=3; altered=true; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("note: AY-3-8914 is not supported by the VGM format!"); + } } ImGui::BeginDisabled(type==DIV_SYSTEM_AY8910 && chipType==2); if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { From 262eaa19c12ef3862defeef62968c1e5414f386b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 16 Mar 2023 01:44:35 -0500 Subject: [PATCH 26/64] Amiga: validation export wave support --- src/asm/68k/amigatest/README.md | 9 ++-- src/asm/68k/amigatest/player.s | 72 ++++++++++++++++++++----- src/engine/export/amigaValidation.cpp | 75 ++++++++++++++++++++++++--- src/engine/platform/amiga.cpp | 2 +- src/engine/platform/amiga.h | 1 + 5 files changed, 135 insertions(+), 24 deletions(-) diff --git a/src/asm/68k/amigatest/README.md b/src/asm/68k/amigatest/README.md index 22022982e..5ac4a9cd4 100644 --- a/src/asm/68k/amigatest/README.md +++ b/src/asm/68k/amigatest/README.md @@ -16,13 +16,14 @@ go to file > export Amiga validation data... put sample.bin, seq.bin and wave.bin in this directory. -compile with vasm: +type `make`. you need vasm (68000 with Mot syntax) in order for it to work. +alternatively, type: ``` -vasmm68k_mot -Fhunkexe -kick1hunks player.s +vasmm68k_mot -Fhunkexe -kick1hunks -nosym -o player player.s ``` -run a.out on Amiga. it should play the exported song. +run `player` on Amiga. it should play the exported song. # sequence format @@ -31,7 +32,7 @@ run a.out on Amiga. it should play the exported song. - 00 xxxxxx yyyy: set loc/len - x: loc - y: len -- 01 xxxx yy: initialize wavetable (xxxx: pos; yy: length) +- 01 xxxxxx yyyy: initialize wavetable (xxxx: pos; yy: length) - 06 xxxx: set period - 08 xx: set volume - 0a xxxx: set data diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index 9234b17bd..1a41bf835 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -10,6 +10,7 @@ COLOR00 = $dff180 chipBase=$dff000 DMACONR = $02 +POTGOR = $16 DMACON = $96 ADKCON = $9e AUDBASE = $a0 @@ -25,6 +26,7 @@ AUD3VOL = $d8 code_c init: + move.b #2,$bfe001 lea chipBase,a0 move.w #15,DMACON(a0) waitCon: @@ -43,24 +45,37 @@ waitCon: main: bsr waitVBlank - ;move.w curColor,d0 - ;move.w d0,COLOR00 - ;addi.w #1,d0 - ;move.w d0,curColor + move.w #$000,d4 + move.w d4,COLOR00 + lea chipBase,a0 + btst.b #2,POTGOR(a0) + bne next + + lea state(pc),a0 + move.w #1,2(a0) + +next: bsr nextTick - bra main + lea state(pc),a0 + tst.w 2(a0) + beq main +finish: + lea chipBase,a0 + move.w #15,DMACON(a0) + clr.l d0 + rts waitVBlank: move.l (VPOSR),d0 and.l #$1ff00,d0 - cmp.l #$8c00,d0 + cmp.l #$bc00,d0 bne waitVBlank waitVBlank2: move.l (VPOSR),d0 and.l #$1ff00,d0 - cmp.l #$8d00,d0 + cmp.l #$bd00,d0 bne waitVBlank2 rts @@ -146,9 +161,7 @@ testFF: cmp.b #$ff,d0 bne testOther theEnd: - move.w #$fff,d4 - move.w d4,COLOR00 - bra theEnd + lea sequence(pc),a2 testOther: ; something else bra nextTick1 @@ -187,11 +200,44 @@ testChannel: chanNotZero: ; check for 8 (VOL) cmp.b #8,d0 - bne chanOther + bne chanWaveChange ; write volume clr.w d2 move.b (a2)+,d2 bra chanWrite +chanWaveChange: + ; check for 1 (wave change) + cmp.b #1,d0 + bne chanOther + ; copy wave + clr.l d2 + move.b (a2)+,d2 + lsl.l #8,d2 + or.b (a2)+,d2 + lsl.l #8,d2 + or.b (a2)+,d2 + add.l #wavetable,d2 + move.l d2,a0 + + lea sampleData,a1 + andi.l #$30,d1 + lsl.l #4,d1 + adda.l d1,a1 + + clr.l d2 + move.b (a2)+,d2 + lsl.l #8,d2 + or.b (a2)+,d2 + + ; don't copy a zero-length wave + tst.l d2 + beq nextTick1 +copyWave: + move.w (a0)+,(a1)+ + subq.l #2,d2 + bne copyWave + + bra nextTick1 chanOther: ; get value and write clr.w d2 @@ -225,6 +271,7 @@ curColor: state: dc.w 0 ; ticks + dc.w 0 ; quit cnop 0,4 @@ -241,7 +288,8 @@ sequence: sampleData: incbin "sample.bin" -;data_f + cnop 0,4 wavetable: incbin "wave.bin" + diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index 385c8e3df..7e63d3be4 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -21,8 +21,23 @@ #include "../engine.h" #include "../platform/amiga.h" +struct WaveEntry { + unsigned int pos; + short width; + signed char data[256]; + WaveEntry(): + pos(0), + width(32) { + memset(data,0,256); + } +}; + std::vector DivExportAmigaValidation::go(DivEngine* e) { std::vector ret; + std::vector waves; + unsigned int wavesDataPtr=0; + WaveEntry curWaveState[4]; + DivPlatformAmiga* amiga=(DivPlatformAmiga*)e->getDispatch(0); e->stop(); @@ -54,13 +69,6 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { sample->write(&((const unsigned char*)amiga->getSampleMem(0))[0x400],amiga->getSampleMemUsage(0)-0x400); if (sample->tell()&1) sample->writeC(0); - // wave.bin - SafeWriter* wave=new SafeWriter; - wave->init(); - for (int i=0; i<32; i++) { - wave->writeC(i<<3); - } - // seq.bin SafeWriter* seq=new SafeWriter; seq->init(); @@ -86,8 +94,54 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { if (e->nextTick(false,true)) { done=true; amiga->getRegisterWrites().clear(); + if (lastTick!=songTick) { + int delta=songTick-lastTick; + if (delta==1) { + seq->writeC(0xf1); + } else if (delta<256) { + seq->writeC(0xf2); + seq->writeC(delta-1); + } else if (delta<32768) { + seq->writeC(0xf3); + seq->writeS_BE(delta-1); + } + lastTick=songTick; + } break; } + // check wavetable changes + for (int i=0; i<4; i++) { + if (amiga->chan[i].useWave) { + if ((amiga->chan[i].audLen*2)!=curWaveState[i].width || memcmp(curWaveState[i].data,&(((signed char*)amiga->getSampleMem())[i<<8]),amiga->chan[i].audLen*2)!=0) { + curWaveState[i].width=amiga->chan[i].audLen*2; + memcpy(curWaveState[i].data,&(((signed char*)amiga->getSampleMem())[i<<8]),amiga->chan[i].audLen*2); + + int waveNum=-1; + for (size_t j=0; jwriteC((i<<4)|1); + seq->writeC(waves[waveNum].pos>>16); + seq->writeC(waves[waveNum].pos>>8); + seq->writeC(waves[waveNum].pos); + seq->writeS_BE(waves[waveNum].width); + } + } + } + // get register writes std::vector& writes=amiga->getRegisterWrites(); for (DivRegWrite& j: writes) { @@ -144,6 +198,13 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { EXTERN_BUSY_END; + // wave.bin + SafeWriter* wave=new SafeWriter; + wave->init(); + for (WaveEntry& i: waves) { + wave->write(i.data,i.width); + } + // finish ret.push_back(DivROMExportOutput("sample.bin",sample)); ret.push_back(DivROMExportOutput("wave.bin",wave)); diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index e07fa80a7..14f2de80a 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -509,7 +509,7 @@ void DivPlatformAmiga::tick(bool sysTick) { if (dmaOn) rWrite(0x96,0x8000|dmaOn); for (int i=0; i<4; i++) { - if ((dmaOn&(1< Date: Thu, 16 Mar 2023 03:33:55 -0500 Subject: [PATCH 27/64] Amiga: validation export sample/wave book tiny optimization --- src/asm/68k/amigatest/README.md | 3 + src/asm/68k/amigatest/player.s | 121 +++++++++++++++++++++++--- src/engine/export/amigaValidation.cpp | 82 ++++++++++++++--- src/engine/safeWriter.cpp | 10 +++ 4 files changed, 192 insertions(+), 24 deletions(-) diff --git a/src/asm/68k/amigatest/README.md b/src/asm/68k/amigatest/README.md index 5ac4a9cd4..10af4a3b9 100644 --- a/src/asm/68k/amigatest/README.md +++ b/src/asm/68k/amigatest/README.md @@ -33,6 +33,9 @@ run `player` on Amiga. it should play the exported song. - x: loc - y: len - 01 xxxxxx yyyy: initialize wavetable (xxxx: pos; yy: length) +- 02 xx: set loc/len from sample book +- 03 xx: initialize wavetable from wave book +- 04 xxxx: initialize wavetable from wave book (short) - 06 xxxx: set period - 08 xx: set volume - 0a xxxx: set data diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index 1a41bf835..42396ebf7 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -174,6 +174,7 @@ testChannel: andi.b #15,d0 ; check for 0 bne chanNotZero +sampleWrite: ; write loc/len move.w #$f0f,d4 move.w d4,COLOR00 @@ -200,15 +201,38 @@ testChannel: chanNotZero: ; check for 8 (VOL) cmp.b #8,d0 - bne chanWaveChange + bne chanSampleBook ; write volume clr.w d2 move.b (a2)+,d2 bra chanWrite +chanSampleBook: + ; check for 2 (loc/len from book) + cmp.b #2,d0 + bne chanWaveChange + + move.w #$f0f,d4 + move.w d4,COLOR00 + clr.l d3 + move.b (a2)+,d3 + bsr getSampleBook + + ; write loc/len + lea sampleData,a0 + add.l a0,d2 + lea chipBase,a0 + move.b d1,d0 + andi.l #$f0,d0 + addi.l #$a0,d0 + adda.l d0,a0 + move.l d2,(a0)+ ; location + move.w d3,(a0) ; length + + bra nextTick1 chanWaveChange: ; check for 1 (wave change) cmp.b #1,d0 - bne chanOther + bne chanWaveBookB ; copy wave clr.l d2 move.b (a2)+,d2 @@ -229,14 +253,41 @@ chanWaveChange: lsl.l #8,d2 or.b (a2)+,d2 - ; don't copy a zero-length wave - tst.l d2 - beq nextTick1 -copyWave: - move.w (a0)+,(a1)+ - subq.l #2,d2 - bne copyWave + bsr copyWave + bra nextTick1 +chanWaveBookB: + ; check for 3 (wave change from book) + cmp.b #3,d0 + bne chanWaveBookW + clr.l d3 + move.b (a2)+,d3 + bsr getWaveBook + + lea sampleData,a1 + andi.l #$30,d1 + lsl.l #4,d1 + adda.l d1,a1 + + bsr copyWave + bra nextTick1 +chanWaveBookW: + ; check for 4 (wave change from book short) + cmp.b #4,d0 + bne chanOther + + clr.l d3 + move.b (a2)+,d3 + lsl.l #8,d3 + or.b (a2)+,d3 + bsr getWaveBook + + lea sampleData,a1 + andi.l #$30,d1 + lsl.l #4,d1 + adda.l d1,a1 + + bsr copyWave bra nextTick1 chanOther: ; get value and write @@ -263,6 +314,45 @@ endTick: move.w d4,COLOR00 rts +; a0: source. a1: destination. d2: length. +copyWave: + ; don't copy a zero-length wave + tst.l d2 + beq noCopy +copyWaveLoop: + move.w (a0)+,(a1)+ + subq.l #2,d2 + bne copyWaveLoop +noCopy: + rts + +; put wave book entry in a0/d2. d3: index (modified). +getWaveBook: + lea waveBook(pc),a3 + lsl.l #2,d3 + move.l (a3,d3),d2 + move.l d2,d3 + rol.l #8,d2 + andi.l #$ff,d2 + bne getWaveBook2 + move.w #$100,d2 +getWaveBook2: + andi.l #$ffffff,d3 + add.l #wavetable,d3 + move.l d3,a0 + rts + +; get sample book entry in d2/d3. d3: index. +getSampleBook: + lea sampleBook(pc),a3 + lsl.l #3,d3 + adda.l d3,a3 + move.l (a3)+,d2 + move.l (a3)+,d3 + andi.l #$ffffff,d2 + andi.l #$ffff,d3 + rts + data_c cnop 0,4 @@ -272,24 +362,27 @@ curColor: state: dc.w 0 ; ticks dc.w 0 ; quit - cnop 0,4 seqAddr: dc.l 0 - + cnop 0,4 + +sampleBook: + incbin "sbook.bin" + cnop 0,4 + +waveBook: + incbin "wbook.bin" cnop 0,4 sequence: incbin "seq.bin" - cnop 0,4 sampleData: incbin "sample.bin" - cnop 0,4 wavetable: incbin "wave.bin" - diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index 7e63d3be4..dc836f33e 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -32,11 +32,21 @@ struct WaveEntry { } }; +struct SampleBookEntry { + unsigned int loc; + unsigned short len; + SampleBookEntry(): + loc(0), + len(0) {} +}; + std::vector DivExportAmigaValidation::go(DivEngine* e) { std::vector ret; std::vector waves; + std::vector sampleBook; unsigned int wavesDataPtr=0; WaveEntry curWaveState[4]; + unsigned int sampleBookLoc=0; DivPlatformAmiga* amiga=(DivPlatformAmiga*)e->getDispatch(0); @@ -133,11 +143,19 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { wavesDataPtr+=curWaveState[i].width; } - seq->writeC((i<<4)|1); - seq->writeC(waves[waveNum].pos>>16); - seq->writeC(waves[waveNum].pos>>8); - seq->writeC(waves[waveNum].pos); - seq->writeS_BE(waves[waveNum].width); + if (waveNum<256) { + seq->writeC((i<<4)|3); + seq->writeC(waveNum); + } else if (waveNum<65536) { + seq->writeC((i<<4)|4); + seq->writeS_BE(waveNum); + } else{ + seq->writeC((i<<4)|1); + seq->writeC(waves[waveNum].pos>>16); + seq->writeC(waves[waveNum].pos>>8); + seq->writeC(waves[waveNum].pos); + seq->writeS_BE(waves[waveNum].width); + } } } } @@ -160,12 +178,35 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { } if (j.addr>=0x200) { // direct loc/len change if (j.addr&4) { // len - seq->writeS_BE(j.val); + int sampleBookIndex=-1; + for (size_t i=0; iwriteC((j.addr&3)<<4); + seq->writeC(sampleBookLoc>>16); + seq->writeC(sampleBookLoc>>8); + seq->writeC(sampleBookLoc); + seq->writeS_BE(j.val); + } else { + seq->writeC(((j.addr&3)<<4)|2); + seq->writeC(sampleBookIndex); + } } else { // loc - seq->writeC((j.addr&3)<<4); - seq->writeC(j.val>>16); - seq->writeC(j.val>>8); - seq->writeC(j.val); + sampleBookLoc=j.val; } } else if (j.addr<0xa0) { // don't write INTENA @@ -204,8 +245,29 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { for (WaveEntry& i: waves) { wave->write(i.data,i.width); } + + // sbook.bin + SafeWriter* sbook=new SafeWriter; + sbook->init(); + for (SampleBookEntry& i: sampleBook) { + // 8 bytes per entry + sbook->writeI_BE(i.loc); + sbook->writeI_BE(i.len); + } + + // wbook.bin + SafeWriter* wbook=new SafeWriter; + wbook->init(); + for (WaveEntry& i: waves) { + wbook->writeC(i.width); + wbook->writeC(i.pos>>16); + wbook->writeC(i.pos>>8); + wbook->writeC(i.pos); + } // finish + ret.push_back(DivROMExportOutput("sbook.bin",sbook)); + ret.push_back(DivROMExportOutput("wbook.bin",wbook)); ret.push_back(DivROMExportOutput("sample.bin",sample)); ret.push_back(DivROMExportOutput("wave.bin",wave)); ret.push_back(DivROMExportOutput("seq.bin",seq)); diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index a0c295d30..bd327607b 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -144,6 +144,16 @@ int SafeWriter::writeI(int val) { return write(&val,4); } +int SafeWriter::writeI_BE(int val) { + unsigned char bytes[4] { + (unsigned char)((val>>24)&0xff), + (unsigned char)((val>>16)&0xff), + (unsigned char)((val>>8)&0xff), + (unsigned char)(val&0xff) + }; + return write(bytes,4); +} + int SafeWriter::writeL(int64_t val) { return write(&val,8); } From c7d625c82011ed58f3cec26ce332b9ead90ef4af Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 16 Mar 2023 03:52:54 -0500 Subject: [PATCH 28/64] Amiga: validation export is finished --- src/asm/68k/amigatest/README.md | 7 ++++++- src/asm/68k/amigatest/player.s | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/asm/68k/amigatest/README.md b/src/asm/68k/amigatest/README.md index 10af4a3b9..300928a80 100644 --- a/src/asm/68k/amigatest/README.md +++ b/src/asm/68k/amigatest/README.md @@ -14,7 +14,7 @@ iCannotWait=1 go to file > export Amiga validation data... -put sample.bin, seq.bin and wave.bin in this directory. +put sample.bin, sbook.bin, seq.bin, wave.bin and wbook.bin in this directory. type `make`. you need vasm (68000 with Mot syntax) in order for it to work. alternatively, type: @@ -25,6 +25,11 @@ vasmm68k_mot -Fhunkexe -kick1hunks -nosym -o player player.s run `player` on Amiga. it should play the exported song. +# notes + +may not work correctly if you have slow/fast memory! +sequence and wave data should reside in fast memory but I haven't figured out how to... + # sequence format ## 00-0F: per-channel (00, 10, 20, 30) diff --git a/src/asm/68k/amigatest/player.s b/src/asm/68k/amigatest/player.s index 42396ebf7..94a2c05e5 100644 --- a/src/asm/68k/amigatest/player.s +++ b/src/asm/68k/amigatest/player.s @@ -1,8 +1,6 @@ ; Furnace validation player code ; this is NOT the ROM export you're looking for! -; incomplete! - VPOSR = $dff004 VHPOSR = $dff006 COLOR00 = $dff180 From f8e5afc2bf6f961eeb9f744c953775472fd1727a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 16 Mar 2023 03:53:02 -0500 Subject: [PATCH 29/64] RF5C68: fix forceIns() panning --- src/engine/platform/rf5c68.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 7301bb40c..149098b8c 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -307,6 +307,7 @@ void DivPlatformRF5C68::forceIns() { chan[i].insChanged=true; chan[i].freqChanged=true; chan[i].sample=-1; + chWrite(i,1,isMuted[i]?0:chan[i].panning); } } From 9a672196fde7d8dfe8df3462e780db047c26e64c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 16 Mar 2023 04:21:52 -0500 Subject: [PATCH 30/64] TODO: Diagnosis of 163 --- src/engine/platform/n163.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index afd4898a0..0cd0a7ed1 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -19,6 +19,7 @@ #include "n163.h" #include "../engine.h" +#include "../../ta-log.h" #include #define rRead(a,v) n163.addr_w(a); n163.data_r(v); @@ -166,6 +167,7 @@ void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) { void DivPlatformN163::updateWaveCh(int ch) { if (ch<=chanMax) { + logV("updateWave with pos %d and len %d",chan[ch].wavePos,chan[ch].waveLen); updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen); if (chan[ch].active && !isMuted[ch]) { chan[ch].volumeChanged=true; @@ -337,10 +339,10 @@ int DivPlatformN163::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163); if (chan[c.chan].insChanged) { chan[c.chan].wave=ins->n163.wave; - chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].wavePos=ins->n163.wavePos; chan[c.chan].waveLen=ins->n163.waveLen; chan[c.chan].waveMode=ins->n163.waveMode; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].waveChanged=true; if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) { chan[c.chan].waveUpdated=true; From 8fd26289a2d4dca65388e857712aa6d0f6d37866 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 16 Mar 2023 05:27:43 -0500 Subject: [PATCH 31/64] Namco WSG: add ROM mode --- src/engine/platform/namcowsg.cpp | 36 +++++++++++++++++++++++++++----- src/engine/platform/namcowsg.h | 2 ++ src/engine/waveSynth.cpp | 2 ++ src/gui/sysConf.cpp | 25 ++++++++++++++++++---- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 10eb3453c..0fe2ccd34 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -183,6 +183,7 @@ void DivPlatformNamcoWSG::acquire(short** buf, size_t len) { } void DivPlatformNamcoWSG::updateWave(int ch) { + if (romMode) return; if (devType==30) { for (int i=0; i<32; i++) { ((namco_cus30_device*)namco)->namcos1_cus30_w(i+ch*32,chan[ch].ws.output[i]); @@ -291,9 +292,9 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { rWrite(0x1d,(chan[2].freq>>12)&15); rWrite(0x1e,(chan[2].freq>>16)&15); - rWrite(0x05,0); - rWrite(0x0a,1); - rWrite(0x0f,2); + rWrite(0x05,romMode?(chan[0].wave&7):0); + rWrite(0x0a,romMode?(chan[1].wave&7):1); + rWrite(0x0f,romMode?(chan[2].wave&7):2); break; case 15: for (int i=0; i<8; i++) { @@ -304,7 +305,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { } rWrite((i<<3)+0x04,chan[i].freq&0xff); rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff); - rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4)); + rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|((romMode?(chan[i].wave&7):i)<<4)); } break; case 30: @@ -496,10 +497,11 @@ void DivPlatformNamcoWSG::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } - // TODO: wave memory namco->set_voices(chans); namco->set_stereo((devType==2 || devType==30)); namco->device_start(NULL); + + updateROMWaves(); } int DivPlatformNamcoWSG::getOutputCount() { @@ -510,6 +512,27 @@ bool DivPlatformNamcoWSG::keyOffAffectsArp(int ch) { return true; } +void DivPlatformNamcoWSG::updateROMWaves() { + if (romMode) { + // copy wavetables + for (int i=0; i<8; i++) { + int data=0; + DivWavetable* w=parent->getWave(i); + + for (int j=0; j<32; j++) { + if (w->max<1 || w->len<1) { + data=0; + } else { + data=w->data[j*w->len/32]*15/w->max; + if (data<0) data=0; + if (data>15) data=15; + } + namco->update_namco_waveform(i*32+j,data); + } + } + } +} + void DivPlatformNamcoWSG::notifyWaveChange(int wave) { for (int i=0; irate=rate; } newNoise=flags.getBool("newNoise",true); + romMode=flags.getBool("romMode",true); + if (devType==30) romMode=false; } void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) { diff --git a/src/engine/platform/namcowsg.h b/src/engine/platform/namcowsg.h index dcc52549f..9d418a9d2 100644 --- a/src/engine/platform/namcowsg.h +++ b/src/engine/platform/namcowsg.h @@ -50,8 +50,10 @@ class DivPlatformNamcoWSG: public DivDispatch { namco_audio_device* namco; int devType, chans; bool newNoise; + bool romMode; unsigned char regPool[512]; void updateWave(int ch); + void updateROMWaves(); friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index 757c2c7eb..caa7294b8 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -20,6 +20,7 @@ #include "waveSynth.h" #include "engine.h" #include "instrument.h" +#include "../ta-log.h" bool DivWaveSynth::activeChanged() { if (activeChangedB) { @@ -211,6 +212,7 @@ void DivWaveSynth::setWidth(int val) { void DivWaveSynth::changeWave1(int num) { DivWavetable* w1=e->getWave(num); + logV("changeWave1 (%d)",width); if (width<1) return; for (int i=0; imax<1 || w1->len<1) { diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 3650d1b96..cf23857e6 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1735,6 +1735,27 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } + case DIV_SYSTEM_NAMCO: + case DIV_SYSTEM_NAMCO_15XX: { + bool romMode=flags.getBool("romMode",false); + + ImGui::Text("Waveform storage mode:"); + if (ImGui::RadioButton("RAM",!romMode)) { + romMode=false; + altered=true; + } + if (ImGui::RadioButton("ROM (up to 8 waves)",romMode)) { + romMode=true; + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("romMode",romMode); + }); + } + break; + } case DIV_SYSTEM_NAMCO_CUS30: { bool newNoise=flags.getBool("newNoise",true); @@ -1769,10 +1790,6 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo case DIV_SYSTEM_VBOY: case DIV_SYSTEM_GA20: case DIV_SYSTEM_PV1000: - case DIV_SYSTEM_NAMCO: - case DIV_SYSTEM_NAMCO_15XX: - ImGui::Text("nothing to configure"); - break; case DIV_SYSTEM_VERA: case DIV_SYSTEM_YMU759: supportsCustomRate=false; From 6436919974cace92bddeb81dd79019010addb852 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 16 Mar 2023 05:52:29 -0500 Subject: [PATCH 32/64] i don't know --- src/engine/platform/n163.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 0cd0a7ed1..4f60e44eb 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -347,7 +347,6 @@ int DivPlatformN163::dispatch(DivCommand c) { if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) { chan[c.chan].waveUpdated=true; } - chan[c.chan].insChanged=false; } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); @@ -362,6 +361,7 @@ int DivPlatformN163::dispatch(DivCommand c) { } chan[c.chan].macroInit(ins); chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: From 165d0809dfa848ea6559373d73ebb22199ca776b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 16 Mar 2023 18:52:20 -0500 Subject: [PATCH 33/64] Namco 163: fix weird waveform on first note on issue #1021 --- src/engine/platform/n163.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 4f60e44eb..ead44cc1e 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -342,6 +342,7 @@ int DivPlatformN163::dispatch(DivCommand c) { chan[c.chan].wavePos=ins->n163.wavePos; chan[c.chan].waveLen=ins->n163.waveLen; chan[c.chan].waveMode=ins->n163.waveMode; + chan[c.chan].ws.init(NULL,chan[c.chan].waveLen,15,false); chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].waveChanged=true; if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) { From 6985b85c09db1712ecd96f8998b9fd44030903a6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 17 Mar 2023 14:11:55 -0500 Subject: [PATCH 34/64] YM2612: YMF276 clipping --- src/engine/platform/genesis.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 118ebdebc..6cf1837dd 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -172,7 +172,14 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { flushFirst=false; } - OPN2_Clock(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + OPN2_Clock(&fm,o); + if (chipType==2) { + os[0]+=CLAMP(o[0],-8192,8191); + os[1]+=CLAMP(o[1],-8192,8191); + } else { + os[0]+=o[0]; + os[1]+=o[1]; + } //OPN2_Write(&fm,0,0); if (i==5) { if (fm.dacen) { From 5fc36b1d4cf6e4a43b378fbf9478c44f47f086bf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 17 Mar 2023 14:32:39 -0500 Subject: [PATCH 35/64] PCM DAC: fix muting --- src/engine/platform/pcmdac.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 5f1911f25..bf4a1df9a 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -30,7 +30,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) { const int depthScale=(15-outDepth); int output=0; for (size_t h=0; hdata[oscBuf->needle++]=0; @@ -171,7 +171,11 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) { } } } - output=output*chan[0].vol*chan[0].envVol/16384; + if (isMuted) { + output=0; + } else { + output=output*chan[0].vol*chan[0].envVol/16384; + } oscBuf->data[oscBuf->needle++]=output; if (outStereo) { buf[0][h]=((output*chan[0].panL)>>(depthScale+8))< Date: Fri, 17 Mar 2023 14:54:00 -0500 Subject: [PATCH 36/64] GUI: C64 macro mode usability fix reset macro zoom when changing macro modes --- src/gui/insEdit.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8badcf2f9..1f9951da4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4262,9 +4262,18 @@ void FurnaceGUI::drawInsEdit() { } popToggleColors(); - P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)); - P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)); - P(ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs)); + if (ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)) { + ins->std.volMacro.vZoom=-1; + PARAMETER; + } + if (ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)) { + ins->std.volMacro.vZoom=-1; + PARAMETER; + } + if (ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs)) { + ins->std.dutyMacro.vZoom=-1; + PARAMETER; + } P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest)); ImGui::EndTabItem(); } From de2a6c418bc94e0020c195573f55334c72c7271d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 17 Mar 2023 16:50:42 -0500 Subject: [PATCH 37/64] VIC-20: add on/off macro --- src/engine/platform/vic20.cpp | 17 ++++++++++++++++- src/engine/platform/vic20.h | 4 +++- src/gui/insEdit.cpp | 6 +++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 253c4fb76..aea126432 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -79,7 +79,7 @@ void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) { } void DivPlatformVIC20::writeOutVol(int ch) { - if (!isMuted[ch] && chan[ch].active) { + if (chan[ch].active) { rWrite(14,chan[ch].outVol); } } @@ -99,6 +99,20 @@ void DivPlatformVIC20::tick(bool sysTick) { } chan[i].freqChanged=true; } + if (chan[i].std.duty.had) { + if (chan[i].onOff!=chan[i].std.duty.val) { + chan[i].onOff=chan[i].std.duty.val; + if (chan[i].active) { + if (chan[i].onOff) { + chan[i].keyOn=true; + chan[i].keyOff=false; + } else { + chan[i].keyOn=false; + chan[i].keyOff=true; + } + } + } + } if (chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val) { chan[i].wave=chan[i].std.wave.val&0x0f; @@ -156,6 +170,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } + chan[c.chan].onOff=true; chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); diff --git a/src/engine/platform/vic20.h b/src/engine/platform/vic20.h index 1b8584a75..5125bd96a 100644 --- a/src/engine/platform/vic20.h +++ b/src/engine/platform/vic20.h @@ -27,10 +27,12 @@ class DivPlatformVIC20: public DivDispatch { struct Channel: public SharedChannel { int wave, waveWriteCycle; + bool onOff; Channel(): SharedChannel(15), wave(0), - waveWriteCycle(-1) {} + waveWriteCycle(-1), + onOff(true) {} }; Channel chan[4]; DivDispatchOscBuffer* oscBuf[4]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1f9951da4..1b16e2dc0 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -5125,7 +5125,7 @@ void FurnaceGUI::drawInsEdit() { dutyMax=ins->amiga.useSample?0:255; } if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || - ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM || + ins->type==DIV_INS_PET || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 || ins->type==DIV_INS_SM8521 || ins->type==DIV_INS_PV1000) { dutyMax=0; @@ -5142,6 +5142,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Noise"; dutyMax=1; } + if (ins->type==DIV_INS_VIC) { + dutyLabel="On/Off"; + dutyMax=1; + } if (ins->type==DIV_INS_SWAN) { dutyLabel="Noise"; dutyMax=ins->amiga.useSample?0:8; From 69c3700ab46c0ac7a31fd60c14c672fea527fa94 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 17 Mar 2023 18:00:38 -0500 Subject: [PATCH 38/64] VIC-20: fix MSVC --- src/engine/platform/vic20.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index aea126432..5a58f97fa 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -19,6 +19,7 @@ #include "vic20.h" #include "../engine.h" +#include "../../ta-log.h" #include #define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);} @@ -80,6 +81,7 @@ void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) { void DivPlatformVIC20::writeOutVol(int ch) { if (chan[ch].active) { + logV("writeOutVol (%d): %d",ch,chan[ch].outVol); rWrite(14,chan[ch].outVol); } } @@ -100,8 +102,8 @@ void DivPlatformVIC20::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].std.duty.had) { - if (chan[i].onOff!=chan[i].std.duty.val) { - chan[i].onOff=chan[i].std.duty.val; + if (chan[i].onOff!=(bool)chan[i].std.duty.val) { + chan[i].onOff=(bool)chan[i].std.duty.val; if (chan[i].active) { if (chan[i].onOff) { chan[i].keyOn=true; From d58270efed847a25dd8b38c7a5419bd04454d937 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 17 Mar 2023 18:16:04 -0500 Subject: [PATCH 39/64] fix some macroInt issues with volume --- src/engine/macroInt.cpp | 8 ++++++-- src/engine/platform/vic20.cpp | 6 ++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 103d9ee6b..e06f61b57 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -20,6 +20,7 @@ #include "macroInt.h" #include "instrument.h" #include "engine.h" +#include "../ta-log.h" #define ADSR_LOW source.val[0] #define ADSR_HIGH source.val[1] @@ -52,6 +53,7 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic } if (masked) { had=false; + has=false; return; } if (delay>0) { @@ -246,8 +248,10 @@ void DivMacroInt::setEngine(DivEngine* eng) { } #define ADD_MACRO(m,s) \ - macroList[macroListLen]=&m; \ - macroSource[macroListLen++]=&s; + if (!m.masked) { \ + macroList[macroListLen]=&m; \ + macroSource[macroListLen++]=&s; \ + } void DivMacroInt::init(DivInstrument* which) { ins=which; diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 5a58f97fa..5188eb15f 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -75,15 +75,13 @@ void DivPlatformVIC20::acquire(short** buf, size_t len) { } void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) { + logV("calcAndWriteOutVol (%d, %d)",ch,env); chan[ch].outVol=MIN(chan[ch].vol*env/15,15); writeOutVol(ch); } void DivPlatformVIC20::writeOutVol(int ch) { - if (chan[ch].active) { - logV("writeOutVol (%d): %d",ch,chan[ch].outVol); - rWrite(14,chan[ch].outVol); - } + rWrite(14,chan[ch].outVol); } void DivPlatformVIC20::tick(bool sysTick) { From 57f4cc8561cac415ac9fccde01f53599e0571c10 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 17 Mar 2023 19:12:09 -0500 Subject: [PATCH 40/64] GUI: prepare for FM preview --- CMakeLists.txt | 1 + src/gui/debugWindow.cpp | 12 ++++++ src/gui/fmPreview.cpp | 84 +++++++++++++++++++++++++++++++++++++++++ src/gui/gui.h | 4 ++ 4 files changed, 101 insertions(+) create mode 100644 src/gui/fmPreview.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dd1418c8..dcd981669 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -624,6 +624,7 @@ src/gui/editing.cpp src/gui/editControls.cpp src/gui/effectList.cpp src/gui/findReplace.cpp +src/gui/fmPreview.cpp src/gui/gradient.cpp src/gui/grooves.cpp src/gui/insEdit.cpp diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index d7179204c..5042ec3a4 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -519,6 +519,18 @@ void FurnaceGUI::drawDebug() { ImGui::InputFloat("maxRr",&maxRr); ImGui::TreePop(); } + if (ImGui::TreeNode("FM Preview")) { + if (ImGui::Button("Generate")) { + DivInstrument* ins=e->getIns(curIns); + if (ins!=NULL) renderFMPreview(ins->fm); + } + float asFloat[FM_PREVIEW_SIZE]; + for (int i=0; i32767) aOut=32767; + fmPreview[i]=aOut; + } +} diff --git a/src/gui/gui.h b/src/gui/gui.h index a42c47429..8a24857e2 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -68,6 +68,8 @@ #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() +#define FM_PREVIEW_SIZE 1024 + // TODO: // - add colors for FM envelope and waveform // - maybe add "alternate" color for FM modulators/carriers (a bit difficult) @@ -1192,6 +1194,7 @@ class FurnaceGUI { ImVec2 mobileEditButtonPos, mobileEditButtonSize; const int* curSysSection; DivInstrumentFM opllPreview; + short fmPreview[FM_PREVIEW_SIZE]; String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; @@ -1887,6 +1890,7 @@ class FurnaceGUI { void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size); bool drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange); void kvsConfig(DivInstrument* ins); + void renderFMPreview(const DivInstrumentFM& params, int pos=0); // these ones offer ctrl-wheel fine value changes. bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0); From 09a30570dedbae40e5eb1f7db5d9245d8b69bf02 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 18 Mar 2023 04:22:50 -0500 Subject: [PATCH 41/64] GUI: add an FM preview currently for OPN only --- src/gui/dataList.cpp | 3 ++ src/gui/debugWindow.cpp | 4 -- src/gui/doAction.cpp | 7 +++ src/gui/fmPreview.cpp | 84 +++++++++++++++--------------- src/gui/gui.cpp | 8 +++ src/gui/gui.h | 5 +- src/gui/insEdit.cpp | 112 +++++++++++++++++++++++++++------------- 7 files changed, 141 insertions(+), 82 deletions(-) diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 73197a815..2a37c7e8e 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -455,12 +455,14 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) { curIns=i; wavePreviewInit=true; + updateFMPreview=true; } if (wantScrollList && curIns==i) ImGui::SetScrollHereY(); if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { nextWindow=GUI_WINDOW_PATTERN; curIns=i; wavePreviewInit=true; + updateFMPreview=true; } if (ImGui::IsItemHovered() && i>=0 && !mobileUI) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); @@ -474,6 +476,7 @@ void FurnaceGUI::drawInsList(bool asChild) { if (i>=0) { if (ImGui::BeginPopupContextItem("InsRightMenu")) { curIns=i; + updateFMPreview=true; ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); if (ImGui::MenuItem("replace...")) { doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 5042ec3a4..09610bdf8 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -520,10 +520,6 @@ void FurnaceGUI::drawDebug() { ImGui::TreePop(); } if (ImGui::TreeNode("FM Preview")) { - if (ImGui::Button("Generate")) { - DivInstrument* ins=e->getIns(curIns); - if (ins!=NULL) renderFMPreview(ins->fm); - } float asFloat[FM_PREVIEW_SIZE]; for (int i=0; i=(int)e->song.ins.size()) { @@ -134,6 +135,7 @@ void FurnaceGUI::doAction(int what) { } wavePreviewInit=true; wantScrollList=true; + updateFMPreview=true; break; case GUI_ACTION_STEP_UP: if (++editStep>64) editStep=64; @@ -593,6 +595,7 @@ void FurnaceGUI::doAction(int what) { wantScrollList=true; MARK_MODIFIED; wavePreviewInit=true; + updateFMPreview=true; } break; case GUI_ACTION_INS_LIST_DUPLICATE: @@ -606,6 +609,7 @@ void FurnaceGUI::doAction(int what) { wantScrollList=true; MARK_MODIFIED; wavePreviewInit=true; + updateFMPreview=true; } } break; @@ -653,11 +657,13 @@ void FurnaceGUI::doAction(int what) { if (--curIns<0) curIns=0; wantScrollList=true; wavePreviewInit=true; + updateFMPreview=true; break; case GUI_ACTION_INS_LIST_DOWN: if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; wantScrollList=true; wavePreviewInit=true; + updateFMPreview=true; break; case GUI_ACTION_WAVE_LIST_ADD: @@ -1366,6 +1372,7 @@ void FurnaceGUI::doAction(int what) { nextWindow=GUI_WINDOW_INS_EDIT; MARK_MODIFIED; wavePreviewInit=true; + updateFMPreview=true; } break; } diff --git a/src/gui/fmPreview.cpp b/src/gui/fmPreview.cpp index 8f6dd14fb..eaf449ed9 100644 --- a/src/gui/fmPreview.cpp +++ b/src/gui/fmPreview.cpp @@ -22,61 +22,63 @@ #include "../../extern/opn/ym3438.h" #define FM_WRITE(addr,val) \ - OPN2_Write(&fm,0,(addr)); \ + OPN2_Write((ym3438_t*)fmPreviewOPN,0,(addr)); \ do { \ - OPN2_Clock(&fm,out); \ - } while (fm.write_busy); \ - OPN2_Write(&fm,1,(val)); \ + OPN2_Clock((ym3438_t*)fmPreviewOPN,out); \ + } while (((ym3438_t*)fmPreviewOPN)->write_busy); \ + OPN2_Write((ym3438_t*)fmPreviewOPN,1,(val)); \ do { \ - OPN2_Clock(&fm,out); \ - } while (fm.write_busy); \ + OPN2_Clock((ym3438_t*)fmPreviewOPN,out); \ + } while (((ym3438_t*)fmPreviewOPN)->write_busy); \ + +const unsigned char dtTableFMP[8]={ + 7,6,5,0,1,2,3,4 +}; void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) { - ym3438_t fm; + if (fmPreviewOPN==NULL) { + fmPreviewOPN=new ym3438_t; + } short out[2]; int aOut=0; + bool mult0=false; - OPN2_Reset(&fm); - OPN2_SetChipType(&fm,ym3438_mode_opn); + if (pos==0) { + OPN2_Reset((ym3438_t*)fmPreviewOPN); + OPN2_SetChipType((ym3438_t*)fmPreviewOPN,ym3438_mode_opn); - // set params - FM_WRITE(0x50,31); // AR - FM_WRITE(0x54,31); - FM_WRITE(0x58,31); - FM_WRITE(0x5c,31); - FM_WRITE(0x60,0); // DR - FM_WRITE(0x64,0); - FM_WRITE(0x68,0); - FM_WRITE(0x6c,0); - FM_WRITE(0x70,0); // D2R - FM_WRITE(0x74,0); - FM_WRITE(0x78,0); - FM_WRITE(0x7c,0); - FM_WRITE(0x80,0); // SL/RR - FM_WRITE(0x84,0); - FM_WRITE(0x88,0); - FM_WRITE(0x8c,0); - FM_WRITE(0xa4,0x0c); // frequency - FM_WRITE(0xa0,0); - FM_WRITE(0xb0,(params.alg&7)|((params.fb&7)<<3)); // ALG/FB - FM_WRITE(0xb4,0xc0); // pan - FM_WRITE(0x30,params.op[0].mult&15); // MULT - FM_WRITE(0x34,params.op[1].mult&15); - FM_WRITE(0x38,params.op[2].mult&15); - FM_WRITE(0x3c,params.op[3].mult&15); - FM_WRITE(0x40,params.op[0].tl&127); // TL - FM_WRITE(0x44,params.op[1].tl&127); - FM_WRITE(0x48,params.op[2].tl&127); - FM_WRITE(0x4c,params.op[3].tl&127); - FM_WRITE(0x28,0xf0); // key on + // set params + for (int i=0; i<4; i++) { + if ((params.op[i].mult&15)==0) { + mult0=true; + break; + } + } + for (int i=0; i<4; i++) { + const DivInstrumentFM::Operator& op=params.op[i]; + unsigned short baseAddr=i*4; + FM_WRITE(baseAddr+0x40,op.tl); + FM_WRITE(baseAddr+0x30,(op.mult&15)|(dtTableFMP[op.dt&7]<<4)); + FM_WRITE(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + FM_WRITE(baseAddr+0x60,(op.dr&31)|(op.am<<7)); + FM_WRITE(baseAddr+0x70,op.d2r&31); + FM_WRITE(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + FM_WRITE(baseAddr+0x90,op.ssgEnv&15); + } + FM_WRITE(0xb0,(params.alg&7)|((params.fb&7)<<3)); + FM_WRITE(0xb4,0xc0|(params.fms&7)|((params.ams&3)<<4)); + FM_WRITE(0xa4,mult0?0x1c:0x14); // frequency + FM_WRITE(0xa0,0); + FM_WRITE(0x28,0xf0); // key on + } // render for (int i=0; ich_out[0]; if (aOut<-32768) aOut=-32768; if (aOut>32767) aOut=32767; fmPreview[i]=aOut; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 95be83891..d1f401d39 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1175,6 +1175,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { if (settings.absorbInsInput) { curIns=pat->data[cursor.y][target]; wavePreviewInit=true; + updateFMPreview=true; } makeUndo(GUI_UNDO_PATTERN_EDIT); if (direct) { @@ -3404,6 +3405,7 @@ bool FurnaceGUI::loop() { curIns=msg.data[0]; if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; wavePreviewInit=true; + updateFMPreview=true; } break; case TA_MIDI_CONTROL: @@ -4158,6 +4160,7 @@ bool FurnaceGUI::loop() { } else { curIns=prevIns; wavePreviewInit=true; + updateFMPreview=true; } prevIns=-3; } @@ -5155,6 +5158,7 @@ bool FurnaceGUI::loop() { if (i!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true; nextWindow=GUI_WINDOW_INS_EDIT; wavePreviewInit=true; + updateFMPreview=true; } MARK_MODIFIED; } @@ -6016,6 +6020,10 @@ FurnaceGUI::FurnaceGUI(): mobileEditButtonPos(0.7f,0.7f), mobileEditButtonSize(60.0f,60.0f), curSysSection(NULL), + updateFMPreview(true), + fmPreviewOn(false), + fmPreviewPaused(false), + fmPreviewOPN(NULL), pendingRawSampleDepth(8), pendingRawSampleChannels(1), pendingRawSampleUnsigned(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 8a24857e2..f3f96fb31 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -68,7 +68,7 @@ #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() -#define FM_PREVIEW_SIZE 1024 +#define FM_PREVIEW_SIZE 512 // TODO: // - add colors for FM envelope and waveform @@ -1195,6 +1195,8 @@ class FurnaceGUI { const int* curSysSection; DivInstrumentFM opllPreview; short fmPreview[FM_PREVIEW_SIZE]; + bool updateFMPreview, fmPreviewOn, fmPreviewPaused; + void* fmPreviewOPN; String pendingRawSample; int pendingRawSampleDepth, pendingRawSampleChannels; @@ -1890,6 +1892,7 @@ class FurnaceGUI { void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size); bool drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange); void kvsConfig(DivInstrument* ins); + void drawFMPreview(const ImVec2& size); void renderFMPreview(const DivInstrumentFM& params, int pos=0); // these ones offer ctrl-wheel fine value changes. diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1b16e2dc0..abac70c1a 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1259,9 +1259,10 @@ void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char s #define P(x) if (x) { \ MARK_MODIFIED; \ e->notifyInsChange(curIns); \ + updateFMPreview=true; \ } -#define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); +#define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); updateFMPreview=true; String genericGuide(float value) { return fmt::sprintf("%d",(int)value); @@ -1279,42 +1280,67 @@ inline bool enBit30(const int val) { void FurnaceGUI::kvsConfig(DivInstrument* ins) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("(click to configure TL scaling)"); - } - int opCount=4; - if (ins->type==DIV_INS_OPLL) opCount=2; - if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; - if (ImGui::BeginPopupContextItem("IKVSOpt",ImGuiPopupFlags_MouseButtonLeft)) { - ImGui::Text("operator level changes with volume?"); - if (ImGui::BeginTable("KVSTable",4,ImGuiTableFlags_BordersInner)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); - for (int i=0; i<4; i++) { - int o=(opCount==4)?orderedOps[i]:i; - if (!(i&1)) ImGui::TableNextRow(); - const char* label="AUTO##OPKVS"; - if (ins->fm.op[o].kvs==0) { - label="NO##OPKVS"; - } else if (ins->fm.op[o].kvs==1) { - label="YES##OPKVS"; - } - ImGui::TableNextColumn(); - ImGui::Text("%d",i+1); - ImGui::TableNextColumn(); - ImGui::PushID(o); - if (ImGui::Button(label,ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) { - if (++ins->fm.op[o].kvs>2) ins->fm.op[o].kvs=0; - PARAMETER; - } - ImGui::PopID(); - } - ImGui::EndTable(); + if (ins->type==DIV_INS_FM && fmPreviewOn) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("left click to restart\nmiddle click to pause\nright click to see algorithm"); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + updateFMPreview=true; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + fmPreviewPaused=!fmPreviewPaused; + } + } else { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("left click to configure TL scaling\nright click to see FM preview"); } - ImGui::EndPopup(); } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && ins->type==DIV_INS_FM) { + fmPreviewOn=!fmPreviewOn; + } + if (!fmPreviewOn || ins->type!=DIV_INS_FM) { + int opCount=4; + if (ins->type==DIV_INS_OPLL) opCount=2; + if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; + if (ImGui::BeginPopupContextItem("IKVSOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("operator level changes with volume?"); + if (ImGui::BeginTable("KVSTable",4,ImGuiTableFlags_BordersInner)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); + for (int i=0; i<4; i++) { + int o=(opCount==4)?orderedOps[i]:i; + if (!(i&1)) ImGui::TableNextRow(); + const char* label="AUTO##OPKVS"; + if (ins->fm.op[o].kvs==0) { + label="NO##OPKVS"; + } else if (ins->fm.op[o].kvs==1) { + label="YES##OPKVS"; + } + ImGui::TableNextColumn(); + ImGui::Text("%d",i+1); + ImGui::TableNextColumn(); + ImGui::PushID(o); + if (ImGui::Button(label,ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) { + if (++ins->fm.op[o].kvs>2) ins->fm.op[o].kvs=0; + PARAMETER; + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::EndPopup(); + } + } +} + +void FurnaceGUI::drawFMPreview(const ImVec2& size) { + float asFloat[FM_PREVIEW_SIZE]; + for (int i=0; isong.ins[curIns]; + if (updateFMPreview) { + renderFMPreview(ins->fm); + updateFMPreview=false; + } if (settings.insEditColorize) { pushAccentColors(uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],ImVec4(0.0f,0.0f,0.0f,0.0f)); } @@ -2167,6 +2198,7 @@ void FurnaceGUI::drawInsEdit() { curIns=i; ins=e->song.ins[curIns]; wavePreviewInit=true; + updateFMPreview=true; } } ImGui::EndCombo(); @@ -2306,7 +2338,15 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); - drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (ins->type==DIV_INS_FM && fmPreviewOn) { + drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (!fmPreviewPaused) { + renderFMPreview(ins->fm,1); + WAKE_UP; + } + } else { + drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + } kvsConfig(ins); break; case DIV_INS_OPZ: From 293d2b87753c4a473f6ca6ecd9e17b772417f873 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 18 Mar 2023 17:15:13 -0500 Subject: [PATCH 42/64] PV-1000: more accurate emulation --- CMakeLists.txt | 2 +- src/engine/platform/pv1000.cpp | 2 +- src/engine/platform/pv1000.h | 2 +- .../sound/{d65010g031.c => d65modified.c} | 34 +++++++++++++++++-- .../sound/{d65010g031.h => d65modified.h} | 0 5 files changed, 35 insertions(+), 5 deletions(-) rename src/engine/platform/sound/{d65010g031.c => d65modified.c} (74%) rename src/engine/platform/sound/{d65010g031.h => d65modified.h} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcd981669..1e3caa0b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -459,7 +459,7 @@ src/engine/platform/sound/ga20/iremga20.cpp src/engine/platform/sound/sm8521.c -src/engine/platform/sound/d65010g031.c +src/engine/platform/sound/d65modified.c src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index 823ca871b..81bfacfb4 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -40,7 +40,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) { for (size_t h=0; hdata[oscBuf[i]->needle++]=(d65010g031.square[i].out<<12); } diff --git a/src/engine/platform/pv1000.h b/src/engine/platform/pv1000.h index 953709abe..7c3541df5 100644 --- a/src/engine/platform/pv1000.h +++ b/src/engine/platform/pv1000.h @@ -21,7 +21,7 @@ #define _PV1000_H #include "../dispatch.h" -#include "sound/d65010g031.h" +#include "sound/d65modified.h" #include class DivPlatformPV1000: public DivDispatch { diff --git a/src/engine/platform/sound/d65010g031.c b/src/engine/platform/sound/d65modified.c similarity index 74% rename from src/engine/platform/sound/d65010g031.c rename to src/engine/platform/sound/d65modified.c index dbcd63784..8f53b34c8 100644 --- a/src/engine/platform/sound/d65010g031.c +++ b/src/engine/platform/sound/d65modified.c @@ -34,9 +34,31 @@ freely, subject to the following restrictions: TODO: - needs hardware test +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! +ALTERED VERSION!!! + + +THIS IS **NOT** NOT NOT NOT!!!! THE ORIGINAL SOFTWARE +IT ISN'T +THE MODIFICATIONS THAT WERE MADE ARE: + +1. FIX VOLUMES - APPARENTLY THE SQUARES HAVE DIFFERENT VOLUMES (thanks forple) + */ -#include "d65010g031.h" +#include "d65modified.h" #include static int d65010g031_max(int a, int b) { return (a > b) ? a : b; } @@ -57,12 +79,20 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle) return 0; } +// this is the bit I altered +// THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such! +const int d65Volumes[3]={ + 3840, + 5120, + 8192 +}; + int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle) { int out = 0; for (int i = 0; i < 3; i++) { - out += d65010g031_square_tick(&d65010g031->square[i], cycle); + out += d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i]; } return out; } diff --git a/src/engine/platform/sound/d65010g031.h b/src/engine/platform/sound/d65modified.h similarity index 100% rename from src/engine/platform/sound/d65010g031.h rename to src/engine/platform/sound/d65modified.h From 2e9bc14459845a19d7d774b21e35f0c15c422366 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 18 Mar 2023 18:22:54 -0500 Subject: [PATCH 43/64] PV-1000: DC offset correction required --- src/engine/platform/pv1000.cpp | 4 ++++ src/engine/platform/pv1000.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index 81bfacfb4..d5f54e6a6 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -263,6 +263,10 @@ void DivPlatformPV1000::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } +bool DivPlatformPV1000::getDCOffRequired() { + return true; +} + int DivPlatformPV1000::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { parent=p; dumpWrites=false; diff --git a/src/engine/platform/pv1000.h b/src/engine/platform/pv1000.h index 7c3541df5..c32540763 100644 --- a/src/engine/platform/pv1000.h +++ b/src/engine/platform/pv1000.h @@ -55,6 +55,7 @@ class DivPlatformPV1000: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); + bool getDCOffRequired(); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); ~DivPlatformPV1000(); From f80a2b886412b3cbf3dbbc87ba06abccc7571fd1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 19 Mar 2023 03:12:08 -0500 Subject: [PATCH 44/64] GUI: make playing needles in sample editor work currently only for YM2612 but I will implement more soon --- src/engine/dispatch.h | 25 +++++++++++++++++++- src/engine/engine.cpp | 5 ++++ src/engine/engine.h | 3 +++ src/engine/platform/abstract.cpp | 4 ++++ src/engine/platform/genesis.cpp | 10 ++++++++ src/engine/platform/genesis.h | 1 + src/gui/sampleEdit.cpp | 40 ++++++++++++++++++++++++++++++++ 7 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 9a87cc874..1d5932b69 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -293,6 +293,18 @@ struct DivDelayedWrite { write(a,v) {} }; +struct DivSamplePos { + int sample, pos, freq; + DivSamplePos(int s, int p, int f): + sample(s), + pos(p), + freq(f) {} + DivSamplePos(): + sample(-1), + pos(0), + freq(0) {} +}; + struct DivDispatchOscBuffer { bool follow; unsigned int rate; @@ -371,18 +383,29 @@ class DivDispatch { /** * get the state of a channel. + * @param chan the channel. * @return a pointer, or NULL. */ virtual void* getChanState(int chan); /** - * get the DivMacroInt of a chanmel. + * get the DivMacroInt of a channel. + * @param chan the channel. * @return a pointer, or NULL. */ virtual DivMacroInt* getChanMacroInt(int chan); + /** + * get currently playing sample (and its position). + * @param chan the channel. + * @return a DivSamplePos. if sample is -1 then nothing is playing or the + * channel doesn't play samples. + */ + virtual DivSamplePos getSamplePos(int chan); + /** * get an oscilloscope buffer for a channel. + * @param chan the channel. * @return a pointer to a DivDispatchOscBuffer, or NULL if not supported. */ virtual DivDispatchOscBuffer* getOscBuffer(int chan); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index d03b83b57..a45a19cba 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2119,6 +2119,11 @@ DivMacroInt* DivEngine::getMacroInt(int chan) { return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]); } +DivSamplePos DivEngine::getSamplePos(int chan) { + if (chan<0 || chan>=chans) return DivSamplePos(); + return disCont[dispatchOfChan[chan]].dispatch->getSamplePos(dispatchChanOfChan[chan]); +} + DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) { if (chan<0 || chan>=chans) return NULL; return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]); diff --git a/src/engine/engine.h b/src/engine/engine.h index 7b84f5da2..c15302ba9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -914,6 +914,9 @@ class DivEngine { // get macro interpreter DivMacroInt* getMacroInt(int chan); + // get sample position + DivSamplePos getSamplePos(int chan); + // get osc buffer DivDispatchOscBuffer* getOscBuffer(int chan); diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 74ec8cddf..82694e003 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -37,6 +37,10 @@ DivMacroInt* DivDispatch::getChanMacroInt(int chan) { return NULL; } +DivSamplePos DivDispatch::getSamplePos(int chan) { + return DivSamplePos(); +} + DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) { return NULL; } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 6cf1837dd..9491b07bf 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -1209,6 +1209,16 @@ DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformGenesis::getSamplePos(int ch) { + if (ch<5) return DivSamplePos(); + if (ch>5 && !softPCM) return DivSamplePos(); + return DivSamplePos( + chan[ch].dacSample, + chan[ch].dacPos, + chan[ch].dacRate + ); +} + DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 4c3d57137..210fca9d0 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -105,6 +105,7 @@ class DivPlatformGenesis: public DivPlatformOPN { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 115223cc8..9200b6829 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -1407,6 +1407,46 @@ void FurnaceGUI::drawSampleEdit() { dl->AddLine(p1,p2,ImGui::GetColorU32(posColor)); } + if (e->isRunning()) { + for (int i=0; igetTotalChannelCount(); i++) { + DivSamplePos chanPos=e->getSamplePos(i); + if (chanPos.sample!=curSample) continue; + + int start=sampleSelStart; + int end=sampleSelEnd; + if (start>end) { + start^=end; + end^=start; + start^=end; + } + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImVec2 p1=rectMin; + p1.x+=(chanPos.pos-samplePos)/sampleZoom; + ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE_PLAYING]; + ImVec4 posTrail1=posColor; + ImVec4 posTrail2=posColor; + posTrail1.w*=0.5f; + posTrail2.w=0.0f; + float trailDistance=((float)chanPos.freq/100.0f)/sampleZoom; + + if (p1.xrectMax.x) p1.x=rectMax.x; + + ImVec2 p2=p1; + p2.y=rectMax.y; + + dl->AddRectFilledMultiColor( + ImVec2(p1.x-trailDistance,p1.y), + p2, + ImGui::GetColorU32(posTrail2), + ImGui::GetColorU32(posTrail1), + ImGui::GetColorU32(posTrail1), + ImGui::GetColorU32(posTrail2) + ); + dl->AddLine(p1,p2,ImGui::GetColorU32(posColor)); + } + } + if (drawSelection) { int start=sampleSelStart; int end=sampleSelEnd; From 6cd24a8008adb7f96595749831633ecbfce11e5c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 19 Mar 2023 03:56:13 -0500 Subject: [PATCH 45/64] implement getSamplePos() on some chips Amiga, AY, Lynx, PCE, PCM DAC and VRC6 --- src/engine/platform/amiga.cpp | 10 ++++++++++ src/engine/platform/amiga.h | 1 + src/engine/platform/ay.cpp | 9 +++++++++ src/engine/platform/ay.h | 1 + src/engine/platform/ay8930.cpp | 9 +++++++++ src/engine/platform/ay8930.h | 1 + src/engine/platform/genesis.cpp | 1 + src/engine/platform/lynx.cpp | 10 ++++++++++ src/engine/platform/lynx.h | 1 + src/engine/platform/pce.cpp | 10 ++++++++++ src/engine/platform/pce.h | 1 + src/engine/platform/pcmdac.cpp | 9 +++++++++ src/engine/platform/pcmdac.h | 1 + src/engine/platform/vrc6.cpp | 10 ++++++++++ src/engine/platform/vrc6.h | 1 + 15 files changed, 75 insertions(+) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 14f2de80a..1ab3d3f60 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -765,6 +765,16 @@ DivMacroInt* DivPlatformAmiga::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformAmiga::getSamplePos(int ch) { + if (ch>=4) return DivSamplePos(); + if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos(); + return DivSamplePos( + chan[ch].sample, + amiga.dmaLoc[ch]-sampleOff[chan[ch].sample], + chipClock/amiga.audPer[ch] + ); +} + void DivPlatformAmiga::notifyInsChange(int ins) { for (int i=0; i<4; i++) { if (chan[i].ins==ins) { diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 36ba2253a..1c793296f 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -137,6 +137,7 @@ class DivPlatformAmiga: public DivDispatch { int getOutputCount(); bool keyOffAffectsArp(int ch); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); void setFlags(const DivConfig& flags); void notifyInsChange(int ins); void notifyWaveChange(int wave); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 181901137..145ab5622 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -700,6 +700,15 @@ DivMacroInt* DivPlatformAY8910::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformAY8910::getSamplePos(int ch) { + if (ch>=3) return DivSamplePos(); + return DivSamplePos( + chan[ch].dac.sample, + chan[ch].dac.pos, + chan[ch].dac.rate + ); +} + DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index f20a71eac..8f938d0a9 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -143,6 +143,7 @@ class DivPlatformAY8910: public DivDispatch { int getOutputCount(); bool keyOffAffectsArp(int ch); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); bool getDCOffRequired(); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 0eb509db4..ba4e8099a 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -696,6 +696,15 @@ DivMacroInt* DivPlatformAY8930::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformAY8930::getSamplePos(int ch) { + if (ch>=3) return DivSamplePos(); + return DivSamplePos( + chan[ch].dac.sample, + chan[ch].dac.pos, + chan[ch].dac.rate + ); +} + DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 3ffba63e7..3b47cf0d1 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -145,6 +145,7 @@ class DivPlatformAY8930: public DivDispatch { int getOutputCount(); bool keyOffAffectsArp(int ch); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 9491b07bf..089d33e27 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -1210,6 +1210,7 @@ DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) { } DivSamplePos DivPlatformGenesis::getSamplePos(int ch) { + if (!chan[5].dacMode) return DivSamplePos(); if (ch<5) return DivSamplePos(); if (ch>5 && !softPCM) return DivSamplePos(); return DivSamplePos( diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 7dec2102d..a8b2cb88d 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -415,6 +415,16 @@ DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformLynx::getSamplePos(int ch) { + if (ch>=4) return DivSamplePos(); + if (!chan[ch].pcm) return DivSamplePos(); + return DivSamplePos( + chan[ch].sample, + chan[ch].samplePos, + chan[ch].sampleFreq + ); +} + DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/lynx.h b/src/engine/platform/lynx.h index 7cde207e4..c68106dec 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -72,6 +72,7 @@ class DivPlatformLynx: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 2ed98b36b..95d483264 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -505,6 +505,16 @@ DivMacroInt* DivPlatformPCE::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformPCE::getSamplePos(int ch) { + if (ch>=6) return DivSamplePos(); + if (!chan[ch].pcm) return DivSamplePos(); + return DivSamplePos( + chan[ch].dacSample, + chan[ch].dacPos, + chan[ch].dacRate + ); +} + DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index fd4f81320..9b8c610c8 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -81,6 +81,7 @@ class DivPlatformPCE: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index bf4a1df9a..f7b3af715 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -441,6 +441,15 @@ DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) { return &chan[0].std; } +DivSamplePos DivPlatformPCMDAC::getSamplePos(int ch) { + if (ch>=1) return DivSamplePos(); + return DivSamplePos( + chan[ch].sample, + chan[ch].audPos, + chan[ch].freq + ); +} + void DivPlatformPCMDAC::notifyInsChange(int ins) { if (chan[0].ins==ins) { chan[0].insChanged=true; diff --git a/src/engine/platform/pcmdac.h b/src/engine/platform/pcmdac.h index e32b7b099..f9435e3e0 100644 --- a/src/engine/platform/pcmdac.h +++ b/src/engine/platform/pcmdac.h @@ -79,6 +79,7 @@ class DivPlatformPCMDAC: public DivDispatch { void muteChannel(int ch, bool mute); int getOutputCount(); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); void setFlags(const DivConfig& flags); void notifyInsChange(int ins); void notifyWaveChange(int wave); diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index b08255d36..912a589ea 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -448,6 +448,16 @@ DivMacroInt* DivPlatformVRC6::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformVRC6::getSamplePos(int ch) { + if (ch>=2) return DivSamplePos(); + if (!chan[ch].pcm) return DivSamplePos(); + return DivSamplePos( + chan[ch].dacSample, + chan[ch].dacPos, + chan[ch].dacRate + ); +} + DivDispatchOscBuffer* DivPlatformVRC6::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/vrc6.h b/src/engine/platform/vrc6.h index 5688cb5c6..5a2416101 100644 --- a/src/engine/platform/vrc6.h +++ b/src/engine/platform/vrc6.h @@ -65,6 +65,7 @@ class DivPlatformVRC6: public DivDispatch, public vrcvi_intf { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); From 62b9b98300c55b7ce998f9a6235b3b3e94545e67 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 19 Mar 2023 04:18:57 -0500 Subject: [PATCH 46/64] SNES: implement getSamplePos() but gotta fix one thing --- src/engine/platform/snes.cpp | 14 ++++++++++++++ src/engine/platform/snes.h | 1 + src/engine/platform/sound/snes/SPC_DSP.h | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 7184cdc43..1fb5a12cd 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -681,6 +681,20 @@ DivMacroInt* DivPlatformSNES::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformSNES::getSamplePos(int ch) { + if (ch>=8) return DivSamplePos(); + if (!chan[ch].active) return DivSamplePos(); + if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos(); + const SPC_DSP::voice_t* v=dsp.get_voice(ch); + // TODO: fix? + if (sampleMem[v->brr_addr&0xffff]==0) return DivSamplePos(); + return DivSamplePos( + chan[ch].sample, + ((v->brr_addr-sampleOff[chan[ch].sample])*16/9)+v->brr_offset, + (chan[ch].freq*125)/16 + ); +} + DivDispatchOscBuffer* DivPlatformSNES::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/snes.h b/src/engine/platform/snes.h index 8783e61c3..3c3426466 100644 --- a/src/engine/platform/snes.h +++ b/src/engine/platform/snes.h @@ -97,6 +97,7 @@ class DivPlatformSNES: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/sound/snes/SPC_DSP.h b/src/engine/platform/sound/snes/SPC_DSP.h index 879ee703d..924d9ae2c 100644 --- a/src/engine/platform/sound/snes/SPC_DSP.h +++ b/src/engine/platform/sound/snes/SPC_DSP.h @@ -123,6 +123,9 @@ public: uint8_t t_envx_out; sample_t out[2]; // Furnace addition, for per-channel oscilloscope }; + + // Furnace addition, gets a voice + const voice_t* get_voice(int n); private: enum { brr_block_size = 9 }; @@ -298,6 +301,10 @@ inline void SPC_DSP::get_voice_outputs( sample_t* outs ) } } +inline const SPC_DSP::voice_t* SPC_DSP::get_voice(int n) { + return &m.voices[n]; +} + #if !SPC_NO_COPY_STATE_FUNCS class SPC_State_Copier { From 591cd633d7d380496e529ae8c857de7becd8e2f3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 19 Mar 2023 16:49:31 -0500 Subject: [PATCH 47/64] SegaPCM: implement getSamplePos() --- src/engine/platform/segapcm.cpp | 11 +++++++++++ src/engine/platform/segapcm.h | 1 + src/engine/platform/sound/segapcm.cpp | 14 +++++++++++++- src/engine/platform/sound/segapcm.h | 2 ++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 0b02bb408..a4003a2be 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -382,6 +382,17 @@ DivMacroInt* DivPlatformSegaPCM::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformSegaPCM::getSamplePos(int ch) { + if (ch>=16) return DivSamplePos(); + if (chan[ch].pcm.sample<0 || chan[ch].pcm.sample>=parent->song.sampleLen) return DivSamplePos(); + if (!pcm.is_playing(ch)) return DivSamplePos(); + return DivSamplePos( + chan[ch].pcm.sample, + pcm.get_addr(ch)-sampleOffSegaPCM[chan[ch].pcm.sample], + 122*(chan[ch].pcm.freq+1) + ); +} + DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index f99a24df3..9e2ad5df1 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -87,6 +87,7 @@ class DivPlatformSegaPCM: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/sound/segapcm.cpp b/src/engine/platform/sound/segapcm.cpp index 4dd58eb26..d469e3a15 100644 --- a/src/engine/platform/sound/segapcm.cpp +++ b/src/engine/platform/sound/segapcm.cpp @@ -134,6 +134,18 @@ uint8_t* segapcm_device::get_ram() { return m_ram; } +unsigned int segapcm_device::get_addr(int ch) { + uint8_t *regs = &m_ram[8*ch]; + int offset = (regs[0x86] & m_bankmask) << m_bankshift; + uint32_t addr = (regs[0x85] << 8) | (regs[0x84]) | offset; + return addr; +} + +bool segapcm_device::is_playing(int ch) { + uint8_t *regs = &m_ram[8*ch]; + return !(regs[0x86]&1); +} + void segapcm_device::mute(int ch, bool doMute) { m_muted[ch&15]=doMute; -} \ No newline at end of file +} diff --git a/src/engine/platform/sound/segapcm.h b/src/engine/platform/sound/segapcm.h index 40ca35a4b..819202cd7 100644 --- a/src/engine/platform/sound/segapcm.h +++ b/src/engine/platform/sound/segapcm.h @@ -34,6 +34,8 @@ public: void write(unsigned int offset, uint8_t data); uint8_t read(unsigned int offset); uint8_t* get_ram(); + unsigned int get_addr(int ch); + bool is_playing(int ch); void mute(int ch, bool doMute); // device-level overrides From 1dc05f0777dfa9526c473be2e2114df8f8520ccc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 19 Mar 2023 19:51:26 -0500 Subject: [PATCH 48/64] GUI: use ClipRect instead of clamp in sample edit --- src/gui/sampleEdit.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 9200b6829..3a8fae4b1 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -1367,6 +1367,7 @@ void FurnaceGUI::drawSampleEdit() { } } + dl->PushClipRect(rectMin,rectMax); if (e->isPreviewingSample()) { if (!statusBar2.empty()) { statusBar2+=" | "; @@ -1380,7 +1381,6 @@ void FurnaceGUI::drawSampleEdit() { end^=start; start^=end; } - ImDrawList* dl=ImGui::GetWindowDrawList(); ImVec2 p1=rectMin; p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom; ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE]; @@ -1390,8 +1390,8 @@ void FurnaceGUI::drawSampleEdit() { posTrail2.w=0.0f; float trailDistance=(e->getSamplePreviewRate()/100.0f)/sampleZoom; - if (p1.xrectMax.x) p1.x=rectMax.x; + //if (p1.xrectMax.x) p1.x=rectMax.x; ImVec2 p2=p1; p2.y=rectMax.y; @@ -1419,7 +1419,6 @@ void FurnaceGUI::drawSampleEdit() { end^=start; start^=end; } - ImDrawList* dl=ImGui::GetWindowDrawList(); ImVec2 p1=rectMin; p1.x+=(chanPos.pos-samplePos)/sampleZoom; ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE_PLAYING]; @@ -1429,8 +1428,8 @@ void FurnaceGUI::drawSampleEdit() { posTrail2.w=0.0f; float trailDistance=((float)chanPos.freq/100.0f)/sampleZoom; - if (p1.xrectMax.x) p1.x=rectMax.x; + //if (p1.xrectMax.x) p1.x=rectMax.x; ImVec2 p2=p1; p2.y=rectMax.y; @@ -1446,6 +1445,7 @@ void FurnaceGUI::drawSampleEdit() { dl->AddLine(p1,p2,ImGui::GetColorU32(posColor)); } } + dl->PopClipRect(); if (drawSelection) { int start=sampleSelStart; @@ -1455,7 +1455,6 @@ void FurnaceGUI::drawSampleEdit() { end^=start; start^=end; } - ImDrawList* dl=ImGui::GetWindowDrawList(); ImVec2 p1=rectMin; p1.x+=(start-samplePos)/sampleZoom; From c27dbdab96f9b7a6ad3dd1b9022162b9de36751e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 20 Mar 2023 03:55:12 -0500 Subject: [PATCH 49/64] VIC-20: get rid of debug message --- src/engine/platform/vic20.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 5188eb15f..bd25b5284 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -75,7 +75,6 @@ void DivPlatformVIC20::acquire(short** buf, size_t len) { } void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) { - logV("calcAndWriteOutVol (%d, %d)",ch,env); chan[ch].outVol=MIN(chan[ch].vol*env/15,15); writeOutVol(ch); } From d92bbcf2eef3d6ce18b0587175632aa283b413e2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 20 Mar 2023 15:09:52 -0500 Subject: [PATCH 50/64] GUI: fix cur order out of bounds when undo/redo --- src/gui/editing.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 0d8341b28..77d76da24 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -1005,6 +1005,13 @@ void FurnaceGUI::doUndo() { break; } + if (curOrder>=e->curSubSong->ordersLen) { + curOrder=e->curSubSong->ordersLen-1; + oldOrder=curOrder; + oldOrder1=curOrder; + e->setOrder(curOrder); + } + undoHist.pop_back(); } @@ -1058,5 +1065,12 @@ void FurnaceGUI::doRedo() { break; } + if (curOrder>=e->curSubSong->ordersLen) { + curOrder=e->curSubSong->ordersLen-1; + oldOrder=curOrder; + oldOrder1=curOrder; + e->setOrder(curOrder); + } + redoHist.pop_back(); } From 7bfdad8361084750a6d3a41d0d4e87249b635baf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 20 Mar 2023 21:50:22 -0500 Subject: [PATCH 51/64] new demo song --- demos/misc/empty_PV-1000.fur | Bin 0 -> 2833 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/misc/empty_PV-1000.fur diff --git a/demos/misc/empty_PV-1000.fur b/demos/misc/empty_PV-1000.fur new file mode 100644 index 0000000000000000000000000000000000000000..d6ec13ce5a951f3d5aef75d887eebb9781008fa6 GIT binary patch literal 2833 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 ByfOd) literal 0 HcmV?d00001 From 6125ff7c0a9ce3e6fdc17b107ce22562acc60e85 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 20 Mar 2023 21:51:21 -0500 Subject: [PATCH 52/64] new demo songs by Yuzu4K --- demos/misc/TerminalZone_SM8521.fur | Bin 0 -> 3050 bytes demos/snes/ManbowSMW.fur | Bin 0 -> 30747 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/misc/TerminalZone_SM8521.fur create mode 100644 demos/snes/ManbowSMW.fur diff --git a/demos/misc/TerminalZone_SM8521.fur b/demos/misc/TerminalZone_SM8521.fur new file mode 100644 index 0000000000000000000000000000000000000000..e9176c5f444977a873b346e1289ec0a265324abc GIT binary patch literal 3050 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

p(PDM$REHnxibLtX?VI4l6 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 qy^LUYaUYbKbzkgIA!`Z|w*McZ?}G;b literal 0 HcmV?d00001 From 5f5617fc2caad8d5091cb96ab8b82a553e0c851c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 21 Mar 2023 03:39:20 -0500 Subject: [PATCH 53/64] GUI: fix some chips not having custom clocks --- src/gui/sysConf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index cf23857e6..77bc6c726 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1791,6 +1791,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo case DIV_SYSTEM_GA20: case DIV_SYSTEM_PV1000: case DIV_SYSTEM_VERA: + break; case DIV_SYSTEM_YMU759: supportsCustomRate=false; ImGui::Text("nothing to configure"); From 5af3804195df50e6df7c855ddbef0ea53dde0862 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 21 Mar 2023 03:56:17 -0500 Subject: [PATCH 54/64] GA20: implement getSamplePos() --- src/engine/platform/amiga.cpp | 4 +++- src/engine/platform/ga20.cpp | 12 ++++++++++++ src/engine/platform/ga20.h | 1 + src/engine/platform/sound/ga20/iremga20.h | 7 +++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 1ab3d3f60..b04eefbeb 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -768,10 +768,12 @@ DivMacroInt* DivPlatformAmiga::getChanMacroInt(int ch) { DivSamplePos DivPlatformAmiga::getSamplePos(int ch) { if (ch>=4) return DivSamplePos(); if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos(); + int audPer=amiga.audPer[ch]; + if (audPer<1) audPer=1; return DivSamplePos( chan[ch].sample, amiga.dmaLoc[ch]-sampleOff[chan[ch].sample], - chipClock/amiga.audPer[ch] + chipClock/audPer ); } diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index 9da33543e..527e80f1d 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -336,6 +336,18 @@ DivMacroInt* DivPlatformGA20::getChanMacroInt(int ch) { return &chan[ch].std; } +DivSamplePos DivPlatformGA20::getSamplePos(int ch) { + if (ch>=4) return DivSamplePos(); + if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos(); + if (!ga20.is_playing(ch)) return DivSamplePos(); + unsigned char f=chan[ch].freq; + return DivSamplePos( + chan[ch].sample, + ga20.get_position(ch)-sampleOffGA20[chan[ch].sample], + chipClock/(4*(0x100-(int)f)) + ); +} + DivDispatchOscBuffer* DivPlatformGA20::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/ga20.h b/src/engine/platform/ga20.h index 9cd6869a2..1e06378f1 100644 --- a/src/engine/platform/ga20.h +++ b/src/engine/platform/ga20.h @@ -78,6 +78,7 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf { virtual int dispatch(DivCommand c) override; virtual void* getChanState(int chan) override; virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual DivSamplePos getSamplePos(int ch) override; virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; virtual unsigned char* getRegisterPool() override; virtual int getRegisterPoolSize() override; diff --git a/src/engine/platform/sound/ga20/iremga20.h b/src/engine/platform/sound/ga20/iremga20.h index dc29d86f4..1a27891ec 100644 --- a/src/engine/platform/sound/ga20/iremga20.h +++ b/src/engine/platform/sound/ga20/iremga20.h @@ -39,12 +39,19 @@ public: u8 read(u32 offset); inline void set_mute(const int ch, const bool mute) { m_channel[ch & 3].mute = mute; } + inline unsigned int get_position(const int ch) { + return m_channel[ch&3].pos; + } + inline bool is_playing(const int ch) { + return m_channel[ch&3].play; + } // device-level overrides void device_reset(); // sound stream update overrides void sound_stream_update(short** outputs, int len); + private: struct channel_def From 913d22fd57fc7d671430e9ae90ace5f5ce5aa9c6 Mon Sep 17 00:00:00 2001 From: KMoene Date: Wed, 22 Mar 2023 00:51:54 -0400 Subject: [PATCH 55/64] init support for setting program change --- src/engine/engine.cpp | 1 + src/engine/engine.h | 2 ++ src/engine/playback.cpp | 4 ++-- src/gui/gui.h | 2 ++ src/gui/settings.cpp | 8 ++++++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a45a19cba..a3669a184 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -4371,6 +4371,7 @@ bool DivEngine::initAudioBackend() { lowLatency=getConfInt("lowLatency",0); metroVol=(float)(getConfInt("metroVol",100))/100.0f; midiOutClock=getConfInt("midiOutClock",0); + midiOutProgramChange = getConfInt("midiOutProgramChange",0); midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE); if (metroVol<0.0f) metroVol=0.0f; if (metroVol>2.0f) metroVol=2.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index c15302ba9..b76a3e5b8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -365,6 +365,7 @@ class DivEngine { bool systemsRegistered; bool hasLoadedSomething; bool midiOutClock; + bool midiOutProgramChange; int midiOutMode; int softLockCount; int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed; @@ -1110,6 +1111,7 @@ class DivEngine { systemsRegistered(false), hasLoadedSomething(false), midiOutClock(false), + midiOutProgramChange(false), midiOutMode(DIV_MIDI_MODE_NOTE), softLockCount(0), subticks(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 9176a0678..c58763b40 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -278,7 +278,7 @@ int DivEngine::dispatchCmd(DivCommand c) { cmdStream.push_back(c); } - if (output) if (!skipping && output->midiOut!=NULL) { + if (output) if (!skipping && output->midiOut!=NULL && !isChannelMuted(c.chan)) { if (output->midiOut->isDeviceOpen()) { if (midiOutMode==DIV_MIDI_MODE_NOTE) { int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax); @@ -305,7 +305,7 @@ int DivEngine::dispatchCmd(DivCommand c) { chan[c.chan].curMidiNote=-1; break; case DIV_CMD_INSTRUMENT: - if (chan[c.chan].lastIns!=c.value) { + if (chan[c.chan].lastIns!=c.value && midiOutProgramChange) { output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0)); } break; diff --git a/src/gui/gui.h b/src/gui/gui.h index f3f96fb31..9d4ee9fbb 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1362,6 +1362,7 @@ class FurnaceGUI { int channelFont; int channelTextCenter; int midiOutClock; + int midiOutProgramChange; int midiOutMode; int maxRecentFile; int centerPattern; @@ -1503,6 +1504,7 @@ class FurnaceGUI { channelFont(1), channelTextCenter(1), midiOutClock(0), + midiOutProgramChange(0), midiOutMode(1), maxRecentFile(10), centerPattern(0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c39d03e0a..b66e52b63 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1149,6 +1149,11 @@ void FurnaceGUI::drawSettings() { settings.midiOutClock=midiOutClockB; } + bool midiOutProgramChangeB=settings.midiOutProgramChange; + if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) { + settings.midiOutProgramChange=midiOutProgramChangeB; + } + ImGui::TreePop(); } } @@ -2608,6 +2613,7 @@ void FurnaceGUI::syncSettings() { settings.channelTextCenter=e->getConfInt("channelTextCenter",1); settings.maxRecentFile=e->getConfInt("maxRecentFile",10); settings.midiOutClock=e->getConfInt("midiOutClock",0); + settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0); settings.midiOutMode=e->getConfInt("midiOutMode",1); settings.centerPattern=e->getConfInt("centerPattern",0); settings.ordersCursor=e->getConfInt("ordersCursor",1); @@ -2726,6 +2732,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.channelTextCenter,0,1); clampSetting(settings.maxRecentFile,0,30); clampSetting(settings.midiOutClock,0,1); + clampSetting(settings.midiOutProgramChange,0,1); clampSetting(settings.midiOutMode,0,2); clampSetting(settings.centerPattern,0,1); clampSetting(settings.ordersCursor,0,1); @@ -2935,6 +2942,7 @@ void FurnaceGUI::commitSettings() { e->setConf("channelTextCenter",settings.channelTextCenter); e->setConf("maxRecentFile",settings.maxRecentFile); e->setConf("midiOutClock",settings.midiOutClock); + e->setConf("midiOutProgramChange",settings.midiOutProgramChange); e->setConf("midiOutMode",settings.midiOutMode); e->setConf("centerPattern",settings.centerPattern); e->setConf("ordersCursor",settings.ordersCursor); From 6f8cfa42eac1cd91e616b4adb1059e3ca835966c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 24 Mar 2023 00:52:11 -0500 Subject: [PATCH 56/64] GUI: fix IGFD glitch when opening empty dir --- extern/igfd/ImGuiFileDialog.cpp | 41 +++++++++++++++++++++++++++------ extern/igfd/ImGuiFileDialog.h | 1 + 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 535fb267f..372287d0f 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -26,6 +26,7 @@ SOFTWARE. */ #include "ImGuiFileDialog.h" +#include "../../src/ta-log.h" #ifdef __cplusplus @@ -1202,11 +1203,13 @@ namespace IGFD SetDefaultFileName("."); else SetDefaultFileName(""); + logV("IGFD: OpenCurrentPath()"); ScanDir(vFileDialogInternal, GetCurrentPath()); } void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool vCanChangeOrder) { + logV("IGFD: SortFields()"); if (vSortingField != SortingFieldEnum::FIELD_NONE) { puHeaderFileName = tableHeaderFileNameString; @@ -1216,10 +1219,17 @@ namespace IGFD #ifdef USE_THUMBNAILS puHeaderFileThumbnails = tableHeaderFileThumbnailsString; #endif // #ifdef USE_THUMBNAILS - } + } else { + logV("IGFD: sorting by NONE!"); + } + + if (prFileList.empty()) { + logV("IGFD: with an empty file list?"); + } if (vSortingField == SortingFieldEnum::FIELD_FILENAME) { + logV("IGFD: sorting by name"); if (vCanChangeOrder && puSortingField == vSortingField) { //printf("Change the sorting\n"); puSortingDirection[0] = true;//!puSortingDirection[0]; @@ -1289,6 +1299,7 @@ namespace IGFD } else if (vSortingField == SortingFieldEnum::FIELD_TYPE) { + logV("IGFD: sorting by type"); if (vCanChangeOrder && puSortingField == vSortingField) puSortingDirection[1] = !puSortingDirection[1]; @@ -1327,6 +1338,7 @@ namespace IGFD } else if (vSortingField == SortingFieldEnum::FIELD_SIZE) { + logV("IGFD: sorting by size"); if (vCanChangeOrder && puSortingField == vSortingField) puSortingDirection[2] = !puSortingDirection[2]; @@ -1367,6 +1379,7 @@ namespace IGFD } else if (vSortingField == SortingFieldEnum::FIELD_DATE) { + logV("IGFD: sorting by date"); if (vCanChangeOrder && puSortingField == vSortingField) puSortingDirection[3] = !puSortingDirection[3]; @@ -1458,6 +1471,8 @@ namespace IGFD puSortingField = vSortingField; } + logV("IGFD: applying filtering on file list"); + ApplyFilteringOnFileList(vFileDialogInternal); } @@ -1518,14 +1533,17 @@ namespace IGFD void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) { std::string path = vPath; + logV("IGFD: ScanDir(%s)",vPath); if (prCurrentPathDecomposition.empty()) { + logV("IGFD: the current path decomposition is empty. setting."); SetCurrentDir(path); } if (!prCurrentPathDecomposition.empty()) { + logV("IGFD: the current path decomposition is not empty. trying."); #ifdef WIN32 if (path == puFsRoot) path += std::string(1u, PATH_SEP); @@ -1553,6 +1571,7 @@ namespace IGFD #else // dirent struct dirent** files = nullptr; int n = scandir(path.c_str(), &files, nullptr, inAlphaSort); + logV("IGFD: %d entries in directory",n); if (n>0) { int i; @@ -1620,11 +1639,18 @@ namespace IGFD } free(files); - } + } else { + logV("IGFD: it's empty"); + } #endif // USE_STD_FILESYSTEM + logV("IGFD: sorting fields..."); SortFields(vFileDialogInternal, puSortingField, false); - } + } else { + logE("IGFD: current path decomposition is empty!"); + } + + fileListActuallyEmpty=prFileList.empty(); } bool IGFD::FileManager::GetDrives() @@ -2436,7 +2462,7 @@ namespace IGFD void IGFD::FileDialogInternal::ResetForNewDialog() { - + puFileManager.fileListActuallyEmpty=false; } ///////////////////////////////////////////////////////////////////////////////////// @@ -3737,16 +3763,17 @@ namespace IGFD fdFilter.SetDefaultFilterIfNotDefined(); // init list of files - if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives) + if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives && !fdFile.fileListActuallyEmpty) { IGFD::Utils::ReplaceString(fdFile.puDLGDefaultFileName, fdFile.puDLGpath, ""); // local path if (!fdFile.puDLGDefaultFileName.empty()) { fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName); fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt); - } - else if (fdFile.puDLGDirectoryMode) // directory mode + } else if (fdFile.puDLGDirectoryMode) { // directory mode fdFile.SetDefaultFileName("."); + } + logV("IGFD: fdFile.IsFileListEmpty() and !fdFile.puShowDrives"); fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath); } diff --git a/extern/igfd/ImGuiFileDialog.h b/extern/igfd/ImGuiFileDialog.h index 2725223f5..7b2523959 100644 --- a/extern/igfd/ImGuiFileDialog.h +++ b/extern/igfd/ImGuiFileDialog.h @@ -875,6 +875,7 @@ namespace IGFD #endif SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column bool puShowDrives = false; // drives are shown (only on os windows) + bool fileListActuallyEmpty = false; std::string puDLGpath; // base path set by user when OpenDialog/OpenModal was called std::string puDLGDefaultFileName; // base default file path name set by user when OpenDialog/OpenModal was called From d8471ce937bc34a1e0d5f135738cba456311d6a9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 24 Mar 2023 19:17:28 -0500 Subject: [PATCH 57/64] GUI: remove use of Columns() in orders --- src/gui/orders.cpp | 437 +++++++++++++++++++++++---------------------- 1 file changed, 222 insertions(+), 215 deletions(-) diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 16a9c1bbb..783c088a7 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -107,242 +107,249 @@ void FurnaceGUI::drawOrders() { } else { //ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH)); } - if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags)) { - float regionX=ImGui::GetContentRegionAvail().x; - ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale)); - ImGui::Columns(2,NULL,false); - ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale); - int displayChans=0; - for (int i=0; igetTotalChannelCount(); i++) { - if (e->curSubSong->chanShow[i]) displayChans++; - } - ImGui::PushFont(patFont); - bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x))); - ImGui::PopFont(); - if (ImGui::BeginTable("OrdersTable",1+displayChans,(tooSmall?ImGuiTableFlags_SizingFixedFit:ImGuiTableFlags_SizingStretchSame)|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) { - ImGui::PushFont(patFont); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,prevSpacing); - ImGui::TableSetupScrollFreeze(1,1); - float lineHeight=(ImGui::GetTextLineHeight()+4*dpiScale); - if (e->isPlaying()) { - if (followOrders) { - ImGui::SetScrollY((e->getOrder()+1)*lineHeight-(ImGui::GetContentRegionAvail().y/2)); - } - } - ImGui::TableNextRow(0,lineHeight); - ImVec2 ra=ImGui::GetContentRegionAvail(); - ImGui::TableNextColumn(); - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); - for (int i=0; igetTotalChannelCount(); i++) { - if (!e->curSubSong->chanShow[i]) continue; - ImGui::TableNextColumn(); - ImGui::Text("%s",e->getChannelShortName(i)); - } - ImGui::PopStyleColor(); - for (int i=0; icurSubSong->ordersLen; i++) { - ImGui::TableNextRow(0,lineHeight); - if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); - ImGui::TableNextColumn(); - if ((!followPattern && curOrder==i) || (followPattern && oldOrder1==i)) { - // draw a border - ImDrawList* dl=ImGui::GetWindowDrawList(); - ImVec2 rBegin=ImGui::GetCursorScreenPos(); - rBegin.y-=ImGui::GetStyle().CellPadding.y; - ImVec2 rEnd=ImVec2(rBegin.x+ra.x,rBegin.y+lineHeight); - dl->AddRect(rBegin,rEnd,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_SELECTED]),2.0f*dpiScale); - } - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); - bool highlightLoop=(i>=loopOrder && i<=loopEnd); - if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP])); - if (settings.orderRowsBase==1) { - snprintf(selID,4096,"%.2X##O_S%.2x",i,i); - } else { - snprintf(selID,4096,"%d##O_S%.2x",i,i); - } - if (ImGui::Selectable(selID)) { - setOrder(i); - curNibble=false; - orderCursor=-1; + if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::BeginTable("OrdColumn",2,ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); - if (orderEditMode==0) { - handleUnimportant; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale)); + int displayChans=0; + for (int i=0; igetTotalChannelCount(); i++) { + if (e->curSubSong->chanShow[i]) displayChans++; + } + ImGui::PushFont(patFont); + bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x))); + ImGui::PopFont(); + if (ImGui::BeginTable("OrdersTable",1+displayChans,(tooSmall?ImGuiTableFlags_SizingFixedFit:ImGuiTableFlags_SizingStretchSame)|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) { + ImGui::PushFont(patFont); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,prevSpacing); + ImGui::TableSetupScrollFreeze(1,1); + float lineHeight=(ImGui::GetTextLineHeight()+4*dpiScale); + if (e->isPlaying()) { + if (followOrders) { + ImGui::SetScrollY((e->getOrder()+1)*lineHeight-(ImGui::GetContentRegionAvail().y/2)); } } - ImGui::PopStyleColor(); - for (int j=0; jgetTotalChannelCount(); j++) { - if (!e->curSubSong->chanShow[j]) continue; + ImGui::TableNextRow(0,lineHeight); + ImVec2 ra=ImGui::GetContentRegionAvail(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); + for (int i=0; igetTotalChannelCount(); i++) { + if (!e->curSubSong->chanShow[i]) continue; ImGui::TableNextColumn(); - DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false); - /*if (!pat->name.empty()) { - snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i); - } else {*/ - snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i); - //} - - ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); - if (ImGui::Selectable(selID,settings.ordersCursor?(cursor.xCoarse==j && oldOrder1!=i):false)) { - if (curOrder==i) { - if (orderEditMode==0) { - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->lockSave([this,i,j]() { - if (changeAllOrders) { - for (int k=0; kgetTotalChannelCount(); k++) { - if (e->curOrders->ord[k][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[k][i]++; - } - } else { - if (e->curOrders->ord[j][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[j][i]++; - } - }); - e->walkSong(loopOrder,loopRow,loopEnd); - makeUndo(GUI_UNDO_CHANGE_ORDER); - } else { - orderCursor=j; - curNibble=false; - } - } else { - setOrder(i); - e->walkSong(loopOrder,loopRow,loopEnd); - if (orderEditMode!=0) { - orderCursor=j; - curNibble=false; - } - } + ImGui::Text("%s",e->getChannelShortName(i)); + } + ImGui::PopStyleColor(); + for (int i=0; icurSubSong->ordersLen; i++) { + ImGui::TableNextRow(0,lineHeight); + if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); + ImGui::TableNextColumn(); + if ((!followPattern && curOrder==i) || (followPattern && oldOrder1==i)) { + // draw a border + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImVec2 rBegin=ImGui::GetCursorScreenPos(); + rBegin.y-=ImGui::GetStyle().CellPadding.y; + ImVec2 rEnd=ImVec2(rBegin.x+ra.x,rBegin.y+lineHeight); + dl->AddRect(rBegin,rEnd,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_SELECTED]),2.0f*dpiScale); + } + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); + bool highlightLoop=(i>=loopOrder && i<=loopEnd); + if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP])); + if (settings.orderRowsBase==1) { + snprintf(selID,4096,"%.2X##O_S%.2x",i,i); + } else { + snprintf(selID,4096,"%d##O_S%.2x",i,i); + } + if (ImGui::Selectable(selID)) { + setOrder(i); + curNibble=false; + orderCursor=-1; if (orderEditMode==0) { handleUnimportant; } } ImGui::PopStyleColor(); - if (orderEditMode!=0 && curOrder==i && orderCursor==j) { - // draw a border - ImDrawList* dl=ImGui::GetWindowDrawList(); - dl->AddRect(ImGui::GetItemRectMin(),ImGui::GetItemRectMax(),ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]),2.0f*dpiScale); - } - if (!pat->name.empty() && ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s",pat->name.c_str()); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - if (curOrder==i) { - if (orderEditMode==0) { - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->lockSave([this,i,j]() { - if (changeAllOrders) { - for (int k=0; kgetTotalChannelCount(); k++) { - if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--; + for (int j=0; jgetTotalChannelCount(); j++) { + if (!e->curSubSong->chanShow[j]) continue; + ImGui::TableNextColumn(); + DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false); + /*if (!pat->name.empty()) { + snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i); + } else {*/ + snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i); + //} + + ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); + if (ImGui::Selectable(selID,settings.ordersCursor?(cursor.xCoarse==j && oldOrder1!=i):false)) { + if (curOrder==i) { + if (orderEditMode==0) { + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->lockSave([this,i,j]() { + if (changeAllOrders) { + for (int k=0; kgetTotalChannelCount(); k++) { + if (e->curOrders->ord[k][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[k][i]++; + } + } else { + if (e->curOrders->ord[j][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[j][i]++; } - } else { - if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--; - } - }); - e->walkSong(loopOrder,loopRow,loopEnd); - makeUndo(GUI_UNDO_CHANGE_ORDER); + }); + e->walkSong(loopOrder,loopRow,loopEnd); + makeUndo(GUI_UNDO_CHANGE_ORDER); + } else { + orderCursor=j; + curNibble=false; + } } else { - orderCursor=j; - curNibble=false; + setOrder(i); + e->walkSong(loopOrder,loopRow,loopEnd); + if (orderEditMode!=0) { + orderCursor=j; + curNibble=false; + } } - } else { - setOrder(i); - e->walkSong(loopOrder,loopRow,loopEnd); - if (orderEditMode!=0) { - orderCursor=j; - curNibble=false; + + if (orderEditMode==0) { + handleUnimportant; + } + } + ImGui::PopStyleColor(); + if (orderEditMode!=0 && curOrder==i && orderCursor==j) { + // draw a border + ImDrawList* dl=ImGui::GetWindowDrawList(); + dl->AddRect(ImGui::GetItemRectMin(),ImGui::GetItemRectMax(),ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]),2.0f*dpiScale); + } + if (!pat->name.empty() && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s",pat->name.c_str()); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (curOrder==i) { + if (orderEditMode==0) { + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->lockSave([this,i,j]() { + if (changeAllOrders) { + for (int k=0; kgetTotalChannelCount(); k++) { + if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--; + } + } else { + if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--; + } + }); + e->walkSong(loopOrder,loopRow,loopEnd); + makeUndo(GUI_UNDO_CHANGE_ORDER); + } else { + orderCursor=j; + curNibble=false; + } + } else { + setOrder(i); + e->walkSong(loopOrder,loopRow,loopEnd); + if (orderEditMode!=0) { + orderCursor=j; + curNibble=false; + } } } } } + ImGui::PopStyleVar(); + ImGui::PopFont(); + ImGui::EndTable(); + } + ImGui::TableNextColumn(); + if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant + // add order row (new) + doAction(GUI_ACTION_ORDERS_ADD); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add new order"); + } + if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant + // remove this order row + doAction(GUI_ACTION_ORDERS_REMOVE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Remove order"); + } + if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant + // duplicate order row + doAction(GUI_ACTION_ORDERS_DUPLICATE); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + doAction(GUI_ACTION_ORDERS_DEEP_CLONE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); + } + if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant + // move order row up + doAction(GUI_ACTION_ORDERS_MOVE_UP); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move order up"); + } + if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant + // move order row down + doAction(GUI_ACTION_ORDERS_MOVE_DOWN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move order down"); + } + if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant + // duplicate order row at end + doAction(GUI_ACTION_ORDERS_DUPLICATE_END); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + doAction(GUI_ACTION_ORDERS_DEEP_CLONE_END); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); + } + if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant + // whether to change one or all orders in a row + changeAllOrders=!changeAllOrders; + } + if (ImGui::IsItemHovered()) { + if (changeAllOrders) { + ImGui::SetTooltip("Order change mode: entire row"); + } else { + ImGui::SetTooltip("Order change mode: one"); + } + } + const char* orderEditModeLabel="?##OrderEditMode"; + if (orderEditMode==3) { + orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode"; + } else if (orderEditMode==2) { + orderEditModeLabel=ICON_FA_ARROWS_H "##OrderEditMode"; + } else if (orderEditMode==1) { + orderEditModeLabel=ICON_FA_I_CURSOR "##OrderEditMode"; + } else { + orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode"; + } + if (ImGui::Button(orderEditModeLabel)) { handleUnimportant + orderEditMode++; + if (orderEditMode>3) orderEditMode=0; + curNibble=false; + } + if (ImGui::IsItemHovered()) { + if (orderEditMode==3) { + ImGui::SetTooltip("Order edit mode: Select and type (scroll vertically)"); + } else if (orderEditMode==2) { + ImGui::SetTooltip("Order edit mode: Select and type (scroll horizontally)"); + } else if (orderEditMode==1) { + ImGui::SetTooltip("Order edit mode: Select and type (don't scroll)"); + } else { + ImGui::SetTooltip("Order edit mode: Click to change"); + } } ImGui::PopStyleVar(); - ImGui::PopFont(); + ImGui::EndTable(); } - ImGui::NextColumn(); - if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant - // add order row (new) - doAction(GUI_ACTION_ORDERS_ADD); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Add new order"); - } - if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant - // remove this order row - doAction(GUI_ACTION_ORDERS_REMOVE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Remove order"); - } - if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant - // duplicate order row - doAction(GUI_ACTION_ORDERS_DUPLICATE); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - doAction(GUI_ACTION_ORDERS_DEEP_CLONE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); - } - if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant - // move order row up - doAction(GUI_ACTION_ORDERS_MOVE_UP); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move order up"); - } - if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant - // move order row down - doAction(GUI_ACTION_ORDERS_MOVE_DOWN); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move order down"); - } - if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant - // duplicate order row at end - doAction(GUI_ACTION_ORDERS_DUPLICATE_END); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - doAction(GUI_ACTION_ORDERS_DEEP_CLONE_END); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); - } - if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant - // whether to change one or all orders in a row - changeAllOrders=!changeAllOrders; - } - if (ImGui::IsItemHovered()) { - if (changeAllOrders) { - ImGui::SetTooltip("Order change mode: entire row"); - } else { - ImGui::SetTooltip("Order change mode: one"); - } - } - const char* orderEditModeLabel="?##OrderEditMode"; - if (orderEditMode==3) { - orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode"; - } else if (orderEditMode==2) { - orderEditModeLabel=ICON_FA_ARROWS_H "##OrderEditMode"; - } else if (orderEditMode==1) { - orderEditModeLabel=ICON_FA_I_CURSOR "##OrderEditMode"; - } else { - orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode"; - } - if (ImGui::Button(orderEditModeLabel)) { handleUnimportant - orderEditMode++; - if (orderEditMode>3) orderEditMode=0; - curNibble=false; - } - if (ImGui::IsItemHovered()) { - if (orderEditMode==3) { - ImGui::SetTooltip("Order edit mode: Select and type (scroll vertically)"); - } else if (orderEditMode==2) { - ImGui::SetTooltip("Order edit mode: Select and type (scroll horizontally)"); - } else if (orderEditMode==1) { - ImGui::SetTooltip("Order edit mode: Select and type (don't scroll)"); - } else { - ImGui::SetTooltip("Order edit mode: Click to change"); - } - } - ImGui::PopStyleVar(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ORDERS; oldOrder1=e->getOrder(); From f20da6b202e43473aaff8f342399ca8dfab0f8a3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 25 Mar 2023 03:55:42 -0500 Subject: [PATCH 58/64] FT -----____ | -----____ | FFFFF -----____ | FFFFFFFFF TTTTTTT | | FF TTTTTTTT | | FF TT | | FF TT | | FFFFFFFFF TT | | FFFFFFFFF TT | | FF TT | | FF TT | | FF TT | | FF TT | | FF __________------| |--------- --- src/engine/engine.h | 1 + src/engine/fileOps.cpp | 272 +++++++++++++++++++++++++++++++++++--- src/engine/safeReader.cpp | 6 +- 3 files changed, 256 insertions(+), 23 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index b76a3e5b8..7fc2a110c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -58,6 +58,7 @@ #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 #define DIV_VERSION_S3M 0xff03 +#define DIV_VERSION_FTM 0xff04 // "Namco C163" #define DIV_C163_DEFAULT_NAME "Namco 163" diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 41e2f6ffc..e1ae97b13 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -4061,12 +4061,58 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { #define CHECK_BLOCK_VERSION(x) \ if (blockVersion>x) { \ - logE("incompatible block version %d for %s!",blockVersion,blockName); \ - lastError="incompatible block version"; \ - delete[] file; \ - return false; \ + logW("incompatible block version %d for %s!",blockVersion,blockName); \ } +const int ftEffectMap[]={ + -1, // none + 0x0f, + 0x0b, + 0x0d, + 0xff, + -1, // volume? not supported in Furnace yet + 0x03, + 0x03, // unused? + 0x13, + 0x14, + 0x00, + 0x04, + 0x07, + 0xe5, + 0xed, + 0x11, + 0x01, // porta up + 0x02, // porta down + 0x12, + 0x90, // sample offset - not supported yet + 0xe1, + 0xe2, + 0x0a, + 0xec, + 0x0c, + -1, // delayed volume - not supported yet + 0x11, // FDS + 0x12, + 0x13, + 0x20, // DPCM pitch + 0x22, // 5B + 0x24, + 0x23, + 0x21, + -1, // VRC7 "custom patch port" - not supported? + -1, // VRC7 "custom patch write" + -1, // release - not supported yet + 0x09, // select groove + -1, // transpose - not supported + 0x10, // Namco 163 + -1, // FDS vol env - not supported + -1, // FDS auto FM - not supported yet + -1, // phase reset - not supported + -1, // harmonic - not supported +}; + +constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int); + bool DivEngine::loadFTM(unsigned char* file, size_t len) { SafeReader reader=SafeReader(file,len); warnings=""; @@ -4078,6 +4124,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { unsigned int n163Chans=0; bool hasSequence[256][8]; unsigned char sequenceIndex[256][8]; + unsigned int hilightA=4; + unsigned int hilightB=16; + double customHz=60; memset(hasSequence,0,256*8*sizeof(bool)); memset(sequenceIndex,0,256*8); @@ -4098,6 +4147,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { return false; } + for (DivSubSong* i: ds.subsong) { + i->clearData(); + delete i; + } + ds.subsong.clear(); + + ds.linearPitch=0; + while (true) { blockName=reader.readString(3); if (blockName=="END") { @@ -4115,7 +4172,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize); if (blockName=="PARAMS") { - CHECK_BLOCK_VERSION(6); + // versions 7-9 don't change anything? + CHECK_BLOCK_VERSION(9); unsigned int oldSpeedTempo=0; if (blockVersion<=1) { oldSpeedTempo=reader.readI(); @@ -4125,15 +4183,32 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { } tchans=reader.readI(); unsigned int pal=reader.readI(); - unsigned int customHz=reader.readI(); + if (blockVersion>=7) { + // advanced Hz control + int controlType=reader.readI(); + switch (controlType) { + case 1: + customHz=1000000.0/(double)reader.readI(); + break; + default: + reader.readI(); + break; + } + } else { + customHz=reader.readI(); + } unsigned int newVibrato=0; + bool sweepReset=false; unsigned int speedSplitPoint=0; if (blockVersion>=3) { newVibrato=reader.readI(); } - if (blockVersion>=4) { - ds.subsong[0]->hilightA=reader.readI(); - ds.subsong[0]->hilightB=reader.readI(); + if (blockVersion>=9) { + sweepReset=reader.readI(); + } + if (blockVersion>=4 && blockVersion<7) { + hilightA=reader.readI(); + hilightB=reader.readI(); } if (expansions&8) if (blockVersion>=5) { // N163 channels n163Chans=reader.readI(); @@ -4142,20 +4217,24 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { speedSplitPoint=reader.readI(); } + if (blockVersion>=8) { + int fineTuneCents=reader.readC()*100; + fineTuneCents+=reader.readC(); + + ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0); + } + logV("old speed/tempo: %d",oldSpeedTempo); logV("expansions: %x",expansions); logV("channels: %d",tchans); logV("PAL: %d",pal); - logV("custom Hz: %d",customHz); + logV("custom Hz: %f",customHz); logV("new vibrato: %d",newVibrato); logV("N163 channels: %d",n163Chans); - logV("highlight 1: %d",ds.subsong[0]->hilightA); - logV("highlight 2: %d",ds.subsong[0]->hilightB); + logV("highlight 1: %d",hilightA); + logV("highlight 2: %d",hilightB); logV("split point: %d",speedSplitPoint); - - if (customHz!=0) { - ds.subsong[0]->hz=customHz; - } + logV("sweep reset: %d",sweepReset); // initialize channels int systemID=0; @@ -4200,28 +4279,46 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { CHECK_BLOCK_VERSION(1); ds.name=reader.readString(32); ds.author=reader.readString(32); - ds.copyright=reader.readString(32); + ds.category=reader.readString(32); + ds.systemName="NES"; } else if (blockName=="HEADER") { - CHECK_BLOCK_VERSION(3); + CHECK_BLOCK_VERSION(4); unsigned char totalSongs=reader.readC(); logV("%d songs:",totalSongs+1); for (int i=0; i<=totalSongs; i++) { String subSongName=reader.readString(); + ds.subsong.push_back(new DivSubSong); + ds.subsong[i]->name=subSongName; + ds.subsong[i]->hilightA=hilightA; + ds.subsong[i]->hilightB=hilightB; + if (customHz!=0) { + ds.subsong[i]->hz=customHz; + } logV("- %s",subSongName); } for (unsigned int i=0; ipat[i].effectCols=effectCols+1; - } + ds.subsong[j]->pat[i].effectCols=effectCols+1; logV("- song %d has %d effect columns",j,effectCols); } } + + if (blockVersion>=4) { + for (int i=0; i<=totalSongs; i++) { + ds.subsong[i]->hilightA=(unsigned char)reader.readC(); + ds.subsong[i]->hilightB=(unsigned char)reader.readC(); + } + } } else if (blockName=="INSTRUMENTS") { CHECK_BLOCK_VERSION(6); + + reader.seek(blockSize,SEEK_CUR); + + /* ds.insLen=reader.readI(); if (ds.insLen<0 || ds.insLen>256) { logE("too many instruments/out of range!"); @@ -4381,21 +4478,131 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { ins->name=reader.readString((unsigned int)reader.readI()); logV("- %d: %s",insIndex,ins->name); } + */ } else if (blockName=="SEQUENCES") { CHECK_BLOCK_VERSION(6); + reader.seek(blockSize,SEEK_CUR); } else if (blockName=="FRAMES") { CHECK_BLOCK_VERSION(3); + + for (size_t i=0; iordersLen=reader.readI(); + if (blockVersion>=3) { + s->speeds.val[0]=reader.readI(); + } + if (blockVersion>=2) { + s->virtualTempoN=reader.readI(); + s->patLen=reader.readI(); + } + int why=tchans; + if (blockVersion==1) { + why=reader.readI(); + } + logV("reading %d and %d orders",tchans,s->ordersLen); + + for (int j=0; jordersLen; j++) { + for (int k=0; korders.ord[k][j]=o; + } + } + } } else if (blockName=="PATTERNS") { - CHECK_BLOCK_VERSION(5); + CHECK_BLOCK_VERSION(6); + + size_t blockEnd=reader.tell()+blockSize; + + if (blockVersion==1) { + int patLenOld=reader.readI(); + for (DivSubSong* i: ds.subsong) { + i->patLen=patLenOld; + } + } + + // so it appears .ftm doesn't keep track of how many patterns are stored in the file.... + while (reader.tell()=2) subs=reader.readI(); + int ch=reader.readI(); + int patNum=reader.readI(); + int numRows=reader.readI(); + + DivPattern* pat=ds.subsong[subs]->pat[ch].getPattern(patNum,true); + for (int i=0; i=2 && blockVersion<6) { // row index + row=reader.readI(); + } else { + row=reader.readC(); + } + + unsigned char nextNote=reader.readC(); + unsigned char nextOctave=reader.readC(); + if (nextNote==0x0d) { + pat->data[row][0]=100; + } else if (nextNote==0x0e) { + pat->data[row][0]=101; + } else if (nextNote==0x01) { + pat->data[row][0]=12; + pat->data[row][1]=nextOctave-1; + } else if (nextNote==0) { + pat->data[row][0]=0; + } else if (nextNote<0x0d) { + pat->data[row][0]=nextNote-1; + pat->data[row][1]=nextOctave; + } + + unsigned char nextIns=reader.readC(); + if (nextIns<0x40) { + pat->data[row][2]=nextIns; + } else { + pat->data[row][2]=-1; + } + + unsigned char nextVol=reader.readC(); + if (nextVol<0x10) { + pat->data[row][3]=nextVol; + } else { + pat->data[row][3]=-1; + } + + int effectCols=ds.subsong[subs]->pat[ch].effectCols; + if (blockVersion>=6) effectCols=4; + + for (int j=0; jdata[row][4+(j*2)]=-1; + pat->data[row][5+(j*2)]=-1; + } else { + if (nextEffectdata[row][4+(j*2)]=ftEffectMap[nextEffect]; + } else { + pat->data[row][4+(j*2)]=-1; + } + pat->data[row][5+(j*2)]=nextEffectVal; + } + } + } + } } else if (blockName=="DPCM SAMPLES") { CHECK_BLOCK_VERSION(1); + reader.seek(blockSize,SEEK_CUR); } else if (blockName=="SEQUENCES_VRC6") { // where are the 5B and FDS sequences? CHECK_BLOCK_VERSION(6); + reader.seek(blockSize,SEEK_CUR); } else if (blockName=="SEQUENCES_N163") { CHECK_BLOCK_VERSION(1); + reader.seek(blockSize,SEEK_CUR); } else if (blockName=="COMMENTS") { CHECK_BLOCK_VERSION(1); + reader.seek(blockSize,SEEK_CUR); } else { logE("block %s is unknown!",blockName); lastError="unknown block "+blockName; @@ -4410,6 +4617,27 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { return false; } } + + addWarning("FamiTracker import is experimental!"); + + ds.version=DIV_VERSION_FTM; + + if (active) quitDispatch(); + BUSY_BEGIN_SOFT; + saveLock.lock(); + song.unload(); + song=ds; + changeSong(0); + recalcChans(); + saveLock.unlock(); + BUSY_END; + if (active) { + initDispatch(); + BUSY_BEGIN; + renderSamples(); + reset(); + BUSY_END; + } } catch (EndOfFileException& e) { logE("premature end of file!"); lastError="incomplete file"; diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index cf2effbec..5ac416e27 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -236,10 +236,14 @@ String SafeReader::readString(size_t stlen) { #endif size_t curPos=0; if (isEOF()) throw EndOfFileException(this, len); + bool zero=false; while (!isEOF() && curPos Date: Sat, 25 Mar 2023 18:13:22 -0500 Subject: [PATCH 59/64] GUI: orders view with dynamic icons --- src/gui/orders.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 783c088a7..a746d5983 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -91,6 +91,13 @@ void FurnaceGUI::drawMobileOrderSel() { ImGui::End(); } +#define NEXT_BUTTON \ + if (++buttonColumn>=buttonColumns) { \ + buttonColumn=0; \ + } else { \ + ImGui::SameLine(); \ + } + void FurnaceGUI::drawOrders() { static char selID[4096]; if (nextWindow==GUI_WINDOW_ORDERS) { @@ -260,7 +267,16 @@ void FurnaceGUI::drawOrders() { ImGui::PopFont(); ImGui::EndTable(); } + ImGui::TableNextColumn(); + + int buttonColumns=1; + int buttonColumn=0; + + while (buttonColumns<8 && ((8/buttonColumns)*ImGui::GetFrameHeightWithSpacing())>ImGui::GetContentRegionAvail().y) { + buttonColumns++; + } + if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant // add order row (new) doAction(GUI_ACTION_ORDERS_ADD); @@ -268,6 +284,8 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Add new order"); } + NEXT_BUTTON; + if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant // remove this order row doAction(GUI_ACTION_ORDERS_REMOVE); @@ -275,6 +293,8 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove order"); } + NEXT_BUTTON; + if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant // duplicate order row doAction(GUI_ACTION_ORDERS_DUPLICATE); @@ -285,6 +305,8 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); } + NEXT_BUTTON; + if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant // move order row up doAction(GUI_ACTION_ORDERS_MOVE_UP); @@ -292,6 +314,8 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order up"); } + NEXT_BUTTON; + if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant // move order row down doAction(GUI_ACTION_ORDERS_MOVE_DOWN); @@ -299,6 +323,8 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order down"); } + NEXT_BUTTON; + if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant // duplicate order row at end doAction(GUI_ACTION_ORDERS_DUPLICATE_END); @@ -309,6 +335,8 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); } + NEXT_BUTTON; + if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant // whether to change one or all orders in a row changeAllOrders=!changeAllOrders; @@ -320,6 +348,8 @@ void FurnaceGUI::drawOrders() { ImGui::SetTooltip("Order change mode: one"); } } + NEXT_BUTTON; + const char* orderEditModeLabel="?##OrderEditMode"; if (orderEditMode==3) { orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode"; From 8d89abb60e4cea0fa816f34c4b58f4446d2446e8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 25 Mar 2023 18:43:21 -0500 Subject: [PATCH 60/64] GUI: add order button pos setting --- src/gui/gui.h | 4 + src/gui/orders.cpp | 252 ++++++++++++++++++++++++------------------- src/gui/settings.cpp | 14 +++ 3 files changed, 157 insertions(+), 113 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 9d4ee9fbb..dba62b8fc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1376,6 +1376,7 @@ class FurnaceGUI { int disableFadeIn; int alwaysPlayIntro; int iCannotWait; + int orderButtonPos; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1518,6 +1519,7 @@ class FurnaceGUI { disableFadeIn(0), alwaysPlayIntro(0), iCannotWait(0), + orderButtonPos(2), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1930,6 +1932,8 @@ class FurnaceGUI { void drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float availableWidth, int index); void drawMacros(std::vector& macros, FurnaceGUIMacroEditState& state); + void drawOrderButtons(); + void actualWaveList(); void actualSampleList(); diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index a746d5983..dad280d46 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -98,6 +98,115 @@ void FurnaceGUI::drawMobileOrderSel() { ImGui::SameLine(); \ } +void FurnaceGUI::drawOrderButtons() { + int buttonColumns=(settings.orderButtonPos==0)?8:1; + int buttonColumn=0; + + while (buttonColumns<8 && ((8/buttonColumns)*ImGui::GetFrameHeightWithSpacing())>ImGui::GetContentRegionAvail().y) { + buttonColumns++; + } + + if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant + // add order row (new) + doAction(GUI_ACTION_ORDERS_ADD); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add new order"); + } + NEXT_BUTTON; + + if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant + // remove this order row + doAction(GUI_ACTION_ORDERS_REMOVE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Remove order"); + } + NEXT_BUTTON; + + if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant + // duplicate order row + doAction(GUI_ACTION_ORDERS_DUPLICATE); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + doAction(GUI_ACTION_ORDERS_DEEP_CLONE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); + } + NEXT_BUTTON; + + if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant + // move order row up + doAction(GUI_ACTION_ORDERS_MOVE_UP); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move order up"); + } + NEXT_BUTTON; + + if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant + // move order row down + doAction(GUI_ACTION_ORDERS_MOVE_DOWN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move order down"); + } + NEXT_BUTTON; + + if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant + // duplicate order row at end + doAction(GUI_ACTION_ORDERS_DUPLICATE_END); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + doAction(GUI_ACTION_ORDERS_DEEP_CLONE_END); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); + } + NEXT_BUTTON; + + if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant + // whether to change one or all orders in a row + changeAllOrders=!changeAllOrders; + } + if (ImGui::IsItemHovered()) { + if (changeAllOrders) { + ImGui::SetTooltip("Order change mode: entire row"); + } else { + ImGui::SetTooltip("Order change mode: one"); + } + } + NEXT_BUTTON; + + const char* orderEditModeLabel="?##OrderEditMode"; + if (orderEditMode==3) { + orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode"; + } else if (orderEditMode==2) { + orderEditModeLabel=ICON_FA_ARROWS_H "##OrderEditMode"; + } else if (orderEditMode==1) { + orderEditModeLabel=ICON_FA_I_CURSOR "##OrderEditMode"; + } else { + orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode"; + } + if (ImGui::Button(orderEditModeLabel)) { handleUnimportant + orderEditMode++; + if (orderEditMode>3) orderEditMode=0; + curNibble=false; + } + if (ImGui::IsItemHovered()) { + if (orderEditMode==3) { + ImGui::SetTooltip("Order edit mode: Select and type (scroll vertically)"); + } else if (orderEditMode==2) { + ImGui::SetTooltip("Order edit mode: Select and type (scroll horizontally)"); + } else if (orderEditMode==1) { + ImGui::SetTooltip("Order edit mode: Select and type (don't scroll)"); + } else { + ImGui::SetTooltip("Order edit mode: Click to change"); + } + } +} + void FurnaceGUI::drawOrders() { static char selID[4096]; if (nextWindow==GUI_WINDOW_ORDERS) { @@ -115,15 +224,33 @@ void FurnaceGUI::drawOrders() { //ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH)); } if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { - if (ImGui::BeginTable("OrdColumn",2,ImGuiTableFlags_BordersInnerV)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); + if (ImGui::BeginTable("OrdColumn",(settings.orderButtonPos==0)?1:2,ImGuiTableFlags_BordersInnerV)) { + if (settings.orderButtonPos==2) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + } else if (settings.orderButtonPos==1) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + } ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale)); + if (settings.orderButtonPos!=0) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale)); + } + + ImGui::TableNextRow(); + + if (settings.orderButtonPos<2) { + ImGui::TableNextColumn(); + drawOrderButtons(); + } + + if (settings.orderButtonPos==0) { + ImGui::TableNextRow(); + } + + ImGui::TableNextColumn(); + int displayChans=0; for (int i=0; igetTotalChannelCount(); i++) { if (e->curSubSong->chanShow[i]) displayChans++; @@ -268,115 +395,14 @@ void FurnaceGUI::drawOrders() { ImGui::EndTable(); } - ImGui::TableNextColumn(); - - int buttonColumns=1; - int buttonColumn=0; - - while (buttonColumns<8 && ((8/buttonColumns)*ImGui::GetFrameHeightWithSpacing())>ImGui::GetContentRegionAvail().y) { - buttonColumns++; + if (settings.orderButtonPos==2) { + ImGui::TableNextColumn(); + drawOrderButtons(); } - if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant - // add order row (new) - doAction(GUI_ACTION_ORDERS_ADD); + if (settings.orderButtonPos!=0) { + ImGui::PopStyleVar(); } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Add new order"); - } - NEXT_BUTTON; - - if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant - // remove this order row - doAction(GUI_ACTION_ORDERS_REMOVE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Remove order"); - } - NEXT_BUTTON; - - if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant - // duplicate order row - doAction(GUI_ACTION_ORDERS_DUPLICATE); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - doAction(GUI_ACTION_ORDERS_DEEP_CLONE); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); - } - NEXT_BUTTON; - - if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant - // move order row up - doAction(GUI_ACTION_ORDERS_MOVE_UP); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move order up"); - } - NEXT_BUTTON; - - if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant - // move order row down - doAction(GUI_ACTION_ORDERS_MOVE_DOWN); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move order down"); - } - NEXT_BUTTON; - - if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant - // duplicate order row at end - doAction(GUI_ACTION_ORDERS_DUPLICATE_END); - } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - doAction(GUI_ACTION_ORDERS_DEEP_CLONE_END); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); - } - NEXT_BUTTON; - - if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant - // whether to change one or all orders in a row - changeAllOrders=!changeAllOrders; - } - if (ImGui::IsItemHovered()) { - if (changeAllOrders) { - ImGui::SetTooltip("Order change mode: entire row"); - } else { - ImGui::SetTooltip("Order change mode: one"); - } - } - NEXT_BUTTON; - - const char* orderEditModeLabel="?##OrderEditMode"; - if (orderEditMode==3) { - orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode"; - } else if (orderEditMode==2) { - orderEditModeLabel=ICON_FA_ARROWS_H "##OrderEditMode"; - } else if (orderEditMode==1) { - orderEditModeLabel=ICON_FA_I_CURSOR "##OrderEditMode"; - } else { - orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode"; - } - if (ImGui::Button(orderEditModeLabel)) { handleUnimportant - orderEditMode++; - if (orderEditMode>3) orderEditMode=0; - curNibble=false; - } - if (ImGui::IsItemHovered()) { - if (orderEditMode==3) { - ImGui::SetTooltip("Order edit mode: Select and type (scroll vertically)"); - } else if (orderEditMode==2) { - ImGui::SetTooltip("Order edit mode: Select and type (scroll horizontally)"); - } else if (orderEditMode==1) { - ImGui::SetTooltip("Order edit mode: Select and type (don't scroll)"); - } else { - ImGui::SetTooltip("Order edit mode: Click to change"); - } - } - ImGui::PopStyleVar(); ImGui::EndTable(); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b66e52b63..207c8838b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1452,6 +1452,17 @@ void FurnaceGUI::drawSettings() { settings.controlLayout=3; } + ImGui::Text("Position of buttons in Orders:"); + if (ImGui::RadioButton("Top##obp0",settings.orderButtonPos==0)) { + settings.orderButtonPos=0; + } + if (ImGui::RadioButton("Left##obp1",settings.orderButtonPos==1)) { + settings.orderButtonPos=1; + } + if (ImGui::RadioButton("Right##obp2",settings.orderButtonPos==2)) { + settings.orderButtonPos=2; + } + ImGui::Text("FM parameter editor layout:"); if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) { settings.fmLayout=0; @@ -2627,6 +2638,7 @@ void FurnaceGUI::syncSettings() { settings.alwaysPlayIntro=e->getConfInt("alwaysPlayIntro",0); settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1); settings.iCannotWait=e->getConfInt("iCannotWait",0); + settings.orderButtonPos=e->getConfInt("orderButtonPos",2); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2744,6 +2756,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.alwaysPlayIntro,0,3); clampSetting(settings.cursorFollowsOrder,0,1); clampSetting(settings.iCannotWait,0,1); + clampSetting(settings.orderButtonPos,0,2); if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; @@ -2956,6 +2969,7 @@ void FurnaceGUI::commitSettings() { e->setConf("alwaysPlayIntro",settings.alwaysPlayIntro); e->setConf("cursorFollowsOrder",settings.cursorFollowsOrder); e->setConf("iCannotWait",settings.iCannotWait); + e->setConf("orderButtonPos",settings.orderButtonPos); // colors for (int i=0; i Date: Sun, 26 Mar 2023 16:50:03 +0700 Subject: [PATCH 61/64] Add sample instrument to AY SysDefs It was not possible to select sampple instrument for these chips even though the engine has a PCM driver --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 80c987e28..fbb718cb5 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -789,7 +789,7 @@ void DivEngine::registerSystems() { {"S1", "S2", "S3"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, - {}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, ayPostEffectHandlerMap ); @@ -870,7 +870,7 @@ void DivEngine::registerSystems() { {"S1", "S2", "S3"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930}, - {}, + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, {}, ay8930PostEffectHandlerMap ); From 24c39c7819c58d398632a5dc6b2208cf43010950 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 26 Mar 2023 13:19:57 -0500 Subject: [PATCH 62/64] GUI: separate text/binary command stream buttons --- src/gui/editControls.cpp | 8 +++++++- src/gui/gui.cpp | 40 ++++++++++++++++++++++++---------------- src/gui/gui.h | 1 + 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index ccc1bfcaf..8de859afd 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -516,7 +516,13 @@ void FurnaceGUI::drawMobileControls() { openFileDialog(GUI_FILE_EXPORT_VGM); } - ImGui::Button("CmdStream"); + if (ImGui::Button("CmdStream")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + } + ImGui::SameLine(); + if (ImGui::Button("CmdStream Text")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + } ImGui::Separator(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d1f401d39..9258c4d97 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1737,9 +1737,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export Command Stream", - {"text file", "*.txt", - "binary file", "*.bin"}, - "text file{.txt},binary file{.bin}", + {"text file", "*.txt"}, + "text file{.txt}", + workingDirROMExport, + dpiScale + ); + break; + case GUI_FILE_EXPORT_CMDSTREAM_BINARY: + if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export Command Stream", + {"binary file", "*.bin"}, + "binary file{.bin}", workingDirROMExport, dpiScale ); @@ -3727,7 +3736,10 @@ bool FurnaceGUI::loop() { "technical/development use only!" ); - if (ImGui::Button("export")) { + if (ImGui::Button("export (binary)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + } + if (ImGui::Button("export (text)")) { openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); } ImGui::EndMenu(); @@ -4205,6 +4217,7 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_EXPORT_ROM: case GUI_FILE_EXPORT_CMDSTREAM: + case GUI_FILE_EXPORT_CMDSTREAM_BINARY: workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_LOAD_MAIN_FONT: @@ -4292,9 +4305,10 @@ bool FurnaceGUI::loop() { checkExtension(".zsm"); } if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) { - // we can't tell whether the user chose .txt or .bin in the system file picker - const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin"; - checkExtensionDual(".txt",".bin",fallbackExt); + checkExtension(".txt"); + } + if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY) { + checkExtension(".bin"); } if (curFileDialog==GUI_FILE_EXPORT_COLORS) { checkExtension(".cfgc"); @@ -4636,15 +4650,9 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; - case GUI_FILE_EXPORT_CMDSTREAM: { - String lowerCase=fileName; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } - bool isBinary=true; - if ((lowerCase.size()<4 || lowerCase.rfind(".bin")!=lowerCase.size()-4)) { - isBinary=false; - } + case GUI_FILE_EXPORT_CMDSTREAM: + case GUI_FILE_EXPORT_CMDSTREAM_BINARY: { + bool isBinary=(curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY); SafeWriter* w=e->saveCommand(isBinary); if (w!=NULL) { diff --git a/src/gui/gui.h b/src/gui/gui.h index dba62b8fc..cb2e047b4 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -370,6 +370,7 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_VGM, GUI_FILE_EXPORT_ZSM, GUI_FILE_EXPORT_CMDSTREAM, + GUI_FILE_EXPORT_CMDSTREAM_BINARY, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, GUI_FILE_LOAD_PAT_FONT, From c4510e16e04dd0478195954232ff710f6bee5749 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 26 Mar 2023 18:48:16 -0500 Subject: [PATCH 63/64] add experimental command stream player for verification after that I am going to write optimization code --- CMakeLists.txt | 1 + src/engine/cmdStream.cpp | 301 +++++++++++++++++++++++++++++++++++++++ src/engine/cmdStream.h | 59 ++++++++ src/engine/engine.h | 10 +- src/engine/playback.cpp | 9 ++ src/gui/debugWindow.cpp | 2 + src/gui/editControls.cpp | 16 ++- src/gui/gui.cpp | 77 ++++++++++ src/gui/gui.h | 4 +- src/gui/subSongs.cpp | 21 ++- 10 files changed, 490 insertions(+), 10 deletions(-) create mode 100644 src/engine/cmdStream.cpp create mode 100644 src/engine/cmdStream.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e3caa0b8..78a68aa83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -469,6 +469,7 @@ src/engine/blip_buf.c src/engine/brrUtils.c src/engine/safeReader.cpp src/engine/safeWriter.cpp +src/engine/cmdStream.cpp src/engine/config.cpp src/engine/configEngine.cpp src/engine/dispatchContainer.cpp diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp new file mode 100644 index 000000000..4ef2c5e2b --- /dev/null +++ b/src/engine/cmdStream.cpp @@ -0,0 +1,301 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "cmdStream.h" +#include "engine.h" +#include "../ta-log.h" + +void DivCSPlayer::cleanup() { + delete b; +} + +bool DivCSPlayer::tick() { + bool ticked=false; + for (int i=0; igetTotalChannelCount(); i++) { + bool sendVolume=false; + if (chan[i].readPos==0) continue; + + ticked=true; + + chan[i].waitTicks--; + while (chan[i].waitTicks<=0) { + stream.seek(chan[i].readPos,SEEK_SET); + unsigned char next=stream.readC(); + unsigned char command=0; + + if (next<0xb3) { // note + e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,next-60)); + } else if (next>=0xd0 && next<=0xdf) { + command=fastCmds[next&15]; + } else if (next>=0xe0 && next<=0xef) { // preset delay + chan[i].waitTicks=fastDelays[next&15]; + } else switch (next) { + case 0xb4: // note on null + e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); + break; + case 0xb5: // note off + e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); + break; + case 0xb6: // note off env + e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,i)); + break; + case 0xb7: // env release + e->dispatchCmd(DivCommand(DIV_CMD_ENV_RELEASE,i)); + break; + case 0xb8: case 0xbe: case 0xc0: case 0xc2: + case 0xc3: case 0xc4: case 0xc5: case 0xc6: + case 0xc7: case 0xc8: case 0xc9: case 0xca: + command=next-0xb4; + break; + case 0xf7: + command=stream.readC(); + break; + case 0xf8: + logE("TODO: CALL"); + break; + case 0xf9: + logE("TODO: RET"); + break; + case 0xfa: + logE("TODO: JMP"); + break; + case 0xfb: + logE("TODO: RATE"); + break; + case 0xfc: + chan[i].waitTicks=(unsigned short)stream.readS(); + break; + case 0xfd: + chan[i].waitTicks=(unsigned char)stream.readC(); + break; + case 0xfe: + chan[i].waitTicks=1; + break; + case 0xff: + chan[i].readPos=0; + break; + } + + if (chan[i].readPos==0) break; + + if (command) { + int arg0=0; + int arg1=0; + switch (command) { + case DIV_CMD_INSTRUMENT: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_VOLUME: + arg0=(unsigned char)stream.readC(); + break; + case DIV_CMD_PANNING: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_PORTA: + arg0=(unsigned char)stream.readC(); + arg1=(unsigned char)stream.readC(); + break; + case DIV_CMD_PRE_PORTA: + arg0=(unsigned char)stream.readC(); + arg1=(arg0&0x40)?1:0; + arg0=(arg0&0x80)?1:0; + break; + case DIV_CMD_HINT_VOL_SLIDE: + arg0=(short)stream.readS(); + break; + case DIV_CMD_SAMPLE_MODE: + case DIV_CMD_SAMPLE_FREQ: + case DIV_CMD_SAMPLE_BANK: + case DIV_CMD_SAMPLE_POS: + case DIV_CMD_SAMPLE_DIR: + case DIV_CMD_FM_HARD_RESET: + case DIV_CMD_FM_LFO: + case DIV_CMD_FM_LFO_WAVE: + case DIV_CMD_FM_FB: + case DIV_CMD_FM_EXTCH: + case DIV_CMD_FM_AM_DEPTH: + case DIV_CMD_FM_PM_DEPTH: + case DIV_CMD_STD_NOISE_FREQ: + case DIV_CMD_STD_NOISE_MODE: + case DIV_CMD_WAVE: + case DIV_CMD_GB_SWEEP_TIME: + case DIV_CMD_GB_SWEEP_DIR: + case DIV_CMD_PCE_LFO_MODE: + case DIV_CMD_PCE_LFO_SPEED: + case DIV_CMD_NES_DMC: + case DIV_CMD_C64_CUTOFF: + case DIV_CMD_C64_RESONANCE: + case DIV_CMD_C64_FILTER_MODE: + case DIV_CMD_C64_RESET_TIME: + case DIV_CMD_C64_RESET_MASK: + case DIV_CMD_C64_FILTER_RESET: + case DIV_CMD_C64_DUTY_RESET: + case DIV_CMD_C64_EXTENDED: + case DIV_CMD_AY_ENVELOPE_SET: + case DIV_CMD_AY_ENVELOPE_LOW: + case DIV_CMD_AY_ENVELOPE_HIGH: + case DIV_CMD_AY_ENVELOPE_SLIDE: + case DIV_CMD_AY_NOISE_MASK_AND: + case DIV_CMD_AY_NOISE_MASK_OR: + case DIV_CMD_AY_AUTO_ENVELOPE: + case DIV_CMD_FDS_MOD_DEPTH: + case DIV_CMD_FDS_MOD_HIGH: + case DIV_CMD_FDS_MOD_LOW: + case DIV_CMD_FDS_MOD_POS: + case DIV_CMD_FDS_MOD_WAVE: + case DIV_CMD_SAA_ENVELOPE: + case DIV_CMD_AMIGA_FILTER: + case DIV_CMD_AMIGA_AM: + case DIV_CMD_AMIGA_PM: + case DIV_CMD_MACRO_OFF: + case DIV_CMD_MACRO_ON: + arg0=(unsigned char)stream.readC(); + break; + case DIV_CMD_FM_TL: + case DIV_CMD_FM_AM: + case DIV_CMD_FM_AR: + case DIV_CMD_FM_DR: + case DIV_CMD_FM_SL: + case DIV_CMD_FM_D2R: + case DIV_CMD_FM_RR: + case DIV_CMD_FM_DT: + case DIV_CMD_FM_DT2: + case DIV_CMD_FM_RS: + case DIV_CMD_FM_KSR: + case DIV_CMD_FM_VIB: + case DIV_CMD_FM_SUS: + case DIV_CMD_FM_WS: + case DIV_CMD_FM_SSG: + case DIV_CMD_FM_REV: + case DIV_CMD_FM_EG_SHIFT: + case DIV_CMD_FM_MULT: + case DIV_CMD_FM_FINE: + case DIV_CMD_AY_IO_WRITE: + case DIV_CMD_AY_AUTO_PWM: + case DIV_CMD_SURROUND_PANNING: + arg0=(unsigned char)stream.readC(); + arg1=(unsigned char)stream.readC(); + break; + case DIV_CMD_C64_FINE_DUTY: + case DIV_CMD_C64_FINE_CUTOFF: + case DIV_CMD_LYNX_LFSR_LOAD: + arg0=(unsigned short)stream.readS(); + break; + case DIV_CMD_FM_FIXFREQ: + arg0=(unsigned short)stream.readS(); + arg1=arg0&0x7ff; + arg0>>=12; + break; + case DIV_CMD_NES_SWEEP: + arg0=(unsigned char)stream.readC(); + arg1=arg0&0x77; + arg0=(arg0&8)?1:0; + break; + } + + switch (command) { + case DIV_CMD_HINT_VOLUME: + chan[i].volume=arg0<<8; + sendVolume=true; + break; + case DIV_CMD_HINT_VOL_SLIDE: + chan[i].volSpeed=arg0; + break; + default: // dispatch it + e->dispatchCmd(DivCommand((DivDispatchCmds)command,i,arg0,arg1)); + break; + } + } + + chan[i].readPos=stream.tell(); + } + + if (sendVolume || chan[i].volSpeed!=0) { + chan[i].volume+=chan[i].volSpeed; + if (chan[i].volume<0) { + chan[i].volume=0; + } + if (chan[i].volume>chan[i].volMax) { + chan[i].volume=chan[i].volMax; + } + + e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } + } + + return ticked; +} + +bool DivCSPlayer::init() { + unsigned char magic[4]; + stream.seek(0,SEEK_SET); + stream.read(magic,4); + + if (memcmp(magic,"FCS",4)!=0) return false; + + unsigned int chans=stream.readI(); + + for (unsigned int i=0; i=DIV_MAX_CHANS) { + stream.readI(); + continue; + } + if ((int)i>=e->getTotalChannelCount()) { + stream.readI(); + continue; + } + chan[i].readPos=stream.readI(); + } + + stream.read(fastDelays,16); + stream.read(fastCmds,16); + + // initialize state + for (int i=0; igetTotalChannelCount(); i++) { + chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff; + chan[i].volume=chan[i].volMax; + } + + return true; +} + +// DivEngine + +bool DivEngine::playStream(unsigned char* f, size_t length) { + BUSY_BEGIN; + cmdStreamInt=new DivCSPlayer(this,f,length); + if (!cmdStreamInt->init()) { + logE("not a command stream!"); + lastError="not a command stream"; + delete[] f; + delete cmdStreamInt; + cmdStreamInt=NULL; + BUSY_END; + return false; + } + + if (!playing) { + reset(); + freelance=true; + playing=true; + } + BUSY_END; + return true; +} diff --git a/src/engine/cmdStream.h b/src/engine/cmdStream.h new file mode 100644 index 000000000..e12b0599e --- /dev/null +++ b/src/engine/cmdStream.h @@ -0,0 +1,59 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _CMD_STREAM_H +#define _CMD_STREAM_H + +#include "defines.h" +#include "safeReader.h" + +class DivEngine; + +struct DivCSChannelState { + unsigned int readPos; + int waitTicks; + + int volume, volMax, volSpeed; + + DivCSChannelState(): + readPos(0), + waitTicks(0), + volume(0x7f00), + volMax(0), + volSpeed(0) {} +}; + +class DivCSPlayer { + DivEngine* e; + unsigned char* b; + SafeReader stream; + DivCSChannelState chan[DIV_MAX_CHANS]; + unsigned char fastDelays[16]; + unsigned char fastCmds[16]; + public: + void cleanup(); + bool tick(); + bool init(); + DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len): + e(en), + b(buf), + stream(buf,len) {} +}; + +#endif diff --git a/src/engine/engine.h b/src/engine/engine.h index 7fc2a110c..0f5c40901 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -26,6 +26,7 @@ #include "export.h" #include "dataErrors.h" #include "safeWriter.h" +#include "cmdStream.h" #include "../audio/taAudio.h" #include "blip_buf.h" #include @@ -405,6 +406,8 @@ class DivEngine { static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS]; static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS]; + DivCSPlayer* cmdStreamInt; + struct SamplePreview { double rate; int sample; @@ -449,7 +452,6 @@ class DivEngine { // MIDI stuff std::function midiCallback=[](const TAMidiMessage&) -> int {return -2;}; - int dispatchCmd(DivCommand c); void processRow(int i, bool afterDelay); void nextOrder(); void nextRow(); @@ -537,6 +539,8 @@ class DivEngine { void createNewFromDefaults(); // load a file. bool load(unsigned char* f, size_t length); + // play a binary command stream. + bool playStream(unsigned char* f, size_t length); // save as .dmf. SafeWriter* saveDMF(unsigned char version); // save as .fur. @@ -567,6 +571,9 @@ class DivEngine { // notify wavetable change void notifyWaveChange(int wave); + // dispatch a command + int dispatchCmd(DivCommand c); + // get system IDs static DivSystem systemFromFileFur(unsigned char val); static unsigned char systemToFileFur(DivSystem val); @@ -1152,6 +1159,7 @@ class DivEngine { audioEngine(DIV_AUDIO_NULL), exportMode(DIV_EXPORT_MODE_ONE), exportFadeOut(0.0), + cmdStreamInt(NULL), midiBaseChan(0), midiPoly(true), midiAgeCounter(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index c58763b40..c77afaee5 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1385,6 +1385,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } + if (subticks==tickMult && cmdStreamInt) { + if (!cmdStreamInt->tick()) { + cmdStreamInt->cleanup(); + delete cmdStreamInt; + cmdStreamInt=NULL; + } + } + + firstTick=false; if (shallStop) { diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 09610bdf8..ae1a1dfce 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -58,6 +58,8 @@ void FurnaceGUI::drawDebug() { ImGui::SameLine(); if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN); + if (ImGui::Button("Play Command Stream")) openFileDialog(GUI_FILE_CMDSTREAM_OPEN); + if (ImGui::Button("Panic")) e->syncReset(); ImGui::SameLine(); if (ImGui::Button("Abort")) { diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 8de859afd..98babf574 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -526,7 +526,21 @@ void FurnaceGUI::drawMobileControls() { ImGui::Separator(); - drawSongInfo(true); + if (ImGui::BeginTabBar("MobileSong")) { + if (ImGui::BeginTabItem("Song Info")) { + drawSongInfo(true); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Subsongs")) { + drawSubSongs(true); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Speed")) { + drawSpeed(true); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } break; } case GUI_SCENE_CHANNELS: diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9258c4d97..7c23114ad 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1849,6 +1849,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_CMDSTREAM_OPEN: + if (!dirExists(workingDirROM)) workingDirROM=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Play Command Stream", + {"command stream", "*.bin", + "all files", "*"}, + "command stream{.bin},.*", + workingDirROM, + dpiScale + ); + break; case GUI_FILE_TEST_OPEN: if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); hasOpened=fileDialog->openLoad( @@ -2103,6 +2114,64 @@ void FurnaceGUI::pushRecentFile(String path) { } } +int FurnaceGUI::loadStream(String path) { + if (!path.empty()) { + logI("loading stream..."); + FILE* f=ps_fopen(path.c_str(),"rb"); + if (f==NULL) { + perror("error"); + lastError=strerror(errno); + return 1; + } + if (fseek(f,0,SEEK_END)<0) { + perror("size error"); + lastError=fmt::sprintf("on seek: %s",strerror(errno)); + fclose(f); + return 1; + } + ssize_t len=ftell(f); + if (len==(SIZE_MAX>>1)) { + perror("could not get file length"); + lastError=fmt::sprintf("on pre tell: %s",strerror(errno)); + fclose(f); + return 1; + } + if (len<1) { + if (len==0) { + logE("that file is empty!"); + lastError="file is empty"; + } else { + perror("tell error"); + lastError=fmt::sprintf("on tell: %s",strerror(errno)); + } + fclose(f); + return 1; + } + if (fseek(f,0,SEEK_SET)<0) { + perror("size error"); + lastError=fmt::sprintf("on get size: %s",strerror(errno)); + fclose(f); + return 1; + } + unsigned char* file=new unsigned char[len]; + if (fread(file,1,(size_t)len,f)!=(size_t)len) { + perror("read error"); + lastError=fmt::sprintf("on read: %s",strerror(errno)); + fclose(f); + delete[] file; + return 1; + } + fclose(f); + if (!e->playStream(file,(size_t)len)) { + lastError=e->getLastError(); + logE("could not open file!"); + return 1; + } + } + return 0; +} + + void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); displayExporting=true; @@ -4241,6 +4310,9 @@ bool FurnaceGUI::loop() { case GUI_FILE_MU5_ROM_OPEN: workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_CMDSTREAM_OPEN: + workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; case GUI_FILE_TEST_OPEN: case GUI_FILE_TEST_OPEN_MULTI: case GUI_FILE_TEST_SAVE: @@ -4706,6 +4778,11 @@ bool FurnaceGUI::loop() { case GUI_FILE_MU5_ROM_OPEN: settings.mu5Path=copyOfName; break; + case GUI_FILE_CMDSTREAM_OPEN: + if (loadStream(copyOfName)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + break; case GUI_FILE_TEST_OPEN: showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC); break; diff --git a/src/gui/gui.h b/src/gui/gui.h index cb2e047b4..7dddb53e7 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -383,6 +383,7 @@ enum FurnaceGUIFileDialogs { GUI_FILE_YRW801_ROM_OPEN, GUI_FILE_TG100_ROM_OPEN, GUI_FILE_MU5_ROM_OPEN, + GUI_FILE_CMDSTREAM_OPEN, GUI_FILE_TEST_OPEN, GUI_FILE_TEST_OPEN_MULTI, @@ -1982,7 +1983,7 @@ class FurnaceGUI { void drawNewSong(); void drawLog(); void drawEffectList(); - void drawSubSongs(); + void drawSubSongs(bool asChild=false); void drawFindReplace(); void drawSpoiler(); void drawClock(); @@ -2074,6 +2075,7 @@ class FurnaceGUI { void openFileDialog(FurnaceGUIFileDialogs type); int save(String path, int dmfVersion); int load(String path); + int loadStream(String path); void pushRecentFile(String path); void exportAudio(String path, DivAudioExportModes mode); diff --git a/src/gui/subSongs.cpp b/src/gui/subSongs.cpp index b6e49e5b2..c0056b723 100644 --- a/src/gui/subSongs.cpp +++ b/src/gui/subSongs.cpp @@ -4,15 +4,18 @@ #include "misc/cpp/imgui_stdlib.h" #include "intConst.h" -void FurnaceGUI::drawSubSongs() { +void FurnaceGUI::drawSubSongs(bool asChild) { if (nextWindow==GUI_WINDOW_SUBSONGS) { subSongsOpen=true; ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } - if (!subSongsOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH)); - if (ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags)) { + if (!subSongsOpen && !asChild) return; + if (!asChild) { + ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH)); + } + bool began=asChild?ImGui::BeginChild("Subsongs"):ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags); + if (began) { char id[1024]; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x); if (e->curSubSong->name.empty()) { @@ -107,12 +110,16 @@ void FurnaceGUI::drawSubSongs() { MARK_MODIFIED; } - if (ImGui::GetContentRegionAvail().y>(10.0f*dpiScale)) { + if (!asChild && ImGui::GetContentRegionAvail().y>(10.0f*dpiScale)) { if (ImGui::InputTextMultiline("##SubSongNotes",&e->curSubSong->notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED; } } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS; - ImGui::End(); + if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS; + if (asChild) { + ImGui::EndChild(); + } else { + ImGui::End(); + } } From 84e13cc91e6d82d1320995e78d69344bd8943c4d Mon Sep 17 00:00:00 2001 From: Plide <89441238+Plide@users.noreply.github.com> Date: Sun, 26 Mar 2023 23:30:55 -0400 Subject: [PATCH 64/64] Some Instruments I made (#1041) * instruments * Delete Single Clap.fui * Delete Slow Strings.fui * Delete Slap Bass.fui * Delete Finger Bass.fui * Delete 4-7 Snare.fui * Add files via upload * Add files via upload * Add files via upload --- instruments/FM/bass/Finger Bass.fui | Bin 0 -> 57 bytes instruments/FM/bass/Slap Bass.fui | Bin 0 -> 61 bytes instruments/FM/drums/4-7 Snare.fui | Bin 0 -> 94 bytes instruments/FM/drums/Single Clap.fui | Bin 0 -> 94 bytes instruments/FM/strings/Slow Strings.fui | Bin 0 -> 82 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 instruments/FM/bass/Finger Bass.fui create mode 100644 instruments/FM/bass/Slap Bass.fui create mode 100644 instruments/FM/drums/4-7 Snare.fui create mode 100644 instruments/FM/drums/Single Clap.fui create mode 100644 instruments/FM/strings/Slow Strings.fui diff --git a/instruments/FM/bass/Finger Bass.fui b/instruments/FM/bass/Finger Bass.fui new file mode 100644 index 0000000000000000000000000000000000000000..9082043cd181eebc793b7a7586e5724d826e7291 GIT binary patch literal 57 zcmZ?s^b4NAz{ud|$jac9SX|8D=BvW+#fX`qfsB3U