Skip to content

Commit

Permalink
Add low pass filter.
Browse files Browse the repository at this point in the history
  • Loading branch information
klknn committed Feb 22, 2021
1 parent 3a92c27 commit e8ee1bc
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 3 deletions.
11 changes: 10 additions & 1 deletion source/synth2/client.d
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import dplug.client.params : Parameter;
import dplug.client.midi : MidiMessage, makeMidiMessageNoteOn;
import mir.math.common : exp2, log, sqrt;

import synth2.filter : Filter2Pole, FilterKind;
import synth2.gui : Synth2GUI;
import synth2.oscillator : Oscillator, Waveform, waveformNames;
import synth2.params : Params, ParamBuilder, paramNames;
Expand Down Expand Up @@ -70,6 +71,7 @@ class Synth2Client : Client {
}
this._osc2.setSampleRate(sampleRate);
this._oscSub.setSampleRate(sampleRate);
this._filter2p.setSampleRate(sampleRate);
}

override void processAudio(const(float*)[] inputs, float*[] outputs,
Expand Down Expand Up @@ -146,6 +148,12 @@ class Synth2Client : Client {
if (useOsc2) _osc2.updateFreq();
if (oscSubVol != 0) _oscSub.updateFreq();

_filter2p.setParams(
readParam!FilterKind(Params.filterKind),
readParam!float(Params.filterCutoff) - ParamBuilder.logBias,
readParam!float(Params.filterQ),
);

// Generate samples.
foreach (frame; 0 .. frames) {
// osc1
Expand Down Expand Up @@ -176,14 +184,15 @@ class Synth2Client : Client {
output += oscSubVol * _oscSub.front;
_oscSub.popFront();
}
outputs[0][frame] = ampGain * output;
outputs[0][frame] = ampGain * _filter2p.apply(output);
}
foreach (chan; 1 .. outputs.length) {
outputs[chan][0 .. frames] = outputs[0][0 .. frames];
}
}

private:
Filter2Pole _filter2p;
Oscillator _osc2, _oscSub;
Oscillator[8] _osc1s; // +7 for detune
Synth2GUI _gui;
Expand Down
105 changes: 105 additions & 0 deletions source/synth2/filter.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/// Filter constants are based on these books
/// https://cs.gmu.edu/~sean/book/synthesis/Synthesis.pdf
/// https://www.discodsp.net/VAFilterDesign_2.1.0.pdf
module synth2.filter;

import mir.math : approxEqual, PI, SQRT2;

@nogc nothrow @safe pure:

enum FilterKind {
lowpass,
highpass,
bandpass,
notch,
}

static immutable filterNames = [__traits(allMembers, FilterKind)];

struct Filter2Pole {
enum nFIR = 3;
enum nIIR = 2;

@nogc nothrow @safe pure:

void clear() {
x[] = 0f;
y[] = 0f;
}

float apply(float input) {
// TODO: use ring buffer
static foreach_reverse (i; 1 .. nFIR) {
x[i] = x[i - 1];
}
x[0] = input;

float output = 0;
static foreach (i; 0 .. nFIR) {
output += b[i] * x[i];
}
static foreach (i; 0 .. nIIR) {
output -= a[i] * y[i];
}

static foreach_reverse (i; 1 .. nIIR) {
y[i] = y[i - 1];
}
y[0] = output;
return output;
}

void setSampleRate(float sampleRate) {
sampleRate = sampleRate;
this.clear();
}

void setParams(FilterKind kind, float freqPercent, float q) {
q += 1f / SQRT2;
float t =1f / sampleRate;
float w = 2f * PI * freqPercent / 100f * sampleRate;
float j = 4f * q + 2f * w * t + w * w * q * t * t;

if (kind == FilterKind.lowpass) {
b[0] = 1f / j * w * w * q * t * t;
b[1] = 1f / j * 2f * w * w * q * t * t;
b[2] = 1f / j * w * w * q * t * t;
a[0] = 1f / j * (-8f * q + 2f * w * w * q * t * t);
a[1] = 1f / j * (4f * q - 2f * w * t + w * w * q * t * t);
}
else {
assert(false, "not implemented");
}
}

private:
float sampleRate = 44100;
// filter and prev inputs
float[nFIR] b, x;
// filter and prev outputs
float[nIIR] a, y;
}

unittest {
Filter2Pole f;
f.setSampleRate(20);
f.setParams(FilterKind.lowpass, 5, 2);

// with padding
auto y0 = f.apply(0.1);
assert(approxEqual(y0, f.b[0] * 0.1));

auto y1 = f.apply(0.2);
assert(approxEqual(y1, f.b[0] * 0.2 + f.b[1] * 0.1 - f.a[0] * y0));

auto y2 = f.apply(0.3);
assert(approxEqual(y2,
f.b[0] * 0.3 + f.b[1] * 0.2 + f.b[0] * 0.1
-f.a[0] * y1 - f.a[1] * y0));

// without padding
auto y3 = f.apply(0.4);
assert(approxEqual(y3,
f.b[0] * 0.4 + f.b[1] * 0.3 + f.b[0] * 0.2
-f.a[0] * y2 - f.a[1] * y1));
}
24 changes: 22 additions & 2 deletions source/synth2/params.d
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import dplug.client.params : BoolParameter, EnumParameter, FloatParameter,
Parameter, PowFloatParameter;

import synth2.oscillator : Waveform, waveformNames;

import synth2.filter : filterNames, FilterKind;

/// Parameter ids.
enum Params : int {
Expand Down Expand Up @@ -45,6 +45,11 @@ enum Params : int {
ampRelease,
ampGain,
ampVel,

/// Filter section
filterKind,
filterCutoff,
filterQ,
}

static immutable paramNames = [__traits(allMembers, Params)];
Expand Down Expand Up @@ -152,7 +157,22 @@ struct ParamBuilder {
return mallocNew!LinearFloatParameter(
Params.ampVel, "Amp/Vel", "", 0, 1.0, 0);
}


static filterKind() {
return mallocNew!EnumParameter(
Params.filterKind, "Filter/kind", filterNames, FilterKind.lowpass);
}

static filterCutoff() {
return mallocNew!LogFloatParameter(
Params.filterCutoff, "Filter/cutoff", "%", logBias, 100, logBias);
}

static filterQ() {
return mallocNew!LinearFloatParameter(
Params.filterQ, "Filter/Q", "%", 0, 100, 0);
}

@nogc nothrow:
static Parameter[] buildParameters() {
auto params = makeVec!Parameter(EnumMembers!Params.length);
Expand Down

0 comments on commit e8ee1bc

Please sign in to comment.