diff --git a/papers/newIns.md b/papers/newIns.md index 557bb7119..34afe3b28 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -556,6 +556,31 @@ size | description size | description -----|------------------------------------ 1 | switch roles of phase reset timer and frequency + 1 | hardware sequence length (>=185) + ??? | hardware sequence... + | - length: 5*hwSeqLen +``` + +a value in the hardware sequence has the following format: + +``` +size | description +-----|------------------------------------ + 1 | command + | - 0: set volume sweep + | - 1: set frequency sweep + | - 2: set cutoff sweep + | - 3: wait + | - 4: wait for release + | - 5: loop + | - 6: loop until release + 1 | sweep bound + 1 | sweep amount/command data + | - if "set sweep", this is amount. + | - for wait: length in ticks + | - for wait for release: nothing + | - for loop/loop until release: position + 2 | sweep period ``` # ES5506 data (ES) diff --git a/src/engine/engine.h b/src/engine/engine.h index c634459e4..394639505 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -54,8 +54,8 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "dev184" -#define DIV_ENGINE_VERSION 184 +#define DIV_VERSION "dev185" +#define DIV_ENGINE_VERSION 185 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index abd06a26a..43bf02299 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -196,7 +196,10 @@ bool DivInstrumentWaveSynth::operator==(const DivInstrumentWaveSynth& other) { } bool DivInstrumentSoundUnit::operator==(const DivInstrumentSoundUnit& other) { - return _C(switchRoles); + return ( + _C(switchRoles) && + _C(hwSeqLen) + ); } bool DivInstrumentES5506::operator==(const DivInstrumentES5506& other) { @@ -691,6 +694,14 @@ void DivInstrument::writeFeatureSU(SafeWriter* w) { w->writeC(su.switchRoles); + w->writeC(su.hwSeqLen); + for (int i=0; iwriteC(su.hwSeq[i].cmd); + w->writeC(su.hwSeq[i].bound); + w->writeC(su.hwSeq[i].val); + w->writeS(su.hwSeq[i].speed); + } + FEATURE_END; } @@ -2536,6 +2547,16 @@ void DivInstrument::readFeatureSU(SafeReader& reader, short version) { su.switchRoles=reader.readC(); + if (version>=185) { + su.hwSeqLen=reader.readC(); + for (int i=0; igetIns(chan[i].ins,DIV_INS_SU); + int hwSeqCount=0; + while (chan[i].hwSeqPossu.hwSeqLen && hwSeqCount<8) { + bool leave=false; + unsigned char bound=ins->su.hwSeq[chan[i].hwSeqPos].bound; + unsigned char val=ins->su.hwSeq[chan[i].hwSeqPos].val; + unsigned short speed=ins->su.hwSeq[chan[i].hwSeqPos].speed; + switch (ins->su.hwSeq[chan[i].hwSeqPos].cmd) { + case DivInstrumentSoundUnit::DIV_SU_HWCMD_VOL: + chan[i].volSweepP=speed; + chan[i].volSweepV=val; + chan[i].volSweepB=bound; + chan[i].volSweep=(val>0); + chWrite(i,0x14,chan[i].volSweepP&0xff); + chWrite(i,0x15,chan[i].volSweepP>>8); + chWrite(i,0x16,chan[i].volSweepV); + chWrite(i,0x17,chan[i].volSweepB); + writeControlUpper(i); + break; + case DivInstrumentSoundUnit::DIV_SU_HWCMD_PITCH: + chan[i].freqSweepP=speed; + chan[i].freqSweepV=val; + chan[i].freqSweepB=bound; + chan[i].freqSweep=(val>0); + chWrite(i,0x10,chan[i].freqSweepP&0xff); + chWrite(i,0x11,chan[i].freqSweepP>>8); + chWrite(i,0x12,chan[i].freqSweepV); + chWrite(i,0x13,chan[i].freqSweepB); + writeControlUpper(i); + break; + case DivInstrumentSoundUnit::DIV_SU_HWCMD_CUT: + chan[i].cutSweepP=speed; + chan[i].cutSweepV=val; + chan[i].cutSweepB=bound; + chan[i].cutSweep=(val>0); + chWrite(i,0x18,chan[i].cutSweepP&0xff); + chWrite(i,0x19,chan[i].cutSweepP>>8); + chWrite(i,0x1a,chan[i].cutSweepV); + chWrite(i,0x1b,chan[i].cutSweepB); + writeControlUpper(i); + break; + case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT: + chan[i].hwSeqDelay=(val+1)*parent->tickMult; + leave=true; + break; + case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT_REL: + if (!chan[i].released) { + chan[i].hwSeqPos--; + leave=true; + } + break; + case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP: + chan[i].hwSeqPos=val-1; + break; + case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP_REL: + if (!chan[i].released) { + chan[i].hwSeqPos=val-1; + } + break; + } + + chan[i].hwSeqPos++; + if (leave) break; + hwSeqCount++; + } + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE); @@ -220,6 +293,9 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; + chan[c.chan].released=false; + chan[c.chan].hwSeqPos=0; + chan[c.chan].hwSeqDelay=0; chWrite(c.chan,0x02,chan[c.chan].vol); chan[c.chan].macroInit(ins); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { @@ -231,11 +307,14 @@ int DivPlatformSoundUnit::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/su.h b/src/engine/platform/su.h index 4f227e5b5..7062cbc8b 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -30,12 +30,14 @@ class DivPlatformSoundUnit: public DivDispatch { signed char pan; unsigned char duty; bool noise, pcm, phaseReset, filterPhaseReset, switchRoles; - bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep; + bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep, released; unsigned short freqSweepP, volSweepP, cutSweepP; unsigned char freqSweepB, volSweepB, cutSweepB; unsigned char freqSweepV, volSweepV, cutSweepV; unsigned short syncTimer; signed short wave; + unsigned short hwSeqPos; + short hwSeqDelay; Channel(): SharedChannel(127), cutoff(16383), @@ -56,6 +58,7 @@ class DivPlatformSoundUnit: public DivDispatch { freqSweep(false), volSweep(false), cutSweep(false), + released(false), freqSweepP(0), volSweepP(0), cutSweepP(0), @@ -66,7 +69,9 @@ class DivPlatformSoundUnit: public DivDispatch { volSweepV(0), cutSweepV(0), syncTimer(0), - wave(0) {} + wave(0), + hwSeqPos(0), + hwSeqDelay(0) {} }; Channel chan[8]; DivDispatchOscBuffer* oscBuf[8]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 1a18a0e2e..731211722 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -343,6 +343,16 @@ const char* gbHWSeqCmdTypes[6]={ "Loop until Release" }; +const char* suHWSeqCmdTypes[7]={ + "Volume Sweep", + "Frequency Sweep", + "Cutoff Sweep", + "Wait", + "Wait for Release", + "Loop", + "Loop until Release" +}; + const char* snesGainModes[5]={ "Direct", "Decrease (linear)", @@ -2310,7 +2320,6 @@ void FurnaceGUI::alterSampleMap(int column, int val) { void FurnaceGUI::insTabSample(DivInstrument* ins) { const char* sampleTabName="Sample"; - if (ins->type==DIV_INS_SU) sampleTabName="Sound Unit"; if (ins->type==DIV_INS_NES) sampleTabName="DPCM"; if (ImGui::BeginTabItem(sampleTabName)) { if (ins->type==DIV_INS_NES && e->song.oldDPCM) { @@ -2339,9 +2348,6 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) { ins->type==DIV_INS_VRC6 || ins->type==DIV_INS_SU) { P(ImGui::Checkbox("Use sample",&ins->amiga.useSample)); - if (ins->type==DIV_INS_SU) { - P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles)); - } if (ins->type==DIV_INS_X1_010) { if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER if (ins->x1_010.bankSlot<0) ins->x1_010.bankSlot=0; @@ -5013,6 +5019,242 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Don't test before new note",&ins->c64.noTest)); ImGui::EndTabItem(); } + if (ins->type==DIV_INS_SU) if (ImGui::BeginTabItem("Sound Unit")) { + P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles)); + if (ImGui::BeginChild("HWSeqSU",ImGui::GetContentRegionAvail(),true,ImGuiWindowFlags_MenuBar)) { + ImGui::BeginMenuBar(); + ImGui::Text("Hardware Sequence"); + ImGui::EndMenuBar(); + + if (ins->su.hwSeqLen>0) if (ImGui::BeginTable("HWSeqListSU",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + int curFrame=0; + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Tick"); + ImGui::TableNextColumn(); + ImGui::Text("Command"); + ImGui::TableNextColumn(); + ImGui::Text("Move/Remove"); + for (int i=0; isu.hwSeqLen; i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d (#%d)",curFrame,i); + ImGui::TableNextColumn(); + ImGui::PushID(i); + if (ins->su.hwSeq[i].cmd>=DivInstrumentSoundUnit::DIV_SU_HWCMD_MAX) { + ins->su.hwSeq[i].cmd=0; + } + int cmd=ins->su.hwSeq[i].cmd; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##HWSeqCmd",&cmd,suHWSeqCmdTypes,DivInstrumentSoundUnit::DIV_SU_HWCMD_MAX)) { + if (ins->su.hwSeq[i].cmd!=cmd) { + ins->su.hwSeq[i].cmd=cmd; + ins->su.hwSeq[i].val=0; + ins->su.hwSeq[i].bound=0; + ins->su.hwSeq[i].speed=0; + } + } + bool somethingChanged=false; + switch (ins->su.hwSeq[i].cmd) { + case DivInstrumentSoundUnit::DIV_SU_HWCMD_VOL: { + int swPeriod=ins->su.hwSeq[i].speed; + int swBound=ins->su.hwSeq[i].bound; + int swVal=ins->su.hwSeq[i].val&31; + bool swDir=ins->su.hwSeq[i].val&32; + bool swLoop=ins->su.hwSeq[i].val&64; + bool swInvert=ins->su.hwSeq[i].val&128; + + if (ImGui::InputInt("Period",&swPeriod,1,16)) { + if (swPeriod<0) swPeriod=0; + if (swPeriod>65535) swPeriod=65535; + somethingChanged=true; + } + if (CWSliderInt("Amount",&swVal,0,31)) { + somethingChanged=true; + } + if (CWSliderInt("Bound",&swBound,0,255)) { + somethingChanged=true; + } + if (ImGui::RadioButton("Up",swDir)) { PARAMETER + swDir=true; + somethingChanged=true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Down",!swDir)) { PARAMETER + swDir=false; + somethingChanged=true; + } + if (ImGui::Checkbox("Loop",&swLoop)) { PARAMETER + somethingChanged=true; + } + ImGui::SameLine(); + if (ImGui::Checkbox("Flip",&swInvert)) { PARAMETER + somethingChanged=true; + } + + if (somethingChanged) { + ins->su.hwSeq[i].speed=swPeriod; + ins->su.hwSeq[i].bound=swBound; + ins->su.hwSeq[i].val=(swVal&31)|(swDir?32:0)|(swLoop?64:0)|(swInvert?128:0); + PARAMETER; + } + break; + } + case DivInstrumentSoundUnit::DIV_SU_HWCMD_PITCH: + case DivInstrumentSoundUnit::DIV_SU_HWCMD_CUT: { + int swPeriod=ins->su.hwSeq[i].speed; + int swBound=ins->su.hwSeq[i].bound; + int swVal=ins->su.hwSeq[i].val&127; + bool swDir=ins->su.hwSeq[i].val&128; + + if (ImGui::InputInt("Period",&swPeriod,1,16)) { + if (swPeriod<0) swPeriod=0; + if (swPeriod>65535) swPeriod=65535; + somethingChanged=true; + } + if (CWSliderInt("Amount",&swVal,0,31)) { + somethingChanged=true; + } + if (CWSliderInt("Bound",&swBound,0,255)) { + somethingChanged=true; + } + if (ImGui::RadioButton("Up",swDir)) { PARAMETER + swDir=true; + somethingChanged=true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Down",!swDir)) { PARAMETER + swDir=false; + somethingChanged=true; + } + + if (somethingChanged) { + ins->su.hwSeq[i].speed=swPeriod; + ins->su.hwSeq[i].bound=swBound; + ins->su.hwSeq[i].val=(swVal&127)|(swDir?128:0); + PARAMETER; + } + break; + } + case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT: { + int len=ins->su.hwSeq[i].val+1; + curFrame+=ins->su.hwSeq[i].val+1; + + if (ImGui::InputInt("Ticks",&len)) { + if (len<1) len=1; + if (len>255) len=256; + somethingChanged=true; + } + + if (somethingChanged) { + ins->su.hwSeq[i].val=len-1; + PARAMETER; + } + break; + } + case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT_REL: + curFrame++; + break; + case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP: + case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP_REL: { + int pos=ins->su.hwSeq[i].val; + + if (ImGui::InputInt("Position",&pos)) { + if (pos<0) pos=0; + if (pos>(ins->su.hwSeqLen-1)) pos=(ins->su.hwSeqLen-1); + somethingChanged=true; + } + + if (somethingChanged) { + ins->su.hwSeq[i].val=pos; + PARAMETER; + } + break; + } + default: + break; + } + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID(i+512); + if (ImGui::Button(ICON_FA_CHEVRON_UP "##HWCmdUp")) { + if (i>0) { + e->lockEngine([ins,i]() { + ins->su.hwSeq[i-1].cmd^=ins->su.hwSeq[i].cmd; + ins->su.hwSeq[i].cmd^=ins->su.hwSeq[i-1].cmd; + ins->su.hwSeq[i-1].cmd^=ins->su.hwSeq[i].cmd; + + ins->su.hwSeq[i-1].speed^=ins->su.hwSeq[i].speed; + ins->su.hwSeq[i].speed^=ins->su.hwSeq[i-1].speed; + ins->su.hwSeq[i-1].speed^=ins->su.hwSeq[i].speed; + + ins->su.hwSeq[i-1].val^=ins->su.hwSeq[i].val; + ins->su.hwSeq[i].val^=ins->su.hwSeq[i-1].val; + ins->su.hwSeq[i-1].val^=ins->su.hwSeq[i].val; + + ins->su.hwSeq[i-1].bound^=ins->su.hwSeq[i].bound; + ins->su.hwSeq[i].bound^=ins->su.hwSeq[i-1].bound; + ins->su.hwSeq[i-1].bound^=ins->su.hwSeq[i].bound; + }); + } + MARK_MODIFIED; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_CHEVRON_DOWN "##HWCmdDown")) { + if (isu.hwSeqLen-1) { + e->lockEngine([ins,i]() { + ins->su.hwSeq[i+1].cmd^=ins->su.hwSeq[i].cmd; + ins->su.hwSeq[i].cmd^=ins->su.hwSeq[i+1].cmd; + ins->su.hwSeq[i+1].cmd^=ins->su.hwSeq[i].cmd; + + ins->su.hwSeq[i+1].speed^=ins->su.hwSeq[i].speed; + ins->su.hwSeq[i].speed^=ins->su.hwSeq[i+1].speed; + ins->su.hwSeq[i+1].speed^=ins->su.hwSeq[i].speed; + + ins->su.hwSeq[i+1].val^=ins->su.hwSeq[i].val; + ins->su.hwSeq[i].val^=ins->su.hwSeq[i+1].val; + ins->su.hwSeq[i+1].val^=ins->su.hwSeq[i].val; + + ins->su.hwSeq[i+1].bound^=ins->su.hwSeq[i].bound; + ins->su.hwSeq[i].bound^=ins->su.hwSeq[i+1].bound; + ins->su.hwSeq[i+1].bound^=ins->su.hwSeq[i].bound; + }); + } + MARK_MODIFIED; + } + ImGui::SameLine(); + pushDestColor(); + if (ImGui::Button(ICON_FA_TIMES "##HWCmdDel")) { + for (int j=i; jsu.hwSeqLen-1; j++) { + ins->su.hwSeq[j].cmd=ins->su.hwSeq[j+1].cmd; + ins->su.hwSeq[j].speed=ins->su.hwSeq[j+1].speed; + ins->su.hwSeq[j].val=ins->su.hwSeq[j+1].val; + ins->su.hwSeq[j].bound=ins->su.hwSeq[j+1].bound; + } + ins->su.hwSeqLen--; + } + popDestColor(); + ImGui::PopID(); + } + ImGui::EndTable(); + } + + if (ImGui::Button(ICON_FA_PLUS "##HWCmdAdd")) { + if (ins->su.hwSeqLen<255) { + ins->su.hwSeq[ins->su.hwSeqLen].cmd=0; + ins->su.hwSeq[ins->su.hwSeqLen].speed=0; + ins->su.hwSeq[ins->su.hwSeqLen].val=0; + ins->su.hwSeq[ins->su.hwSeqLen].bound=0; + ins->su.hwSeqLen++; + } + } + } + ImGui::EndChild(); + ImGui::EndTabItem(); + } if (ins->type==DIV_INS_MSM6258 || ins->type==DIV_INS_MSM6295 || ins->type==DIV_INS_ADPCMA ||