CSL  5.2
Granulator.cpp
Go to the documentation of this file.
1 //
2 // Granulator.h -- CSL class for doing granular synthesis
3 //
4 // See the copyright notice and acknowledgment of authors in the file COPYRIGHT
5 //
6 
7 #include "Granulator.h"
8 
9 using namespace csl;
10 using namespace std;
11 
12 // GrainPlayer implementation - pretty simple
13 
14 GrainPlayer::GrainPlayer(GrainCloud * cloud) : UnitGenerator(), mCloud(cloud) {
15  mNumChannels = 2; // I'm always stereo
16 }
17 
19 
20 // The nextBuffer method does all the work of the synthesis, looping through the playing grains
21 
22 void GrainPlayer::nextBuffer(Buffer & outputBuffer) throw (CException) {
23  sample * out1; // assumes stereo output -- ToDo: fix me
24  sample * out2;
25  int index, length;
26  sample samp, env;
27  float durHalf;
28  unsigned numFrames = outputBuffer.mNumFrames;
29  out1 = outputBuffer.buffer(0);
30  out2 = outputBuffer.buffer(1);
31  memset(out1, 0, numFrames * sizeof(sample));
32  memset(out2, 0, numFrames * sizeof(sample));
33 
34  while (mCloud->gState != kFree) { // wait until done creating grains in other thread
35 // printf("!\n");
36  csl::sleepUsec(100);
37  }
38  mCloud->gState = kDSP; // set flag to keep scheduler from running while I'm calculating samples
39  // grain loop
40  for (Grain * curGrain = mCloud->mPlayingGrains; curGrain != 0;
41  curGrain = curGrain->nextGrain) {
42 // printf(".");
43  out1 = outputBuffer.buffer(0); // assume stereo output
44  out2 = outputBuffer.buffer(1);
45  length = curGrain->numSamples;
46  sample * sPtr = curGrain->samples;
47  for (unsigned i = 0; i < numFrames; i++) { // sample loop
48  if (curGrain->time >= curGrain->duration) // if grain has ended already
49  break;
50  if (curGrain->delay) { // if grain hasn't started yet
51  curGrain->delay--;
52  continue;
53  }
54  index = (int)(curGrain->position); // get index and wrap it
55  while (index < 0)
56  index += length;
57  while (index >= length)
58  index -= length;
59  samp = sPtr[index]; // get sample value (should interpolate samples)
60  durHalf = curGrain->duration * curGrain->env; // really cheap triangle envelope
61  if (curGrain->time < durHalf) // if before middle
62  env = (float) curGrain->time / durHalf;
63  else // else after middle
64  env = (float)(curGrain->duration - curGrain->time)
65  / (curGrain->duration - durHalf);
66  samp *= curGrain->amplitude * env; // scale by envelope
67  // Add to the current output samples
68  *out1++ += samp * (1.0f - curGrain->pan); // write to Left channel
69  *out2++ += samp * curGrain->pan; // write to Right channel
70 
71  curGrain->position += curGrain->rate; // Update position
72  curGrain->time += 1.0f; // Update time
73  }
74  }
75  mCloud->gNow = C_TIME; // increment time
76  mCloud->gState = kFree; // release lock
77 // printf("\n");
78 }
79 
80 // GrainCloud implementation -- grain mgmnt loops are forked as a separate threads
81 
82 // Constructor allocates global pool of MAXGRAINS available grains
83 
85  Grain * newGrain;
86  Grain * prevGrain;
87  SAFE_MALLOC(prevGrain, Grain, sizeof(Grain));
88  mSilentGrains = prevGrain; // first in list
89  // allocate a list of passive grains
90  for (unsigned i = 0; i < MAXGRAINS - 1; i++) {
91  SAFE_MALLOC(newGrain, Grain, sizeof(Grain));
92  prevGrain->nextGrain = newGrain;
93  prevGrain = newGrain;
94  }
95  prevGrain->nextGrain = 0; // set last grain's Next to 0;
96  unsigned count = 0; // testing: count silent grains
97  for (newGrain = mSilentGrains; newGrain != 0; newGrain = newGrain->nextGrain)
98  count++;
99  logMsg("Create grain lists: %d available", count);
100  mPlayingGrains = 0; // empty list of active grains
101  // now set up threads
104  threadOn = false;
105  isPlaying = false;
106  gState = kFree;
107  mSamples = 0;
108 }
109 
111  // should delete mGrains here
112 }
113 
114 // Grain creation loop: take the head item of silentGrains, fill it in based on the random ranges,
115 // and put it at the head of playingGrains
116 
117 void * createGrains(void * theArg) {
118  GrainCloud * cloud = (GrainCloud * ) theArg;
119  Grain * newGrain;
120  logMsg("Starting grain creation loop");
121 // printf("\tG: ADDR \t\tDUR \tPOS \tRATE\tPAN\n");
122  while (true) {
123  if ( ! cloud->isPlaying) {
124  logMsg("Grain creation loop exits");
125  return NULL; // exit thread
126  }
127  while (cloud->gState != kFree) // wait until done creating samples in other thread
128  csl::sleepMsec(1);
129  cloud->gState = kSched; // set flag to keep from calculating samples while I'm creating grains
130 
131  if (cloud->isPlaying) {
132  newGrain = cloud->mSilentGrains; // take the first silent grain and put it into the temp variable
133  if (newGrain == 0) { // if no free grains, wait a bit
134  printf("\tNo free grains\n");
135 // cloud->reset(); // KLUDJ -- reset all if none free
136  goto next;
137  }
138  cloud->mSilentGrains = newGrain->nextGrain; // set the temp variable's Next to the head of silentGrains
139  // fill it in from the GUI object's ranges
140  newGrain->position = fRandB(cloud->mOffsetBase, cloud->mOffsetRange) * cloud->numSamples;
141  newGrain->duration = fRandB(cloud->mDurationBase, cloud->mDurationRange) * CGestalt::frameRate();
142  newGrain->rate = fRandB(cloud->mRateBase, cloud->mRateRange);
143  newGrain->amplitude = fRandB(cloud->mVolumeBase, cloud->mVolumeRange) / cloud->mDensityBase;
144  newGrain->time = 0.0f;
145  newGrain->pan = fRandB(cloud->mWidthBase, cloud->mWidthRange);
146  newGrain->env = fRandB(cloud->mEnvelopeBase, cloud->mEnvelopeRange);
147  // delay within the next callback
148  newGrain->delay = (unsigned) ((float) (C_TIME - cloud->gNow) * cloud->sampsPerTick);
149  newGrain->samples = cloud->mSamples;
150  newGrain->numSamples = cloud->numSamples;
151  newGrain->nextGrain = cloud->mPlayingGrains; // set the temp variable's Next to the current head of playingGrains
152  cloud->mPlayingGrains = newGrain; // set the head of playingGrains to the temp variable
153 // printf("\tG: %x\t\t%5.1f \t%7.2f \t%4.2f \t%4.2f\n",
154 // newGrain, newGrain->duration, newGrain->position, newGrain->rate, newGrain->pan);
155  } else {
156  logMsg("Grain creation loop exits 2");
157  return NULL; // exit loop
158  }
159 
160 next: cloud->gState = kFree; // sleep for the inter-grain delay (may not be absolutely accurate)
161  csl::sleepUsec(1000000.0f / fRandB(cloud->mDensityBase, cloud->mDensityRange));
162  }
163  return NULL; // never reached
164 }
165 
166 // Clean up completed grains, taking them off of mPlayingGrains and returning them to mSilentGrains
167 
168 void * reapGrains(void *theArg) {
169  GrainCloud * cloud = (GrainCloud * ) theArg;
170  Grain *prevGrain, *curGrain, *nextGrain;
171 
172  logMsg("Starting grain culling loop");
173  csl::sleepMsec(200); // sleep a bit before starting
174  while (true) {
175  if ( ! cloud->isPlaying) {
176  logMsg("Grain culling loop exits");
177  return NULL; // exit thread
178  }
179  while (cloud->gState != kFree) // wait until done creating samples in other thread
180  csl::sleepMsec(10);
181  cloud->gState = kSched; // set flag to keep from calculating samples while I'm creating grains
182  if (cloud->isPlaying) {
183  prevGrain = 0;
184  curGrain = cloud->mPlayingGrains;
185  while (curGrain != 0) {
186  if (curGrain->time >= curGrain->duration) { // if the position is at the end, free the grain
187  nextGrain = curGrain->nextGrain;
188 // printf("\tK: %x -> %x\n", curGrain, nextGrain);
189  if (prevGrain == 0) // If it's the first one
190  cloud->mPlayingGrains = nextGrain;
191  else // else there is a grain before this one
192  prevGrain->nextGrain = nextGrain;
193  curGrain->nextGrain = cloud->mSilentGrains; // put the free one on the silent list
194  cloud->mSilentGrains = curGrain;
195  curGrain = nextGrain;
196  if (curGrain == 0)
197  goto next;
198  } else {
199  prevGrain = curGrain;
200  curGrain = curGrain->nextGrain;
201  }
202  }
203  } else {
204  logMsg("Grain culling loop exits 2");
205  return NULL; // exit thread
206  }
207 next: cloud->gState = kFree;
208  csl::sleepMsec(500); // sleep for 1/2 sec
209  }
210  return NULL; // never reached
211 }
212 
213 // Start the grain-creation thread
214 
216  isPlaying = true;
217  if (threadOn)
218  return;
219  threadOn = true;
220  gNow = C_TIME; // get start time
221  sampsPerTick = (float) CGestalt::frameRate() / (float) C_TIME;
222  logMsg("Starting grain creation/culling threads (%d)", C_TIME);
225  csl::sleepMsec(100); // sleep for a bit
226  logMsg("Grain threads running");
227 
228 }
229 
230 // reset the list of grains to be all silent
231 
233  unsigned live = 0; // testing: count killed grains
234  Grain * curGrain = mPlayingGrains;
235  Grain * nextGrain;
236  while (curGrain != 0) {
237  nextGrain = curGrain->nextGrain;
238  curGrain->nextGrain = mSilentGrains; // put the grain on the silent list
239  mSilentGrains = curGrain;
240  curGrain = nextGrain;
241  live++;
242  }
243  mPlayingGrains = 0; // empty list
244 // unsigned dead = 0; // testing: count silent grains
245 // for (curGrain = silentGrains; curGrain != 0; curGrain = curGrain->nextGrain)
246 // dead++;
247 // printf("\nReset grain lists (%d stopped, %d available)\n", live, dead);
248 }