mirror of
https://github.com/thestk/stk
synced 2026-01-11 20:11:52 +00:00
298 lines
8.3 KiB
C++
298 lines
8.3 KiB
C++
/***************************************************/
|
|
/*! \class Granulate
|
|
\brief STK granular synthesis class.
|
|
|
|
This class implements a real-time granular synthesis algorithm
|
|
that operates on an input soundfile. Multi-channel files are
|
|
supported. Various functions are provided to allow control over
|
|
voice and grain parameters.
|
|
|
|
The functionality of this class is based on the program MacPod by
|
|
Chris Rolfe and Damian Keller, though there are likely to be a
|
|
number of differences in the actual implementation.
|
|
|
|
by Gary Scavone, 2005 - 2010.
|
|
*/
|
|
/***************************************************/
|
|
|
|
#include "Granulate.h"
|
|
#include "FileRead.h"
|
|
#include <cmath>
|
|
|
|
namespace stk {
|
|
|
|
Granulate :: Granulate( void )
|
|
{
|
|
this->setGrainParameters(); // use default values
|
|
this->setRandomFactor();
|
|
gStretch_ = 0;
|
|
stretchCounter_ = 0;
|
|
gain_ = 1.0;
|
|
}
|
|
|
|
Granulate :: Granulate( unsigned int nVoices, std::string fileName, bool typeRaw )
|
|
{
|
|
this->setGrainParameters(); // use default values
|
|
this->setRandomFactor();
|
|
gStretch_ = 0;
|
|
stretchCounter_ = 0;
|
|
this->openFile( fileName, typeRaw );
|
|
this->setVoices( nVoices );
|
|
}
|
|
|
|
Granulate :: ~Granulate( void )
|
|
{
|
|
}
|
|
|
|
void Granulate :: setStretch( unsigned int stretchFactor )
|
|
{
|
|
if ( stretchFactor <= 1 )
|
|
gStretch_ = 0;
|
|
else if ( gStretch_ >= 1000 )
|
|
gStretch_ = 1000;
|
|
else
|
|
gStretch_ = stretchFactor - 1;
|
|
}
|
|
|
|
void Granulate :: setGrainParameters( unsigned int duration, unsigned int rampPercent,
|
|
int offset, unsigned int delay )
|
|
{
|
|
gDuration_ = duration;
|
|
if ( gDuration_ == 0 ) {
|
|
gDuration_ = 1;
|
|
oStream_ << "Granulate::setGrainParameters: duration argument cannot be zero ... setting to 1 millisecond.";
|
|
handleError( StkError::WARNING );
|
|
}
|
|
|
|
gRampPercent_ = rampPercent;
|
|
if ( gRampPercent_ > 100 ) {
|
|
gRampPercent_ = 100;
|
|
oStream_ << "Granulate::setGrainParameters: rampPercent argument cannot be greater than 100 ... setting to 100.";
|
|
handleError( StkError::WARNING );
|
|
}
|
|
|
|
gOffset_ = offset;
|
|
gDelay_ = delay;
|
|
}
|
|
|
|
void Granulate :: setRandomFactor( StkFloat randomness )
|
|
{
|
|
if ( randomness < 0.0 ) gRandomFactor_ = 0.0;
|
|
else if ( randomness > 1.0 ) gRandomFactor_ = 0.97;
|
|
|
|
gRandomFactor_ = 0.97 * randomness;
|
|
};
|
|
|
|
void Granulate :: openFile( std::string fileName, bool typeRaw )
|
|
{
|
|
// Attempt to load the soundfile data.
|
|
FileRead file( fileName, typeRaw );
|
|
data_.resize( file.fileSize(), file.channels() );
|
|
file.read( data_ );
|
|
lastFrame_.resize( 1, file.channels(), 0.0 );
|
|
|
|
this->reset();
|
|
|
|
#if defined(_STK_DEBUG_)
|
|
std::ostringstream message;
|
|
message << "Granulate::openFile: file = " << fileName << ", file frames = " << file.fileSize() << '.';
|
|
handleError( message.str(), StkError::DEBUG_PRINT );
|
|
#endif
|
|
|
|
}
|
|
|
|
void Granulate :: reset( void )
|
|
{
|
|
gPointer_ = 0;
|
|
|
|
// Reset grain parameters.
|
|
size_t count;
|
|
size_t nVoices = (unsigned int)grains_.size();
|
|
for ( unsigned int i=0; i<grains_.size(); i++ ) {
|
|
grains_[i].repeats = 0;
|
|
count = ( i * gDuration_ * 0.001 * Stk::sampleRate() / nVoices );
|
|
grains_[i].counter = count;
|
|
grains_[i].state = GRAIN_STOPPED;
|
|
}
|
|
|
|
for ( unsigned int i=0; i<lastFrame_.channels(); i++ )
|
|
lastFrame_[i] = 0.0;
|
|
}
|
|
|
|
void Granulate :: setVoices( unsigned int nVoices )
|
|
{
|
|
#if defined(_STK_DEBUG_)
|
|
std::ostringstream message;
|
|
message << "Granulate::setVoices: nVoices = " << nVoices << ", existing voices = " << grains_.size() << '.';
|
|
handleError( message.str(), StkError::DEBUG_PRINT );
|
|
#endif
|
|
|
|
size_t oldSize = grains_.size();
|
|
grains_.resize( nVoices );
|
|
|
|
// Initialize new grain voices.
|
|
size_t count;
|
|
for ( size_t i=oldSize; i<nVoices; i++ ) {
|
|
grains_[i].repeats = 0;
|
|
count = ( i * gDuration_ * 0.001 * Stk::sampleRate() / nVoices );
|
|
grains_[i].counter = count;
|
|
grains_[i].pointer = gPointer_;
|
|
grains_[i].state = GRAIN_STOPPED;
|
|
}
|
|
|
|
gain_ = 1.0 / grains_.size();
|
|
}
|
|
|
|
void Granulate :: calculateGrain( Granulate::Grain& grain )
|
|
{
|
|
if ( grain.repeats > 0 ) {
|
|
grain.repeats--;
|
|
grain.pointer = grain.startPointer;
|
|
if ( grain.attackCount > 0 ) {
|
|
grain.eScaler = 0.0;
|
|
grain.eRate = -grain.eRate;
|
|
grain.counter = grain.attackCount;
|
|
grain.state = GRAIN_FADEIN;
|
|
}
|
|
else {
|
|
grain.counter = grain.sustainCount;
|
|
grain.state = GRAIN_SUSTAIN;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Calculate duration and envelope parameters.
|
|
StkFloat seconds = gDuration_ * 0.001;
|
|
seconds += ( seconds * gRandomFactor_ * noise.tick() );
|
|
unsigned long count = (unsigned long) ( seconds * Stk::sampleRate() );
|
|
grain.attackCount = (unsigned int) ( gRampPercent_ * 0.005 * count );
|
|
grain.decayCount = grain.attackCount;
|
|
grain.sustainCount = count - 2 * grain.attackCount;
|
|
grain.eScaler = 0.0;
|
|
if ( grain.attackCount > 0 ) {
|
|
grain.eRate = 1.0 / grain.attackCount;
|
|
grain.counter = grain.attackCount;
|
|
grain.state = GRAIN_FADEIN;
|
|
}
|
|
else {
|
|
grain.counter = grain.sustainCount;
|
|
grain.state = GRAIN_SUSTAIN;
|
|
}
|
|
|
|
// Calculate delay parameter.
|
|
seconds = gDelay_ * 0.001;
|
|
seconds += ( seconds * gRandomFactor_ * noise.tick() );
|
|
count = (unsigned long) ( seconds * Stk::sampleRate() );
|
|
grain.delayCount = count;
|
|
|
|
// Save stretch parameter.
|
|
grain.repeats = gStretch_;
|
|
|
|
// Calculate offset parameter.
|
|
seconds = gOffset_ * 0.001;
|
|
seconds += ( seconds * gRandomFactor_ * std::abs( noise.tick() ) );
|
|
int offset = (int) ( seconds * Stk::sampleRate() );
|
|
|
|
// Add some randomization to the pointer start position.
|
|
seconds = gDuration_ * 0.001 * gRandomFactor_ * noise.tick();
|
|
offset += (int) ( seconds * Stk::sampleRate() );
|
|
grain.pointer += offset;
|
|
while ( grain.pointer >= data_.frames() ) grain.pointer -= data_.frames();
|
|
if ( grain.pointer < 0 ) grain.pointer = 0;
|
|
grain.startPointer = grain.pointer;
|
|
}
|
|
|
|
StkFloat Granulate :: tick( unsigned int channel )
|
|
{
|
|
#if defined(_STK_DEBUG_)
|
|
if ( channel >= data_.channels() ) {
|
|
oStream_ << "Granulate::tick(): channel argument and soundfile data are incompatible!";
|
|
handleError( StkError::FUNCTION_ARGUMENT );
|
|
}
|
|
#endif
|
|
|
|
unsigned int i, j, nChannels = lastFrame_.channels();
|
|
for ( j=0; j<nChannels; j++ ) lastFrame_[j] = 0.0;
|
|
|
|
if ( data_.size() == 0 ) return 0.0;
|
|
|
|
StkFloat sample;
|
|
for ( i=0; i<grains_.size(); i++ ) {
|
|
|
|
if ( grains_[i].counter == 0 ) { // Update the grain state.
|
|
|
|
switch ( grains_[i].state ) {
|
|
|
|
case GRAIN_STOPPED:
|
|
// We're done waiting between grains ... setup for new grain
|
|
this->calculateGrain( grains_[i] );
|
|
break;
|
|
|
|
case GRAIN_FADEIN:
|
|
// We're done ramping up the envelope
|
|
if ( grains_[i].sustainCount > 0 ) {
|
|
grains_[i].counter = grains_[i].sustainCount;
|
|
grains_[i].state = GRAIN_SUSTAIN;
|
|
break;
|
|
}
|
|
// else no sustain state (i.e. perfect triangle window)
|
|
|
|
case GRAIN_SUSTAIN:
|
|
// We're done with flat part of envelope ... setup to ramp down
|
|
if ( grains_[i].decayCount > 0 ) {
|
|
grains_[i].counter = grains_[i].decayCount;
|
|
grains_[i].eRate = -grains_[i].eRate;
|
|
grains_[i].state = GRAIN_FADEOUT;
|
|
break;
|
|
}
|
|
// else no fade out state (gRampPercent = 0)
|
|
|
|
case GRAIN_FADEOUT:
|
|
// We're done ramping down ... setup for wait between grains
|
|
if ( grains_[i].delayCount > 0 ) {
|
|
grains_[i].counter = grains_[i].delayCount;
|
|
grains_[i].state = GRAIN_STOPPED;
|
|
break;
|
|
}
|
|
// else no delay (gDelay = 0)
|
|
|
|
this->calculateGrain( grains_[i] );
|
|
}
|
|
}
|
|
|
|
// Accumulate the grain outputs.
|
|
if ( grains_[i].state > 0 ) {
|
|
for ( j=0; j<nChannels; j++ ) {
|
|
sample = data_[ nChannels * grains_[i].pointer + j ];
|
|
|
|
if ( grains_[i].state == GRAIN_FADEIN || grains_[i].state == GRAIN_FADEOUT ) {
|
|
sample *= grains_[i].eScaler;
|
|
grains_[i].eScaler += grains_[i].eRate;
|
|
}
|
|
|
|
lastFrame_[j] += sample;
|
|
}
|
|
|
|
|
|
// Increment and check pointer limits.
|
|
grains_[i].pointer++;
|
|
if ( grains_[i].pointer >= data_.frames() )
|
|
grains_[i].pointer = 0;
|
|
}
|
|
|
|
// Decrement counter for all states.
|
|
grains_[i].counter--;
|
|
}
|
|
|
|
// Increment our global file pointer at the stretch rate.
|
|
if ( stretchCounter_++ == gStretch_ ) {
|
|
gPointer_++;
|
|
if ( (unsigned long) gPointer_ >= data_.frames() ) gPointer_ = 0;
|
|
stretchCounter_ = 0;
|
|
}
|
|
|
|
return lastFrame_[channel];
|
|
}
|
|
|
|
} // stk namespace
|