Skip to content

Commit

Permalink
Implementation of level filtering and external master level overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
ZILtoid1991 committed Jun 16, 2024
1 parent 4852fec commit 11c4d0a
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 167 deletions.
136 changes: 1 addition & 135 deletions pixelperfectengine/src/pixelperfectengine/audio/modules/delaylines.d
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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] +
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down
73 changes: 51 additions & 22 deletions pixelperfectengine/src/pixelperfectengine/audio/modules/pcm8.d
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 () {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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.
Expand Down Expand 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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
Loading

0 comments on commit 11c4d0a

Please sign in to comment.