CSL  6.0
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  mCloud->isPlaying = false;
20 }
21 
22 // The nextBuffer method does all the work of the synthesis, looping through the playing grains
23 
24 void GrainPlayer::nextBuffer(Buffer & outputBuffer) throw (CException) {
25  sample * out1; // assumes stereo output -- ToDo: fix me
26  sample * out2;
27  int index, length;
28  sample samp, env;
29  float durHalf;
30  unsigned numFrames = outputBuffer.mNumFrames;
31  out1 = outputBuffer.buffer(0);
32  out2 = outputBuffer.buffer(1);
33  memset(out1, 0, numFrames * sizeof(sample));
34  memset(out2, 0, numFrames * sizeof(sample));
35 
36  while (mCloud->gState != kFree) { // wait until done creating grains in other thread
37 // printf("!\n");
38  csl::sleepUsec(100);
39  }
40  mCloud->gState = kDSP; // set flag to keep scheduler from running while I'm calculating samples
41  // grain loop
42  for (Grain * curGrain = mCloud->mPlayingGrains; curGrain != 0;
43  curGrain = curGrain->nextGrain) {
44 // printf(".");
45  out1 = outputBuffer.buffer(0); // assume stereo output
46  out2 = outputBuffer.buffer(1);
47  length = curGrain->numSamples;
48  sample * sPtr = curGrain->samples;
49  for (unsigned i = 0; i < numFrames; i++) { // sample loop
50  if (curGrain->time >= curGrain->duration) // if grain has ended already
51  break;
52  if (curGrain->delay) { // if grain hasn't started yet
53  curGrain->delay--;
54  continue;
55  }
56  index = (int)(curGrain->position); // get index and wrap it
57  while (index < 0)
58  index += length;
59  while (index >= length)
60  index -= length;
61  samp = sPtr[index]; // get sample value (should interpolate samples)
62  durHalf = curGrain->duration * curGrain->env; // really cheap triangle envelope
63  if (curGrain->time < durHalf) // if before middle
64  env = (float) curGrain->time / durHalf;
65  else // else after middle
66  env = (float)(curGrain->duration - curGrain->time)
67  / (curGrain->duration - durHalf);
68  samp *= curGrain->amplitude * env; // scale by envelope
69  // Add to the current output samples
70  *out1++ += samp * (1.0f - curGrain->pan); // write to Left channel
71  *out2++ += samp * curGrain->pan; // write to Right channel
72 
73  curGrain->position += curGrain->rate; // Update position
74  curGrain->time += 1.0f; // Update time
75  }
76  }
77  mCloud->gNow = C_TIME; // increment time
78  mCloud->gState = kFree; // release lock
79 // printf("\n");
80 }
81 
82 // GrainCloud implementation -- grain mgmnt loops are forked as a separate threads
83 
84 // Constructor allocates global pool of MAXGRAINS available grains
85 
87  Grain * newGrain;
88  Grain * prevGrain;
89  SAFE_MALLOC(prevGrain, Grain, sizeof(Grain));
90  mSilentGrains = prevGrain; // first in list
91  // allocate a list of passive grains
92  for (unsigned i = 0; i < MAXGRAINS - 1; i++) {
93  SAFE_MALLOC(newGrain, Grain, sizeof(Grain));
94  prevGrain->nextGrain = newGrain;
95  prevGrain = newGrain;
96  }
97  prevGrain->nextGrain = 0; // set last grain's Next to 0;
98  unsigned count = 0; // testing: count silent grains
99  for (newGrain = mSilentGrains; newGrain != 0; newGrain = newGrain->nextGrain)
100  count++;
101  logMsg("Create grain lists: %d available", count);
102  mPlayingGrains = 0; // empty list of active grains
103  // now set up threads
106  threadOn = false;
107  isPlaying = false;
108  gState = kFree;
109  mSamples = 0;
110 }
111 
112 // stop threads and delete lists
113 
116  reaperThread->stopThread(100);
117  csl::sleepMsec(150);
118 }
119 
120 // Grain creation loop: take the head item of silentGrains, fill it in based on the random ranges,
121 // and put it at the head of playingGrains
122 
123 void * createGrains(void * theArg) {
124  GrainCloud * cloud = (GrainCloud * ) theArg;
125  Grain * newGrain;
126  logMsg("Starting grain creation loop");
127 // printf("\tG: ADDR \t\tDUR \tPOS \tRATE\tPAN\n");
128  while (true) {
129  if ( ! cloud->isPlaying) {
130  logMsg("Grain creation loop exits");
131  return NULL; // exit thread
132  }
133  while (cloud->gState != kFree) // wait until done creating samples in other thread
134  csl::sleepMsec(1);
135  cloud->gState = kSched; // set flag to keep from calculating samples while I'm creating grains
136 
137  if (cloud->isPlaying) {
138  newGrain = cloud->mSilentGrains; // take the first silent grain and put it into the temp variable
139  if (newGrain == 0) { // if no free grains, wait a bit
140  printf("\tNo free grains\n");
141 // cloud->reset(); // KLUDJ -- reset all if none free
142  goto next;
143  }
144  cloud->mSilentGrains = newGrain->nextGrain; // set the temp variable's Next to the head of silentGrains
145  // fill it in from the GUI object's ranges
146  newGrain->position = fRandB(cloud->mOffsetBase, cloud->mOffsetRange) * cloud->numSamples;
147  newGrain->duration = fRandB(cloud->mDurationBase, cloud->mDurationRange) * CGestalt::frameRate();
148  newGrain->rate = fRandB(cloud->mRateBase, cloud->mRateRange);
149  newGrain->amplitude = fRandB(cloud->mVolumeBase, cloud->mVolumeRange) / cloud->mDensityBase;
150  newGrain->time = 0.0f;
151  newGrain->pan = fRandB(cloud->mWidthBase, cloud->mWidthRange);
152  newGrain->env = fRandB(cloud->mEnvelopeBase, cloud->mEnvelopeRange);
153  // delay within the next callback
154  newGrain->delay = (unsigned) ((float) (C_TIME - cloud->gNow) * cloud->sampsPerTick);
155  newGrain->samples = cloud->mSamples;
156  newGrain->numSamples = cloud->numSamples;
157  newGrain->nextGrain = cloud->mPlayingGrains; // set the temp variable's Next to the current head of playingGrains
158  cloud->mPlayingGrains = newGrain; // set the head of playingGrains to the temp variable
159 // printf("\tG: %x\t\t%5.1f \t%7.2f \t%4.2f \t%4.2f\n",
160 // newGrain, newGrain->duration, newGrain->position, newGrain->rate, newGrain->pan);
161  } else {
162  logMsg("Grain creation loop exits 2");
163  return NULL; // exit loop
164  }
165 
166 next: cloud->gState = kFree; // sleep for the inter-grain delay (may not be absolutely accurate)
167  csl::sleepUsec(1000000.0f / fRandB(cloud->mDensityBase, cloud->mDensityRange));
168  }
169  return NULL; // never reached
170 }
171 
172 // Clean up completed grains, taking them off of mPlayingGrains and returning them to mSilentGrains
173 
174 void * reapGrains(void *theArg) {
175  GrainCloud * cloud = (GrainCloud * ) theArg;
176  Grain *prevGrain, *curGrain, *nextGrain;
177 
178  logMsg("Starting grain culling loop");
179  csl::sleepMsec(200); // sleep a bit before starting
180  while (true) {
181  if ( ! cloud->isPlaying) {
182  logMsg("Grain culling loop exits");
183  return NULL; // exit thread
184  }
185  while (cloud->gState != kFree) // wait until done creating samples in other thread
186  csl::sleepMsec(10);
187  cloud->gState = kSched; // set flag to keep from calculating samples while I'm creating grains
188  if (cloud->isPlaying) {
189  prevGrain = 0;
190  curGrain = cloud->mPlayingGrains;
191  while (curGrain != 0) {
192  if (curGrain->time >= curGrain->duration) { // if the position is at the end, free the grain
193  nextGrain = curGrain->nextGrain;
194 // printf("\tK: %x -> %x\n", curGrain, nextGrain);
195  if (prevGrain == 0) // If it's the first one
196  cloud->mPlayingGrains = nextGrain;
197  else // else there is a grain before this one
198  prevGrain->nextGrain = nextGrain;
199  curGrain->nextGrain = cloud->mSilentGrains; // put the free one on the silent list
200  cloud->mSilentGrains = curGrain;
201  curGrain = nextGrain;
202  if (curGrain == 0)
203  goto next;
204  } else {
205  prevGrain = curGrain;
206  curGrain = curGrain->nextGrain;
207  }
208  }
209  } else {
210  logMsg("Grain culling loop exits 2");
211  return NULL; // exit thread
212  }
213 next: cloud->gState = kFree;
214  csl::sleepMsec(500); // sleep for 1/2 sec
215  }
216  return NULL; // never reached
217 }
218 
219 // Start the grain-creation thread
220 
222  isPlaying = true;
223  if (threadOn)
224  return;
225  threadOn = true;
226  gNow = C_TIME; // get start time
227  sampsPerTick = (float) CGestalt::frameRate() / (float) C_TIME;
228  logMsg("Starting grain creation/culling threads (%d)", C_TIME);
231  csl::sleepMsec(100); // sleep for a bit
232  logMsg("Grain threads running");
233 }
234 
235 // reset the list of grains to be all silent
236 
238  unsigned live = 0; // testing: count killed grains
239  Grain * curGrain = mPlayingGrains;
240  Grain * nextGrain;
241  while (curGrain != 0) {
242  nextGrain = curGrain->nextGrain;
243  curGrain->nextGrain = mSilentGrains; // put the grain on the silent list
244  mSilentGrains = curGrain;
245  curGrain = nextGrain;
246  live++;
247  }
248  mPlayingGrains = 0; // empty list
249 // unsigned dead = 0; // testing: count silent grains
250 // for (curGrain = silentGrains; curGrain != 0; curGrain = curGrain->nextGrain)
251 // dead++;
252 // printf("\nReset grain lists (%d stopped, %d available)\n", live, dead);
253 }
void logMsg(const char *format,...)
These are the public logging messages.
Definition: CGestalt.cpp:292
float * mSamples
sample buffer pointer
Definition: Granulator.h:70
float mWidthBase
stereo width
Definition: Granulator.h:63
bool sleepMsec(float dur)
Definition: CGestalt.cpp:372
GrainPlayer(GrainCloud *cloud)
Definition: Granulator.cpp:14
AdditiveInstrument.h – Sum-of-sines synthesis instrument class.
Definition: Accessor.h:17
float pan
stereo pan 0 - 1
Definition: Granulator.h:26
float mDurationBase
grain duration base
Definition: Granulator.h:61
#define C_TIME
Which kind of accurate timer to use?
Definition: CGestalt.h:202
free state
Definition: Granulator.h:38
Grain * mSilentGrains
shared grain lists - ptr to the free pool (silent)
Definition: Granulator.h:74
float sampsPerTick
resolution of hi-res clock(s-rate / 1 billion)
Definition: Granulator.h:78
float mVolumeBase
amplitude scale
Definition: Granulator.h:65
float mOffsetRange
offset range
Definition: Granulator.h:58
float * samples
sample buffer pointer
Definition: Granulator.h:30
void startThreads()
method to start-up the create/reap threads
Definition: Granulator.cpp:221
float time
current time (index) in samples
Definition: Granulator.h:25
float rate
playback rate (1.0 for normal pitch, < 0 reads backwards)
Definition: Granulator.h:23
GrainCloud * mCloud
the cloud I play
Definition: Granulator.h:95
float mOffsetBase
starting index offset base
Definition: Granulator.h:57
void stopThread(int timeOutMilliseconds)
calculating audio samples
Definition: Granulator.h:39
void reset()
reset all grains to silent
Definition: Granulator.cpp:237
#define MAXGRAINS
Definition: Granulator.h:15
Grain * nextGrain
A pointer to the next grain in the linked list.
Definition: Granulator.h:32
float mEnvelopeRange
envelope range
Definition: Granulator.h:68
void * reapGrains(void *theArg)
Definition: Granulator.cpp:174
float sample
(could be changed to int, or double)
Definition: CSL_Types.h:191
unsigned mNumChannels
my "expected" number of output channels
Definition: CSL_Core.h:292
Grain * mPlayingGrains
ptr to the list of active grains
Definition: Granulator.h:75
float mWidthRange
stereo width
Definition: Granulator.h:64
float fRandB(float base, float range)
b +- r (base)
Definition: CGestalt.cpp:438
static unsigned frameRate()
default frame rate
Definition: CGestalt.cpp:51
void * createGrains(void *theArg)
Definition: Granulator.cpp:123
float position
running sample index
Definition: Granulator.h:28
CThread * spawnerThread
thread to create grains
Definition: Granulator.h:81
scheduling grains
Definition: Granulator.h:40
float mRateBase
grain rate base
Definition: Granulator.h:55
float mDensityBase
grain density base
Definition: Granulator.h:59
unsigned delay
initial delay in samples
Definition: Granulator.h:29
float mEnvelopeBase
envelope base: 0 = perc, 0.5 = triangle, 1 = reverse perc
Definition: Granulator.h:67
virtual int createThread(VoidFcnPtr *func, void *args)=0
unsigned numSamples
of samples in buffer
Definition: Granulator.h:71
Grain data structure This implementation uses a linked list data structure. You might want to add a f...
Definition: Granulator.h:21
static CThread * MakeThread()
factory method
float mVolumeRange
amplitude range
Definition: Granulator.h:66
GrainulatorState gState
granulator state flag
Definition: Granulator.h:76
Buffer – the multi-channel sample buffer class (passed around between generators and IO guys)...
Definition: CSL_Core.h:106
float mDensityRange
grain density range
Definition: Granulator.h:60
void nextBuffer(Buffer &outputBuffer)
this sums up the list of live grains – very simple
Definition: Granulator.cpp:24
float duration
duration in samples
Definition: Granulator.h:24
GrainCloud()
simple constructor
Definition: Granulator.cpp:86
long gNow
clock for accurate timing
Definition: Granulator.h:77
float mRateRange
rate random range
Definition: Granulator.h:56
bool sleepUsec(float dur)
Misc. global functions in the csl namespace.
Definition: CGestalt.cpp:345
GrainCloud – routine for playing clouds under GUI control. This could be called a cloud or a stream...
Definition: Granulator.h:47
float mDurationRange
grain duration range
Definition: Granulator.h:62
forward declaration
Definition: CSL_Core.h:241
unsigned numSamples
length of sample vector
Definition: Granulator.h:31
bool isPlaying
whether I'm on or off
Definition: Granulator.h:72
CThread * reaperThread
thread to kill finished grains
Definition: Granulator.h:82
bool threadOn
if the thread's running
Definition: Granulator.h:83
#define SAFE_MALLOC(ptr, type, len)
Useful Macros.
Definition: CGestalt.h:103
float amplitude
amplitude scale (0 - 1)
Definition: Granulator.h:22
Base class of CSL exceptions (written upper-case). Has a string message.
float env
envelope: 0 = perc, 0.5 = triangle, 1 = reverse perc
Definition: Granulator.h:27