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