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