diff --git a/TODO.md b/TODO.md index 46773705a..13f31300e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ # to-do for 0.6pre1.5-0.6pre2 -- Game Boy envelope macro/sequence - volume commands should work on Game Boy - ability to customize `OFF`, `===` and `REL` - stereo separation control for AY diff --git a/papers/format.md b/papers/format.md index 263955022..1a8cd7219 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,7 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: -- 105: Furance dev105 +- 106: Furnace dev106 +- 105: Furnace dev105 - 104: Furnace dev104 - 103: Furnace dev103 - 102: Furnace 0.6pre1 (dev102) @@ -846,6 +847,9 @@ size | description | - 2 bytes: nothing | - for loop/loop until release: | - 2 bytes: position + --- | **Game Boy extra flags** (>=106) + 1 | use software envelope + 1 | always init hard env on new note ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 001ea1941..483ebcf0d 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev105" -#define DIV_ENGINE_VERSION 105 +#define DIV_VERSION "dev106" +#define DIV_ENGINE_VERSION 106 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index b1afc3953..555d7d17f 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -539,6 +539,10 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeS(gb.hwSeq[i].data); } + // GB additional flags + w->writeC(gb.softEnv); + w->writeC(gb.alwaysInit); + blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); @@ -1101,6 +1105,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } } + // GB additional flags + if (version>=106) { + gb.softEnv=reader.readC(); + gb.alwaysInit=reader.readC(); + } + return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index eddf9f79e..df7a6b361 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -262,6 +262,7 @@ struct DivInstrumentSTD { struct DivInstrumentGB { unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; + bool softEnv, alwaysInit; enum HWSeqCommands: unsigned char { DIV_GB_HWCMD_ENVELOPE=0, DIV_GB_HWCMD_SWEEP, @@ -281,7 +282,9 @@ struct DivInstrumentGB { envDir(0), envLen(2), soundLen(64), - hwSeqLen(0) { + hwSeqLen(0), + softEnv(false), + alwaysInit(false) { memset(hwSeq,0,256*sizeof(int)); } }; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 2ec9bfdf9..5b7393ef3 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -233,12 +233,61 @@ void DivPlatformGB::tick(bool sysTick) { } } } + // run hardware sequence + if (chan[i].active) { + if (--chan[i].hwSeqDelay<=0) { + chan[i].hwSeqDelay=0; + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB); + int hwSeqCount=0; + while (chan[i].hwSeqPosgb.hwSeqLen && hwSeqCount<4) { + bool leave=false; + unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data; + switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) { + case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE: + chan[i].envLen=data&7; + chan[i].envDir=(data&8)?1:0; + chan[i].envVol=(data>>4)&15; + chan[i].soundLen=data>>8; + chan[i].keyOn=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_SWEEP: + chan[i].sweep=data; + chan[i].sweepChanged=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_WAIT: + chan[i].hwSeqDelay=data+1; + leave=true; + break; + case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL: + if (!chan[i].released) { + chan[i].hwSeqPos--; + leave=true; + } + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP: + chan[i].hwSeqPos=data-1; + break; + case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL: + if (!chan[i].released) { + chan[i].hwSeqPos=data-1; + } + break; + } + + chan[i].hwSeqPos++; + if (leave) break; + hwSeqCount++; + } + } + } + if (chan[i].sweepChanged) { chan[i].sweepChanged=false; if (i==0) { rWrite(16+i*5,chan[i].sweep); } } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==3) { // noise int ntPos=chan[i].baseFreq; @@ -300,6 +349,9 @@ int DivPlatformGB::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; + chan[c.chan].hwSeqPos=0; + chan[c.chan].hwSeqDelay=0; + chan[c.chan].released=false; chan[c.chan].macroInit(ins); if (c.chan==2) { if (chan[c.chan].wave<0) { @@ -308,7 +360,7 @@ int DivPlatformGB::dispatch(DivCommand c) { } ws.init(ins,32,15,chan[c.chan].insChanged); } - if (chan[c.chan].insChanged) { + if (chan[c.chan].insChanged || ins->gb.alwaysInit) { chan[c.chan].envVol=ins->gb.envVol; chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envDir=ins->gb.envDir; @@ -320,11 +372,14 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; + chan[c.chan].hwSeqPos=0; + chan[c.chan].hwSeqDelay=0; chan[c.chan].macroInit(NULL); break; case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: chan[c.chan].std.release(); + chan[c.chan].released=true; break; case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 58cce8805..a4432f1e7 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -29,10 +29,11 @@ class DivPlatformGB: public DivDispatch { struct Channel { int freq, baseFreq, pitch, pitch2, note, ins; unsigned char duty, sweep; - bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released; signed char vol, outVol, wave; unsigned char envVol, envDir, envLen, soundLen; - unsigned short hwSeqPos, hwSeqDelay; + unsigned short hwSeqPos; + short hwSeqDelay; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -54,6 +55,7 @@ class DivPlatformGB: public DivDispatch { keyOn(false), keyOff(false), inPorta(false), + released(false), vol(15), outVol(15), wave(-1), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e0c18b6c1..ce9406ba1 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2965,6 +2965,10 @@ void FurnaceGUI::drawInsEdit() { } } if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { + P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv)); + P(ImGui::Checkbox("Initialize envelope on every note",&ins->gb.alwaysInit)); + + ImGui::BeginDisabled(ins->gb.softEnv); P(CWSliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable P(CWSliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable P(CWSliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable @@ -3060,13 +3064,13 @@ void FurnaceGUI::drawInsEdit() { somethingChanged=true; } - if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER - hwsDir=true; + if (ImGui::RadioButton("Up",!hwsDir)) { PARAMETER + hwsDir=false; somethingChanged=true; } ImGui::SameLine(); - if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER - hwsDir=false; + if (ImGui::RadioButton("Down",hwsDir)) { PARAMETER + hwsDir=true; somethingChanged=true; } @@ -3128,6 +3132,7 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndChild(); + ImGui::EndDisabled(); } ImGui::EndTabItem(); } @@ -3689,8 +3694,10 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) { volMax=127; } - if (ins->type==DIV_INS_GB) { + if (ins->type==DIV_INS_GB && !ins->gb.softEnv) { volMax=0; + } else { + volMax=15; } if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) { volMax=1;