1 /***************************************************/
2 /*! \class Granulate
3     \brief STK granular synthesis class.
4 
5     This class implements a real-time granular synthesis algorithm
6     that operates on an input soundfile.  Multi-channel files are
7     supported.  Various functions are provided to allow control over
8     voice and grain parameters.
9 
10     The functionality of this class is based on the program MacPod by
11     Chris Rolfe and Damian Keller, though there are likely to be a
12     number of differences in the actual implementation.
13 
14     by Gary Scavone, 2005--2021.
15 */
16 /***************************************************/
17 
18 #include "Granulate.h"
19 #include "FileRead.h"
20 #include <cmath>
21 
22 namespace stk {
23 
Granulate(void)24 Granulate :: Granulate( void )
25 {
26   this->setGrainParameters(); // use default values
27   this->setRandomFactor();
28   gStretch_ = 0;
29   stretchCounter_ = 0;
30   gain_ = 1.0;
31 }
32 
Granulate(unsigned int nVoices,std::string fileName,bool typeRaw)33 Granulate :: Granulate( unsigned int nVoices, std::string fileName, bool typeRaw )
34 {
35   this->setGrainParameters(); // use default values
36   this->setRandomFactor();
37   gStretch_ = 0;
38   stretchCounter_ = 0;
39   this->openFile( fileName, typeRaw );
40   this->setVoices( nVoices );
41 }
42 
~Granulate(void)43 Granulate :: ~Granulate( void )
44 {
45 }
46 
setStretch(unsigned int stretchFactor)47 void Granulate :: setStretch( unsigned int stretchFactor )
48 {
49   if ( stretchFactor <= 1 )
50     gStretch_ = 0;
51   else if ( gStretch_ >= 1000 )
52     gStretch_ = 1000;
53   else
54     gStretch_ = stretchFactor - 1;
55 }
56 
setGrainParameters(unsigned int duration,unsigned int rampPercent,int offset,unsigned int delay)57 void Granulate :: setGrainParameters( unsigned int duration, unsigned int rampPercent,
58                                       int offset, unsigned int delay )
59 {
60   gDuration_ = duration;
61   if ( gDuration_ == 0 ) {
62     gDuration_ = 1;
63     oStream_ << "Granulate::setGrainParameters: duration argument cannot be zero ... setting to 1 millisecond.";
64     handleError( StkError::WARNING );
65   }
66 
67   gRampPercent_ = rampPercent;
68   if ( gRampPercent_ > 100 ) {
69     gRampPercent_ = 100;
70     oStream_ << "Granulate::setGrainParameters: rampPercent argument cannot be greater than 100 ... setting to 100.";
71     handleError( StkError::WARNING );
72   }
73 
74   gOffset_ = offset;
75   gDelay_ = delay;
76 }
77 
setRandomFactor(StkFloat randomness)78 void Granulate :: setRandomFactor( StkFloat randomness )
79 {
80   if ( randomness < 0.0 ) gRandomFactor_ = 0.0;
81   else if ( randomness > 1.0 ) gRandomFactor_ = 0.97;
82   else gRandomFactor_ = 0.97 * randomness;
83 }
84 
openFile(std::string fileName,bool typeRaw)85 void Granulate :: openFile( std::string fileName, bool typeRaw )
86 {
87   // Attempt to load the soundfile data.
88   FileRead file( fileName, typeRaw );
89   data_.resize( file.fileSize(), file.channels() );
90   file.read( data_ );
91   lastFrame_.resize( 1, file.channels(), 0.0 );
92 
93   this->reset();
94 
95 #if defined(_STK_DEBUG_)
96   std::ostringstream message;
97   message << "Granulate::openFile: file = " << fileName << ", file frames = " << file.fileSize() << '.';
98   handleError( message.str(), StkError::DEBUG_PRINT );
99 #endif
100 
101 }
102 
reset(void)103 void Granulate :: reset( void )
104 {
105   gPointer_ = 0;
106 
107   // Reset grain parameters.
108   size_t count;
109   size_t nVoices = (unsigned int)grains_.size();
110   for ( unsigned int i=0; i<grains_.size(); i++ ) {
111     grains_[i].repeats = 0;
112     count = ( i * gDuration_ * 0.001 * Stk::sampleRate() / nVoices );
113     grains_[i].counter = count;
114     grains_[i].state = GRAIN_STOPPED;
115   }
116 
117   for ( unsigned int i=0; i<lastFrame_.channels(); i++ )
118     lastFrame_[i] = 0.0;
119 }
120 
setVoices(unsigned int nVoices)121 void Granulate :: setVoices( unsigned int nVoices )
122 {
123 #if defined(_STK_DEBUG_)
124   std::ostringstream message;
125   message << "Granulate::setVoices: nVoices = " << nVoices << ", existing voices = " << grains_.size() << '.';
126   handleError( message.str(), StkError::DEBUG_PRINT );
127 #endif
128 
129   size_t oldSize = grains_.size();
130   grains_.resize( nVoices );
131 
132   // Initialize new grain voices.
133   size_t count;
134   for ( size_t i=oldSize; i<nVoices; i++ ) {
135     grains_[i].repeats = 0;
136     count = ( i * gDuration_ * 0.001 * Stk::sampleRate() / nVoices );
137     grains_[i].counter = count;
138     grains_[i].pointer = gPointer_;
139     grains_[i].state = GRAIN_STOPPED;
140   }
141 
142   gain_ = 1.0 / grains_.size();
143 }
144 
calculateGrain(Granulate::Grain & grain)145 void Granulate :: calculateGrain( Granulate::Grain& grain )
146 {
147   if ( grain.repeats > 0 ) {
148     grain.repeats--;
149     grain.pointer = grain.startPointer;
150     if ( grain.attackCount > 0 ) {
151       grain.eScaler = 0.0;
152       grain.eRate = -grain.eRate;
153       grain.counter = grain.attackCount;
154       grain.state = GRAIN_FADEIN;
155     }
156     else {
157       grain.counter = grain.sustainCount;
158       grain.state = GRAIN_SUSTAIN;
159     }
160     return;
161   }
162 
163   // Calculate duration and envelope parameters.
164   StkFloat seconds = gDuration_ * 0.001;
165   seconds += ( seconds * gRandomFactor_ * noise.tick() );
166   unsigned long count = (unsigned long) ( seconds * Stk::sampleRate() );
167   grain.attackCount = (unsigned int) ( gRampPercent_ * 0.005 * count );
168   grain.decayCount = grain.attackCount;
169   grain.sustainCount = count - 2 * grain.attackCount;
170   grain.eScaler = 0.0;
171   if ( grain.attackCount > 0 ) {
172     grain.eRate = 1.0 / grain.attackCount;
173     grain.counter = grain.attackCount;
174     grain.state = GRAIN_FADEIN;
175   }
176   else {
177     grain.counter = grain.sustainCount;
178     grain.state = GRAIN_SUSTAIN;
179   }
180 
181   // Calculate delay parameter.
182   seconds = gDelay_ * 0.001;
183   seconds += ( seconds * gRandomFactor_ * noise.tick() );
184   count = (unsigned long) ( seconds * Stk::sampleRate() );
185   grain.delayCount = count;
186 
187   // Save stretch parameter.
188   grain.repeats = gStretch_;
189 
190   // Calculate offset parameter.
191   seconds = gOffset_ * 0.001;
192   seconds += ( seconds * gRandomFactor_ * std::abs( noise.tick() ) );
193   int offset = (int) ( seconds * Stk::sampleRate() );
194 
195   // Add some randomization to the pointer start position.
196   seconds = gDuration_ * 0.001 * gRandomFactor_ * noise.tick();
197   offset += (int) ( seconds * Stk::sampleRate() );
198   grain.pointer += offset;
199   while ( grain.pointer >= data_.frames() ) grain.pointer -= data_.frames();
200   if ( grain.pointer <  0 ) grain.pointer = 0;
201   grain.startPointer = grain.pointer;
202 }
203 
tick(unsigned int channel)204 StkFloat Granulate :: tick( unsigned int channel )
205 {
206 #if defined(_STK_DEBUG_)
207   if ( channel >= data_.channels() ) {
208     oStream_ << "Granulate::tick(): channel argument and soundfile data are incompatible!";
209     handleError( StkError::FUNCTION_ARGUMENT );
210   }
211 #endif
212 
213   unsigned int i, j, nChannels = lastFrame_.channels();
214   for ( j=0; j<nChannels; j++ ) lastFrame_[j] = 0.0;
215 
216   if ( data_.size() == 0 ) return 0.0;
217 
218   StkFloat sample;
219   for ( i=0; i<grains_.size(); i++ ) {
220 
221     if ( grains_[i].counter == 0 ) { // Update the grain state.
222 
223       switch ( grains_[i].state ) {
224 
225       case GRAIN_STOPPED:
226         // We're done waiting between grains ... setup for new grain
227         this->calculateGrain( grains_[i] );
228         break;
229 
230       case GRAIN_FADEIN:
231         // We're done ramping up the envelope
232         if ( grains_[i].sustainCount > 0 ) {
233           grains_[i].counter = grains_[i].sustainCount;
234           grains_[i].state = GRAIN_SUSTAIN;
235           break;
236         }
237         // else no sustain state (i.e. perfect triangle window)
238 
239       case GRAIN_SUSTAIN:
240         // We're done with flat part of envelope ... setup to ramp down
241         if ( grains_[i].decayCount > 0 ) {
242           grains_[i].counter = grains_[i].decayCount;
243           grains_[i].eRate = -grains_[i].eRate;
244           grains_[i].state = GRAIN_FADEOUT;
245           break;
246         }
247         // else no fade out state (gRampPercent = 0)
248 
249       case GRAIN_FADEOUT:
250         // We're done ramping down ... setup for wait between grains
251         if ( grains_[i].delayCount > 0 ) {
252           grains_[i].counter = grains_[i].delayCount;
253           grains_[i].state = GRAIN_STOPPED;
254           break;
255         }
256         // else no delay (gDelay = 0)
257 
258         this->calculateGrain( grains_[i] );
259       }
260     }
261 
262     // Accumulate the grain outputs.
263     if ( grains_[i].state > 0 ) {
264       for ( j=0; j<nChannels; j++ ) {
265         sample = data_[ nChannels * grains_[i].pointer + j ];
266 
267         if ( grains_[i].state == GRAIN_FADEIN || grains_[i].state == GRAIN_FADEOUT ) {
268           sample *= grains_[i].eScaler;
269           grains_[i].eScaler += grains_[i].eRate;
270         }
271 
272         lastFrame_[j] += sample;
273       }
274 
275 
276       // Increment and check pointer limits.
277       grains_[i].pointer++;
278       if ( grains_[i].pointer >= data_.frames() )
279         grains_[i].pointer = 0;
280     }
281 
282     // Decrement counter for all states.
283     grains_[i].counter--;
284   }
285 
286   // Increment our global file pointer at the stretch rate.
287   if ( stretchCounter_++ == gStretch_ ) {
288     gPointer_++;
289     if ( (unsigned long) gPointer_ >= data_.frames() ) gPointer_ = 0;
290     stretchCounter_ = 0;
291   }
292 
293   return lastFrame_[channel];
294 }
295 
296 } // stk namespace
297