Mercurial > hg > opencollidoscope
diff CollidoscopeApp/include/PGranular.h @ 3:7fb593d53361
added comments
author | Fiore Martin <f.martin@qmul.ac.uk> |
---|---|
date | Tue, 12 Jul 2016 18:29:38 +0200 |
parents | 02467299402e |
children | 75b744078d66 |
line wrap: on
line diff
--- a/CollidoscopeApp/include/PGranular.h Mon Jul 11 17:03:40 2016 +0200 +++ b/CollidoscopeApp/include/PGranular.h Tue Jul 12 18:29:38 2016 +0200 @@ -10,6 +10,29 @@ using std::size_t; +/** + * The very core of the Collidoscope audio engine: the granular synthesizer. + * Based on SuperCollider's TGrains and Ross Becina's "Implementing Real-Time Granular Synthesis" + * + * It implements Collidoscope's selection-based approach to granular synthesis. + * A grain is basically a selection of a recorded sample of audio. + * Grains are played in a loop: they are retriggered each time they reach the end of the selection. + * However, if the duration coefficient is greater than one, a new grain is re-triggered before the previous one is done. + * The grains start to overlap with each other and create the typical eerie sound of grnular synthesis. + * Also every time a new grain is triggered, it is offset of a few samples from the initial position to make the timbre more interesting. + * + * + * PGranular uses a linear ASR envelope with 10 milliseconds attack and 50 milliseconds release. + * + * Note that PGranular is header based and only depends on std library and on "EnvASR.h" (also header based). + * This means you can embedd it in two your project just by copying these two files over. + * + * Template arguments: + * T: type of the audio samples (normally float or double) + * RandOffsetFunc: type of the callable passed as argument to the contructor + * TriggerCallbackFunc: type of the callable passed as argument to the contructor + * + */ template <typename T, typename RandOffsetFunc, typename TriggerCallbackFunc> class PGranular { @@ -24,6 +47,9 @@ return static_cast<T> ((1 - decimal) * xn + decimal * xn_1); } + /** + * A single grain of the granular synthesis + */ struct PGrain { double phase; // read pointer to mBuffer of this grain @@ -32,13 +58,24 @@ size_t age; // age of this grain in samples size_t duration; // duration of this grain in samples. minimum = 4 - double b1; // hann envelope from Ross Becina "Implementing real time Granular Synthesis" + double b1; // hann envelope from Ross Becina's "Implementing real time Granular Synthesis" double y1; double y2; }; + /** + * Constructor. + * + * \param buffer a pointer to an array of T that contains the original sample that will be granulized + * \param bufferLen length of buffer in samples + * \rand function returning of type size_t ()(void) that is called back each time a new grain is generated. The returned value is used + * to offset the starting sample of the grain. This adds more colour to the sound especially with small selections. + * \triggerCallback function of type void ()(char, int) that is called back each time a new grain is triggered. + * The function is passed the character 't' as first parameter when a new grain is triggered and the characted 't' when the synths becomes idle. + * \ID id of this PGrain is passed to the triggerCallback function as second parameter to identify this PGranular as the caller. + */ PGranular( const T* buffer, size_t bufferLen, size_t sampleRate, RandOffsetFunc & rand, TriggerCallbackFunc & triggerCallback, int ID ) : mBuffer( buffer ), mBufferLen( bufferLen ), @@ -71,29 +108,30 @@ ~PGranular(){} - /* sets multiplier of duration of grains in seconds */ + /** Sets multiplier of duration of grains in seconds */ void setGrainsDurationCoeff( double coeff ) { mGrainsDurationCoeff = coeff; - mGrainsDuration = std::lround( mTriggerRate * coeff ); // FIXME check if right rounding + mGrainsDuration = std::lround( mTriggerRate * coeff ); if ( mGrainsDuration < kMinGrainsDuration ) mGrainsDuration = kMinGrainsDuration; } - /* sets rate of grains. e.g rate = 2 means one octave higer */ + /** Sets rate of grains. e.g rate = 2 means one octave higer */ void setGrainsRate( double rate ) { mGrainsRate = rate; } - // sets trigger rate in samples + /** sets the selection start in samples */ void setSelectionStart( size_t start ) { mGrainsStart = start; } + /** Sets the selection size ( and therefore the trigger rate) in samples */ void setSelectionSize( size_t size ) { @@ -107,11 +145,14 @@ } + /** Sets the attenuation of the grains with respect to the level of the recorded sample + * attenuation is in amp value and defaule value is 0.25118864315096 (-12dB) */ void setAttenuation( T attenuation ) { mAttenuation = attenuation; } + /** Starts the synthesis engine */ void noteOn( double rate ) { if ( mEnvASR.getState() == EnvASR<T>::State::eIdle ){ @@ -125,6 +166,7 @@ } } + /** Stops the synthesis engine */ void noteOff() { if ( mEnvASR.getState() != EnvASR<T>::State::eIdle ){ @@ -132,11 +174,19 @@ } } + /** Whether the synthesis engine is active or not. After noteOff is called the synth stays active until the envelope decays to 0 */ bool isIdle() { return mEnvASR.getState() == EnvASR<T>::State::eIdle; } + /** + * Runs the granular engine and stores the output in \a audioOut + * + * \param pointer to an array of T. This will be filled with the output of PGranular. It needs to be at least \a numSamples lond + * \param tempBuffer a temporary buffer used to store the envelope value. It needs to be at leas \a numSamples long + * \param numSamples number of samples to be processed + */ void process( T* audioOut, T* tempBuffer, size_t numSamples ) { @@ -144,7 +194,7 @@ size_t envSamples = 0; bool becameIdle = false; - // do the envelope first and store it in the tempBuffer + // process the envelope first and store it in the tempBuffer for ( size_t i = 0; i < numSamples; i++ ){ tempBuffer[i] = mEnvASR.tick(); envSamples++; @@ -156,8 +206,10 @@ } } + // does the actual grains processing processGrains( audioOut, tempBuffer, envSamples ); + // becomes idle if the envelope goes to idle state if ( becameIdle ){ mTriggerCallback( 'e', mID ); reset(); @@ -174,7 +226,7 @@ synthesizeGrain( mGrains[grainIdx], audioOut, envelopeValues, numSamples ); if ( !mGrains[grainIdx].alive ){ - // this grain is dead so copyu the last of the active grains here + // this grain is dead so copy the last of the active grains here // so as to keep all active grains at the beginning of the array // don't increment grainIdx so the last active grain is processed next cycle // if this grain is the last active grain then mNumAliveGrains is decremented @@ -243,6 +295,7 @@ } } + // synthesize a single grain // audioOut = pointer to audio block to fill // numSamples = numpber of samples to process for this block void synthesizeGrain( PGrain &grain, T* audioOut, T* envelopeValues, size_t numSamples )