diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index 8fe6e6ef4..a8ca70b35 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -42,12 +42,21 @@ however, effects are continuous (unless specified), which means you only need to - `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. - --- -- `04xy`: **Vibrato.** changes pitch to be "wavy" with a sine LFO. `x` is the speed, while `y` is the depth. +- `04xy`: **Vibrato.** makes the pitch oscillate. `x` is the speed, while `y` is the depth. - maximum vibrato depth is ±1 semitone. -- `E3xx`: **Set vibrato direction.** `xx` may be one of the following: - - `00`: up and down. default. - - `01`: up only. - - `02`: down only. +- `E3xx`: **Set vibrato shape.** `xx` may be one of the following: + - `00`: sine (default) + - `01`: sine (upper portion only) + - `02`: sine (lower portion only) + - `03`: triangle + - `04`: ramp up + - `05`: ramp down + - `06`: square + - `07`: random + - `08`: square (up) + - `09`: square (down) + - `0a`: half sine (up) + - `0b`: half sine (down) - `E4xx`: **Set vibrato range** in 1/16th of a semitone. ## panning diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1d0cef2aa..df24c917d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -101,7 +101,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul case 0xe2: return _("E2xy: Note slide down (x: speed; y: semitones)"); case 0xe3: - return _("E3xx: Set vibrato shape (0: up/down; 1: up only; 2: down only)"); + return _("E3xx: Set vibrato shape"); case 0xe4: return _("E4xx: Set vibrato range"); case 0xe5: diff --git a/src/engine/engine.h b/src/engine/engine.h index c16a6f204..d92a7a114 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -135,7 +135,7 @@ struct DivChannelState { int note, oldNote, lastIns, pitch, portaSpeed, portaNote; int volume, volSpeed, cut, legatoDelay, legatoTarget, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; - int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; + int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; int sampleOff; unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta, cutType; @@ -169,7 +169,7 @@ struct DivChannelState { vibratoRate(0), vibratoPos(0), vibratoPosGiant(0), - vibratoDir(0), + vibratoShape(0), vibratoFine(15), tremoloDepth(0), tremoloRate(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index dd8b90f75..77e885f69 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -951,9 +951,9 @@ void DivEngine::processRow(int i, bool afterDelay) { if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } break; - case 0xe3: // vibrato direction - chan[i].vibratoDir=effectVal; - dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_SHAPE,i,chan[i].vibratoDir)); + case 0xe3: // vibrato shape + chan[i].vibratoShape=effectVal; + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_SHAPE,i,chan[i].vibratoShape)); break; case 0xe4: // vibrato fine chan[i].vibratoFine=effectVal; @@ -1579,19 +1579,55 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { while (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; chan[i].vibratoPosGiant+=chan[i].vibratoRate; - while (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512; + while (chan[i].vibratoPosGiant>=512) chan[i].vibratoPosGiant-=512; - switch (chan[i].vibratoDir) { - case 1: // up - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + int vibratoOut=0; + switch (chan[i].vibratoShape) { + case 1: // sine, up only + vibratoOut=MAX(0,vibTable[chan[i].vibratoPos]); break; - case 2: // down - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MIN(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + case 2: // sine, down only + vibratoOut=MIN(0,vibTable[chan[i].vibratoPos]); break; - default: // both - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + case 3: // triangle + vibratoOut=(chan[i].vibratoPos&31); + if (chan[i].vibratoPos&16) { + vibratoOut=32-(chan[i].vibratoPos&31); + } + if (chan[i].vibratoPos&32) { + vibratoOut=-vibratoOut; + } + vibratoOut<<=3; + break; + case 4: // ramp up + vibratoOut=chan[i].vibratoPos<<1; + break; + case 5: // ramp down + vibratoOut=-chan[i].vibratoPos<<1; + break; + case 6: // square + vibratoOut=(chan[i].vibratoPos>=32)?-127:127; + break; + case 7: // random (TODO: use LFSR) + vibratoOut=(rand()&255)-128; + break; + case 8: // square up + vibratoOut=(chan[i].vibratoPos>=32)?0:127; + break; + case 9: // square down + vibratoOut=(chan[i].vibratoPos>=32)?0:-127; + break; + case 10: // half sine up + vibratoOut=vibTable[chan[i].vibratoPos>>1]; + break; + case 11: // half sine down + vibratoOut=vibTable[32|(chan[i].vibratoPos>>1)]; + break; + default: // sine + vibratoOut=vibTable[chan[i].vibratoPos]; break; } + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibratoOut*chan[i].vibratoFine)>>4)/15))); } if (chan[i].legatoDelay>0) { if (--chan[i].legatoDelay<1) { diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 07c9d6c9b..594554c57 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -155,7 +155,7 @@ void FurnaceGUI::drawDebug() { ImGui::Text("- depth = %d",ch->vibratoDepth); ImGui::Text("- rate = %d",ch->vibratoRate); ImGui::Text("- pos = %d",ch->vibratoPos); - ImGui::Text("- dir = %d",ch->vibratoDir); + ImGui::Text("- shape = %d",ch->vibratoShape); ImGui::Text("- fine = %d",ch->vibratoFine); ImGui::PopStyleColor(); ImGui::PushStyleColor(ImGuiCol_Text,(ch->tremoloDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]);