/***************************************************/ /*! \class Recorder \brief A recorder / flute physical model. This class implements a physical model of a recorder / flute instrument, based on the paper "Sound production in recorderlike instruments. II. A simulation model." by M.P. Verge, A. Hirschberg and R. Causse, Journal of the Acoustical Society of America, 1997. Control Change Numbers: - Softness = 2 - Noise Gain = 4 - Noise Cutoff = 16 - Vibrato Frequency = 11 - Vibrato Gain = 1 - Breath Pressure = 128 by Mathias Bredholt, McGill University. Formatted for STK by Gary Scavone, 2021. */ /***************************************************/ #include "Recorder.h" #include "SKINImsg.h" namespace stk { // Air constants const stk::StkFloat rho = 1.2041; // density of air const stk::StkFloat c0 = 343.21; // speed of sound in air // Flute constants const stk::StkFloat lc = 0.02; // length of flue canal const stk::StkFloat h = 0.001; // height of flue exit const stk::StkFloat H = 0.02; // pipe diameter const stk::StkFloat W = 4 * h; // width of mouth const stk::StkFloat Sp = H * H; // cross-section of pipe const stk::StkFloat Sm = W * H; // cross-section of mouth const stk::StkFloat din = 0.0030; // end correction const stk::StkFloat dout = 0.0063; // end correction const stk::StkFloat dm = din + dout; // end correction of mouth const stk::StkFloat dd = 0.0035; // acoustic distance between Q1 and Q2 const stk::StkFloat rp = sqrt(Sp / stk::PI); const stk::StkFloat b = 0.4 * h; // jet width // Calculation coefficients const stk::StkFloat b2 = Sp / (rho * c0); Recorder :: Recorder() { vibratoGain_ = 0.0; noiseGain_ = 0.2; breathCutoff_ = 0.0; outputGain_ = 0.0; psi_ = 1.0; poutL_ = 0; pout_ = 0; poutm1_ = 0; poutm2_ = 0; pin_ = 0; pinm1_ = 0; pinm2_ = 0; Uj_ = 0; Ujm1_ = 0; Qj_ = 0; Qjm1_ = 0; Qjm2_ = 0; Q1_ = 0; Q1m1_ = 0; Q1m2_ = 0; Qp_ = 0; Qpm1_ = 0; pm_ = 0; pinDelay_.tick( 0 ); poutDelay_.tick( 0 ); jetDelay_.tick( 0 ); jetDelay_.setDelay(200); vibrato_.setFrequency(4); // Calculation coefficients ... would need to be recalculated if sample rate changes StkFloat T = 1.0 / Stk::sampleRate(); b1 = rho / (4.0 * PI * c0 * T * T); b3 = dm * Sp / (T * Sm * c0); b4 = rho * dout / (Sm * T); // Radiation loss filter StkFloat A = rp * rp / (4 * c0 * c0 * T * T); StkFloat B = 0.82 * rp / (c0*T); StkFloat b_rad[3] = { 1 + A - B, B - 2 * A, A }; StkFloat a_rad[3] = { A - B - 1, B - 2 * A, A }; std::vector b_coeffs( &b_rad[0], &b_rad[0]+3 ); std::vector a_coeffs( &a_rad[0], &a_rad[0]+3 ); radiation_filter_.setCoefficients(b_coeffs, a_coeffs); // Visco-thermal loss filter StkFloat b_visco[4] = { 0.83820223947141, -0.16888603248373, -0.64759781930259, 0.07424498608506 }; StkFloat a_visco[4] = { 1.0, -0.33623476246554, -0.71257915055968, 0.14508304017256 }; b_coeffs.clear(); b_coeffs.assign( &b_visco[0], &b_visco[0]+4 ); a_coeffs.clear(); a_coeffs.assign( &a_visco[0], &a_visco[0]+4 ); visco_in_filter_.setCoefficients(b_coeffs, a_coeffs); visco_out_filter_.setCoefficients(b_coeffs, a_coeffs); setBreathCutoff( 500 ); setFrequency( 880 ); } Recorder :: ~Recorder( void ) { } void Recorder :: clear( void ) { pinDelay_.clear(); poutDelay_.clear(); jetDelay_.clear(); radiation_filter_.clear(); visco_in_filter_.clear(); visco_out_filter_.clear(); turbFilter_.clear(); } void Recorder :: setFrequency( StkFloat val ) { #if defined(_STK_DEBUG_) if ( val <= 0.0 ) { oStream_ << "Recorder::setFrequency: argument is less than or equal to zero!"; handleError( StkError::WARNING ); return; } #endif StkFloat M = Stk::sampleRate() / val - 4 - 3; pinDelay_.setDelay( M ); poutDelay_.setDelay( M ); } void Recorder :: setBlowPressure( StkFloat val ) { maxPressure_ = val; } void Recorder :: setVibratoGain( StkFloat val ) { vibratoGain_ = val; } void Recorder :: setVibratoFrequency( StkFloat val ) { vibrato_.setFrequency( val ); } void Recorder :: setNoiseGain( StkFloat val ) { noiseGain_ = val; } void Recorder :: setBreathCutoff( StkFloat val ) { // The gain of this filter is quite high breathCutoff_ = val; StkFloat Q = 0.99; StkFloat r = 2.0 * sin(PI * val / sampleRate()); StkFloat q = 1.0 - r * Q; StkFloat as[3] = { 1.0, r * r - q - 1, q }; std::vector b_turb(1, r*r); std::vector a_turb( &as[0], &as[0]+3 ); turbFilter_.setCoefficients(b_turb, a_turb); } void Recorder :: setSoftness( StkFloat val ) { psi_ = val; } void Recorder :: startBlowing( StkFloat amplitude, StkFloat rate ) { if ( amplitude <= 0.0 || rate <= 0.0 ) { oStream_ << "Recorder::startBlowing: one or more arguments is less than or equal to zero!"; handleError( StkError::WARNING ); return; } adsr_.setAttackRate( rate ); //maxPressure_ = amplitude / (StkFloat) 0.8; maxPressure_ = 35 * amplitude; adsr_.keyOn(); } void Recorder :: stopBlowing( StkFloat rate ) { if ( rate <= 0.0 ) { oStream_ << "Recorder::stopBlowing: argument is less than or equal to zero!"; handleError( StkError::WARNING ); return; } adsr_.setReleaseRate( rate ); adsr_.keyOff(); } void Recorder :: noteOn( StkFloat frequency, StkFloat amplitude ) { this->setFrequency( frequency ); this->startBlowing( 1.1 + (amplitude * 0.20), amplitude * 0.02 ); outputGain_ = amplitude / 40.0; } void Recorder :: noteOff( StkFloat amplitude ) { this->stopBlowing( amplitude * 0.02 ); } void Recorder :: controlChange( int number, StkFloat value ) { #if defined(_STK_DEBUG_) if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { oStream_ << "Recorder::controlChange: value (" << value << ") is out of range!"; handleError( StkError::WARNING ); return; } #endif StkFloat normalizedValue = value * ONE_OVER_128; if (number == 2) // 2 psi_ = 2.0 * normalizedValue; else if (number == 16) setBreathCutoff( normalizedValue * 2000 ); else if (number == __SK_NoiseLevel_) // 4 noiseGain_ = normalizedValue; else if (number == __SK_ModFrequency_) // 11 vibrato_.setFrequency( normalizedValue * 12.0); else if (number == __SK_ModWheel_) // 1 vibratoGain_ = ( normalizedValue * 0.4 ); else if (number == __SK_AfterTouch_Cont_) // 128 maxPressure_ = 35.0 * normalizedValue; #if defined(_STK_DEBUG_) else { oStream_ << "Recorder::controlChange: undefined control number (" << number << ")!"; handleError( StkError::WARNING ); } #endif } StkFloat Recorder::tick( unsigned int ) { // Read in from delay lines pinm2_ = pinm1_; pinm1_ = pin_; pin_ = pinDelay_.lastOut(); poutm2_ = poutm1_; poutm1_ = pout_; poutL_ = poutDelay_.lastOut(); // Filter wave components for visco-thermal losses pin_ = visco_in_filter_.tick(pin_); poutL_ = visco_out_filter_.tick(poutL_); // Get input blow pressure StkFloat pf = maxPressure_ * adsr_.tick() * (vibrato_.tick() * vibratoGain_ + (1 - vibratoGain_)); StkFloat T = 1.0 / sampleRate(); // Jet velocity at flue exit Ujm1_ = Uj_; Uj_ = Ujm1_ + T / (rho * lc) * (pf - pm_ - 0.5 * rho * Ujm1_ * Ujm1_); // Jet flow at flue exit Qjm2_ = Qjm1_; Qjm1_ = Qj_; Qj_ = h * H * Uj_; // Jet drive StkFloat Uj_steady = fmax(sqrt(2 * pf / rho), 0.1); StkFloat fc_jet = 0.36 / W * Uj_steady; StkFloat g_jet = 0.002004 * exp(-0.06046 * Uj_steady); StkFloat r_jet = 0.95 - Uj_steady * 0.015; StkFloat b0_jet = g_jet * (1 - r_jet * r_jet) / 2; // Calculate coefficients for resonant filter StkFloat b_jet[3] = { b0_jet, 0, -b0_jet }; StkFloat a_jet[3] = { 1, -2 * r_jet * cos(2 * PI * fc_jet * T), r_jet * r_jet }; std::vector b_jetcoeffs( &b_jet[0], &b_jet[0]+3 ); std::vector a_jetcoeffs( &a_jet[0], &a_jet[0]+3 ); jetFilter_.setCoefficients( b_jetcoeffs, a_jetcoeffs ); StkFloat eta = jetFilter_.tick(jetDelay_.lastOut()); // Calculate flow source Q1 Q1m1_ = Q1_; Q1_ = b * H * Uj_ * (1 + tanh(eta / (psi_ * b))); // Calculate pressure pulse modeling the jet drive StkFloat pjd = -rho * dd / Sm * (Q1_ - Q1m1_) / T; // Vortex shedding int Qp_sign = 0; if (Qp_ < 0) Qp_sign = -1; else if (Qp_ > 0) Qp_sign = 1; StkFloat pa = -0.5 * rho * (Qp_ / (0.6 * Sm)) * (Qp_ / (0.6 * Sm)) * Qp_sign; // Turbulence StkFloat pt = turbFilter_.tick(noiseGain_ * turb_.tick() * 0.5 * rho * Uj_ * Uj_); // Pressure pulse delta p StkFloat dp = pjd + pa + pt; // Calculate outgoing pressure pout pout_ = ((b3 - b1 * b2 - 1) * pin_ + (2 * b1 * b2 - b3) * (pinm1_ - poutm1_) + b1 * b2 * (poutm2_ - pinm2_) - b1 * (Qj_ - 2 * Qjm1_ + Qjm2_) + b4 * (Qj_ - Qjm1_) + dp) / (1 - b1 * b2 + b3); // Flow in the pipe Qpm1_ = Qp_; Qp_ = Sp / (rho * c0) * (pout_ - pin_); // Mouth pressure pm_ = pout_ + pin_ - dp + rho * din / Sm * (Qp_ - Qpm1_)/T; // Calculate transverse acoustic velocity StkFloat Q1d = Q1_ - 0.5 * b * H * Uj_; StkFloat Vac = 2.0 / PI * Qp_ / Sm - 0.38 * Q1d / Sm; jetDelay_.tick(Vac); // Calculate new jet delay line length //jet_.setDelay(fmin(W / (0.6 * Uj_steady) * sampleRate(), 200.0)); jetDelay_.setDelay(fmin(W / (0.6 * Uj_steady * T), 200.0)); // Radiation loss filtering StkFloat pin_L = radiation_filter_.tick(poutL_); // Write to delay lines poutDelay_.tick(pout_); pinDelay_.tick(pin_L); lastFrame_[0] = outputGain_ * (pout_ + pin_); return lastFrame_[0]; //return (pout_0 + pin_0) * 0.01; } } // stk namespace