diff --git a/papers/format.md b/papers/format.md index 72122e0ac..a2c962fbf 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 131: Furnace dev131 - 130: Furnace dev130 - 129: Furnace dev129 - 128: Furnace dev128 diff --git a/papers/newIns.md b/papers/newIns.md index 548edbdeb..31dff219c 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -440,7 +440,7 @@ size | description | - bit 0-4: release 1 | flags | - bit 4: envelope on - | - bit 3: make sustain effective + | - bit 3: make sustain effective (<131) | - bit 0-2: gain mode | - 0: direct | - 4: dec @@ -448,6 +448,13 @@ size | description | - 6: inc | - 7: bent 1 | gain + 1 | decay 2/sustain mode (>=131) + | - bit 5-6: sustain mode + | - 0: direct + | - 1: sustain (release with dec) + | - 2: sustain (release with exp) + | - 3: sustain (release with rel) + | - bit 0-4: decay 2 ``` # Namco 163 data (N1) diff --git a/src/engine/engine.h b/src/engine/engine.h index f57bd8d0c..109a9abd1 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -47,8 +47,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev130" -#define DIV_ENGINE_VERSION 130 +#define DIV_VERSION "dev131" +#define DIV_ENGINE_VERSION 131 // 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 384e0b780..d30e1fb9c 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -506,6 +506,8 @@ void DivInstrument::writeFeatureSN(SafeWriter* w) { w->writeC(snes.gain); + w->writeC(((snes.sus&3)<<5)|(snes.d2&31)); + FEATURE_END; } @@ -1809,7 +1811,7 @@ void DivInstrument::putInsData(SafeWriter* w) { #define READ_FEAT_END \ if (reader.tell()=131) { + next=reader.readC(); + snes.sus=(next>>5)&3; + snes.d2=next&31; + } READ_FEAT_END; } -void DivInstrument::readFeatureN1(SafeReader& reader) { +void DivInstrument::readFeatureN1(SafeReader& reader, short version) { READ_FEAT_BEGIN; n163.wave=reader.readI(); @@ -2260,7 +2267,7 @@ void DivInstrument::readFeatureN1(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureFD(SafeReader& reader) { +void DivInstrument::readFeatureFD(SafeReader& reader, short version) { READ_FEAT_BEGIN; fds.modSpeed=reader.readI(); @@ -2271,7 +2278,7 @@ void DivInstrument::readFeatureFD(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureWS(SafeReader& reader) { +void DivInstrument::readFeatureWS(SafeReader& reader, short version) { READ_FEAT_BEGIN; ws.wave1=reader.readI(); @@ -2406,7 +2413,7 @@ void DivInstrument::readFeatureWL(SafeReader& reader, DivSong* song, short versi READ_FEAT_END; } -void DivInstrument::readFeatureMP(SafeReader& reader) { +void DivInstrument::readFeatureMP(SafeReader& reader, short version) { READ_FEAT_BEGIN; multipcm.ar=reader.readC(); @@ -2422,7 +2429,7 @@ void DivInstrument::readFeatureMP(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureSU(SafeReader& reader) { +void DivInstrument::readFeatureSU(SafeReader& reader, short version) { READ_FEAT_BEGIN; su.switchRoles=reader.readC(); @@ -2430,7 +2437,7 @@ void DivInstrument::readFeatureSU(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureES(SafeReader& reader) { +void DivInstrument::readFeatureES(SafeReader& reader, short version) { READ_FEAT_BEGIN; es5506.filter.mode=(DivInstrumentES5506::Filter::FilterMode)reader.readC(); @@ -2447,7 +2454,7 @@ void DivInstrument::readFeatureES(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureX1(SafeReader& reader) { +void DivInstrument::readFeatureX1(SafeReader& reader, short version) { READ_FEAT_BEGIN; x1_010.bankSlot=reader.readI(); @@ -2479,47 +2486,47 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b if (memcmp(featCode,"EN",2)==0) { // end of instrument break; } else if (memcmp(featCode,"NA",2)==0) { // name - readFeatureNA(reader); + readFeatureNA(reader,version); } else if (memcmp(featCode,"FM",2)==0) { // FM - readFeatureFM(reader); + readFeatureFM(reader,version); } else if (memcmp(featCode,"MA",2)==0) { // macros - readFeatureMA(reader); + readFeatureMA(reader,version); } else if (memcmp(featCode,"64",2)==0) { // C64 - readFeature64(reader); + readFeature64(reader,version); } else if (memcmp(featCode,"GB",2)==0) { // Game Boy - readFeatureGB(reader); + readFeatureGB(reader,version); } else if (memcmp(featCode,"SM",2)==0) { // sample - readFeatureSM(reader); + readFeatureSM(reader,version); } else if (memcmp(featCode,"O1",2)==0) { // op1 macros - readFeatureOx(reader,0); + readFeatureOx(reader,0,version); } else if (memcmp(featCode,"O2",2)==0) { // op2 macros - readFeatureOx(reader,1); + readFeatureOx(reader,1,version); } else if (memcmp(featCode,"O3",2)==0) { // op3 macros - readFeatureOx(reader,2); + readFeatureOx(reader,2,version); } else if (memcmp(featCode,"O4",2)==0) { // op4 macros - readFeatureOx(reader,3); + readFeatureOx(reader,3,version); } else if (memcmp(featCode,"LD",2)==0) { // OPL drums - readFeatureLD(reader); + readFeatureLD(reader,version); } else if (memcmp(featCode,"SN",2)==0) { // SNES - readFeatureSN(reader); + readFeatureSN(reader,version); } else if (memcmp(featCode,"N1",2)==0) { // Namco 163 - readFeatureN1(reader); + readFeatureN1(reader,version); } else if (memcmp(featCode,"FD",2)==0) { // FDS/VB - readFeatureFD(reader); + readFeatureFD(reader,version); } else if (memcmp(featCode,"WS",2)==0) { // WaveSynth - readFeatureWS(reader); + readFeatureWS(reader,version); } else if (memcmp(featCode,"SL",2)==0 && fui && song!=NULL) { // sample list readFeatureSL(reader,song,version); } else if (memcmp(featCode,"WL",2)==0 && fui && song!=NULL) { // wave list readFeatureWL(reader,song,version); } else if (memcmp(featCode,"MP",2)==0) { // MultiPCM - readFeatureMP(reader); + readFeatureMP(reader,version); } else if (memcmp(featCode,"SU",2)==0) { // Sound Unit - readFeatureSU(reader); + readFeatureSU(reader,version); } else if (memcmp(featCode,"ES",2)==0) { // ES5506 - readFeatureES(reader); + readFeatureES(reader,version); } else if (memcmp(featCode,"X1",2)==0) { // X1-010 - readFeatureX1(reader); + readFeatureX1(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing @@ -3134,7 +3141,7 @@ DivDataErrors DivInstrument::readInsDataOld(SafeReader &reader, short version) { snes.a=reader.readC(); snes.d=reader.readC(); snes.s=reader.readC(); - snes.sus=snes.s&8; + snes.sus=(snes.s&8)?1:0; snes.s&=7; snes.r=reader.readC(); } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 65a81f8b6..23c353971 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -604,10 +604,15 @@ struct DivInstrumentSNES { GAIN_MODE_INC_LINEAR=6, GAIN_MODE_INC_INVLOG=7 }; - bool useEnv, sus; + bool useEnv; + // 0: no sustain (key off = cut) + // 1: sustain (R = d2; key off = dec linear to r) + // 2: sustain (R = d2; key off = dec exponential to r) + // 3: sustain (R = d2; key off = R to r) + unsigned char sus; GainMode gainMode; unsigned char gain; - unsigned char a, d, s, r; + unsigned char a, d, s, r, d2; bool operator==(const DivInstrumentSNES& other); bool operator!=(const DivInstrumentSNES& other) { @@ -616,13 +621,14 @@ struct DivInstrumentSNES { DivInstrumentSNES(): useEnv(true), - sus(false), + sus(0), gainMode(GAIN_MODE_DIRECT), gain(127), a(15), d(7), s(7), - r(0) {} + r(0), + d2(0) {} }; struct DivInstrument { @@ -665,24 +671,24 @@ struct DivInstrument { void writeFeatureES(SafeWriter* w); void writeFeatureX1(SafeWriter* w); - void readFeatureNA(SafeReader& reader); - void readFeatureFM(SafeReader& reader); - void readFeatureMA(SafeReader& reader); - void readFeature64(SafeReader& reader); - void readFeatureGB(SafeReader& reader); - void readFeatureSM(SafeReader& reader); - void readFeatureOx(SafeReader& reader, int op); - void readFeatureLD(SafeReader& reader); - void readFeatureSN(SafeReader& reader); - void readFeatureN1(SafeReader& reader); - void readFeatureFD(SafeReader& reader); - void readFeatureWS(SafeReader& reader); + void readFeatureNA(SafeReader& reader, short version); + void readFeatureFM(SafeReader& reader, short version); + void readFeatureMA(SafeReader& reader, short version); + void readFeature64(SafeReader& reader, short version); + void readFeatureGB(SafeReader& reader, short version); + void readFeatureSM(SafeReader& reader, short version); + void readFeatureOx(SafeReader& reader, int op, short version); + void readFeatureLD(SafeReader& reader, short version); + void readFeatureSN(SafeReader& reader, short version); + void readFeatureN1(SafeReader& reader, short version); + void readFeatureFD(SafeReader& reader, short version); + void readFeatureWS(SafeReader& reader, short version); void readFeatureSL(SafeReader& reader, DivSong* song, short version); void readFeatureWL(SafeReader& reader, DivSong* song, short version); - void readFeatureMP(SafeReader& reader); - void readFeatureSU(SafeReader& reader); - void readFeatureES(SafeReader& reader); - void readFeatureX1(SafeReader& reader); + void readFeatureMP(SafeReader& reader, short version); + void readFeatureSU(SafeReader& reader, short version); + void readFeatureES(SafeReader& reader, short version); + void readFeatureX1(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 20b9a52b4..8752c08b2 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -607,10 +607,23 @@ void DivPlatformSNES::writeEnv(int ch) { if (chan[ch].state.sus) { if (chan[ch].active) { chWrite(ch,5,chan[ch].state.a|(chan[ch].state.d<<4)|0x80); - chWrite(ch,6,chan[ch].state.s<<5); - } else { // dec linear - chWrite(ch,7,0x80|chan[ch].state.r); - chWrite(ch,5,0); + chWrite(ch,6,(chan[ch].state.s<<5)|(chan[ch].state.d2&31)); + } else { + switch (chan[ch].state.sus) { + case 1: // dec linear + chWrite(ch,7,0x80|chan[ch].state.r); + chWrite(ch,5,0); + break; + case 2: // dec exp + chWrite(ch,7,0xa0|chan[ch].state.r); + chWrite(ch,5,0); + break; + case 3: // update r + chWrite(ch,6,(chan[ch].state.s<<5)|(chan[ch].state.r&31)); + break; + default: // what? + break; + } } } else { chWrite(ch,5,chan[ch].state.a|(chan[ch].state.d<<4)|0x80); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index b5e1cf711..6aae392d0 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4601,10 +4601,13 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv)); ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ins->snes.useEnv) { - if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) { + if (ImGui::BeginTable("SNESEnvParams",ins->snes.sus?6:5,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + if (ins->snes.sus) { + ImGui::TableSetupColumn("c2x",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + } ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch); @@ -4618,6 +4621,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT("S"); ImGui::TextUnformatted("S"); + if (ins->snes.sus) { + ImGui::TableNextColumn(); + CENTER_TEXT("D2"); + ImGui::TextUnformatted("D2"); + } ImGui::TableNextColumn(); CENTER_TEXT("R"); ImGui::TextUnformatted("R"); @@ -4632,14 +4640,30 @@ void FurnaceGUI::drawInsEdit() { P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); ImGui::TableNextColumn(); P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); + if (ins->snes.sus) { + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); + } ImGui::TableNextColumn(); P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); ImGui::TableNextColumn(); - drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.r,ins->snes.r,(14-ins->snes.s*2),(ins->snes.r==0 || ins->snes.sus),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.sus?ins->snes.d2:ins->snes.r,ins->snes.sus?ins->snes.r:31,(14-ins->snes.s*2),(ins->snes.r==0 || (ins->snes.sus && ins->snes.d2==0)),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); ImGui::EndTable(); } - ImGui::Checkbox("Make sustain effective",&ins->snes.sus); + ImGui::Text("Sustain/release mode:"); + if (ImGui::RadioButton("Direct (cut on release)",ins->snes.sus==0)) { + ins->snes.sus=0; + } + if (ImGui::RadioButton("Effective (linear decrease)",ins->snes.sus==1)) { + ins->snes.sus=1; + } + if (ImGui::RadioButton("Effective (exponential decrease)",ins->snes.sus==2)) { + ins->snes.sus=2; + } + if (ImGui::RadioButton("Delayed (write R on release)",ins->snes.sus==3)) { + ins->snes.sus=3; + } } else { if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);