From e6966b68a9844cf315638ca9a11c349f1b30b2a1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 7 Jul 2024 18:55:22 -0500 Subject: [PATCH] implement panning slide and panbrello effects --- doc/3-pattern/effects.md | 7 +++ src/engine/engine.cpp | 4 ++ src/engine/engine.h | 5 +++ src/engine/playback.cpp | 95 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+) diff --git a/doc/3-pattern/effects.md b/doc/3-pattern/effects.md index a8ca70b35..c7f3cd7fd 100644 --- a/doc/3-pattern/effects.md +++ b/doc/3-pattern/effects.md @@ -74,6 +74,13 @@ not all chips support these effects. - `00` is left. - `80` is center. - `FF` is right. + - --- +- `83xy`: **Panning slide.** + - if `y` is 0 then this pans to the left by `x` each tick. + - if `x` is 0 then this pans to the right by `y` each tick. + - be noted that panning macros override this effect. +- `84xy`: **Panbrello.** makes panning oscillate. `x` is the speed, while `y` is the depth. + - be noted that panning macros override this effect. ## time diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1b89b34c6..7ca58611b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -83,6 +83,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul return _("81xx: Set panning (left channel)"); case 0x82: return _("82xx: Set panning (right channel)"); + case 0x83: + return _("83xy: Panning slide (x0: left; 0y: right)"); + case 0x84: + return _("84xy: Panbrello (x: speed; y: depth)"); case 0x88: return _("88xy: Set panning (rear channels; x: left; y: right)"); break; diff --git a/src/engine/engine.h b/src/engine/engine.h index c4b0d6b91..40440b67f 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -137,6 +137,7 @@ struct DivChannelState { int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; + int panDepth, panRate, panPos, panSpeed; int sampleOff; unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta, cutType; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff, releasing; @@ -174,6 +175,10 @@ struct DivChannelState { tremoloDepth(0), tremoloRate(0), tremoloPos(0), + panDepth(0), + panRate(0), + panPos(0), + panSpeed(0), sampleOff(0), arp(0), arpStage(-1), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 541c761d7..8f13c83d5 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -686,6 +686,31 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].panR=effectVal; panChanged=true; break; + case 0x83: // pan slide + if (effectVal!=0) { + if ((effectVal&15)!=0) { + chan[i].panSpeed=(effectVal&15); + } else { + chan[i].panSpeed=(effectVal>>4); + } + // panbrello and slides are incompatible + chan[i].panDepth=0; + chan[i].panRate=0; + chan[i].panPos=0; + } else { + chan[i].panSpeed=0; + } + break; + case 0x84: // panbrello + if (chan[i].panDepth==0) { + chan[i].panPos=0; + } + chan[i].panDepth=effectVal&15; + chan[i].panRate=effectVal>>4; + if (chan[i].panDepth!=0) { + chan[i].panSpeed=0; + } + break; case 0x88: // panning rear (split 4-bit) chan[i].panRL=(effectVal>>4)|(effectVal&0xf0); chan[i].panRR=(effectVal&15)|((effectVal&15)<<4); @@ -1536,11 +1561,14 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } // process stuff if (!shallStop) for (int i=0; i0) { if (--chan[i].rowDelay==0) { processRow(i,true); } } + + // retrigger if (chan[i].retrigSpeed) { if (--chan[i].retrigTick<0) { chan[i].retrigTick=chan[i].retrigSpeed-1; @@ -1548,6 +1576,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { keyHit[i]=true; } } + + // volume slides and tremolo if (!song.noSlidesOnFirstTick || !firstTick) { if (chan[i].volSpeed!=0) { chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); @@ -1577,6 +1607,63 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,MAX(0,chan[i].volume-(tremTable[chan[i].tremoloPos]*chan[i].tremoloDepth))>>8)); } } + + // panning slides + // TODO: UNTESTED... can't test right now + if (chan[i].panSpeed!=0) { + int newPanL=chan[i].panL; + int newPanR=chan[i].panR; + if (chan[i].panSpeed>0) { // right + if (newPanR>=0xff) { + newPanL-=chan[i].panSpeed; + } else { + newPanR+=chan[i].panSpeed; + } + } else { // left + if (newPanL>=0xff) { + newPanR+=chan[i].panSpeed; + } else { + newPanL-=chan[i].panSpeed; + } + } + + if (newPanL<0) newPanL=0; + if (newPanL>0xff) newPanL=0xff; + if (newPanR<0) newPanR=0; + if (newPanR>0xff) newPanR=0xff; + + chan[i].panL=newPanL; + chan[i].panR=newPanR; + + dispatchCmd(DivCommand(DIV_CMD_PANNING,i,chan[i].panL,chan[i].panR)); + } else if (chan[i].panDepth>0) { + chan[i].panPos+=chan[i].panRate; + chan[i].panPos&=255; + + // calculate... + switch (chan[i].panPos&0xe0) { + case 0: // center -> right + chan[i].panL=0xff-(chan[i].panPos<<2); + chan[i].panR=0xff; + break; + case 0x40: // right -> center + chan[i].panL=chan[i].panPos<<2; + chan[i].panR=0xff; + break; + case 0x80: // center -> left + chan[i].panL=0xff; + chan[i].panR=0xff-(chan[i].panPos<<2); + break; + case 0xc0: // left -> center + chan[i].panL=0xff; + chan[i].panR=chan[i].panPos<<2; + break; + } + + dispatchCmd(DivCommand(DIV_CMD_PANNING,i,chan[i].panL,chan[i].panR)); + } + + // vibrato if (chan[i].vibratoDepth>0) { chan[i].vibratoPos+=chan[i].vibratoRate; while (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; @@ -1632,6 +1719,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibratoOut*chan[i].vibratoFine)>>4)/15))); } + + // delayed legato if (chan[i].legatoDelay>0) { if (--chan[i].legatoDelay<1) { chan[i].note+=chan[i].legatoTarget; @@ -1641,6 +1730,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { chan[i].legatoTarget=0; } } + + // portamento and pitch slides 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) { @@ -1654,6 +1745,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } } + + // note cut if (chan[i].cut>0) { if (--chan[i].cut<1) { if (chan[i].cutType==2) { @@ -1688,6 +1781,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } } + + // arpeggio if (chan[i].resetArp) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));