CSL 5.2

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

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
18 GrainPlayer::~GrainPlayer() { }
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
84 GrainCloud::GrainCloud() {
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
102 spawnerThread = CThread::MakeThread();
103 reaperThread = CThread::MakeThread();
104 threadOn = false;
105 isPlaying = false;
106 gState = kFree;
107 mSamples = 0;
108 }
109
110 GrainCloud::~GrainCloud() {
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
215 void GrainCloud::startThreads() {
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);
223 spawnerThread->createThread(createGrains, this);
224 reaperThread->createThread(reapGrains, this);
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
232 void GrainCloud::reset() {
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 }