Skip to content

Commit

Permalink
Refactoring of delaylines
Browse files Browse the repository at this point in the history
  • Loading branch information
ZILtoid1991 committed May 30, 2023
1 parent 9fc3f67 commit dad8520
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 179 deletions.
38 changes: 24 additions & 14 deletions docs/design/delaylines.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
# Delay line tap layout
# Delay line layout and introduction

## Single tap layout

```
[tap in]──[FIR]─┬─[MixerOut A/B]─[To mixer]
└─[FBOut A/B]────[To feedback]
```

## Effects layout

```
[Input A]──╔═══════╗ [LFO ×4] ╔═════╗
║Routing║───────────[DelayLineA/B]──────────║Mixer║─────[Output A/B]
[Input B]──╚═══════╝ FBOut└(Taps ×4) ╚═════╝
(IIR ×4)
```
[tap in]──┬──────────────────────────┐
│ - + │ ┌─[outLevelL]
├──[IIR0]────[filterAm0] │ │
│ │ │ │ ├─[outLevelR]
├──[IIR1]────[filterAm1] │ │
│ │ │ │ │
└──[IIR2]────[filterAm2] │ │
│ │ │ |------\ │ ┌─[feedbackSec]
│ └────┴──|+ \ │ │
│ | Mixer >────┴──────[IIR3]───┴─[feedbackPri]
└───────────────|- /
|------/
```

## Effects properties

* 2 inputs and outputs
* 4 LFOs assignable to position and level
* 2 Delay lines, with each having 4 taps with a 16 element FIR line
* A mixer with 4×2 IIR filters for equalization
227 changes: 62 additions & 165 deletions pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d
Original file line number Diff line number Diff line change
Expand Up @@ -33,59 +33,46 @@ public class DelayLines : AudioModule {
shared static this () {
import std.conv : to;
for (uint i ; i < 8 ; i++) {
for (uint j ; j < 8 ; j++) {
for (uint j ; j < 16 ; j++) {
SET_VALS ~= MValue(MValueType.Float, (i<<7) | j, "Tap" ~ i.to!string ~ "_FIR" ~ j.to!string);
}
for (uint j ; j < 4 ; j++) {
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (8 + j * 3 + 0), "Tap" ~ i.to!string ~ "_IIR" ~ j.to!string ~ "Freq");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (8 + j * 3 + 1), "Tap" ~ i.to!string ~ "_IIR" ~ j.to!string ~ "Q");
SET_VALS ~= MValue(MValueType.Int32, (i<<7) | (8 + j * 3 + 2), "Tap" ~ i.to!string ~ "_IIR" ~ j.to!string ~ "Type");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (8 + j * 3 + 3), "Tap" ~ i.to!string ~ "_IIR" ~ j.to!string ~"Level");
}
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (24), "Tap" ~ i.to!string ~ "_OutputL");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (25), "Tap" ~ i.to!string ~ "_OutputR");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (26), "Tap" ~ i.to!string ~ "_FeedbackPri");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (27), "Tap" ~ i.to!string ~ "_FeedbackSec");
SET_VALS ~= MValue(MValueType.Int32, (i<<7) | (28), "Tap" ~ i.to!string ~ "_Pos");
SET_VALS ~= MValue(MValueType.Boolean, (i<<7) | (29), "Tap" ~ i.to!string ~ "_TapEnable");
SET_VALS ~= MValue(MValueType.Boolean, (i<<7) | (30), "Tap" ~ i.to!string ~ "_BypassDrySig");
SET_VALS ~= MValue(MValueType.Boolean, (i<<7) | (31), "Tap" ~ i.to!string ~ "_FilterAlg");

SET_VALS ~= MValue(MValueType.Float, (i<<7) | (16), "Tap" ~ i.to!string ~ "_OutputL");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (17), "Tap" ~ i.to!string ~ "_OutputR");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (18), "Tap" ~ i.to!string ~ "_FeedbackPri");
SET_VALS ~= MValue(MValueType.Float, (i<<7) | (19), "Tap" ~ i.to!string ~ "_FeedbackSec");
SET_VALS ~= MValue(MValueType.Int32, (i<<7) | (20), "Tap" ~ i.to!string ~ "_Pos");
SET_VALS ~= MValue(MValueType.Boolean, (i<<7) | (21), "Tap" ~ i.to!string ~ "_TapEnable");
}
for (uint i ; i < 4 ; i++){
for (uint i ; i < 4 ; i++) {
SET_VALS ~= MValue(MValueType.Int32, (8<<7) | (i<<3) | (0), "LFO" ~ i.to!string ~ "_Waveform");
SET_VALS ~= MValue(MValueType.Float, (8<<7) | (i<<3) | (1), "LFO" ~ i.to!string ~ "_Level");
SET_VALS ~= MValue(MValueType.Float, (8<<7) | (i<<3) | (2), "LFO" ~ i.to!string ~ "_Freq");
SET_VALS ~= MValue(MValueType.Float, (8<<7) | (i<<3) | (3), "LFO" ~ i.to!string ~ "_PWM");
SET_VALS ~= MValue(MValueType.Int32, (8<<7) | (i<<3) | (4), "LFO" ~ i.to!string ~ "_Target");
}
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (0), "InputAtoPri");
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (1), "InputAtoSec");
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (2), "InputBtoPri");
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (3), "InputBtoSec");
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (4), "MasterL");
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (5), "MasterR");
for (uint i ; i < 4 ; i++) {
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (i<<3) | (0), "EQ" ~ i.to!string ~ "_Level");
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (i<<3) | (1), "EQ" ~ i.to!string ~ "_Freq");
SET_VALS ~= MValue(MValueType.Float, (9<<7) | (i<<3) | (2), "EQ" ~ i.to!string ~ "_Q");
}
SET_VALS ~= MValue(MValueType.Float, (10<<7) | (0), "InputAtoPri");
SET_VALS ~= MValue(MValueType.Float, (10<<7) | (1), "InputAtoSec");
SET_VALS ~= MValue(MValueType.Float, (10<<7) | (2), "InputBtoPri");
SET_VALS ~= MValue(MValueType.Float, (10<<7) | (3), "InputBtoSec");
SET_VALS ~= MValue(MValueType.Float, (10<<7) | (4), "MasterL");
SET_VALS ~= MValue(MValueType.Float, (10<<7) | (5), "MasterR");

}
protected static MValue[] SET_VALS;
/**
* Defines a delay line tap.
*/
protected struct Tap {
//uint pos; ///Median position of the tap
union {
///used for preset save
uint flags;
mixin(bitfields!(
bool, "tapEnable", 1, ///Enables the tap, otherwise just skips it to save CPU time
bool, "bypassDrySig", 1, ///Bypasses unfiltered signal
bool, "filterAlg", 1, ///Toggles filter algorithm (serial/parallel)
uint, "pos", 29 ///Median position of the tap
));
}
float filterAm0 = 0.0; ///IIR0 mix amount
float filterAm1 = 0.0; ///IIR1 mix amount
float filterAm2 = 0.0; ///IIR2 mix amount
__m128 outLevels = __m128(0.0);///Output levels (0: Left; 1: Right, 2: Primary feedback, 3: Secondary feedback)
__m128[2] fir = [__m128(0.0),__m128(0.0)];///Short finite impulse response after tap
__m128[4] fir = [__m128(0.0),__m128(0.0),__m128(0.0),__m128(0.0)];///Short finite impulse response for the tap tap
uint pos; ///Median position of the tap (unaffected by LFO)
bool tapEnable; ///True if tap is enabled
}

///Defines an LFO target
Expand Down Expand Up @@ -122,23 +109,22 @@ public class DelayLines : AudioModule {
}
}
Tap[4][2] taps; ///Defines delay line taps
__m128[4][2] iirFreq; ///Defines IIR frequencies
__m128[4][2] iirQ; ///Defines IIR Q value
__m128[2] iirFreq; ///Defines IIR frequencies
__m128[2] iirQ; ///Defines IIR Q value
__m128 inputLevel;
__m128 oscLevels; ///Defines the amount of effect a given LFO has on a parameter
float[4] oscFrequencies; ///Defines LFO freqencies
float[4] oscPWM; ///Defines the PWM of the LFOs
float[2] outputLevel;
ubyte[4] oscTargets; ///Sets the target of a given LFO
ubyte[4][4][2] iirType; ///Defines IIR types
ubyte[4][2] iirType; ///Defines IIR types
OscWaveform[4] oscWaveform; ///Sets the waveform output of the LFOs

}
protected TreeMap!(uint,Preset) presetBank; ///Stores presets
protected Preset currPreset; ///Contains the copy of the current preset
protected IIRBank[4][2] filterBanks; ///Stores filter data for each taps
protected __m128[4][2] filterOuts; ///Last filter outputs
protected float[4][2] feedbackSends; ///Stores feedback send levels
protected IIRBank[2] filterBanks; ///Stores filter data for the EQ
protected float[2] feedbackSends; ///Stores feedback send levels
protected float[][2] delayLines; ///The two delay lines of the
protected __m128 inLevel; ///0: A to pri, 1: A to sec, 2: B to pri, 3: B to sec
protected float[2] outLevel; ///Output levels
Expand Down Expand Up @@ -188,7 +174,10 @@ public class DelayLines : AudioModule {
dummyBuf.length = bufferSize;
feedbackSum[0] = 0;
feedbackSum[1] = 0;
resetBuffer(oscOut);
//resetBuffer(oscOut);
foreach (ref __m128i key; oscOut) {
key = __m128i(0);
}
resetBuffer(dummyBuf);
}

Expand All @@ -206,9 +195,9 @@ public class DelayLines : AudioModule {

override public void renderFrame(float*[] input, float*[] output) @nogc nothrow {
//function to read from the delay line
__m128[2] readDL(int lineNum, uint pos) @nogc @safe pure nothrow {
__m128[2] result;
for (int i ; i < 2 ; i++) {
__m128[4] readDL(int lineNum, uint pos) @nogc @safe pure nothrow {
__m128[4] result;
for (int i ; i < 4 ; i++) {
for (int j ; j < 4 ; j++) {
result[i][j] = delayLines[lineNum][dLMod[lineNum] & (dLPos[lineNum] - pos - j - (i<<2))];
}
Expand All @@ -231,21 +220,12 @@ public class DelayLines : AudioModule {
}
}
//Precalculate values, so they don't need to be done on a per-cycle basis.
__m128[4][2] filterLevels;
float[4][2] filterAlg0;
float[4][2] filterAlg1;
__m128i[2] tapLFOOffset;
__m128[2] tapLFOOffsetSt;
__m128[2] tapLFOLevel = __m128(0.0), tapLFOLevelSt;
__m128[2] tapLFOFB = __m128(0.0), tapLFOFBSt;
for (int i ; i < 2 ; i++) {
for (int j ; j < 4 ; j++) {
filterLevels[i][j][0] = currPreset.taps[i][j].filterAm0;
filterLevels[i][j][1] = currPreset.taps[i][j].filterAm1;
filterLevels[i][j][2] = currPreset.taps[i][j].filterAm2;
filterLevels[i][j][3] = currPreset.taps[i][j].bypassDrySig ? 0.0 : 1.0;
filterAlg0[i][j] = currPreset.taps[i][j].filterAlg ? 0.0 : 1.0;
filterAlg1[i][j] = currPreset.taps[i][j].filterAlg ? 1.0 : 0.0;
tapLFOLevelSt[i][j] = currPreset.oscTargets[j] == ((8<<i) | OscTarget.TapOut) ? 1.0 : 0.0;
tapLFOOffsetSt[i][j] = currPreset.oscTargets[j] == ((8<<i) | OscTarget.TapPosition) ? 1.0 : 0.0;
tapLFOFBSt[i][j] = currPreset.oscTargets[j] == ((8<<i) | OscTarget.TapFeedback) ? 1.0 : 0.0;
Expand All @@ -264,49 +244,13 @@ public class DelayLines : AudioModule {
}
}
for (int outputPos ; outputPos < bufferSize ; outputPos++) {
delayLines[0][dLPos[0] & dLMod[0]] = inBuf[0][outputPos] * inLevel[0] + inBuf[1][outputPos] * inLevel[1] +
feedbackSum[0];
delayLines[1][dLPos[1] & dLMod[1]] = inBuf[0][outputPos] * inLevel[2] + inBuf[1][outputPos] * inLevel[3] +
feedbackSum[1];
feedbackSum[0] = 0.0;
feedbackSum[1] = 0.0;
const __m128 lfoOutWLevel = currPreset.oscLevels *
(_mm_cvtepi32_ps(oscOut[outputPos]) * __m128(1.0 / uint.max) + __m128(0.5));
tapLFOOffset[0] = _mm_cvtps_epi32(tapLFOOffsetSt[0] * lfoOutWLevel * __m128(cast(int)(dLMod[0] + 1)));
tapLFOOffset[1] = _mm_cvtps_epi32(tapLFOOffsetSt[1] * lfoOutWLevel * __m128(cast(int)(dLMod[1] + 1)));
tapLFOLevel[0] = tapLFOLevelSt[0] * lfoOutWLevel;
tapLFOLevel[1] = tapLFOLevelSt[1] * lfoOutWLevel;
tapLFOFB[0] = tapLFOFBSt[0] * lfoOutWLevel;
tapLFOFB[1] = tapLFOFBSt[1] * lfoOutWLevel;
for (int i ; i < 2 ; i++) {
for (int j ; j < 4 ; j++) {
if (currPreset.taps[i][j].tapEnable) {
const __m128[2] firTarget = readDL(i, currPreset.taps[i][j].pos + tapLFOOffset[i][j]);
const __m128 partialOut = firTarget[0] * currPreset.taps[i][j].fir[0] +
firTarget[1] * currPreset.taps[i][j].fir[1];//Apply FIR
//Apply IIRs
const float outSum = partialOut[0] + partialOut[1] + partialOut[2] + partialOut[3];
__m128 toIIR;
toIIR[0] = outSum;
toIIR[1] = filterOuts[i][j][0] * filterAlg1[i][j] + outSum * filterAlg0[i][j];
toIIR[2] = filterOuts[i][j][1] * filterAlg1[i][j] + outSum * filterAlg0[i][j];
toIIR[3] = feedbackSends[i][j];
toIIR = filterBanks[i][j].output(toIIR);
filterOuts[i][j] = toIIR;
toIIR[3] = outSum;
toIIR *= filterLevels[i][j];
feedbackSends[i][j] = toIIR[0] + toIIR[1] + toIIR[2] + toIIR[3];
//Mix to final output
__m128 finalOut;
finalOut[0] = feedbackSends[i][j];
finalOut[1] = feedbackSends[i][j];
finalOut[2] = filterOuts[i][j][3];
finalOut[3] = filterOuts[i][j][3];
finalOut *= currPreset.taps[i][j].outLevels;
outBuf[0][outputPos] += finalOut[0] * outLevel[0] + tapLFOLevel[i][j];
outBuf[1][outputPos] += finalOut[1] * outLevel[1] + tapLFOLevel[i][j];
feedbackSum[0] += finalOut[2] + tapLFOFB[i][j];
feedbackSum[1] += finalOut[3] + tapLFOFB[i][j];
for (int d ; d < 2 ; d++) {
for (int t ; t < 4 ; t++) {
if (currPreset.taps[d][t].tapEnable) {
__m128[4] impulse = readDL(d, currPreset.taps[d][t].pos);
for (int i ; i < 4 ; i++) {
impulse[i] *= currPreset.taps[d][t].fir[i];
}
}
}
}
Expand Down Expand Up @@ -337,7 +281,7 @@ public class DelayLines : AudioModule {
const uint filterID = (subParamID - 8) / 4, filterParamID = (subParamID - 8) % 4;
switch (filterParamID) {
case 2:
presetPtr.iirType[lineID][tapID][filterID] = cast(ubyte)value; //Filtertype

return 0;
default:
return 1;
Expand All @@ -349,10 +293,10 @@ public class DelayLines : AudioModule {
presetPtr.taps[lineID][tapID].tapEnable = value != 0;
return 0;
case 30: //Bypass dry signal
presetPtr.taps[lineID][tapID].bypassDrySig = value != 0;

return 0;
case 31: //Filter algorithm
presetPtr.taps[lineID][tapID].filterAlg = value != 0;

return 0;
default:
break;
Expand Down Expand Up @@ -393,38 +337,19 @@ public class DelayLines : AudioModule {
const uint tapID = paramGr & 3, lineID = paramGr>>2;
const uint subParamID = paramID & 0x3F;
switch (subParamID) {
case 0: .. case 3: //FIR low
case 0: .. case 3: //FIR 0
presetPtr.taps[lineID][tapID].fir[0][subParamID] = value;
return 0;
case 4: .. case 7: //FIR high
case 4: .. case 7: //FIR 1
presetPtr.taps[lineID][tapID].fir[1][subParamID - 4] = value;
return 0;
case 8: .. case 23:
const uint filterID = (subParamID - 8)>>2, filterParamID = (subParamID - 8)&7;
switch (filterParamID) {
case 0: //Filter freq
presetPtr.iirFreq[lineID][tapID][filterID] = value;
return 0;
case 1: //Filter Q
presetPtr.iirQ[lineID][tapID][filterID] = value;
return 0;
case 3: //Filter amount
switch (filterID) {
case 0:
presetPtr.taps[lineID][tapID].filterAm0 = value;
return 0;
case 1:
presetPtr.taps[lineID][tapID].filterAm1 = value;
return 0;
case 2:
presetPtr.taps[lineID][tapID].filterAm2 = value;
return 0;
default:
return 0;
}
default:
return -1;
}
case 8: .. case 11: //FIR 2
presetPtr.taps[lineID][tapID].fir[2][subParamID - 8] = value;
return 0;
case 12: .. case 15://FIR 3
presetPtr.taps[lineID][tapID].fir[3][subParamID - 12] = value;
return 0;

case 24: .. case 27: //Output Levels
const uint levelID = (subParamID - 24) & 3;
presetPtr.taps[lineID][tapID].outLevels[levelID] = value;
Expand Down Expand Up @@ -487,22 +412,11 @@ public class DelayLines : AudioModule {
const uint tapID = paramGr & 3, lineID = paramGr>>2;
const uint subParamID = paramID & 0x3F;
switch (subParamID) {
case 8: .. case 19:
const uint filterID = (subParamID - 8) / 4, filterParamID = (subParamID - 8) % 4;
switch (filterParamID) {
case 2:
return presetPtr.iirType[lineID][tapID][filterID]; //Filtertype
default:
return int.init;
}

case 28: //Position
return presetPtr.taps[lineID][tapID].pos;
case 29: //Tap enable
return presetPtr.taps[lineID][tapID].tapEnable ? 1 : 0;
case 30: //Bypass dry signal
return presetPtr.taps[lineID][tapID].bypassDrySig ? 1 : 0;
case 31: //Filter algorithm
return presetPtr.taps[lineID][tapID].filterAlg ? 1 : 0;
default:
break;
}
Expand Down Expand Up @@ -535,31 +449,14 @@ public class DelayLines : AudioModule {
const uint tapID = paramGr & 3, lineID = paramGr>>2;
const uint subParamID = paramID & 0x3F;
switch (subParamID) {
case 0: .. case 3: //FIR low
case 0: .. case 3: //FIR 0
return presetPtr.taps[lineID][tapID].fir[0][subParamID];
case 4: .. case 7: //FIR high
case 4: .. case 7: //FIR 1
return presetPtr.taps[lineID][tapID].fir[1][subParamID - 4];
case 8: .. case 23:
const uint filterID = (subParamID - 8)>>2, filterParamID = (subParamID - 8)&7;
switch (filterParamID) {
case 0: //Filter freq
return presetPtr.iirFreq[lineID][tapID][filterID];
case 1: //Filter Q
return presetPtr.iirQ[lineID][tapID][filterID];
case 3: //Filter amount
switch (filterID) {
case 0:
return presetPtr.taps[lineID][tapID].filterAm0;
case 1:
return presetPtr.taps[lineID][tapID].filterAm1;
case 2:
return presetPtr.taps[lineID][tapID].filterAm2;
default:
return 0;
}
default:
return double.init;
}
case 8: .. case 11: //FIR 2
return presetPtr.taps[lineID][tapID].fir[2][subParamID - 8];
case 12: .. case 15://FIR 3
return presetPtr.taps[lineID][tapID].fir[3][subParamID - 12];
case 24: .. case 27: //Output Levels
const uint levelID = (subParamID - 24) & 3;
return presetPtr.taps[lineID][tapID].outLevels[levelID];
Expand Down

0 comments on commit dad8520

Please sign in to comment.