dev185 - SoundUnit: hardware sweep sequences

kinda buggy in note off... may require a rewrite of volume handling
This commit is contained in:
tildearrow 2023-10-21 18:42:37 -05:00
parent 10c72f3a10
commit f718fbb5f5
7 changed files with 406 additions and 12 deletions

View file

@ -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)

View file

@ -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

View file

@ -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; i<su.hwSeqLen; i++) {
w->writeC(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; i<su.hwSeqLen; i++) {
su.hwSeq[i].cmd=reader.readC();
su.hwSeq[i].bound=reader.readC();
su.hwSeq[i].val=reader.readC();
su.hwSeq[i].speed=reader.readS();
}
}
READ_FEAT_END;
}

View file

@ -385,7 +385,7 @@ struct DivInstrumentGB {
DIV_GB_HWCMD_MAX
};
struct HWSeqCommand {
struct HWSeqCommandGB {
unsigned char cmd;
unsigned short data;
} hwSeq[256];
@ -403,7 +403,7 @@ struct DivInstrumentGB {
hwSeqLen(0),
softEnv(false),
alwaysInit(false) {
memset(hwSeq,0,256*sizeof(int));
memset(hwSeq,0,256*sizeof(HWSeqCommandGB));
}
};
@ -663,6 +663,25 @@ struct DivInstrumentWaveSynth {
struct DivInstrumentSoundUnit {
bool switchRoles;
unsigned char hwSeqLen;
enum HWSeqCommands: unsigned char {
DIV_SU_HWCMD_VOL=0,
DIV_SU_HWCMD_PITCH,
DIV_SU_HWCMD_CUT,
DIV_SU_HWCMD_WAIT,
DIV_SU_HWCMD_WAIT_REL,
DIV_SU_HWCMD_LOOP,
DIV_SU_HWCMD_LOOP_REL,
DIV_SU_HWCMD_MAX
};
struct HWSeqCommandSU {
unsigned char cmd;
unsigned char bound;
unsigned char val;
unsigned short speed;
unsigned short padding;
} hwSeq[256];
bool operator==(const DivInstrumentSoundUnit& other);
bool operator!=(const DivInstrumentSoundUnit& other) {
@ -670,7 +689,10 @@ struct DivInstrumentSoundUnit {
}
DivInstrumentSoundUnit():
switchRoles(false) {}
switchRoles(false),
hwSeqLen(0) {
memset(hwSeq,0,256*sizeof(HWSeqCommandSU));
}
};
struct DivInstrumentES5506 {

View file

@ -134,6 +134,79 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
}
writeControlUpper(i);
}
// 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_SU);
int hwSeqCount=0;
while (chan[i].hwSeqPos<ins->su.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) {

View file

@ -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<signed char>(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];

View file

@ -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; i<ins->su.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 (i<ins->su.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; j<ins->su.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 ||