//instrument.lib - Faust function of various types usefull for building physical model instruments declare name "Faust-STK Tools Library"; declare author "Romain Michon (rmichon@ccrma.stanford.edu)"; declare copyright "Romain Michon"; declare version "1.0"; declare licence "STK-4.3"; // Synthesis Tool Kit 4.3 (MIT style license); import("math.lib"); import("filter.lib"); import("effect.lib"); //========================= ENVELOPE GENERATORS =============================== //----------------------- VIBRATO ENVELOPE ---------------------------- // 4 phases envelope to control vibrato gain // // USAGE: // _ : *(envVibrato(b,a,s,r,t)) : _ // where // b = beginning duration (silence) in seconds // a = attack duration in seconds // s = sustain as a percentage of the amplitude to be modified // r = release duration in seconds // t = trigger signal envVibrato(b,a,s,r,t) = env ~ (_,_,_) : (!,!,_) // the 3 'state' signals are fed back with { env (p2,cnt,y) = (t>0) & (p2|(y>=1)), (cnt + 1)*(t>0), // counter for the first step "b" (y + p1*p3*u*(s/100) - p4*w*y)*((p4==0)|(y>=eps)) // y = envelop signal //*(y>=eps) // cut off tails to prevent denormals with { p1 = (p2==0) & (t>0) & (y<1) & (cnt>(b*SR)); // p1 = attack phase p3 = 1-(cnt<(nb)); // p3 = beginning phase p4 = (t<=0) & (y>0); // p4 = release phase // #samples in attack, release, must be >0 nb = SR*b+(b==0.0) ; na = SR*a+(a==0.0); nr = SR*r+(r==0.0); // attack and (-60dB) release rates z = s+(s==0.0)*db2linear(-60); u = 1/na; w = 1-1/pow(z*db2linear(60), 1/nr); // values below this threshold are considered zero in the release phase eps = db2linear(-120); }; }; //----------------------- ATTACK - SUSTAIN - RELEASE ---------------------------- // Attack - Sustain - Release envelope // // USAGE: // _ : *(asr(a,s,r,t)) : _ // where // a = attack duration in seconds // s = sustain as a percentage of the amplitude to be modified // r = release duration in seconds // t = trigger signal asr(a,s,r,t) = env ~ (_,_) : (!,_) // the 2 'state' signals are fed back with { env (p2,y) = (t>0) & (p2|(y>=1)), (y + p1*u*(s/100) - p3*w*y) // y = envelop signal *((p3==0)|(y>=eps)) // cut off tails to prevent denormals with { p1 = (p2==0) & (t>0) & (y<1); // p1 = attack phase p3 = (t<=0) & (y>0); // p3 = release phase // #samples in attack, release, must be >0 na = SR*a+(a==0.0); nr = SR*r+(r==0.0); // correct zero sustain level z = s+(s==0.0)*db2linear(-60); // attack and (-60dB) release rates u = 1/na; w = 1-1/pow(z*db2linear(60), 1/nr); // values below this threshold are considered zero in the release phase eps = db2linear(-120); }; }; //----------------------- ASYMPT60 ---------------------------- // Envelope generator which asymptotically approaches a target value. // // USAGE: // asympT60(value,trgt,T60,trig) : _ // where // value = starting value // trgt = target value // T60 = ramping time // trig = trigger signal asympT60(value,trgt,T60,trig) = (_*factor + constant)~_ with{ cntSample = *(trig) + 1~_ : -(1); attDur = float(2); cndFirst = ((cntSample < attDur) & (trig > 0)); target = value*cndFirst + trgt*(cndFirst < 1); factorAtt = exp(-7/attDur); factorT60 = exp(-7/(T60*float(SR))); factor = factorAtt*((cntSample < attDur) & (trig > 0)) + ((cntSample >= attDur) | (trig < 1))*factorT60; constant = (1 - factor)*target; }; //========================= TABLES =============================== //----------------------- CLIPPING FUNCTION ---------------------------- // Positive and negative clipping functions. // // USAGE: // _ : saturationPos : _ // _ : saturationNeg : _ // _ : saturationPos : saturationNeg : _ saturationPos(x) = x <: (_>1),(_<=1 : *(x)) :> +; saturationNeg(x) = x <: (_<-1),(_>=-1 : *(x)) :> *(-1) + _; //----------------------- BOW TABLE ---------------------------- // Simple bow table. // // USAGE: // index : bow(offset,slope) : _ // where // 0 <= index <= 1 bow(offset,slope) = pow(abs(sample) + 0.75, -4) : saturationPos with{ sample(y) = (y + offset)*slope; }; //----------------------- REED TABLE ---------------------------- // Simple reed table to be used with waveguide models of clanrinet, saxophone, etc. // // USAGE: // _ : reed(offset,slope) : _ // where // offset = offset between 0 and 1 // slope = slope between 0 and 1 // REFERENCE: // https://ccrma.stanford.edu/~jos/pasp/View_Single_Reed_Oscillation.html reed(offset,slope) = reedTable : saturationPos : saturationNeg with{ reedTable = offset + (slope*_); }; //========================= FILTERS =============================== //----------------------- ONE POLE ---------------------------- onePole(b0,a1,x) = (b0*x - a1*_)~_; //----------------------- ONE POLE SWEPT ---------------------------- onePoleSwep(a1,x) = (1 + a1)*x - a1*x'; //----------------------- POLE ZERO ---------------------------- poleZero(b0,b1,a1,x) = (b0*x + b1*x' - a1*_)~_; //----------------------- ONE ZEROS ---------------------------- // Simple One zero and One zero recursive filters // // USAGE: // _ : oneZero0(b0,b1) : _ // _ : oneZero1(b0,b1) : _ // REFERENCE: // https://ccrma.stanford.edu/~jos/fp2/One_Zero.html oneZero0(b0,b1,x) = (*(b1) + x*b0)~_; oneZero1(b0,b1,x) = (x'*b1 + x*b0); //----------------------- BANDPASS FILTER WITH CONSTANT UNITY PEAK GAIN BASED ON A BIQUAD ---------------------------- bandPass(resonance,radius) = TF2(b0,b1,b2,a1,a2) with{ a2 = radius*radius; a1 = -2*radius*cos(PI*2*resonance/SR); b0 = 0.5-0.5*a2; b1 = 0; b2 = -b0; }; //----------------------- BANDPASS FILTER BASED ON A BIQUAD ---------------------------- // Band pass filter using a biquad (TF2 is declared in filter.lib) // // USAGE: // _ : bandPassH(resonance,radius) : _ // where // resonance = center frequency // radius = radius bandPassH(resonance,radius) = TF2(b0,b1,b2,a1,a2) with{ a2 = radius*radius; a1 = -2*radius*cos(PI*2*resonance/SR); b0 = 1; b1 = 0; b2 = 0; }; //----------------------- FLUE JET NON-LINEAR FUNCTION ---------------------------- // Jet Table: flue jet non-linear function, computed by a polynomial calculation jetTable(x) = x <: _*(_*_-1) : saturationPos : saturationNeg; //----------------------- NON LINEAR MODULATOR ---------------------------- // nonLinearModulator adapts the function allpassnn from filter.lib for using it with waveguide instruments // // USAGE: // _ : nonLinearModulator(nonlinearity,env,freq,typeMod,freqMod,order) : _ // where // nonlinearity = nonlinearity coefficient between 0 and 1 // env = input to connect any kind of envelope // freq = current tone frequency // typeMod = if 0: theta is modulated by the incoming signal; // if 1: theta is modulated by the averaged incoming signal; // if 2: theta is modulated by the squared incoming signal; // if 3: theta is modulated by a sine wave of frequency freqMod; // if 4: theta is modulated by a sine wave of frequency freq; // freqMod = frequency of the sine wave modulation // order = order of the filter nonLinearModulator(nonlinearity,env,freq,typeMod,freqMod,order) = //theta is modulated by a sine wave _ <: nonLinearFilterOsc*(typeMod >= 3), //theta is modulated by the incoming signal (_ <: nonLinearFilterSig*nonlinearity,_*(1 - nonlinearity) :> +)*(typeMod < 3) :> + with{ //which frequency to use for the sine wave oscillator? freqOscMod = (typeMod == 4)*freq + (typeMod != 4)*freqMod; //the incoming signal is scaled and the envelope is applied tsignorm(x) = nonlinearity*PI*x*env; tsigsquared(x) = nonlinearity*PI*x*x*env; //incoming signal is squared tsigav(x) = nonlinearity*PI*((x + x')/2)*env; //incoming signal is averaged with its previous sample //select which version of the incoming signal of theta to use tsig(x) = tsignorm(x)*(typeMod == 0) + tsigav(x)*(typeMod == 1) + tsigsquared(x)*(typeMod == 2); //theta is modulated by a sine wave generator tosc = nonlinearity*PI*osc(freqOscMod)*env; //incoming signal is sent to the nonlinear passive allpass ladder filter nonLinearFilterSig(x) = x <: allpassnn(order,(par(i,order,tsig(x)))); nonLinearFilterOsc = _ <: allpassnn(order,(par(i,order,tosc))); }; //========================= WAVE TABLES =============================== //----------------------- STICK IMPACT ---------------------------- // Stick impact table. // // USAGE: // index : readMarmstk1 : _ readMarmstk1 = ffunction(float readMarmstk1 (int), ,""); marmstk1TableSize = 246; //========================= TOOLS =============================== //----------------------- STEREOIZER ---------------------------- // This function takes a mono input signal and spacialize it in stereo // in function of the period duration of the tone being played. // // USAGE: // _ : stereo(periodDuration) : _,_ // where // periodDuration = period duration of the tone being played in number of samples // ACKNOWLEDGMENT // Formulation initiated by Julius O. Smith in https://ccrma.stanford.edu/realsimple/faust_strings/ stereoizer(periodDuration) = _ <: _,widthdelay : stereopanner with{ W = hslider("v:Spat/spatial width", 0.5, 0, 1, 0.01); A = hslider("v:Spat/pan angle", 0.6, 0, 1, 0.01); widthdelay = delay(4096,W*periodDuration/2); stereopanner = _,_ : *(1.0-A), *(A); }; //----------------------- INSTRREVERB ---------------------------- // GUI for zita_rev1_stereo from effect.lib // // USAGE: // _,_ : instrRerveb instrReverb = _,_ <: *(reverbGain),*(reverbGain),*(1 - reverbGain),*(1 - reverbGain) : zita_rev1_stereo(rdel,f1,f2,t60dc,t60m,fsmax),_,_ <: _,!,_,!,!,_,!,_ : +,+ with{ reverbGain = hslider("v:Reverb/reverbGain",0.137,0,1,0.01) : smooth(0.999); roomSize = hslider("v:Reverb/roomSize",0.72,0.01,2,0.01); rdel = 20; f1 = 200; f2 = 6000; t60dc = roomSize*3; t60m = roomSize*2; fsmax = 48000; };