Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

commented Aug 13, 2019

C'est très beau :-)

@SpotlightKid

This comment has been minimized.

Copy link

commented Aug 13, 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.

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

@SpotlightKid

This comment has been minimized.

Copy link

commented Aug 13, 2019

Sounds very nice.

Forget that. Sounds fantastic :)

@jpcima

This comment has been minimized.

Copy link
Owner Author

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
You can’t perform that action at this time.