xref: /haiku/src/kits/game/GameSoundDevice.cpp (revision eb47b26534e55948dbb8860916c671f9cf6f37f9)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2002, OpenBeOS
3 //
4 //	Permission is hereby granted, free of charge, to any person obtaining a
5 //	copy of this software and associated documentation files (the "Software"),
6 //	to deal in the Software without restriction, including without limitation
7 //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 //	and/or sell copies of the Software, and to permit persons to whom the
9 //	Software is furnished to do so, subject to the following conditions:
10 //
11 //	The above copyright notice and this permission notice shall be included in
12 //	all copies or substantial portions of the Software.
13 //
14 //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 //	DEALINGS IN THE SOFTWARE.
21 //
22 //	File Name:		BGameSoundDevice.cpp
23 //	Author:			Christopher ML Zumwalt May (zummy@users.sf.net)
24 //	Description:	Manages the game producer. The class may change with out
25 //					notice and was only inteneded for use by the GameKit at
26 //					this time. Use at your own risk.
27 //------------------------------------------------------------------------------
28 
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 
33 #include <Autolock.h>
34 #include <List.h>
35 #include <Locker.h>
36 #include <MediaRoster.h>
37 #include <MediaAddOn.h>
38 #include <TimeSource.h>
39 #include <MediaTheme.h>
40 
41 #include "GSUtility.h"
42 #include "GameSoundDevice.h"
43 #include "GameSoundBuffer.h"
44 #include "GameProducer.h"
45 
46 // BGameSoundDevice definitions ------------------------------------
47 const int32 kInitSoundCount = 32;
48 const int32 kGrowth = 16;
49 
50 static int32 sDeviceCount = 0;
51 static BGameSoundDevice* sDevice = NULL;
52 static BLocker sDeviceRefCountLock = BLocker("GameSound device lock");
53 
54 
55 BGameSoundDevice*
56 GetDefaultDevice()
57 {
58 	BAutolock _(sDeviceRefCountLock);
59 
60 	if (!sDevice)
61 		sDevice = new BGameSoundDevice();
62 
63 	sDeviceCount++;
64 	return sDevice;
65 }
66 
67 
68 void
69 ReleaseDevice()
70 {
71 	BAutolock _(sDeviceRefCountLock);
72 
73 	sDeviceCount--;
74 
75 	if (sDeviceCount <= 0) {
76 		delete sDevice;
77 		sDevice = NULL;
78 	}
79 }
80 
81 
82 // BGameSoundDevice -------------------------------------------------------
83 BGameSoundDevice::BGameSoundDevice()
84 	:
85 	fIsConnected(false),
86 	fSoundCount(kInitSoundCount)
87 {
88 	fConnection = new Connection;
89 	memset(&fFormat, 0, sizeof(gs_audio_format));
90 
91 	fInitError = Connect();
92 
93 	fSounds = new GameSoundBuffer*[kInitSoundCount];
94 	for (int32 i = 0; i < kInitSoundCount; i++)
95 		fSounds[i] = NULL;
96 }
97 
98 
99 BGameSoundDevice::~BGameSoundDevice()
100 {
101 	BMediaRoster* roster = BMediaRoster::Roster();
102 
103 	// We need to stop all the sounds before we stop the mixer
104 	for (int32 i = 0; i < fSoundCount; i++) {
105 		if (fSounds[i])
106 			fSounds[i]->StopPlaying();
107 		delete fSounds[i];
108 	}
109 
110 	if (fIsConnected) {
111 		// stop the nodes if they are running
112 		roster->StopNode(fConnection->producer, 0, true);
113 			// synchronous stop
114 
115 		// Ordinarily we'd stop *all* of the nodes in the chain at this point.
116 		// However, one of the nodes is the System Mixer, and stopping the
117 		// Mixer is a Bad Idea (tm). So, we just disconnect from it, and
118 		// release our references to the nodes that we're using.  We *are*
119 		// supposed to do that even for global nodes like the Mixer.
120 		roster->Disconnect(fConnection->producer.node, fConnection->source,
121 						fConnection->consumer.node, fConnection->destination);
122 
123 		roster->ReleaseNode(fConnection->producer);
124 		roster->ReleaseNode(fConnection->consumer);
125 	}
126 
127 	delete[] fSounds;
128 	delete fConnection;
129 }
130 
131 
132 status_t
133 BGameSoundDevice::InitCheck() const
134 {
135 	return fInitError;
136 }
137 
138 
139 const gs_audio_format&
140 BGameSoundDevice::Format() const
141 {
142 	return fFormat;
143 }
144 
145 
146 const gs_audio_format&
147 BGameSoundDevice::Format(gs_id sound) const
148 {
149 	return fSounds[sound - 1]->Format();
150 }
151 
152 
153 void
154 BGameSoundDevice::SetInitError(status_t error)
155 {
156 	fInitError = error;
157 }
158 
159 
160 status_t
161 BGameSoundDevice::CreateBuffer(gs_id* sound, const gs_audio_format* format,
162 							const void* data, int64 frames)
163 {
164 	if (frames <= 0 || !sound)
165 		return B_BAD_VALUE;
166 
167 	status_t err = B_MEDIA_TOO_MANY_BUFFERS;
168 	int32 position = AllocateSound();
169 
170 	if (position >= 0) {
171 		fSounds[position] = new SimpleSoundBuffer(format, data, frames);
172 		err = fSounds[position]->Connect(&fConnection->producer);
173 	}
174 
175 	if (err == B_OK)
176 		*sound = gs_id(position + 1);
177 	return err;
178 }
179 
180 
181 status_t
182 BGameSoundDevice::CreateBuffer(gs_id* sound, const void* object,
183 								const gs_audio_format* format)
184 {
185 	if (!object || !sound)
186 		return B_BAD_VALUE;
187 
188 	status_t err = B_MEDIA_TOO_MANY_BUFFERS;
189 	int32 position = AllocateSound();
190 
191 	if (position >= 0) {
192 		fSounds[position] = new StreamingSoundBuffer(format, object);
193 		err = fSounds[position]->Connect(&fConnection->producer);
194 	}
195 
196 	if (err == B_OK)
197 		*sound = gs_id(position+1);
198 	return err;
199 }
200 
201 
202 void
203 BGameSoundDevice::ReleaseBuffer(gs_id sound)
204 {
205 	if (sound <= 0)
206 		return;
207 
208 	if (fSounds[sound - 1]) {
209 		// We must stop playback befor destroying the sound or else
210 		// we may recieve fatel errors.
211 		fSounds[sound - 1]->StopPlaying();
212 
213 		delete fSounds[sound - 1];
214 		fSounds[sound - 1] = NULL;
215 	}
216 }
217 
218 
219 status_t
220 BGameSoundDevice::Buffer(gs_id sound, gs_audio_format* format, void*& data)
221 {
222 	if (!format || sound <= 0)
223 		return B_BAD_VALUE;
224 
225 	memcpy(format, &fSounds[sound-1]->Format(), sizeof(gs_audio_format));
226 	if (fSounds[sound-1]->Data()) {
227 		data = malloc(format->buffer_size);
228 		memcpy(data, fSounds[sound-1]->Data(), format->buffer_size);
229 	} else
230 		data = NULL;
231 
232 	return B_OK;
233 }
234 
235 
236 status_t
237 BGameSoundDevice::StartPlaying(gs_id sound)
238 {
239 	if (sound <= 0)
240 		return B_BAD_VALUE;
241 
242 	if (!fSounds[sound - 1]->IsPlaying()) {
243 		// tell the producer to start playing the sound
244 		return fSounds[sound - 1]->StartPlaying();
245 	}
246 
247 	fSounds[sound - 1]->Reset();
248 	return EALREADY;
249 }
250 
251 
252 status_t
253 BGameSoundDevice::StopPlaying(gs_id sound)
254 {
255 	if (sound <= 0)
256 		return B_BAD_VALUE;
257 
258 	if (fSounds[sound - 1]->IsPlaying()) {
259 		// Tell the producer to stop play this sound
260 		fSounds[sound - 1]->Reset();
261 		return fSounds[sound - 1]->StopPlaying();
262 	}
263 
264 	return EALREADY;
265 }
266 
267 
268 bool
269 BGameSoundDevice::IsPlaying(gs_id sound)
270 {
271 	if (sound <= 0)
272 		return false;
273 	return fSounds[sound - 1]->IsPlaying();
274 }
275 
276 
277 status_t
278 BGameSoundDevice::GetAttributes(gs_id sound, gs_attribute* attributes,
279 								size_t attributeCount)
280 {
281 	if (!fSounds[sound - 1])
282 		return B_ERROR;
283 
284 	return fSounds[sound - 1]->GetAttributes(attributes, attributeCount);
285 }
286 
287 
288 status_t
289 BGameSoundDevice::SetAttributes(gs_id sound, gs_attribute* attributes,
290 								size_t attributeCount)
291 {
292 	if (!fSounds[sound - 1])
293 		return B_ERROR;
294 
295 	return fSounds[sound - 1]->SetAttributes(attributes, attributeCount);
296 }
297 
298 
299 status_t
300 BGameSoundDevice::Connect()
301 {
302 	BMediaRoster* roster = BMediaRoster::Roster();
303 
304 	// create your own audio mixer
305 	// TODO: Don't do this!!! See bug #575
306 	// from #575.
307 	// marcusoverhagen : tries to instantiate a new audio mixer, which is bad.
308 	// It should use the system mixer.
309 	dormant_node_info mixer_dormant_info;
310 	int32 mixer_count = 1; // for now, we only care about the first  we find.
311 	status_t err = roster->GetDormantNodes(&mixer_dormant_info,
312 				 &mixer_count, 0, 0, 0, B_SYSTEM_MIXER, 0);
313 	if (err != B_OK)
314 		return err;
315 
316 	//fMixer = new media_node;
317 	err = roster->InstantiateDormantNode(mixer_dormant_info,
318 										&fConnection->producer);
319 	if (err != B_OK)
320 		return err;
321 
322 	// retieve the system's audio mixer
323 	err = roster->GetAudioMixer(&fConnection->consumer);
324 	if (err != B_OK)
325 		return err;
326 
327 	int32 count = 1;
328 	media_input mixerInput;
329 	err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1,
330 									&count);
331 	if (err != B_OK)
332 		return err;
333 
334 	count = 1;
335 	media_output mixerOutput;
336 	err = roster->GetFreeOutputsFor(fConnection->producer, &mixerOutput, 1,
337 									&count);
338 	if (err != B_OK)
339 		return err;
340 
341 	media_format format(mixerOutput.format);
342 	err = roster->Connect(mixerOutput.source, mixerInput.destination,
343 				 &format, &mixerOutput, &mixerInput);
344 	if (err != B_OK)
345 		return err;
346 
347 	// set the producer's time source to be the "default" time source, which
348 	// the Mixer uses too.
349 	roster->GetTimeSource(&fConnection->timeSource);
350 	roster->SetTimeSourceFor(fConnection->producer.node,
351 							fConnection->timeSource.node);
352 
353 	// Start our mixer's time source if need be. Chances are, it won't need to
354 	// be, but if we forget to do this, our mixer might not do anything at all.
355 	BTimeSource* mixerTimeSource = roster->MakeTimeSourceFor(
356 														fConnection->producer);
357 	if (!mixerTimeSource)
358 		return B_ERROR;
359 
360 	if (!mixerTimeSource->IsRunning()) {
361 		status_t err = roster->StartNode(mixerTimeSource->Node(),
362 										BTimeSource::RealTime());
363 		if (err != B_OK) {
364 			mixerTimeSource->Release();
365 			return err;
366 		}
367 	}
368 
369 	// Start up our mixer
370 	bigtime_t tpNow = mixerTimeSource->Now();
371 	err = roster->StartNode(fConnection->producer, tpNow + 10000);
372 	mixerTimeSource->Release();
373 	if (err != B_OK)
374 		return err;
375 
376 	// the inputs and outputs might have been reassigned during the
377 	// nodes' negotiation of the Connect().  That's why we wait until
378 	// after Connect() finishes to save their contents.
379 	fConnection->format = format;
380 	fConnection->source = mixerOutput.source;
381 	fConnection->destination = mixerInput.destination;
382 
383 	// Set an appropriate run mode for the producer
384 	roster->SetRunModeNode(fConnection->producer,
385 							BMediaNode::B_INCREASE_LATENCY);
386 
387 	media_to_gs_format(&fFormat, &format.u.raw_audio);
388 	fIsConnected = true;
389 	return B_OK;
390 }
391 
392 
393 int32
394 BGameSoundDevice::AllocateSound()
395 {
396 	for (int32 i = 0; i < fSoundCount; i++)
397 		if (!fSounds[i])
398 			return i;
399 
400 	// we need to allocate new space for the sound
401 	GameSoundBuffer ** sounds = new GameSoundBuffer*[fSoundCount + kGrowth];
402 	for (int32 i = 0; i < fSoundCount; i++)
403 		sounds[i] = fSounds[i];
404 
405 	for (int32 i = fSoundCount; i < fSoundCount + kGrowth; i++)
406 		sounds[i] = NULL;
407 
408 	// replace the old list
409 	delete [] fSounds;
410 	fSounds = sounds;
411 	fSoundCount += kGrowth;
412 
413 	return fSoundCount - kGrowth;
414 }
415 
416