From 11c4d0a2233bafa1958ba152cdebf94e10af11fc Mon Sep 17 00:00:00 2001 From: ZILtoid1991 Date: Sun, 16 Jun 2024 13:04:13 +0200 Subject: [PATCH] Implementation of level filtering and external master level overrides --- .../audio/modules/delaylines.d | 136 +----------------- .../pixelperfectengine/audio/modules/pcm8.d | 73 +++++++--- .../pixelperfectengine/audio/modules/qm816.d | 23 +-- 3 files changed, 65 insertions(+), 167 deletions(-) diff --git a/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d b/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d index 9b5991c..8a08123 100644 --- a/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d +++ b/pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d @@ -75,16 +75,6 @@ public class DelayLines : AudioModule { bool tapEnable; ///True if tap is enabled bool bypassFIR; ///Bypasses FIR calculation } - /* - ///Defines an LFO target - protected enum OscTarget : ubyte { - init = 0, - TapOut = 1, - TapFeedback = 2, - TapPosition = 4, - ToA = 8, - ToB = 16, - } */ ///Contains recallable preset data. protected struct Preset { ///Defines oscillator waveform selection data. @@ -114,12 +104,7 @@ public class DelayLines : AudioModule { __m128[2] iirQ = [__m128(0.707), __m128(0.707)];///Defines IIR Q value __m128[2] eqLevels = [__m128(0), __m128(0)];///Stores EQ send levels __m128 inputLevel = __m128(1); - /* __m128i oscLevels = __m128i(0);///Defines the amount of effect a given LFO has on a parameter - float[4] oscFrequencies = [4, 4, 4, 4];///Defines LFO freqencies - uint[4] oscPWM; ///Defines the PWM of the LFOs */ float[2] outputLevel = [1.0, 1.0]; - /* ubyte[4] oscTargets; ///Sets the target of a given LFO - OscWaveform[4] oscWaveform; ///Sets the waveform output of the LFOs */ } protected TreeMap!(uint,Preset) presetBank; ///Stores presets @@ -130,8 +115,6 @@ public class DelayLines : AudioModule { 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 protected float[2] feedbackSum; ///Feedback sums to be mixed in with the inputs - /* protected QuadMultitapOsc osc; ///Oscillators to modify fix tap points - protected __m128i[] oscOut; ///LFO buffer */ protected size_t[2] dLPos; ///Delay line positions protected size_t[2] dLMod; ///Delay line modulo protected float[] dummyBuf; ///Buffer used for unused inputs/outputs @@ -260,29 +243,7 @@ public class DelayLines : AudioModule { break; } break; - /* case 8://LFOs - const int lfoGr = paramLSB>>3; - switch (paramLSB & 7) { - case 0: - currPreset.oscWaveform[lfoGr].raw = cast(ubyte)val; - break; - case 1: - currPreset.oscLevels[lfoGr] = cast(short)(val>>17); - break; - case 2: - currPreset.oscFrequencies[lfoGr] = val / cast(double)uint.max * 20; - break; - case 3: - currPreset.oscPWM[lfoGr] = val; - break; - case 4: - currPreset.oscTargets = cast(ubyte)val; - break; - default: - break; - } - resetLFO(lfoGr); - break; */ + case 9://EQ const int eqGr = (paramLSB>>2) & 7; switch (paramLSB & 3) { @@ -323,25 +284,6 @@ public class DelayLines : AudioModule { filterBanks[filterID>>2].fixFilter(); } - /* protected void resetLFO(int lfoID) @safe @nogc nothrow pure { - osc.setRate(sampleRate, currPreset.oscFrequencies[lfoID], lfoID); - osc.pulseWidth[lfoID] = currPreset.oscPWM[lfoID]; - const int waveNum = cast(int)currPreset.oscWaveform[lfoID].sawtooth + cast(int)currPreset.oscWaveform[lfoID].triangle - + cast(int)currPreset.oscWaveform[lfoID].pulse + cast(int)currPreset.oscWaveform[lfoID].sawpulse; - if (waveNum) { - const short level = cast(short)((currPreset.oscLevels[lfoID] / waveNum) * - (currPreset.oscWaveform[lfoID].phaseInvert ? -1 : 1)); - if (currPreset.oscWaveform[lfoID].sawtooth) - osc.levelCtrl01[lfoID * 2] = level; - if (currPreset.oscWaveform[lfoID].triangle) - osc.levelCtrl01[lfoID * 2 + 1] = level; - if (currPreset.oscWaveform[lfoID].pulse) - osc.levelCtrl23[lfoID * 2] = level; - if (currPreset.oscWaveform[lfoID].sawpulse) - osc.levelCtrl23[lfoID * 2 + 1] = level; - } - } */ - protected void presetChangeCmd(uint preset) @nogc nothrow { Preset* presetPtr = presetBank.ptrOf(preset); if (presetPtr is null) return; @@ -383,40 +325,6 @@ public class DelayLines : AudioModule { outBuf[i] = dummyBuf.ptr; } } - //Precalculate values, so they don't need to be done on a per-cycle basis. - - /* __m128i[2] tapLFOOffset; - __m128[2] tapLFOLevel; - __m128[2] tapLFOFB; - for (int j ; j < 4 ; j++) { - if (currPreset.oscTargets[j] & OscTarget.ToA) { - tapLFOLevel[0][j] = (currPreset.oscTargets[j] & OscTarget.TapOut) ? 1.0 / short.max : 0.0; - tapLFOOffset[0][j] = (currPreset.oscTargets[j] & OscTarget.TapPosition) ? 1 : 0; - tapLFOFB[0][j] = (currPreset.oscTargets[j] & OscTarget.TapFeedback) ? 1.0 / short.max : 0.0; - } - if (currPreset.oscTargets[j] & OscTarget.ToB) { - tapLFOLevel[1][j] = (currPreset.oscTargets[j] & OscTarget.TapOut) ? 1.0 / short.max : 0.0; - tapLFOOffset[1][j] = (currPreset.oscTargets[j] & OscTarget.TapPosition) ? 1 : 0; - tapLFOFB[1][j] = (currPreset.oscTargets[j] & OscTarget.TapFeedback) ? 1.0 / short.max : 0.0; - } - } */ - - /* { //Render LFO outs. - for (int lfoPos ; lfoPos < bufferSize ; lfoPos++) { - oscOut[lfoPos] = osc.output(); - oscOut[lfoPos][0]>>=15; - oscOut[lfoPos][1]>>=15; - oscOut[lfoPos][2]>>=15; - oscOut[lfoPos][3]>>=15; - } - for (int i ; i < 4 ; i++) { - if (currPreset.oscWaveform[i].integrate){ //integrate output if needed - for (int lfoPos ; lfoPos < bufferSize ; lfoPos++) { - oscOut[lfoPos][i] = (abs(oscOut[lfoPos][i]) * oscOut[lfoPos][i])>>16; - } - } - } - } */ for (int outputPos ; outputPos < bufferSize ; outputPos++) { delayLines[0][dLMod[0] & dLPos[0]] = inBuf[0][outputPos] * currPreset.inputLevel[0] + @@ -543,22 +451,6 @@ public class DelayLines : AudioModule { break; } break; - /* case 8: //LFO - const uint lfoID = (paramID>>3) & 3, subParamID = paramID & 7; - switch (subParamID) { - //case 1: - // presetPtr.oscLevels[lfoID] = value; - // return 0; - case 2: - presetPtr.oscFrequencies[lfoID] = value; - return 0; - case 3: - presetPtr.oscPWM[lfoID] = cast(uint)(value * uint.max); - return 0; - default: - break; - } - break; */ case 9: //EQ const uint EQID = (paramID>>2) & 7; switch (paramID & 3) { @@ -624,19 +516,6 @@ public class DelayLines : AudioModule { break; } break; - /* case 8: //LFO - const uint lfoID = (paramID>>3) & 3, subParamID = paramID & 7; - switch (subParamID) { - case 0: - return presetPtr.oscWaveform[lfoID].raw; - case 1: - return presetPtr.oscLevels[lfoID]; - case 4: - return presetPtr.oscTargets[lfoID]; - default: - break; - } - break; */ default: break; } @@ -669,19 +548,6 @@ public class DelayLines : AudioModule { break; } break; - /* case 8: //LFO - const uint lfoID = (paramID>>3) & 3, subParamID = paramID & 7; - switch (subParamID) { - //case 1: - // return presetPtr.oscLevels[lfoID]; - case 2: - return presetPtr.oscFrequencies[lfoID]; - case 3: - return presetPtr.oscPWM[lfoID] / uint.max; - default: - break; - } - break; */ case 9: //EQ const uint EQID = (paramID>>2) & 7; switch (paramID & 3) { diff --git a/pixelperfectengine/src/pixelperfectengine/audio/modules/pcm8.d b/pixelperfectengine/src/pixelperfectengine/audio/modules/pcm8.d index 98bbb41..7c05f39 100644 --- a/pixelperfectengine/src/pixelperfectengine/audio/modules/pcm8.d +++ b/pixelperfectengine/src/pixelperfectengine/audio/modules/pcm8.d @@ -19,6 +19,7 @@ import midi2.types.structs; import midi2.types.enums; import bitleveld.datatypes; +import pixelperfectengine.audio.base.filter; /** PCM8 - implements a sample-based synthesizer. @@ -29,7 +30,7 @@ It has support for * IMA ADPCM * Dialogic ADPCM -The module has 8 sample-based channels with looping capabilities and each has an ADSR envelop, and 4 outputs with a filter. +The module has 16 sample-based channels with looping capabilities and each has an ADSR envelop, and 4 outputs with a filter. */ public class PCM8 : AudioModule { shared static this () { @@ -157,10 +158,9 @@ public class PCM8 : AudioModule { double freqRatio; ///Sampling-to-playback frequency ratio, with pitch bend, LFO, and envGen applied. ulong outPos; ///Position in decoded amount, including fractions ulong samplesHave; ///Amount of decoded samples with fractions offsetted - //uint decodeAm; ///Decoded amount, mainly used to determine output buffer half uint jumpAm; ///Jump amount for current sample, calculated from freqRatio. - //WavemodWorkpad savedWMWState; ///The state of the wave modulator when the beginning of the looppoint has been reached. ADSREnvelopGenerator envGen; ///Channel envelop generator. + LinearFilter ctrlFilter; ///Control level filters. ubyte currNote = 255; ///The currently played note + Bit 8 indicates suspension. ubyte presetNum; ///Selected preset. @@ -295,7 +295,7 @@ public class PCM8 : AudioModule { alias PresetMap = TreeMap!(uint, Preset); protected SampleMap sampleBank; ///Stores all current samples. protected PresetMap presetBank; ///Stores all current presets. (bits: 0-6: preset number, 7-13: bank lsb, 14-20: bank msb) - protected Channel[8] channels; ///Channel status data. + protected Channel[16] channels; ///Channel status data. protected MultiTapOsc lfo; ///Low frequency oscillator to modify values in real-time protected float[] lfoOut; ///LFO output buffer protected uint lfoFlags = LFOFlags.triangle;///LFO state flags containing info about current waveform data @@ -311,6 +311,7 @@ public class PCM8 : AudioModule { ///Layout: [LF, LQ, RF, RQ, AF, AQ, BF, BQ] protected float[8] filterCtrl = [16_000, 0.707, 16_000, 0.707, 16_000, 0.707, 16_000, 0.707]; protected float mixdownVal = short.max + 1; + protected float[17] masterLevels; protected ubyte[32] sysExBuf; ///SysEx command buffer [0-30] + length [31] protected ubyte[68][9] chCtrlLower; ///Lower parts of the channel controllers (0-31 / 32-63) + (Un)registered parameter select (64-65) public this() @safe nothrow { @@ -322,6 +323,8 @@ public class PCM8 : AudioModule { info.hasMidiOut = true; info.midiSendback = true; lfo = MultiTapOsc.init; + //reset master level controls + foreach (ref float elem ; masterLevels) elem = 1.0; } /** * Sets the module up. @@ -426,7 +429,7 @@ public class PCM8 : AudioModule { keyOff(data0.note, data0.channel, nv.velocity / ushort.max); break; case MIDI2_0Cmd.CtrlChOld, MIDI2_0Cmd.CtrlCh: - ctrlCh(data0.channel, data0.note, data1); + ctrlCh(data0.channel|(data0.group << 4), data0.note, data1); break; case MIDI2_0Cmd.PrgCh: channels[data0.channel].bankNum = data1 & ushort.max; @@ -502,21 +505,31 @@ public class PCM8 : AudioModule { channels[ch].envGen.keyOff(); channels[ch].status &= ~ChannelStatusFlags.noteOn; } - protected void ctrlCh(ubyte ch, ubyte param, uint val) @nogc pure nothrow { + protected void ctrlCh(int ch, ubyte param, uint val) @nogc pure nothrow { if (param >= 32 && param < 64) param &= 0x1F; - if (ch <= 7) { //Channel locals + if (ch <= 15) { //Channel locals switch (param) { case 7: channels[ch].presetCopy.masterVol = (1.0 / uint.max) * val; + channels[ch].ctrlFilter.setNextTarget(0, + channels[ch].presetCopy.masterVol * sqrt(channels[ch].presetCopy.balance), 64, 1.0 / 64.0); + channels[ch].ctrlFilter.setNextTarget(1, + channels[ch].presetCopy.masterVol * sqrt(1.0-channels[ch].presetCopy.balance), 64, 1.0 / 64); break; case 8: channels[ch].presetCopy.balance = (1.0 / uint.max) * val; + channels[ch].ctrlFilter.setNextTarget(0, + channels[ch].presetCopy.masterVol * sqrt(channels[ch].presetCopy.balance), 64, 1.0 / 64.0); + channels[ch].ctrlFilter.setNextTarget(1, + channels[ch].presetCopy.masterVol * sqrt(1.0-channels[ch].presetCopy.balance), 64, 1.0 / 64); break; case 91: channels[ch].presetCopy.auxSendA = (1.0 / uint.max) * val; + channels[ch].ctrlFilter.setNextTarget(2, channels[ch].presetCopy.auxSendA, 64, 1.0 / 64); break; case 92: channels[ch].presetCopy.auxSendB = (1.0 / uint.max) * val; + channels[ch].ctrlFilter.setNextTarget(3, channels[ch].presetCopy.auxSendB, 64, 1.0 / 64); break; case 73: channels[ch].presetCopy.eAtk = cast(ubyte)(val>>25); @@ -584,7 +597,7 @@ public class PCM8 : AudioModule { default: break; } - } else if (ch == 8) { //Module globals + } else { //Module globals switch (param) { case 2: filterCtrl[0] = (1.0 / uint.max) * val * 16_000; @@ -721,7 +734,7 @@ public class PCM8 : AudioModule { channels[msg[msgPos + 1]].decodeMore(sa, slmp); break; case 0x21: //Dump codec data - if (msg[msgPos + 1] >= 8) return; + if (msg[msgPos + 1] >= 16) return; uint[2] dump; const int delta = channels[msg[msgPos + 1]].decoderWorkpad.outn1; dump[0] = cast(uint)channels[msg[msgPos + 1]].decoderWorkpad.pos; @@ -733,13 +746,13 @@ public class PCM8 : AudioModule { dump[1]); break; case 0xA0: //Jump to sample position by restoring codec data (8bit) - if (msg[msgPos + 1] >= 8) return; - channels[msg[msgPos + 1]].decoderWorkpad.pos = (msg[msgPos + 2] << 24) | (msg[msgPos + 3] << 16) | - (msg[msgPos + 4] << 8) | msg[msgPos + 5]; + if (msg[msgPos + 1] >= 16) return; + channels[msg[msgPos + 1]].decoderWorkpad.pos = (msg[msgPos + 5] << 24) | (msg[msgPos + 4] << 16) | + (msg[msgPos + 3] << 8) | msg[msgPos + 2]; channels[msg[msgPos + 1]].waveModWorkpad.lookupVal = 0; if (msg.length == msgPos + 9) { channels[msg[msgPos + 1]].decoderWorkpad.pred = msg[msgPos + 6]; - channels[msg[msgPos + 1]].decoderWorkpad.outn1 = (msg[msgPos + 7]<<24) | (msg[msgPos + 8]<<16); + channels[msg[msgPos + 1]].decoderWorkpad.outn1 = (msg[msgPos + 8]<<24) | (msg[msgPos + 7]<<16); channels[msg[msgPos + 1]].decoderWorkpad.outn1>>=16; } //decode the sample @@ -779,6 +792,12 @@ public class PCM8 : AudioModule { } protected void presetRecall(ubyte ch) @nogc pure nothrow { channels[ch].presetCopy = presetBank[channels[ch].presetNum | (channels[ch].bankNum<<7)]; + channels[ch].ctrlFilter.setNextTarget(0, + channels[ch].presetCopy.masterVol * sqrt(channels[ch].presetCopy.balance), 64, 1.0 / 64); + channels[ch].ctrlFilter.setNextTarget(1, + channels[ch].presetCopy.masterVol * sqrt(1.0 - channels[ch].presetCopy.balance), 64, 1.0 / 64); + channels[ch].ctrlFilter.setNextTarget(2, channels[ch].presetCopy.auxSendA, 64, 1.0 / 64); + channels[ch].ctrlFilter.setNextTarget(3, channels[ch].presetCopy.auxSendB, 64, 1.0 / 64); } /** * Creates a decoder function. @@ -824,7 +843,7 @@ public class PCM8 : AudioModule { for (int i ; i < bufferSize ; i++) { lfoOut[i] = lfo.outputF(0.5, 1.0 / ushort.max); } - for (int i ; i < 8 ; i++) { + for (int i ; i < channels.length ; i++) { if (!(channels[i].currNote & 128) && channels[i].jumpAm) { channels[i].calculateJumpAm(sampleRate, lfoOut[0]); //get the data for the sample @@ -856,18 +875,20 @@ public class PCM8 : AudioModule { outpos += samplesOutputted; //shift the output position by the amount of the outputted samples } //apply envelop (if needed) and volume, then mix it to the local buffer - __m128 levels; - levels[0] = channels[i].presetCopy.masterVol * channels[i].presetCopy.balance; - levels[1] = channels[i].presetCopy.masterVol * (1 - channels[i].presetCopy.balance); - levels[2] = channels[i].presetCopy.auxSendA; - levels[3] = channels[i].presetCopy.auxSendB; + __m128 levels = __m128(masterLevels[i] * masterLevels[16]); + __m128 sample; + float adsrEnv; + //levels[0] *= channels[i].presetCopy.masterVol * channels[i].presetCopy.balance; + //levels[1] *= channels[i].presetCopy.masterVol * (1 - channels[i].presetCopy.balance); + //levels[2] *= channels[i].presetCopy.auxSendA; + //levels[3] *= channels[i].presetCopy.auxSendB; for (int j ; j < bufferSize ; j++) { - __m128 sample = _mm_cvtepi32_ps(__m128i(iBuf[j])); - const float adsrEnv = channels[i].envGen.shp(channels[i].envGen.position == ADSREnvelopGenerator.Stage.Attack ? + sample = _mm_cvtepi32_ps(__m128i(iBuf[j])); + adsrEnv = channels[i].envGen.shp(channels[i].envGen.position == ADSREnvelopGenerator.Stage.Attack ? channels[i].currShpA : channels[i].currShpR) * channels[i].presetCopy.adsrToVol; channels[i].envGen.advance(); sample *= __m128((1 - channels[i].presetCopy.adsrToVol) + adsrEnv) * __m128((1 - channels[i].presetCopy.lfoToVol) + - (lfoOut[j] * channels[i].presetCopy.lfoToVol)) * levels; + (lfoOut[j] * channels[i].presetCopy.lfoToVol)) * levels * channels[i].ctrlFilter.output(__m128(1.0 / 64.0)); lBuf[j] += sample; } resetBuffer(iBuf); @@ -1434,6 +1455,14 @@ public class PCM8 : AudioModule { * Returns: The new level, or NaN if either channel number or value is out of bounds */ public override float setMasterLevel(float level, int channel = -1) @nogc nothrow { + if (level < 0.0 || level > 0.0) return float.nan; + if (channel == -1) { + masterLevels[16] = level; + return level; + } else if (channel >= 0 && channel <= 15) { + masterLevels[channel] = level; + return level; + } return float.nan; } } diff --git a/pixelperfectengine/src/pixelperfectengine/audio/modules/qm816.d b/pixelperfectengine/src/pixelperfectengine/audio/modules/qm816.d index 59b5a7d..bd33ac5 100644 --- a/pixelperfectengine/src/pixelperfectengine/audio/modules/qm816.d +++ b/pixelperfectengine/src/pixelperfectengine/audio/modules/qm816.d @@ -365,9 +365,9 @@ public class QM816 : AudioModule { ///Either used for audible output, or to modulate other operators int output_0; ///Key scale level factor for output level - float outKSL = 0; + float outKSL = 1.0; ///Key scale level factor for feedback level + phase - float fbKSL = 0; + float fbKSL = 1.0; ///Live calculated out of shpA float shpA0 = 0.0; ///Live calculated out of shpR @@ -479,7 +479,7 @@ public class QM816 : AudioModule { fbKSL = (preset.opCtrl & OpCtrlFlags.FBNeg ? -1 : 1) * (1.0 - (octaves * ((preset.kslAttenFB / ubyte.max) * 0.75))); } else { - outKSL = 0.0; + outKSL = 1.0; fbKSL = (preset.opCtrl & OpCtrlFlags.FBNeg ? -1 : 1); } } @@ -621,12 +621,12 @@ public class QM816 : AudioModule { */ void recalculateOutLevels() @nogc @safe pure nothrow { const float lc = 1.0 / 64.0; - if (preset.chCtrl == ChCtrlFlags.IndivOutChLev) { + if (preset.chCtrl & ChCtrlFlags.IndivOutChLev) { outLevels.setNextTarget(0, preset.masterVol, 64, lc); outLevels.setNextTarget(1, preset.masterBal, 64, lc); } else { outLevels.setNextTarget(0, preset.masterVol * sqrt(preset.masterBal), 64, lc); - outLevels.setNextTarget(1, preset.masterVol * (1 - sqrt(preset.masterBal)), 64, lc); + outLevels.setNextTarget(1, preset.masterVol * sqrt(1.0 - preset.masterBal), 64, lc); } outLevels.setNextTarget(2, preset.auxSendA, 64, lc); outLevels.setNextTarget(3, preset.auxSendB, 64, lc); @@ -930,6 +930,8 @@ public class QM816 : AudioModule { for (int i ; i < chDeleg.length ; i++) { chDeleg[i] = &updateChannelM00; } + //reset master level controls + foreach (ref float elem ; masterLevels) elem = 1.0; } /** * Sets the module up. @@ -1882,10 +1884,11 @@ public class QM816 : AudioModule { case ChannelParamNums.Bal: channels[chNum].preset.masterBal = cast(double)val / uint.max; if (channels[chNum].preset.chCtrl & ChCtrlFlags.IndivOutChLev) { - channels[chNum].outLevels.setNextTarget(1, channels[chNum].preset.masterBal * channels[chNum].preset.masterBal, 64, levelFF); + channels[chNum].outLevels.setNextTarget(1, + channels[chNum].preset.masterBal * channels[chNum].preset.masterBal, 64, levelFF); } else { channels[chNum].outLevels.setNextTarget(0, channels[chNum].preset.masterVol * sqrt(channels[chNum].preset.masterBal), 64, levelFF); - channels[chNum].outLevels.setNextTarget(1, channels[chNum].preset.masterVol * (1.0 - sqrt(channels[chNum].preset.masterBal)), 64, levelFF); + channels[chNum].outLevels.setNextTarget(1, channels[chNum].preset.masterVol * sqrt(1.0 - channels[chNum].preset.masterBal), 64, levelFF); } break; case ChannelParamNums.ChCtrl: @@ -1918,7 +1921,7 @@ public class QM816 : AudioModule { channels[chNum].outLevels.setNextTarget(0, channels[chNum].preset.masterVol * channels[chNum].preset.masterVol, 64, levelFF); } else { channels[chNum].outLevels.setNextTarget(0, channels[chNum].preset.masterVol * sqrt(channels[chNum].preset.masterBal), 64, levelFF); - channels[chNum].outLevels.setNextTarget(1, channels[chNum].preset.masterVol * (1.0 - sqrt(channels[chNum].preset.masterBal)), 64, levelFF); + channels[chNum].outLevels.setNextTarget(1, channels[chNum].preset.masterVol * sqrt(1.0 - channels[chNum].preset.masterBal), 64, levelFF); } break; case ChannelParamNums.PLFO: @@ -2358,7 +2361,7 @@ public class QM816 : AudioModule { ///Macro for output mixing static immutable string CHNL_UPDATE_MIX = q{ - outlevels = mwAuxCtrl * chMast; + outlevels = mwAuxCtrl * chMast * channels[chNum].outLevels.output(__m128(levelFF)); outlevels *= (channels[chNum].preset.eegLevels * eegToMast) + (__m128(1.0) - channels[chNum].preset.eegLevels); outlevels *= (channels[chNum].preset.aLFOlevels * lfoToMast) + (__m128(1.0) - channels[chNum].preset.aLFOlevels); initBuffers[i + 2] += outlevels * outSum; @@ -2366,7 +2369,7 @@ public class QM816 : AudioModule { ///Macro for output mixing in case of combo modes static immutable string CHNL_UPDATE_MIX0 = q{ - outlevels = mwAuxCtrl * chMast; + outlevels = mwAuxCtrl * chMast * channels[chNum].outLevels.output(__m128(levelFF)); outlevels *= (channels[chNum].preset.eegLevels * eegToMast) + (__m128(1.0) - channels[chNum].preset.eegLevels); outlevels *= (channels[chNum + 8].preset.eegLevels * eegToMast0) + (__m128(1.0) - channels[chNum + 8].preset.eegLevels); outlevels *= (channels[chNum].preset.aLFOlevels * lfoToMast) + (__m128(1.0) - channels[chNum].preset.aLFOlevels);