/***************************************************/ /*! \Class Whistle \brief STK police/referee whistle instrument class. This class implements a hybrid physical/spectral model of a police whistle (a la Cook). Control Change Numbers: - Noise Gain = 4 - Fipple Modulation Frequency = 11 - Fipple Modulation Gain = 1 - Blowing Frequency Modulation = 2 - Volume = 128 by Perry R. Cook 1995--2017. */ /***************************************************/ #include "Whistle.h" #include "SKINImsg.h" #include namespace stk { const int CAN_RADIUS = 100; const int PEA_RADIUS = 30; const int BUMP_RADIUS = 5; const StkFloat NORM_CAN_LOSS = 0.97; //const StkFloat SLOW_CAN_LOSS = 0.90; const StkFloat GRAVITY = 20.0; const StkFloat NORM_TICK_SIZE = 0.004; //const StkFloat SLOW_TICK_SIZE = 0.0001; const StkFloat ENV_RATE = 0.001; Whistle :: Whistle( void ) { sine_.setFrequency( 2800.0 ); can_.setRadius( CAN_RADIUS ); can_.setPosition(0, 0, 0); // set can location can_.setVelocity(0, 0, 0); // and the velocity onepole_.setPole(0.95); // 0.99 bumper_.setRadius( BUMP_RADIUS ); bumper_.setPosition(0.0, CAN_RADIUS-BUMP_RADIUS, 0); bumper_.setPosition(0.0, CAN_RADIUS-BUMP_RADIUS, 0); pea_.setRadius( PEA_RADIUS ); pea_.setPosition(0, CAN_RADIUS/2, 0); pea_.setVelocity(35, 15, 0); envelope_.setRate( ENV_RATE ); envelope_.keyOn(); fippleFreqMod_ = 0.5; fippleGainMod_ = 0.5; blowFreqMod_ = 0.25; noiseGain_ = 0.125; baseFrequency_ = 2000; tickSize_ = NORM_TICK_SIZE; canLoss_ = NORM_CAN_LOSS; subSample_ = 1; subSampCount_ = subSample_; } Whistle :: ~Whistle( void ) { #ifdef WHISTLE_ANIMATION printf("Exit, Whistle bye bye!!\n"); #endif } void Whistle :: clear( void ) { } void Whistle :: setFrequency( StkFloat frequency ) { #if defined(_STK_DEBUG_) if ( frequency <= 0.0 ) { oStream_ << "Whistle::setFrequency: parameter is less than or equal to zero!"; handleError( StkError::WARNING ); return; } #endif baseFrequency_ = frequency * 4; // the whistle is a transposing instrument } void Whistle :: startBlowing( StkFloat amplitude, StkFloat rate ) { if ( amplitude <= 0.0 || rate <= 0.0 ) { oStream_ << "Whistle::startBlowing: one or more arguments is less than or equal to zero!"; handleError( StkError::WARNING ); return; } envelope_.setRate( ENV_RATE ); envelope_.setTarget( amplitude ); } void Whistle :: stopBlowing( StkFloat rate ) { if ( rate <= 0.0 ) { oStream_ << "Whistle::stopBlowing: argument is less than or equal to zero!"; handleError( StkError::WARNING ); return; } envelope_.setRate( rate ); envelope_.keyOff(); } void Whistle :: noteOn( StkFloat frequency, StkFloat amplitude ) { this->setFrequency( frequency ); this->startBlowing( amplitude*2.0 ,amplitude * 0.2 ); } void Whistle :: noteOff( StkFloat amplitude ) { this->stopBlowing( amplitude * 0.02 ); } int frameCount = 0; StkFloat Whistle :: tick( unsigned int ) { StkFloat soundMix, tempFreq; StkFloat envOut = 0, temp, temp1, temp2, tempX, tempY; double phi, cosphi, sinphi; double gain = 0.5, mod = 0.0; if ( --subSampCount_ <= 0 ) { tempVectorP_ = pea_.getPosition(); subSampCount_ = subSample_; temp = bumper_.isInside( tempVectorP_ ); #ifdef WHISTLE_ANIMATION frameCount += 1; if ( frameCount >= (1470 / subSample_) ) { frameCount = 0; printf("%f %f %f\n",tempVectorP_->getX(),tempVectorP_->getY(),envOut); fflush(stdout); } #endif envOut = envelope_.tick(); if (temp < (BUMP_RADIUS + PEA_RADIUS)) { tempX = envOut * tickSize_ * 2000 * noise_.tick(); tempY = -envOut * tickSize_ * 1000 * (1.0 + noise_.tick()); pea_.addVelocity( tempX, tempY, 0 ); pea_.tick( tickSize_ ); } mod = exp(-temp * 0.01); // exp. distance falloff of fipple/pea effect temp = onepole_.tick(mod); // smooth it a little gain = (1.0 - (fippleGainMod_*0.5)) + (2.0 * fippleGainMod_ * temp); gain *= gain; // squared distance/gain // tempFreq = 1.0 // Normalized Base Freq // + (fippleFreqMod_ * 0.25) - (fippleFreqMod_ * temp) // fippleModulation // - (blowFreqMod_) + (blowFreqMod_ * envOut); // blowingModulation // short form of above tempFreq = 1.0 + fippleFreqMod_*(0.25-temp) + blowFreqMod_*(envOut-1.0); tempFreq *= baseFrequency_; sine_.setFrequency(tempFreq); tempVectorP_ = pea_.getPosition(); temp = can_.isInside(tempVectorP_); temp = -temp; // We know (hope) it's inside, just how much?? if (temp < (PEA_RADIUS * 1.25)) { pea_.getVelocity( &tempVector_ ); // This is the can/pea collision tempX = tempVectorP_->getX(); // calculation. Could probably tempY = tempVectorP_->getY(); // simplify using tables, etc. phi = -atan2(tempY,tempX); cosphi = cos(phi); sinphi = sin(phi); temp1 = (cosphi*tempVector_.getX()) - (sinphi*tempVector_.getY()); temp2 = (sinphi*tempVector_.getX()) + (cosphi*tempVector_.getY()); temp1 = -temp1; tempX = (cosphi*temp1) + (sinphi*temp2); tempY = (-sinphi*temp1) + (cosphi*temp2); pea_.setVelocity(tempX, tempY, 0); pea_.tick(tickSize_); pea_.setVelocity( tempX*canLoss_, tempY*canLoss_, 0 ); pea_.tick(tickSize_); } temp = tempVectorP_->getLength(); if (temp > 0.01) { tempX = tempVectorP_->getX(); tempY = tempVectorP_->getY(); phi = atan2( tempY, tempX ); phi += 0.3 * temp / CAN_RADIUS; cosphi = cos(phi); sinphi = sin(phi); tempX = 3.0 * temp * cosphi; tempY = 3.0 * temp * sinphi; } else { tempX = 0.0; tempY = 0.0; } temp = (0.9 + 0.1*subSample_*noise_.tick()) * envOut * 0.6 * tickSize_; pea_.addVelocity( temp * tempX, (temp*tempY) - (GRAVITY*tickSize_), 0 ); pea_.tick( tickSize_ ); // bumper_.tick(0.0); } temp = envOut * envOut * gain / 2; soundMix = temp * ( sine_.tick() + ( noiseGain_*noise_.tick() ) ); lastFrame_[0] = 0.20 * soundMix; // should probably do one-zero filter here return lastFrame_[0]; } void Whistle :: controlChange( int number, StkFloat value ) { #if defined(_STK_DEBUG_) if ( Stk::inRange( value, 0.0, 128.0 ) == false ) { oStream_ << "Whistle::controlChange: value (" << value << ") is out of range!"; handleError( StkError::WARNING ); return; } #endif StkFloat normalizedValue = value * ONE_OVER_128; if ( number == __SK_NoiseLevel_ ) // 4 noiseGain_ = 0.25 * normalizedValue; else if ( number == __SK_ModFrequency_ ) // 11 fippleFreqMod_ = normalizedValue; else if ( number == __SK_ModWheel_ ) // 1 fippleGainMod_ = normalizedValue; else if ( number == __SK_AfterTouch_Cont_ ) // 128 envelope_.setTarget( normalizedValue * 2.0 ); else if ( number == __SK_Breath_ ) // 2 blowFreqMod_ = normalizedValue * 0.5; else if ( number == __SK_Sustain_ ) { // 64 subSample_ = (int) value; if ( subSample_ < 1.0 ) subSample_ = 1; envelope_.setRate( ENV_RATE / subSample_ ); } #if defined(_STK_DEBUG_) else { oStream_ << "Whistle::controlChange: undefined control number (" << number << ")!"; handleError( StkError::WARNING ); } #endif } } // stk namespace