diff --git a/docs/design/delaylines.md b/docs/design/delaylines.md index 9f3488b..fb412d5 100644 --- a/docs/design/delaylines.md +++ b/docs/design/delaylines.md @@ -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] - └───────────────|- / - |------/ -``` \ No newline at end of file + +## 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 \ No newline at end of file diff --git a/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d b/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d index 1584370..cc01894 100644 --- a/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d +++ b/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d @@ -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 @@ -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 @@ -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); } @@ -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))]; } @@ -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<>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; @@ -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; } @@ -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];