declare name "Commuted Piano"; declare description "WaveGuide Commuted Piano"; 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); declare description "A commuted WaveGuide piano."; import("music.lib"); import("instrument.lib"); //==================== GUI SPECIFICATION ================ freq = nentry("h:Basic_Parameters/freq [1][unit:Hz] [tooltip:Tone frequency]",440,20,20000,1); gain = nentry("h:Basic_Parameters/gain [1][tooltip:Gain (value between 0 and 1)]",1,0,1,0.01); gate = button("h:Basic_Parameters/gate [1][tooltip:noteOn = 1, noteOff = 0]"); brightnessFactor = hslider("v:Physical_Parameters/Brightness_Factor [2][tooltip:A value between 0 and 1]",0,0,1,0.01); detuningFactor = hslider("v:Physical_Parameters/Detuning_Factor [2][tooltip:A value between 0 and 1]",0.1,0,1,0.01)*10; stiffnessFactor = hslider("v:Physical_Parameters/Stiffness_Factor [2][tooltip:A value between 0 and 1]",0.28,0,1,0.01)*3.7; hammerHardness = hslider("v:Physical_Parameters/Hammer_Hardness [2][tooltip:A value between 0 and 1]",0.1,0,1,0.01)*0.1; //==================== COMMUTED PIANO PARAMETERS ================ //variables to set keybord splitting zone DCB2_TURNOFF_KEYNUM = 92; FIRST_HIGH_NOTE = 88; PEDAL_ENVELOPE_T60 = 7; //convert an amplitude in db dbinv(x) = pow(10,0.05*x); //convert a frequency in a midi note number freqToNoteNumber = (log-log(440))/log(2)*12+69+0.5 : int; freqn = freq : freqToNoteNumber; //a counter that restart a every note-on cntSample = *(gate)+1~_ : -(1); //==================== PIANO SOUND BOARD ================ //exponential envelope with 3 phases for the pedal excitation asympT60pedal(value,T60) = (*(factor) + constant)~_ with{ attDur = hammerHardness*float(SR); target = value*((cntSample < attDur) & (gate > 0)); factorAtt = exp (-1/(attDur)); factorG = exp(-1/(2*float(SR))); factorT60 = exp(-7/(T60*float(SR))); factor = factorAtt*gate*(cntSample < attDur) + (cntSample >= attDur)*gate*factorG + ((gate-1)*-1)*factorT60; constant = (1 - factor)*target; }; //the sound of the piano sound board is generated by noise generator whose output gain is shaped by //an exponential envelope soundBoard = dryTapAmp*noise + pedalEnv*noise : *(0.5) with{ //the values of the envelope cut-off time are stored in an external C++ function dryTapAmpT60 = ffunction(float getValueDryTapAmpT60(float), ,""); sustainPedalLevel = ffunction(float getValueSustainPedalLevel(float), ,""); pedalEnvCutOffTime = 1.4; noteCutOffTime = freqn : dryTapAmpT60*gain; pedalEnvValue = freqn : sustainPedalLevel*0.2; noteEnvValue = 0.15; dryTapAmp = asympT60(noteEnvValue,0,noteCutOffTime,gate); pedalEnv = asympT60pedal(pedalEnvValue,pedalEnvCutOffTime); }; //==================== HAMMER MODELING ================ //To model the exitation hammer, we filter the sound from the soundboard with a serie of 4 one pole filters //connected in serie //onePole is declared in instrument.lib calcHammer = onePole((1-hammerPole)*hammerGain,-hammerPole) with{ //filter gains and coefficients are stored in external C++ files loudPole = ffunction(float getValueLoudPole(float), ,""); softPole = ffunction(float getValuePoleValue(float), ,""); loudGain = ffunction(float getValueLoudGain(float), ,""); softGain = ffunction(float getValueSoftGain(float), ,""); loudPoleValue = loudPole(freqn) + (brightnessFactor*-0.25) + 0.02; softPoleValue = softPole(freqn); normalizedVelocityValue = 1; loudGainValue = loudGain(freqn); softGainValue = softGain(freqn); overallGain = 1; hammerPole = softPoleValue + (loudPoleValue - softPoleValue)*normalizedVelocityValue; hammerGain = overallGain*(softGainValue + (loudGainValue - softGainValue)*normalizedVelocityValue); }; hammer = seq(i,4,calcHammer); //==================== DC BLOCKERS ================ //the values for the dcblockers a1 are stored in an external C++ file DCBa1 = ffunction(float getValueDCBa1(float), ,""); dCBa1Value = freqn : DCBa1; dCBb0Value = 1 - dCBa1Value; dcBlock1 = poleZero((dCBb0Value*0.5),(dCBb0Value*-0.5),dCBa1Value); dcBlock2a = oneZero1(0.5,-0.5); dcBlock2b = onePole(dCBb0Value,dCBa1Value); //==================== HIGH TUNING CALCULATION ================ //high tones are not generated with the waveguide technique but with a serie of biquad filters r1_1 = ffunction(float getValuer1_1db(float), ,""); r1_2 = ffunction(float getValuer1_2db(float), ,""); r2 = ffunction(float getValuer2db(float), ,""); r3 = ffunction(float getValuer3db(float), ,""); e = ffunction(float getValueSecondStageAmpRatio(float), ,""); second_partial_factor = ffunction(float getValueSecondPartialFactor(float), ,""); third_partial_factor = ffunction(float getValueThirdPartialFactor(float), ,""); bq4_gEarBalled = ffunction(float getValueBq4_gEarBalled(float), ,""); r1_1Value = r1_1(freqn)/SR : dbinv; r1_2Value = r1_2(freqn)/SR : dbinv; r2Value = r2(freqn)/SR : dbinv; r3Value = r3(freqn)/SR : dbinv; eValue = e(freqn) : dbinv; second_partial_factorValue = second_partial_factor(freqn); third_partial_factorValue = third_partial_factor(freqn); //set biquad gains and coeffs gainHighBq(0) = bq4_gEarBalled(freqn)/0.5; gainHighBq(1) = bq4_gEarBalled(freqn)/0.5; gainHighBq(2) = 1; gainHighBq(3) = 1; b0HighBq(0) = 1; b0HighBq(1) = 1; b0HighBq(2) = 1; b0HighBq(3) = 1; b1HighBq(0) = 0; b1HighBq(1) = 0; b1HighBq(2) = -2*(eValue*r1_1Value+(1-eValue)*r1_2Value)*cos(2*PI*freq/SR); b1HighBq(3) = 0; b2HighBq(0) = 0; b2HighBq(1) = 0; b2HighBq(2) = eValue*r1_1Value*r1_1Value+(1-eValue)*r1_2Value*r1_2Value; b2HighBq(3) = 0; a1HighBq(0) = -2*r3Value*cos(2*PI*freq*third_partial_factorValue/SR); a1HighBq(1) = -2*r2Value*cos(2*PI*freq*second_partial_factorValue/SR); a1HighBq(2) = -2*r1_1Value*cos(2*PI*freq/SR); a1HighBq(3) = -2*r1_2Value*cos(2*PI*freq/SR); a2HighBq(0) = r3Value*r3Value; a2HighBq(1) = r2Value*r2Value; a2HighBq(2) = r1_1Value*r1_1Value; a2HighBq(3) = r1_2Value*r1_2Value; highBqs = seq(i,4,*(gainHighBq(i)) : TF2(b0HighBq(i),b1HighBq(i),b2HighBq(i),a1HighBq(i),a2HighBq(i))); hiPass = oneZero1(b0,b1) with{ b0 = -0.5; b1 = -0.5; }; //==================== STRIKE POSITION COMB FILTER EQ ================ eq = _*filterGain : TF2(b0,b1,b2,a1,a2) with{ strikePosition = ffunction(float getValueStrikePosition(float), ,""); bandwidthFactors = ffunction(float getValueEQBandWidthFactor(float), ,""); eq_gain = ffunction(float getValueEQGain(float), ,""); eq_tuning = freq/strikePosition(freqn); eq_bandwidth = bandwidthFactors(freqn)*freq; filterGain = eq_gain(freqn); a2 = (eq_bandwidth / SR) * (eq_bandwidth / SR); a1 = -2*(eq_bandwidth / SR)*cos(2*PI*eq_tuning/SR); b0 = 0.5 - 0.5 * a2; b1 = 0; b2 = -b0; }; //==================== PIANO COUPLED STRINGS ================ //values for the couple strings are stored in externals C++ functions singleStringDecRate = ffunction(float getValueSingleStringDecayRate(float), ,""); singleStringZero = ffunction(float getValueSingleStringZero(float), ,""); singleStringPole = ffunction(float getValueSingleStringPole(float), ,""); stiffnessCoefficient = ffunction(float getValueStiffnessCoefficient(float), ,""); //coupling filter parameters g = pow(10,((singleStringDecRate(freqn)/freq)/20)); //attenuation per period b = singleStringZero(freqn); a = singleStringPole(freqn); tempd = 3*(1-b)-g*(1-a); b0Coupling = 2*(g*(1-a)-(1-b))/tempd; b1Coupling = 2*(a*(1-b)-g*(1-a)*b)/tempd; a1Coupling = (g*(1-a)*b - 3*a*(1-b))/tempd; //string stiffness stiffness = stiffnessFactor*stiffnessCoefficient(freqn); stiffnessAP = poleZero(b0s,b1s,a1s) with{ b0s = stiffness; b1s = 1; a1s = stiffness; }; delayG(frequency,stiffnessCoefficient) = fdelay(4096,delayLength) with{ allPassPhase(a1,WT) = atan2((a1*a1-1.0)*sin(WT),(2.0*a1+(a1*a1+1.0)*cos(WT))); poleZeroPhase(b0,b1,a1,WT) = atan2(-b1*sin(WT)*(1 + a1*cos(WT)) + a1*sin(WT)*(b0 + b1*cos(WT)), (b0 + b1*cos(WT))*(1 + a1*cos(WT)) + b1*sin(WT)*a1*sin(WT)); wT = frequency*2*PI/SR; delayLength = (2*PI + 3*allPassPhase(stiffnessCoefficient, wT) + poleZeroPhase((1+2*b0Coupling), a1Coupling + 2*b1Coupling, a1Coupling, wT)) / wT; }; coupledStrings = (parallelStrings <: (_,(_+_ <: _,_),_ : _,_,(_ : couplingFilter),_ : adder))~(_,_) : !,!,_ with{ releaseLoopGain = ffunction(float getValueReleaseLoopGain(float), ,""); hz = ffunction(float getValueDetuningHz(float), ,""); coupledStringLoopGain = gate*0.9996 + ((gate-1)*-1)*releaseLoopGain(freqn)*0.9 : smooth(0.999); couplingFilter = poleZero(b0Coupling,b1Coupling,a1Coupling); hzValue = hz(freqn); freq1 = freq + 0.5*hzValue*detuningFactor; freq2 = freq - 0.5*hzValue*detuningFactor; delay1 = delayG(freq1,stiffness); delay2 = delayG(freq2,stiffness); parallelStrings(x,y) = _ <: (+(x)*coupledStringLoopGain : seq(i,3,stiffnessAP) : delay1), (_+y*coupledStringLoopGain : seq(i,3,stiffnessAP) : delay2); adder(w,x,y,z) = (y <: +(w),+(z)),x ; }; //stereoizer is declared in instrument.lib and implement a stereo spacialisation in function of //the frequency period in number of samples stereo = stereoizer(SR/freq); //==================== PROCESSING ================ conditionLowNote = freqn < FIRST_HIGH_NOTE; conditionHighNote = freqn >= FIRST_HIGH_NOTE; process = soundBoard <: (*(conditionLowNote)*6 : hammer : dcBlock1 : coupledStrings <: +(eq)), (*(conditionHighNote) : hiPass : dcBlock1 : hammer : dcBlock2a : highBqs : dcBlock2b) :> + : *(12) : stereo : instrReverb;