Skip to content

Instantly share code, notes, and snippets.

@jpcima
Created August 13, 2019 18:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpcima/5f38a16ca61d3996bc488e330966e216 to your computer and use it in GitHub Desktop.
Save jpcima/5f38a16ca61d3996bc488e330966e216 to your computer and use it in GitHub Desktop.
declare name "StonePhaser";
declare author "JPC";
declare version "1.1";
declare license "CC0-1.0";
// Référence :
// Kiiski, R., Esqueda, F., & Välimäki, V. (2016).
// Time-variant gray-box modeling of a phaser pedal.
// In 19th International Conference on Digital Audio Effects (DAFx-16).
import("stdfaust.lib");
/////////////
// Control //
/////////////
bypass = checkbox("[0] Bypass");
lfotype = checkbox("[1] Color");
lf = hslider("[2] LFO frequency [unit:Hz] [scale:log]", 0.15, 0.01, 1., 0.01);
w = hslider("[3] Wet gain [unit:dB]", 0., -60., 20., 1.) : ba.db2linear : si.smoo;
d = hslider("[4] Dry gain [unit:dB]", 0., -60., 20., 1.) : ba.db2linear : si.smoo;
fb = hslider("[5] Feedback", 0.5, 0., 0.9, 0.01);
ph = hslider("[6] Stereo phase [unit:deg]", 0., 0., 359., 1.) : /(360.) : si.smoo;
fbCutoff = hslider("[9] Feeback HPF cutoff [unit:Hz]", 5000., 100., 15000., 1.) : si.smoo;
//////////////////////////
// All-pass filter unit //
//////////////////////////
allpass1(f) = fi.iir((a,1.),(a)) with {
a = -1.+2.*ma.PI*f/ma.SR;
};
//////////////////////
// High-pass filter //
//////////////////////
highpass1(f) = fi.iir((0.5*(1.+p), -0.5*(1.+p)), (-p)) with {
p = exp(-2.*ma.PI*f/ma.SR);
};
//////////
// LFOs //
//////////
lfoTriangle(pos, y1, y2) = val*(y2-y1)+y1 with {
val = 1.-abs(2.*pos-1.);
};
lfoRectifiedSine(pos, y1, y2) = val*(y2-y1)+y1 with {
val = rsin(pos);
};
lfoAnalogTriangle(roundness, pos, y1, y2) = val*(y2-y1)+y1 with {
val = sineTri(roundness, pos);
};
////////////
// Phaser //
////////////
mono_phaser(x, lfo_pos) = ba.if(bypass, x, dry + wet) with {
dry = x*d;
wet = (x <: (+:a1:a2:a3:a4)~feedback)*w;
feedback = highpass1(fbCutoff) : *(ba.if(lfotype, fb, 0.1*fb));
modFreq = ba.midikey2hz(ba.if(lfotype,
lfoAnalogTriangle(0.95, lfo_pos, ba.hz2midikey(40.), ba.hz2midikey(2500.)),
lfoAnalogTriangle(0.95, lfo_pos, ba.hz2midikey(300.), ba.hz2midikey(6000.))));
a1 = allpass1(modFreq);
a2 = allpass1(modFreq);
a3 = allpass1(modFreq);
a4 = allpass1(modFreq);
};
stereo_phaser(x1, x2, lfo_pos) = mono_phaser(x1, lfo_pos), mono_phaser(x2, lfo_pos2) with {
lfo_pos2 = wrap(lfo_pos + ph);
wrap(p) = p-float(int(p));
};
/////////////
// Utility //
/////////////
lerp(tab, pos, size) = (tab(i1), tab(i2)) : si.interpolate(mu) with {
fracIndex = pos*size;
i1 = int(fracIndex);
i2 = (i1+1)%size;
mu = fracIndex-float(i1);
};
rsin(pos) = lerp(tab, pos, ts) with {
ts = 128;
tab(i) = rdtable(ts, abs(os.sinwaveform(ts)), i);
};
sineTriWaveform(roundness, tablesize) = 1.-sin(2.*ba.if(x<0.5, x, 1.-x)*asin(a))/a with {
a = max(0., min(1., roundness * 0.5 + 0.5));
x = wrap(float(ba.time)/float(tablesize));
wrap(p) = p-float(int(p));
};
sineTri(roundness, pos) = lerp(tab, pos, ts) with {
ts = 128;
tab(i) = rdtable(ts, sineTriWaveform(roundness, ts), i);
};
/*
# Gnuplot code of the sineTri function
sineTri(r, x)=sineTri_(r, wrap(x+0.5))
sineTri_(r, x)=1.-sin(((x<0.5)?x:(1.-x))*2.*asin(r))/r
wrap(x)=x-floor(x)
set xrange [0:1]
plot(sineTri(0.99, x))
*/
//////////
// Main //
//////////
process_mono(x) = mono_phaser(x, os.lf_sawpos(lf));
process_stereo(x1, x2) = stereo_phaser(x1, x2, os.lf_sawpos(lf));
process = process_mono;
@jujudusud
Copy link

C'est très beau :-)

@SpotlightKid
Copy link

Sounds very nice. Tried this on just a simple sawtooth based sound from my Reface DX. Rather subtle, but with lots of reverb brings out the different tone colours nicely.

The LFO frequency range is rather small. Would be interesting to be able to try higher frequencies.

@SpotlightKid
Copy link

Sounds very nice.

Forget that. Sounds fantastic :)

@jpcima
Copy link
Author

jpcima commented Aug 14, 2019

Sounds very nice. Tried this on just a simple sawtooth based sound from my Reface DX. Rather subtle, but with lots of reverb brings out the different tone colours nicely.

Subtle yes, I compared it against a reputable proprietary program which is a decent emulation of the phaser, since I don't have the pedal, and applied some of the technique from gray-box modeling methods.
From what I could see, my filter notch does not affect as much the gain as the reference.

This is a reference analysis signal. (same as from the paper)
https://gist.github.com/jpcima/6c0d10e426eca9622df6476d0cbdec98
It's applicable at the phaser input and you can see clearly the evolution of the notch position. (in JAPA analysis software, do A/B spectral analysis, connect phaser's output to A and generator to B)

Also, next I should reexpress the LFO so it doesn't rely on exponential, it should be tabulated.
The original has a notch which moves smoothly, in triangle fashion, in the pitch/log-frequency domain.

The LFO frequency range is rather small. Would be interesting to be able to try higher frequencies.

Yeah it's not hard to edit it, my annoyance with faust2jack UI is that it doesn't want to consider my scale:log hint.
I would like that small frequency values occupy more slider space than the higher.
Anyway it's not going a problem when this will be a dpf plugin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment