mirror of
https://github.com/thestk/stk
synced 2026-01-12 12:31:53 +00:00
Version 4.2.1
This commit is contained in:
committed by
Stephen Sinclair
parent
a6381b9d38
commit
2cbce2d8bd
275
src/Granulate.cpp
Normal file
275
src/Granulate.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
/***************************************************/
|
||||
/*! \class Granulate
|
||||
\brief STK granular synthesis class.
|
||||
|
||||
This class implements a real-time granular synthesis algorithm
|
||||
that operates on an input soundfile. Currently, only monophonic
|
||||
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.
|
||||
*/
|
||||
/***************************************************/
|
||||
|
||||
#include "Granulate.h"
|
||||
#include "FileRead.h"
|
||||
|
||||
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 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;
|
||||
errorString_ << "Granulate::setGrainParameters: duration argument cannot be zero ... setting to 1 millisecond.";
|
||||
handleError( StkError::WARNING );
|
||||
}
|
||||
|
||||
gRampPercent_ = rampPercent;
|
||||
if ( gRampPercent_ > 100 ) {
|
||||
gRampPercent_ = 100;
|
||||
errorString_ << "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 );
|
||||
if ( file.channels() != 1 ) {
|
||||
errorString_ << "Granulate::openFile: this class currently only supports monophonic soundfiles.";
|
||||
handleError( StkError::FUNCTION_ARGUMENT );
|
||||
}
|
||||
|
||||
data_.resize( file.fileSize(), file.channels() );
|
||||
file.read( data_ );
|
||||
|
||||
this->reset();
|
||||
|
||||
#if defined(_STK_DEBUG_)
|
||||
errorString_ << "Granulate::openFile: file = " << fileName << ", file frames = " << file.fileSize() << '.';
|
||||
handleError( StkError::DEBUG_WARNING );
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void Granulate :: reset()
|
||||
{
|
||||
gPointer_ = 0;
|
||||
|
||||
// Reset grain parameters.
|
||||
unsigned int count, nVoices = grains_.size();
|
||||
for ( unsigned int i=0; i<grains_.size(); i++ ) {
|
||||
grains_[i].repeats = 0;
|
||||
count = (unsigned int ) ( i * gDuration_ * 0.001 * Stk::sampleRate() / nVoices );
|
||||
grains_[i].counter = count;
|
||||
grains_[i].state = GRAIN_STOPPED;
|
||||
}
|
||||
|
||||
lastOutput_ = 0.0;
|
||||
}
|
||||
|
||||
void Granulate :: setVoices( unsigned int nVoices )
|
||||
{
|
||||
#if defined(_STK_DEBUG_)
|
||||
errorString_ << "Granulate::setGrains: nGrains = " << nGrains << ", existing grains = " << grains_.size() << '.';
|
||||
handleError( StkError::DEBUG_WARNING );
|
||||
#endif
|
||||
|
||||
unsigned int oldSize = grains_.size();
|
||||
grains_.resize( nVoices );
|
||||
|
||||
// Initialize new grain voices.
|
||||
unsigned int count;
|
||||
for ( unsigned int i=oldSize; i<nVoices; i++ ) {
|
||||
grains_[i].repeats = 0;
|
||||
count = (unsigned int ) ( i * gDuration_ * 0.001 * Stk::sampleRate() / nVoices );
|
||||
grains_[i].counter = count;
|
||||
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 int 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_ * noise.tick() );
|
||||
int offset = (int) ( seconds * Stk::sampleRate() );
|
||||
grain.pointer = gPointer_ + offset;
|
||||
while ( grain.pointer >= data_.frames() ) grain.pointer -= data_.frames();
|
||||
if ( grain.pointer < 0 ) grain.pointer = 0;
|
||||
grain.startPointer = grain.pointer;
|
||||
}
|
||||
|
||||
StkFloat Granulate :: computeSample( void )
|
||||
{
|
||||
lastOutput_ = 0.0;
|
||||
if ( data_.size() == 0 ) return lastOutput_;
|
||||
|
||||
StkFloat sample;
|
||||
for ( unsigned int 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 ) {
|
||||
sample = data_[ grains_[i].pointer++ ];
|
||||
|
||||
if ( grains_[i].state == GRAIN_FADEIN || grains_[i].state == GRAIN_FADEOUT ) {
|
||||
sample *= grains_[i].eScaler;
|
||||
grains_[i].eScaler += grains_[i].eRate;
|
||||
}
|
||||
|
||||
lastOutput_ += sample;
|
||||
|
||||
// Check pointer limits.
|
||||
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 lastOutput_ * gain_;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user