diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index e87858431..b416ebb64 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -34,6 +34,11 @@ however, effects are continuous, which means you only need to type it once and t - `E2xy`: **Note slide down.** `x` is the speed, while `y` is how many semitones to slide down. - --- - `EAxx`: **Toggle legato.** while on, new notes instantly change the pitch of the currently playing sound instead of starting it over. +- `E6xy`: **Quick legato (compatibility).** transposes note by `y` semitones after `x` ticks. + - if `x` is between 0 and 7, it transposes up. + - if `x` is between 8 and F, it transposes down. +- `E8xy`: **Quick legato up**. transposes note up by `y` semitones after `x` ticks. +- `E9xy`: **Quick legato down**. transposes note down by `y` semitones after `x` ticks. - `00xy`: **Arpeggio.** this effect produces a rapid cycle between the current note, the note plus `x` semitones and the note plus `y` semitones. - `E0xx`: **Set arpeggio speed.** this sets the number of ticks between arpeggio values. default is 1. - --- diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index c34a20e47..52b0879a5 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -105,8 +105,14 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul return "E4xx: Set vibrato range"; case 0xe5: return "E5xx: Set pitch (80: center)"; + case 0xe6: + return "E6xy: Quick legato (x: time (0-7 up; 8-F down); y: semitones)"; case 0xe7: return "E7xx: Macro release"; + case 0xe8: + return "E8xy: Quick legato up (x: time; y: semitones)"; + case 0xe9: + return "E9xy: Quick legato down (x: time; y: semitones)"; case 0xea: return "EAxx: Legato"; case 0xeb: diff --git a/src/engine/engine.h b/src/engine/engine.h index 4cfdbdc67..601d750e4 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -100,7 +100,7 @@ enum DivMIDIModes { struct DivChannelState { std::vector delayed; int note, oldNote, lastIns, pitch, portaSpeed, portaNote; - int volume, volSpeed, cut, rowDelay, volMax; + int volume, volSpeed, cut, legatoDelay, legatoTarget, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; @@ -123,6 +123,8 @@ struct DivChannelState { volume(0x7f00), volSpeed(0), cut(-1), + legatoDelay(-1), + legatoTarget(0), rowDelay(0), volMax(0), delayOrder(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2f4f2e6c7..58065f5d3 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -956,6 +956,21 @@ void DivEngine::processRow(int i, bool afterDelay) { dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); dispatchCmd(DivCommand(DIV_CMD_HINT_PITCH,i,chan[i].pitch)); break; + case 0xe6: // Delayed legato + // why does this have to follow FamiTracker verbatim + // couldn't you do better? + if ((effectVal&15)!=0) { + chan[i].legatoDelay=(((effectVal&0xf0)>>4)&7)+1; + if (effectVal&128) { + chan[i].legatoTarget=-(effectVal&15); + } else { + chan[i].legatoTarget=(effectVal&15); + } + } else { + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + break; case 0xe7: // delayed macro release // "Bruh" if (effectVal>0 && (song.delayBehavior==2 || effectVal>4)+1; + chan[i].legatoTarget=(effectVal&15); + } else { + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + break; + case 0xe9: // delayed legato down + if ((effectVal&15)!=0) { + chan[i].legatoDelay=((effectVal&0xf0)>>4)+1; + chan[i].legatoTarget=-(effectVal&15); + } else { + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + break; case 0xea: // legato mode chan[i].legato=effectVal; break; @@ -1539,6 +1573,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { break; } } + if (chan[i].legatoDelay>0) { + if (--chan[i].legatoDelay<1) { + chan[i].note+=chan[i].legatoTarget; + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note)); + chan[i].legatoDelay=-1; + chan[i].legatoTarget=0; + } + } if (!song.noSlidesOnFirstTick || !firstTick) { if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index ce93ddfb9..bdcfa0f53 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -489,10 +489,10 @@ const FurnaceGUIColors fxColors[256]={ GUI_COLOR_PATTERN_EFFECT_MISC, // E3 GUI_COLOR_PATTERN_EFFECT_MISC, // E4 GUI_COLOR_PATTERN_EFFECT_PITCH, // E5 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E6 + GUI_COLOR_PATTERN_EFFECT_MISC, // E6 GUI_COLOR_PATTERN_EFFECT_TIME, // E7 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E8 - GUI_COLOR_PATTERN_EFFECT_INVALID, // E9 + GUI_COLOR_PATTERN_EFFECT_MISC, // E8 + GUI_COLOR_PATTERN_EFFECT_MISC, // E9 GUI_COLOR_PATTERN_EFFECT_MISC, // EA GUI_COLOR_PATTERN_EFFECT_MISC, // EB GUI_COLOR_PATTERN_EFFECT_TIME, // EC