Logo Search packages:      
Sourcecode: csound version File versions  Download package

Granulate.cpp

/***************************************************/
/*! \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"

00021 Granulate :: Granulate( void )
{
  this->setGrainParameters(); // use default values
  this->setRandomFactor();
  gStretch_ = 0;
  stretchCounter_ = 0;
  gain_ = 1.0;
}

00030 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 );
}

00040 Granulate :: ~Granulate()
{
}

00044 void Granulate :: setStretch( unsigned int stretchFactor )
{
  if ( stretchFactor <= 1 )
    gStretch_ = 0;
  else if ( gStretch_ >= 1000 )
    gStretch_ = 1000;
  else
    gStretch_ = stretchFactor - 1;
}

00054 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;
}

00075 void Granulate :: setRandomFactor( StkFloat randomness )
{
  if ( randomness < 0.0 ) gRandomFactor_ = 0.0;
  else if ( randomness > 1.0 ) gRandomFactor_ = 0.97;

  gRandomFactor_ = 0.97 * randomness;
};

00083 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

}

00104 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;
}

00120 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_;
}


Generated by  Doxygen 1.6.0   Back to index