Merge pull request #2102 from alederer/volPorta

Volume portamento (vol porta) effect (D3xx, D4xx for fast)
This commit is contained in:
tildearrow 2024-08-24 03:00:29 -05:00 committed by GitHub
commit ee21a996fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 113 additions and 7 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -133,7 +133,7 @@ struct DivAudioExportOptions {
struct DivChannelState {
std::vector<DivDelayedCommand> 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),

View file

@ -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; j<curPat[i].effectCols; j++) {
short effect=pat->data[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 || effectVal<nextSpeed) {
chan[i].cut=effectVal+1;
@ -1590,9 +1631,30 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (chan[i].volSpeed!=0) {
chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8);
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;
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 {

View file

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

View file

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

View file

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

View file

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