diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index d8c77030f..40542ef98 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -215,6 +215,10 @@ bool DivCSPlayer::tick() { case DIV_CMD_HINT_VOL_SLIDE: arg0=(short)stream.readS(); break; + case DIV_CMD_HINT_VOL_SLIDE_TARGET: + arg0=(short)stream.readS(); + arg1=(short)stream.readS(); + break; case DIV_CMD_HINT_LEGATO: arg0=(unsigned char)stream.readC(); if (arg0==0xff) { @@ -321,6 +325,11 @@ bool DivCSPlayer::tick() { break; case DIV_CMD_HINT_VOL_SLIDE: chan[i].volSpeed=arg0; + chan[i].volSpeedTarget=-1; + break; + case DIV_CMD_HINT_VOL_SLIDE_TARGET: + chan[i].volSpeed=arg0; + chan[i].volSpeedTarget=arg0==0 ? -1 : arg1; break; case DIV_CMD_HINT_PITCH: chan[i].pitch=arg0; @@ -356,13 +365,29 @@ bool DivCSPlayer::tick() { if (sendVolume || chan[i].volSpeed!=0) { chan[i].volume+=chan[i].volSpeed; + if (chan[i].volSpeedTarget!=-1) { + bool atTarget=false; + if (chan[i].volSpeed>0) { + atTarget=(chan[i].volume>=chan[i].volSpeedTarget); + } else if (chan[i].volSpeed<0) { + atTarget=(chan[i].volume<=chan[i].volSpeedTarget); + } else { + atTarget=true; + chan[i].volSpeedTarget=chan[i].volume; + } + + if (atTarget) { + chan[i].volume=chan[i].volSpeedTarget; + chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; + } + } 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)); } diff --git a/src/engine/cmdStream.h b/src/engine/cmdStream.h index 4704b41b9..2d8e6e97e 100644 --- a/src/engine/cmdStream.h +++ b/src/engine/cmdStream.h @@ -34,7 +34,7 @@ struct DivCSChannelState { int lastWaitLen; int note, pitch; - int volume, volMax, volSpeed; + int volume, volMax, volSpeed, volSpeedTarget; int vibratoDepth, vibratoRate, vibratoPos; int portaTarget, portaSpeed; unsigned char arp, arpStage, arpTicks; @@ -56,6 +56,7 @@ struct DivCSChannelState { volume(0x7f00), volMax(0), volSpeed(0), + volSpeedTarget(-1), vibratoDepth(0), vibratoRate(0), vibratoPos(0), diff --git a/src/engine/cmdStreamOps.cpp b/src/engine/cmdStreamOps.cpp index 0710e650e..1f3d81ee2 100644 --- a/src/engine/cmdStreamOps.cpp +++ b/src/engine/cmdStreamOps.cpp @@ -59,6 +59,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_HINT_VOLUME: case DIV_CMD_HINT_PORTA: case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_HINT_VOL_SLIDE_TARGET: case DIV_CMD_HINT_LEGATO: w->writeC((unsigned char)c.cmd+0xb4); break; @@ -100,6 +101,10 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_HINT_VOL_SLIDE: w->writeS(c.value); break; + case DIV_CMD_HINT_VOL_SLIDE_TARGET: + w->writeS(c.value); + w->writeS(c.value2); + break; case DIV_CMD_SAMPLE_MODE: case DIV_CMD_SAMPLE_FREQ: case DIV_CMD_SAMPLE_BANK: diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index e077d8f4d..517adc662 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -67,6 +67,7 @@ enum DivDispatchCmds { DIV_CMD_HINT_ARPEGGIO, // (note1, note2) DIV_CMD_HINT_VOLUME, // (vol) DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick) + DIV_CMD_HINT_VOL_SLIDE_TARGET, // (amount, target) DIV_CMD_HINT_PORTA, // (target, speed) DIV_CMD_HINT_LEGATO, // (note) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index fbbf0edc8..18189a29b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -98,6 +98,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: return _("Cxxx: Set tick rate (hz)"); + case 0xd3: + return _("D3xx: Volume portamento"); + case 0xd4: + return _("D4xx: Volume portamento (fast)"); case 0xdc: return _("DCxx: Delayed mute"); case 0xe0: diff --git a/src/engine/engine.h b/src/engine/engine.h index 0720a3e8d..3e942e8a6 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -133,7 +133,7 @@ struct DivAudioExportOptions { struct DivChannelState { std::vector delayed; int note, oldNote, lastIns, pitch, portaSpeed, portaNote; - int volume, volSpeed, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax; + int volume, volSpeed, volSpeedTarget, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; @@ -157,6 +157,7 @@ struct DivChannelState { portaNote(-1), volume(0x7f00), volSpeed(0), + volSpeedTarget(-1), cut(-1), volCut(-1), legatoDelay(-1), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 3b3234934..296183f8c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -67,6 +67,7 @@ const char* cmdName[]={ "HINT_ARPEGGIO", "HINT_VOLUME", "HINT_VOL_SLIDE", + "HINT_VOL_SLIDE_TARGET", "HINT_PORTA", "HINT_LEGATO", @@ -639,7 +640,23 @@ void DivEngine::processRow(int i, bool afterDelay) { } // volume - if (pat->data[whatRow][3]!=-1) { + int volPortaTarget=-1; + bool noApplyVolume=false; + for (int j=0; jdata[whatRow][4+(j<<1)]; + if (effect==0xd3 || effect==0xd4) { // vol porta + volPortaTarget=pat->data[whatRow][3]<<8; // can be -256 + + short effectVal=pat->data[whatRow][5+(j<<1)]; + if (effectVal==-1) effectVal=0; + effectVal&=255; + + noApplyVolume=effectVal>0; // "D3.." or "D300" shouldn't stop volume from applying + break; // technically you could have both D3 and D4... let's not care + } + } + + if (pat->data[whatRow][3]!=-1 && !noApplyVolume) { if (!song.oldAlwaysSetVolume || disCont[dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->data[whatRow][3]) { if (pat->data[whatRow][0]==0 && pat->data[whatRow][1]==0) { chan[i].midiAftertouch=true; @@ -828,6 +845,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x06: // vol slide + porta @@ -869,6 +887,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x07: // tremolo @@ -879,6 +898,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].tremoloRate=effectVal>>4; if (chan[i].tremoloDepth!=0) { chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; } else { dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); @@ -898,6 +918,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x00: // arpeggio @@ -940,6 +961,22 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].cutType=0; } break; + case 0xd3: // volume portamento (vol porta) + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? effectVal : -effectVal; + chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget)); + break; + case 0xd4: // volume portamento fast (vol porta fast) + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? 256*effectVal : -256*effectVal; + chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget)); + break; case 0xe0: // arp speed if (effectVal>0) { curSubSong->arpLen=effectVal; @@ -1077,6 +1114,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].tremoloDepth=0; chan[i].tremoloRate=0; chan[i].volSpeed=effectVal; + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xf4: // fine volume ramp down @@ -1084,6 +1122,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].tremoloDepth=0; chan[i].tremoloRate=0; chan[i].volSpeed=-effectVal; + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xf5: // disable macro @@ -1097,12 +1136,14 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xf8: // single volume ramp up chan[i].volSpeed=0; // add compat flag? + chan[i].volSpeedTarget=-1; chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); break; case 0xf9: // single volume ramp down chan[i].volSpeed=0; // add compat flag? + chan[i].volSpeedTarget=-1; chan[i].volume=MAX(chan[i].volume-effectVal*256,0); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); @@ -1120,9 +1161,9 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; - case 0xfc: // delayed note release if (song.delayBehavior==2 || effectVal0) { + atTarget=(chan[i].volume>=chan[i].volSpeedTarget); + } else if (chan[i].volSpeed<0) { + atTarget=(chan[i].volume<=chan[i].volSpeedTarget); + } else { + atTarget=true; + chan[i].volSpeedTarget=chan[i].volume; + } + + if (atTarget) { + chan[i].volume=chan[i].volSpeedTarget; + chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); + } + } if (chan[i].volume>chan[i].volMax) { chan[i].volume=chan[i].volMax; chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); @@ -1604,6 +1666,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } else { chan[i].volume=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } else { diff --git a/src/gui/csPlayer.cpp b/src/gui/csPlayer.cpp index 6bbae43ce..7be1f4e1d 100644 --- a/src/gui/csPlayer.cpp +++ b/src/gui/csPlayer.cpp @@ -161,6 +161,8 @@ void FurnaceGUI::drawCSPlayer() { ImGui::TableNextColumn(); ImGui::Text(_("vols")); ImGui::TableNextColumn(); + ImGui::Text(_("volst")); + ImGui::TableNextColumn(); ImGui::Text(_("vib")); ImGui::TableNextColumn(); ImGui::Text(_("porta")); @@ -189,6 +191,8 @@ void FurnaceGUI::drawCSPlayer() { ImGui::TableNextColumn(); ImGui::Text("%+d",state->volSpeed); ImGui::TableNextColumn(); + ImGui::Text("%+d",state->volSpeedTarget); + ImGui::TableNextColumn(); ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos); ImGui::TableNextColumn(); ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index b08c60532..b28126fc5 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -145,6 +145,7 @@ void FurnaceGUI::drawDebug() { ImGui::Text("- portaNote = %d",ch->portaNote); ImGui::Text("- volume = %.4x",ch->volume); ImGui::Text("- volSpeed = %d",ch->volSpeed); + ImGui::Text("- volSpeedTarget = %d",ch->volSpeedTarget); ImGui::Text("- cut = %d",ch->cut); ImGui::Text("- rowDelay = %d",ch->rowDelay); ImGui::Text("- volMax = %.4x",ch->volMax); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 8fd662824..fbfdadc0c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -473,8 +473,8 @@ const FurnaceGUIColors fxColors[256]={ GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, - GUI_COLOR_PATTERN_EFFECT_INVALID, - GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_VOLUME, + GUI_COLOR_PATTERN_EFFECT_VOLUME, GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index a2568bd31..c32171161 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -1500,6 +1500,7 @@ void FurnaceGUI::drawPattern() { i.cmd==DIV_CMD_HINT_PORTA || i.cmd==DIV_CMD_HINT_LEGATO || i.cmd==DIV_CMD_HINT_VOL_SLIDE || + i.cmd==DIV_CMD_HINT_VOL_SLIDE_TARGET || i.cmd==DIV_CMD_HINT_ARPEGGIO || i.cmd==DIV_CMD_HINT_PITCH || i.cmd==DIV_CMD_HINT_VIBRATO ||