Experiments with Faust DSP code
Back to Index.
I started experimenting with Faust DSP code back in 2018, and it soon became a very useful tool for building audio and SDR processing stages to perform all kinds of functions such as dynamics control, demodulation, EQing, frequency shifting and many other things.
A Virtual Audio Cable (like VB Cable) can be used to route audio into these apps on a Windows system.
Usually, the apps above will use the default audio devices for I/O.
NLMS NR Filter
From Google:
Adaptive filters were invented in 1960 by Stanford University professor Bernard Widrow and his Ph.D. student, Ted Hoff.
The practical application of adaptive filters for cancelling interference (adaptive noise cancelling) was further developed and demonstrated by Widrow and John Kaunitz in 1971–72, with the work published in 1975.
SETTINGS
HF slope boost: Default 0.4
Used to boost the high frequencies, which helps the NR track them better.
Adapt Constant: Default 0.8
Adapt Beta: Default 480
Both scale the error feedback to increase or decrease the adaptation gain, and they interact.
Coefficient Lowpass: Default 35
Was found to reduce distortion and improve filter performance. It slows the rate-of-change of the coefficients.
Coefficient Pole: Default 0.987
A parallel, lossy integrator that averages the adaptation of each filter coefficient.
This NLMS filter reduces noise and distortion, and enhances audio content like speech and music:
//Faust adaptive filter Jan 2026 DazDSP
import ("filters.lib");
process = _*vin,_*vin:NLMS,NLMS:_*vot,_*vot; //two instances for stereo
order = 100;
//Controls
vin = hslider("[00]NR Volume In", 0.9, 0, 1, 0.01);
vot = hslider("[01]NR Volume Out", 0.9, 0, 1, 0.01);
slo = hslider("[02]NR HF Slope Boost", 0.4, -1, 1, 0.01);
con = hslider("[03]NR Adapt Constant", 0.8, 0, 2, 0.001);
muu = hslider("[04]NR Adapt Beta", 480, 0, 2000, 1)/100000;
nrl = hslider("[05]NR Coeff Lowpass Hz",35,20,300,1);
iii = hslider("[06]NR Coeff Pole", 0.987, 0, 1, 0.0001);
spectilt1 = spectral_tilt(3,50,10000,slo);
spectilt2 = spectral_tilt(3,50,10000,0-slo);
//Normalized Least Mean Squares adaptive noise filter DazDSP 2026
NLMS = spectilt1:pmac2:spectilt2 with {
pmac2(x) = (_,x:finderr <: par(i,order,(_,_,i:mmac2)) :> _)~(_);
ee = 0.00001; //prevent divide by zero
//pout is previous filter output
//adapt all the coefficients on every sample by cc*mu*err*inputdatapoints
finderr(pout,x) = x,(x-pout)*(muu/(sqrt(pout*pout+ee))); //Normalize gain to prevent distortion
//parallel stucture of FIR filter
mmac2(x,ef,i) = (_,x,ef,i:mmac)~(_):!,_; //apply feedback of last output
//p is previous coefficient output (it circles back to be updated next sample)
mmac(p,x,ef,i) = out with {
din = x@i;
coeff = (p*iii)+con*ef*din:lowpass(2,nrl); //adding a lowpass here reduced distortion at low pole settings
out = coeff,coeff*din; //multiply each delayed audio sample by coefficients
};
};
AM Sync ISB Demodulator
This takes an IQ input from an SDR and demodulates it to LSB on Left and USB on Right:
//Frequency locked AM Sync ISB Demodulator Jan 2026 DazDSP
import ("basics.lib");
import ("filters.lib");
import ("analyzers.lib");
import ("maths.lib");
import ("delays.lib");
import ("signals.lib");
import ("misceffects.lib");
import ("noises.lib");
import ("oscillators.lib");
import ("aanl.lib");
ee = -90:db2linear;
tune = hslider("[00]Carrier Fine Tune Hz",0,-50,+50,1); //
drt = hslider("[89]AGC DSB Boost Ratio",4.8,1,10,0.1);
ola = hslider("[90]AGC Lookahead mS",2,0,1000,1)*44.1; //lookahead adjust
att = hslider("[91]AGC Attack mS",2,0,500,1)/1000; //attack
rec = hslider("[92]AGC Recovery mS",500,0,1000,1)/1000; //recovery
thr = hslider("[95]AGC Threshold dB",-50,-100,0,1):db2linear; //set a sensible threshold
beq = hslider("[95]EQ BW",100,30,150,1);
feq = hslider("[95]EQ Freq Hz",35,30,150,1);
deq = hslider("[95]EQ dB",0,-20,20,1);
vol = hslider("[98]Volume dB",-12,-90,12,1):db2linear;
isb = checkbox("[71]IQ output") > 0.5;
mm = checkbox("[72]Force mono output") > 0.5;
sm = checkbox("[73]Mono ISB Output") < 0.5;
process = _,_:
vgroup("Daz DSP - Frequency Locked AM Sync ISB Demod",
finetune: //Manual fine tuning
FLDemod: //Frequency locked demod
DCphaseint: //Demod phase lock
ISB: //ISB matrixing
bb: //L/R bass blend
Mono: //Blend L and R
AAGC: //DC and Audio AGC
EQ,EQ: //Bass boost EQ
Vol(vol) //Set output volume
);
//======================================================================================
//Manual fine tune
finetune(i,q) = mix1,mix2 with {
//correct tuning here so that the carrier reject filter works properly
io = os.oscp(tune,0); //I osc
qo = os.oscp(tune,1.5708); //Q osc
mix1 = (i*io)-(q*qo); //+tune
mix2 = (q*io)+(i*qo); //+tune
};
//======================================================================================
FLDemod = (FLDemod2)~(_):!,_,_;
FLDemod2(ftf,i,q) = ftt,out with {
regen1 = ftf,i,q:squaring;
regen = regen1:upmix(ucf); //regen carrier from sidebands if needed then upmix
//convert iq up in freq after lpf then measure freq and perform forward frequency lock
ucf = 114+ftf;
iif = i:lowpass6e(ucf*0.5);
qqf = q:lowpass6e(ucf*0.5);
iu = regen:_,!;
qu = regen:!,_;
ft1 = iu:pitchTracker(0,0.01)-ucf;
ft2 = qu:pitchTracker(0,0.01)-ucf;
ftt = (ft1+ft2)*0.5:avg_rect(1):lowpass(1,1):hbargraph("[00]Frequency Tracking",-50,50);
ft = 0-ftt;
qo = os.oscp(ftt,1.5708);
io = os.oscp(ftt,0);
//add freq acq lookahead here
fadela = 22050;
iin = i@fadela;
qin = q@fadela;
out = upmixany(iin,qin,io,qo);
};
//======================================================================================
upmix(uptune,id,qd) = out with {
io = oscp(uptune,0); //I osc
qo = oscp(uptune,1.5708); //Q osc
out = (id*io)+(qd*qo),(qd*io)-(id*qo);
};
//======================================================================================
dnmix(dntune,id,qd) = out with {
io = oscp(0-dntune,0); //I osc
qo = oscp(0-dntune,1.5708); //Q osc
out = (id*io)+(qd*qo),(qd*io)-(id*qo);
};
//======================================================================================
upmixany(id,qd,io,qo) = (id*io)-(qd*qo),(qd*io)+(id*qo);
//======================================================================================
//IIR Hilbert transform //out(t) = a^2*(in(t) + out(t-2)) - in(t-2)
ia = allpass1mdaz(0.6923878):allpass1mdaz(0.9360654322959):allpass1mdaz(0.9882295226860):allpass1mdaz(0.9987488452737):mem;
qa = allpass1mdaz(0.4021921162426):allpass1mdaz(0.8561710882420):allpass1mdaz(0.9722909545651):allpass1mdaz(0.9952884791278);
allpass1mdaz(a,x) = (_:mem,x,a:+,_:*:_,x:_,mem:_,mem:-) ~ _ ;
//======================================================================================
//Hilbert clipper
hc(t,i,q) = i*gain,q*gain with {
gain= 0.5/sqrt((i^2) + (q^2) + ee); //threshold is required to prevent divide by zero error crashing the audio
};
//======================================================================================
ISB = ia,qa:matrix;
matrix(ii,qq) = (ii+qq,ii-qq),ii,qq:select2stereo(isb);
//======================================================================================
Mono(l,r) = out with {
lrsum = (l+r)*0.5;
out = l,r,lrsum,lrsum:select2stereo(mm);
};
//======================================================================================
Vol(v,l,r) = l*v,r*v;
//======================================================================================
EQ = peak_eq(deq,feq,beq);
//======================================================================================
//Recover carrier freq from carrier OR sidebands
squaring(f,i,q) = out with {
carupmixfreq = 200-f;
upmixed = i,q:upmix(carupmixfreq); //upmix this so it's always a positive offset
bw=30;
carlpf = 20; //filter freq to separate carrier from modulation
db = 50; //db gain for carrier filter peak
bp1 = peak_eq(db,carupmixfreq+f,bw); //peak carrier freq
bp2 = bp1,bp1;
car = upmixed:copy:upmixany:div2:dnmix(carupmixfreq):lp2;
lp2 = lp1,lp1;
lp1 = lowpass(2,carlpf):lowpass(2,carlpf); //final filter to clean up spurii
copy(i,q) = i,q,i,q;
//Regenerative IQ frequency mixer.. 400Hz in 200Hz out
div2 = (div23)~(_,_);
div23(im,qm,ix,qx) = (ix*im)+(qx*qm),(qx*im)-(ix*qm):bp2:addnoise:bp2:hc(-90); //mix, filter, Hilbert clip
addnoise(i,q) = i+(noise*db2linear(-190)),q+(noise*db2linear(-190)); //add noise to start the process
out = car;
};
//======================================================================================
//Phase locked loop for AM demod (phase only)
DCphaseint = (DCphase2i)~(_):!,_,_;
DCphase2i(pp,i,q) = out with {
qm = cos(pp);
im = sin(pp);
lad = 2000; //lookahead in samples
dephase = i,q,im,qm:upmixany;
dephased = i@lad,q@lad,im,qm:upmixany:pm; //with lookahead
gainadj = 0.1; //hslider("Phase Loop Gain",0,-30,60,1):db2linear;
result = dephase:!,_*gainadj:pole(1):lowpass(1,20);
pm(i,q) = i,attach(q,q:lowpass(1,20):hbargraph("[01]DC Phase Error",-1,1));
out = result,dephased;
};
//======================================================================================
bb(l,r) = out with {
blend = 120; //blend below this frequency
//Linkwitz filters
ll = l:lowpass(2,blend):lowpass(2,blend);
rl = r:lowpass(2,blend):lowpass(2,blend);
lh = l:highpass(2,blend):highpass(2,blend);
rh = r:highpass(2,blend):highpass(2,blend);
out = (ll+rl+lh),(ll+rl+rh);
};
//======================================================================================
AAGC(l,r) = out:m2 with {
//Audio + carrier DC AGC DazDSP Jan 2026
lim(i) = attach(i,i:linear2db:hbargraph("[51]LSB In dB[unit:dB]",-90,10));
rim(i) = attach(i,i:linear2db:hbargraph("[52]USB In dB[unit:dB]",-90,10));
m2 = lm,rm with {
lm(i) = attach(i,abs(i):amp_follower(0.1):linear2db:hbargraph("[53]LSB (L) Out[unit:dB]",-40,0));
rm(i) = attach(i,abs(i):amp_follower(0.1):linear2db:hbargraph("[54]USB (R) Out[unit:dB]",-40,0));
};
lp = lowpass(1,20);
lp2 = lp,lp;
dcamp = l,r:lp2:abs,abs:max:hbargraph("AGC CAR Level",0,1);
lamp = l:amp1:max(thr):tc;
ramp = r:amp1:max(thr):tc;
amp1(x) = x:highpass(1,20):abs;
//if the detected carrier level fades higher than the DSB over the last 2 seconds, clip it off (but only 50% max)
carclip = bothamp1:amp_follower(2),dcamp:min;//:min(dcamp*0.5);
bothamp1 = lamp,ramp:max:nc*drt;
bothamp = bothamp1:hbargraph("AGC DSB Level",0,1),carclip:max:tc:hbargraph("AGC Level Detect",0,1);
//Noise Clipper
nc(x) = x*ncg:highpass(1,100):abs:amp_follower(0.001):x-_:max(ee):min(1);
ncg = 1; //try 200?
tc = amp_follower_ar(att,rec); //fast TC
level = 0.5;
gain = level/bothamp;
hpff = 35;
lh = l:highpass6e(hpff);
rh = r:highpass6e(hpff);
out = gain*lh@ola,gain*rh@ola;
}; //end of AGC
//======================================================================================
That's all for now.. Might add some more later...