1 /* $Id: Engine.cpp,v 1.1 2012/07/08 00:45:55 sarrazip Exp $
2
3 flatzebra - SDL-based sound renderer
4 Copyright (C) 2011 Pierre Sarrazin <http://sarrazip.com/>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public
17 License along with this program; if not, write to the Free
18 Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20 */
21
22 #include <roundbeetle/Engine.h>
23
24 #include <roundbeetle/NativeSDLSoundRenderer.h>
25 #include <roundbeetle/SoundRenderer.h>
26 #include <roundbeetle/FrameSource.h>
27 #include <roundbeetle/FrameSourceAdder.h>
28 #include <roundbeetle/WaveFileSource.h>
29 #include <roundbeetle/SampleToFramePanner.h>
30 #include <roundbeetle/SineSource.h>
31 #include <roundbeetle/SquareWaveSource.h>
32 #include <roundbeetle/WhiteNoiseSource.h>
33 #include <roundbeetle/PausableSource.h>
34 #include <roundbeetle/ADSRSource.h>
35 #include <roundbeetle/ADSR.h>
36 #include <roundbeetle/LoopingSource.h>
37
38 #include <iostream>
39
40
41 namespace roundbeetle {
42
43 using namespace std;
44
45
46 Engine *Engine::inst = NULL;
47
48
Engine(float _mainBusVolumeMeterWindowLengthInSeconds)49 Engine::Engine(float _mainBusVolumeMeterWindowLengthInSeconds)
50 : mainBus(true, _mainBusVolumeMeterWindowLengthInSeconds),
51 reqTable(),
52 reqHandleGenerator(0),
53 pauseCounter(0),
54 onRequestFinishedCallback(NULL),
55 onRequestFinishedUserData(NULL)
56 {
57 }
58
59
60 int
create(int rendererFreq,float mainBusVolumeMeterWindowLengthInSeconds,const char * rawRecordingFilename)61 Engine::create(int rendererFreq,
62 float mainBusVolumeMeterWindowLengthInSeconds,
63 const char *rawRecordingFilename)
64 {
65 assert(inst == NULL);
66
67 NativeSDLSoundRenderer &soundRenderer = NativeSDLSoundRenderer::create(rendererFreq);
68
69 if (rawRecordingFilename != NULL)
70 {
71 soundRenderer.openRecordingFile(rawRecordingFilename);
72 soundRenderer.startRecording();
73 }
74
75 int status = soundRenderer.start();
76 if (status != 0)
77 {
78 NativeSDLSoundRenderer::destroy();
79 return status;
80 }
81
82 inst = new Engine(mainBusVolumeMeterWindowLengthInSeconds);
83 return 0; // success
84 }
85
86
87 Engine &
instance()88 Engine::instance()
89 {
90 assert(inst != NULL);
91 return *inst;
92 }
93
94
95 void
destroy()96 Engine::destroy()
97 {
98 NativeSDLSoundRenderer &soundRenderer = dynamic_cast<NativeSDLSoundRenderer &>(SoundRenderer::instance());
99 if (soundRenderer.stopRecording())
100 {
101 if (! soundRenderer.closeRecordingFile())
102 cerr << "Engine::destroy: failed to close recording file" << endl;
103 }
104
105 delete inst;
106 inst = NULL;
107
108 soundRenderer.requestStop();
109 soundRenderer.waitForEnd();
110 NativeSDLSoundRenderer::destroy();
111 }
112
113
114 void
postBusInit()115 Engine::postBusInit()
116 {
117 SoundRenderer::instance().getTopFrameSourceAdder().addChild(&mainBus.getTopFrameSource());
118
119 // Get notified when a child of the main bus finishes.
120 //
121 mainBus.getAdder().registerChildRemovedCallback(onChildRemovedStatic, this);
122 }
123
124
~Engine()125 Engine::~Engine()
126 {
127 if (! SoundRenderer::instance().getTopFrameSourceAdder().killChild(&mainBus.getTopFrameSource()))
128 assert(!"killChild() failed on mainBus top frame source");
129
130 // mainBus's top frame source and its children have now been
131 // destroyed by killChild().
132 // Tell mainBus to reset its top frame source pointer, so that
133 // ~Bus() does not have anything to destroy (it is already done).
134 //
135 mainBus.reset();
136 }
137
138
139 // FrameSourceAdder::removeChild() invokes this callback under the renderer mutex.
140 // This callback must be as quick as possible.
141 //
142 void //static
onChildRemovedStatic(FrameSource * child,void * userData,FrameSourceAdder * adder)143 Engine::onChildRemovedStatic(FrameSource *child, void *userData, FrameSourceAdder *adder)
144 {
145 assert(child != NULL);
146 assert(userData != NULL);
147 assert(adder != NULL);
148
149 reinterpret_cast<Engine *>(userData)->onChildRemoved(child, adder);
150 }
151
152
153 void
onChildRemoved(FrameSource * child,FrameSourceAdder *)154 Engine::onChildRemoved(FrameSource *child, FrameSourceAdder * /*adder*/)
155 {
156 //cout << "onChildRemoved(" << child << ", _)" << endl;
157 RequestMap::iterator it = findReqHandle(child);
158 assert(it != reqTable.end());
159 if (onRequestFinishedCallback != NULL)
160 (*onRequestFinishedCallback)(it->first, onRequestFinishedUserData);
161 reqTable.erase(it);
162 }
163
164
165 void
registerRequestFinishedCallback(RequestFinishedCallback cb,void * userData)166 Engine::registerRequestFinishedCallback(RequestFinishedCallback cb, void *userData)
167 {
168 AutoLocker a(SoundRenderer::getMutex());
169
170 onRequestFinishedCallback = cb;
171 onRequestFinishedUserData = userData;
172 }
173
174
175 Bus &
getMainBus()176 Engine::getMainBus()
177 {
178 return mainBus;
179 }
180
181
182 bool
isPostBusInitDone() const183 Engine::isPostBusInitDone() const
184 {
185 FrameSourceAdder::OnChildRemovedCallback cb = NULL;
186 void *userData = NULL;
187
188 {
189 AutoLocker a(SoundRenderer::getMutex());
190
191 mainBus.getAdder().getChildRemovedCallback(cb, userData);
192 }
193
194 assert(cb == NULL || cb == onChildRemovedStatic);
195 assert(userData == NULL || userData == this);
196 assert((cb == NULL) == (userData == NULL));
197 return cb == &Engine::onChildRemovedStatic;
198 }
199
200
201 int
requestWaveFileSound(const WaveFileBuffer & wfb,Bus & bus)202 Engine::requestWaveFileSound(const WaveFileBuffer &wfb, Bus &bus)
203 {
204 if (! isPostBusInitDone()) // each request*() method must to this check
205 return -1;
206 WaveFileSource *wfs = new WaveFileSource(wfb);
207 return addSampleSourceToBus(wfs, NULL, 1, bus);
208 }
209
210
211 int
requestSine(float _toneFreq,float _linVol,float _durationInSeconds,Bus & bus)212 Engine::requestSine(float _toneFreq, float _linVol, float _durationInSeconds, Bus &bus)
213 {
214 if (! isPostBusInitDone())
215 return -1;
216 SineSource *ss = new SineSource(_toneFreq, _linVol, _durationInSeconds);
217 return addSampleSourceToBus(ss, NULL, 1, bus);
218 }
219
220
221 int
requestSquareWave(float _freq,const ADSR & adsr,size_t loopCount,Bus & bus)222 Engine::requestSquareWave(float _freq, const ADSR &adsr, size_t loopCount, Bus &bus)
223 {
224 if (! isPostBusInitDone())
225 return -1;
226 SquareWaveSource *ss = new SquareWaveSource(_freq, 1, 0); // infinite
227 return addSampleSourceToBus(ss, &adsr, loopCount, bus);
228 }
229
230
231 int
requestSquareWave(FrequencyFunction & freqFunc,const ADSR & adsr,size_t loopCount,Bus & bus)232 Engine::requestSquareWave(FrequencyFunction &freqFunc, const ADSR &adsr, size_t loopCount, Bus &bus)
233 {
234 if (! isPostBusInitDone())
235 return -1;
236 SquareWaveSource *ss = new SquareWaveSource(freqFunc, 1, 0); // infinite
237 return addSampleSourceToBus(ss, &adsr, loopCount, bus);
238 }
239
240
241 bool
setSquareWaveFreq(int reqHandle,float _newFreq)242 Engine::setSquareWaveFreq(int reqHandle, float _newFreq)
243 {
244 AutoLocker a(SoundRenderer::getMutex());
245
246 RequestMap::const_iterator it = reqTable.find(reqHandle);
247 //cout << "setSquareWaveFreq(" << reqHandle << "): " << it->first << ", " << it->second << endl;
248 if (it == reqTable.end()) // if request handle not found
249 return false; // request is dead
250
251 SampleSource *ss = it->second.clientSampleSource;
252 assert(ss != NULL);
253 SquareWaveSource *svs = dynamic_cast<SquareWaveSource *>(ss);
254 if (svs == NULL)
255 return false; // request is not a square wave
256
257 svs->setFrequency(_newFreq);
258 return true; // success
259 }
260
261
262 int
requestWhiteNoise(const ADSR & adsr,size_t loopCount,Bus & bus)263 Engine::requestWhiteNoise(const ADSR &adsr, size_t loopCount, Bus &bus)
264 {
265 if (! isPostBusInitDone())
266 return -1;
267 WhiteNoiseSource *ss = new WhiteNoiseSource(1, 0); // infinite
268 return addSampleSourceToBus(ss, &adsr, loopCount, bus);
269 }
270
271
272 // ss: must come from new; becomes owned by bus adder.
273 // Increments reqHandleGenerator.
274 // Locks renderer mutex.
275 //
276 // Path:
277 // PausableSource <- SampleToFramePanner <- LoopingSource? <- ADSRSource? <- SampleSource
278 // ? = optional
279 //
280 int
addSampleSourceToBus(SampleSource * ss,const ADSR * adsr,size_t loopCount,Bus & bus)281 Engine::addSampleSourceToBus(SampleSource *ss, const ADSR *adsr, size_t loopCount, Bus &bus)
282 {
283 ADSRSource *as = (adsr == NULL ? NULL : new ADSRSource(ss, *adsr));
284
285 SampleSource *loopableSource = (as == NULL ? ss : as);
286
287 // Only insert a LoopingSource if necessary:
288 SampleSource *ls = (loopCount == 1
289 ? loopableSource
290 : new LoopingSource(loopableSource, loopCount));
291 /*cout << "Engine::addSampleSourceToBus: ss=" << ss
292 << ", as=" << as
293 << ", loopableSource=" << loopableSource
294 << ", ls=" << ls
295 << endl;*/
296
297 SampleToFramePanner *panner = new SampleToFramePanner(ls, NULL);
298 PausableSource *ps = new PausableSource(panner);
299
300 int reqHandle;
301 {
302 AutoLocker a(SoundRenderer::getMutex());
303
304 reqHandle = reqHandleGenerator++;
305 reqTable[reqHandle] = Desc(ss, as, ps, &bus);
306 bus.getAdder().addChildUnsafe(ps);
307 }
308
309 return reqHandle;
310 }
311
312
313 bool
isRequestAlive(int reqHandle) const314 Engine::isRequestAlive(int reqHandle) const
315 {
316 AutoLocker a(SoundRenderer::getMutex());
317
318 RequestMap::const_iterator it = reqTable.find(reqHandle);
319 //cout << "isRequestAlive(" << reqHandle << "): " << it->first << ", " << it->second << endl;
320 return it != reqTable.end();
321 }
322
323
324 // Must be called under protection of renderer mutex.
325 //
326 PausableSource *
getPausableSourceFromRequestHandle(int reqHandle)327 Engine::getPausableSourceFromRequestHandle(int reqHandle)
328 {
329 RequestMap::const_iterator it = reqTable.find(reqHandle);
330 if (it == reqTable.end())
331 return NULL; // request already finished, or inexistent
332
333 FrameSource *fs = it->second.frameSource;
334 assert(fs != NULL);
335 PausableSource *ps = dynamic_cast<PausableSource *>(fs);
336 return ps; // null if request is not pausable
337 }
338
339
340 bool
pauseRequest(int reqHandle)341 Engine::pauseRequest(int reqHandle)
342 {
343 AutoLocker a(SoundRenderer::getMutex());
344
345 PausableSource *ps = getPausableSourceFromRequestHandle(reqHandle);
346 if (ps == NULL)
347 return false;
348 ps->pauseUnsafe(); // unsafe is fine because we have already locked renderer mutex
349 return true;
350 }
351
352
353 bool
resumeRequest(int reqHandle)354 Engine::resumeRequest(int reqHandle)
355 {
356 AutoLocker a(SoundRenderer::getMutex());
357
358 PausableSource *ps = getPausableSourceFromRequestHandle(reqHandle);
359 if (ps == NULL)
360 return false;
361 ps->resumeUnsafe(); // unsafe is fine because we have already locked renderer mutex
362 return true;
363 }
364
365
366 bool
stopRequest(int reqHandle)367 Engine::stopRequest(int reqHandle)
368 {
369 AutoLocker a(SoundRenderer::getMutex());
370
371 RequestMap::const_iterator it = reqTable.find(reqHandle);
372 if (it == reqTable.end())
373 return false; // request already finished, or inexistent
374
375 Bus *bus = it->second.bus;
376 assert(bus != NULL);
377 FrameSourceAdder &adder = bus->getAdder();
378
379 FrameSource *adderChild = it->second.frameSource;
380 assert(adderChild != NULL);
381 return adder.killChildUnsafe(adderChild);
382 }
383
384
385 bool
pauseEngine()386 Engine::pauseEngine()
387 {
388 AutoLocker a(SoundRenderer::getMutex());
389 ++pauseCounter;
390 if (pauseCounter == 1)
391 SDL_PauseAudio(1); // stop callback invocation
392 return true;
393 }
394
395
396 bool
resumeEngine()397 Engine::resumeEngine()
398 {
399 AutoLocker a(SoundRenderer::getMutex());
400 if (pauseCounter == 0) // if already running
401 return true;
402 --pauseCounter;
403 if (pauseCounter > 0) // if still paused
404 return false;
405 SDL_PauseAudio(0); // resume callback invocation
406 return true;
407 }
408
409
410 Engine::RequestMap::iterator
findReqHandle(FrameSource * child)411 Engine::findReqHandle(FrameSource *child)
412 {
413 for (RequestMap::iterator it = reqTable.begin();
414 it != reqTable.end(); ++it)
415 if (it->second.frameSource == child)
416 return it;
417
418 return reqTable.end();
419 }
420
421
422 } // namespace roundbeetle
423