#ifndef STK_SHAKERS_H #define STK_SHAKERS_H #include "Instrmnt.h" #include #include namespace stk { /***************************************************/ /*! \class Shakers \brief PhISEM and PhOLIES class. PhISEM (Physically Informed Stochastic Event Modeling) is an algorithmic approach for simulating collisions of multiple independent sound producing objects. This class is a meta-model that can simulate a Maraca, Sekere, Cabasa, Bamboo Wind Chimes, Water Drops, Tambourine, Sleighbells, and a Guiro. PhOLIES (Physically-Oriented Library of Imitated Environmental Sounds) is a similar approach for the synthesis of environmental sounds. This class implements simulations of breaking sticks, crunchy snow (or not), a wrench, sandpaper, and more. Control Change Numbers: - Shake Energy = 2 - System Decay = 4 - Number Of Objects = 11 - Resonance Frequency = 1 - Shake Energy = 128 - Instrument Selection = 1071 - Maraca = 0 - Cabasa = 1 - Sekere = 2 - Tambourine = 3 - Sleigh Bells = 4 - Bamboo Chimes = 5 - Sand Paper = 6 - Coke Can = 7 - Sticks = 8 - Crunch = 9 - Big Rocks = 10 - Little Rocks = 11 - Next Mug = 12 - Penny + Mug = 13 - Nickle + Mug = 14 - Dime + Mug = 15 - Quarter + Mug = 16 - Franc + Mug = 17 - Peso + Mug = 18 - Guiro = 19 - Wrench = 20 - Water Drops = 21 - Tuned Bamboo Chimes = 22 by Perry R. Cook with updates by Gary Scavone, 1995--2017. */ /***************************************************/ class Shakers : public Instrmnt { public: //! Class constructor taking instrument type argument. Shakers( int type = 0 ); //! Start a note with the given instrument and amplitude. /*! Use the instrument numbers above, converted to frequency values as if MIDI note numbers, to select a particular instrument. */ void noteOn( StkFloat instrument, StkFloat amplitude ); //! Stop a note with the given amplitude (speed of decay). void noteOff( StkFloat amplitude ); //! Perform the control change specified by \e number and \e value (0.0 - 128.0). void controlChange( int number, StkFloat value ); //! Compute and return one output sample. StkFloat tick( unsigned int channel = 0 ); //! Fill a channel of the StkFrames object with computed outputs. /*! The \c channel argument must be less than the number of channels in the StkFrames argument (the first channel is specified by 0). However, range checking is only performed if _STK_DEBUG_ is defined during compilation, in which case an out-of-range value will trigger an StkError exception. */ StkFrames& tick( StkFrames& frames, unsigned int channel = 0 ); struct BiQuad { StkFloat gain; StkFloat b[3]; StkFloat a[3]; // a0 term assumed equal to 1.0 StkFloat inputs[3]; StkFloat outputs[3]; // Default constructor. BiQuad() { gain = 0.0; for ( int i=0; i<3; i++ ) { b[i] = 0.0; a[i] = 0.0; inputs[i] = 0.0; outputs[i] = 0.0; } } }; protected: void setType( int type ); void setResonance( BiQuad &filter, StkFloat frequency, StkFloat radius ); StkFloat tickResonance( BiQuad &filter, StkFloat input ); void setEqualization( StkFloat b0, StkFloat b1, StkFloat b2 ); StkFloat tickEqualize( StkFloat input ); int randomInt( int max ); StkFloat randomFloat( StkFloat max = 1.0 ); StkFloat noise( void ); void waterDrop( void ); int shakerType_; unsigned int nResonances_; StkFloat shakeEnergy_; StkFloat soundDecay_; StkFloat systemDecay_; StkFloat nObjects_; StkFloat sndLevel_; StkFloat baseGain_; StkFloat currentGain_; StkFloat baseDecay_; StkFloat baseObjects_; StkFloat decayScale_; BiQuad equalizer_; StkFloat ratchetCount_; StkFloat ratchetDelta_; StkFloat baseRatchetDelta_; int lastRatchetValue_; std::vector< BiQuad > filters_; std::vector< StkFloat > baseFrequencies_; std::vector< StkFloat > baseRadii_; std::vector< bool > doVaryFrequency_; std::vector< StkFloat > tempFrequencies_; StkFloat varyFactor_; }; inline void Shakers :: setResonance( BiQuad &filter, StkFloat frequency, StkFloat radius ) { filter.a[1] = -2.0 * radius * cos( TWO_PI * frequency / Stk::sampleRate()); filter.a[2] = radius * radius; } inline StkFloat Shakers :: tickResonance( BiQuad &filter, StkFloat input ) { filter.outputs[0] = input * filter.gain * currentGain_; filter.outputs[0] -= filter.a[1] * filter.outputs[1] + filter.a[2] * filter.outputs[2]; filter.outputs[2] = filter.outputs[1]; filter.outputs[1] = filter.outputs[0]; return filter.outputs[0]; } inline void Shakers :: setEqualization( StkFloat b0, StkFloat b1, StkFloat b2 ) { equalizer_.b[0] = b0; equalizer_.b[1] = b1; equalizer_.b[2] = b2; } inline StkFloat Shakers :: tickEqualize( StkFloat input ) { equalizer_.inputs[0] = input; equalizer_.outputs[0] = equalizer_.b[0] * equalizer_.inputs[0] + equalizer_.b[1] * equalizer_.inputs[1] + equalizer_.b[2] * equalizer_.inputs[2]; equalizer_.inputs[2] = equalizer_.inputs[1]; equalizer_.inputs[1] = equalizer_.inputs[0]; return equalizer_.outputs[0]; } inline int Shakers :: randomInt( int max ) // Return random integer between 0 and max-1 { return (int) ((float)max * rand() / (RAND_MAX + 1.0) ); } inline StkFloat Shakers :: randomFloat( StkFloat max ) // Return random float between 0.0 and max { return (StkFloat) (max * rand() / (RAND_MAX + 1.0) ); } inline StkFloat Shakers :: noise( void ) // Return random StkFloat float between -1.0 and 1.0 { return ( (StkFloat) ( 2.0 * rand() / (RAND_MAX + 1.0) ) - 1.0 ); } const StkFloat MIN_ENERGY = 0.001; const StkFloat WATER_FREQ_SWEEP = 1.0001; inline void Shakers :: waterDrop( void ) { if ( randomInt( 32767 ) < nObjects_) { sndLevel_ = shakeEnergy_; unsigned int j = randomInt( 3 ); if ( j == 0 && filters_[0].gain == 0.0 ) { // don't change unless fully decayed tempFrequencies_[0] = baseFrequencies_[1] * (0.75 + (0.25 * noise())); filters_[0].gain = fabs( noise() ); } else if (j == 1 && filters_[1].gain == 0.0) { tempFrequencies_[1] = baseFrequencies_[1] * (1.0 + (0.25 * noise())); filters_[1].gain = fabs( noise() ); } else if ( filters_[2].gain == 0.0 ) { tempFrequencies_[2] = baseFrequencies_[1] * (1.25 + (0.25 * noise())); filters_[2].gain = fabs( noise() ); } } // Sweep center frequencies. for ( unsigned int i=0; i<3; i++ ) { // WATER_RESONANCES = 3 filters_[i].gain *= baseRadii_[i]; if ( filters_[i].gain > 0.001 ) { tempFrequencies_[i] *= WATER_FREQ_SWEEP; filters_[i].a[1] = -2.0 * baseRadii_[i] * cos( TWO_PI * tempFrequencies_[i] / Stk::sampleRate() ); } else filters_[i].gain = 0.0; } } inline StkFloat Shakers :: tick( unsigned int ) { unsigned int iTube = 0; StkFloat input = 0.0; if ( shakerType_ == 19 || shakerType_ == 20 ) { if ( ratchetCount_ <= 0 ) return lastFrame_[0] = 0.0; shakeEnergy_ -= ( ratchetDelta_ + ( 0.002 * shakeEnergy_ ) ); if ( shakeEnergy_ < 0.0 ) { shakeEnergy_ = 1.0; ratchetCount_--; } if ( randomFloat( 1024 ) < nObjects_ ) sndLevel_ += shakeEnergy_ * shakeEnergy_; // Sound is enveloped noise input = sndLevel_ * noise() * shakeEnergy_; } else { if ( shakeEnergy_ < MIN_ENERGY ) return lastFrame_[0] = 0.0; // Exponential system decay shakeEnergy_ *= systemDecay_; // Random events if ( shakerType_ == 21 ) { waterDrop(); input = sndLevel_; } else { if ( randomFloat( 1024.0 ) < nObjects_ ) { sndLevel_ += shakeEnergy_; input = sndLevel_; // Vary resonance frequencies if specified. for ( unsigned int i=0; i 1.0 ) // std::cout << "lastOutput = " << lastFrame_[0] << std::endl; return lastFrame_[0]; } inline StkFrames& Shakers :: tick( StkFrames& frames, unsigned int channel ) { unsigned int nChannels = lastFrame_.channels(); #if defined(_STK_DEBUG_) if ( channel > frames.channels() - nChannels ) { oStream_ << "Shakers::tick(): channel and StkFrames arguments are incompatible!"; handleError( StkError::FUNCTION_ARGUMENT ); } #endif StkFloat *samples = &frames[channel]; unsigned int j, hop = frames.channels() - nChannels; if ( nChannels == 1 ) { for ( unsigned int i=0; i