xref: /haiku/src/kits/game/GameSoundDevice.cpp (revision ddac407426cd3b3d0b4589d7a161b300b3539a2a)
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 	:   fIsConnected(false),
85 		fSoundCount(kInitSoundCount)
86 {
87 	fConnection = new Connection;
88 	memset(&fFormat, 0, sizeof(gs_audio_format));
89 
90 	fInitError = Connect();
91 
92 	fSounds = new GameSoundBuffer*[kInitSoundCount];
93 	for (int32 i = 0; i < kInitSoundCount; i++)
94 		fSounds[i] = NULL;
95 }
96 
97 
98 BGameSoundDevice::~BGameSoundDevice()
99 {
100 	BMediaRoster* roster = BMediaRoster::Roster();
101 
102 	// We need to stop all the sounds before we stop the mixer
103 	for (int32 i = 0; i < fSoundCount; i++) {
104 		if (fSounds[i])
105 			fSounds[i]->StopPlaying();
106 		delete fSounds[i];
107 	}
108 
109 	if (fIsConnected) {
110 		// stop the nodes if they are running
111 		roster->StopNode(fConnection->producer, 0, true);
112 			// synchronous stop
113 
114 		// Ordinarily we'd stop *all* of the nodes in the chain at this point.  However,
115 		// one of the nodes is the System Mixer, and stopping the Mixer is a Bad Idea (tm).
116 		// So, we just disconnect from it, and release our references to the nodes that
117 		// we're using.  We *are* supposed to do that even for global nodes like the Mixer.
118 		roster->Disconnect(fConnection->producer.node, fConnection->source,
119 							fConnection->consumer.node, fConnection->destination);
120 
121 		roster->ReleaseNode(fConnection->producer);
122 		roster->ReleaseNode(fConnection->consumer);
123 	}
124 
125 	delete[] fSounds;
126 	delete fConnection;
127 }
128 
129 
130 status_t
131 BGameSoundDevice::InitCheck() const
132 {
133 	return fInitError;
134 }
135 
136 
137 const gs_audio_format &
138 BGameSoundDevice::Format() const
139 {
140 	return fFormat;
141 }
142 
143 
144 const gs_audio_format &
145 BGameSoundDevice::Format(gs_id sound) const
146 {
147 	return fSounds[sound - 1]->Format();
148 }
149 
150 
151 void
152 BGameSoundDevice::SetInitError(status_t error)
153 {
154 	fInitError = error;
155 }
156 
157 
158 status_t
159 BGameSoundDevice::CreateBuffer(gs_id * sound,
160 								const gs_audio_format * format,
161 								const void * data,
162 								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,
183 								const void * object,
184 								const gs_audio_format * format)
185 {
186 	if (!object || !sound)
187 		return B_BAD_VALUE;
188 
189 	status_t err = B_MEDIA_TOO_MANY_BUFFERS;
190 	int32 position = AllocateSound();
191 
192 	if (position >= 0) {
193 		fSounds[position] = new StreamingSoundBuffer(format, object);
194 		err = fSounds[position]->Connect(&fConnection->producer);
195 	}
196 
197 	if (err == B_OK)
198 		*sound = gs_id(position+1);
199 	return err;
200 }
201 
202 
203 void
204 BGameSoundDevice::ReleaseBuffer(gs_id sound)
205 {
206 	if (sound <= 0)
207 		return;
208 
209 	if (fSounds[sound - 1]) {
210 		// We must stop playback befor destroying the sound or else
211 		// we may recieve fatel errors.
212 		fSounds[sound - 1]->StopPlaying();
213 
214 		delete fSounds[sound - 1];
215 		fSounds[sound - 1] = NULL;
216 	}
217 }
218 
219 
220 status_t
221 BGameSoundDevice::Buffer(gs_id sound, gs_audio_format* format, void *& data)
222 {
223 	if (!format || sound <= 0)
224 		return B_BAD_VALUE;
225 
226 	memcpy(format, &fSounds[sound-1]->Format(), sizeof(gs_audio_format));
227 	if (fSounds[sound-1]->Data()) {
228 		data = malloc(format->buffer_size);
229 		memcpy(data, fSounds[sound-1]->Data(), format->buffer_size);
230 	} else
231 		data = NULL;
232 
233 	return B_OK;
234 }
235 
236 
237 status_t
238 BGameSoundDevice::StartPlaying(gs_id sound)
239 {
240 	if (sound <= 0)
241 		return B_BAD_VALUE;
242 
243 	if (!fSounds[sound - 1]->IsPlaying()) {
244 		// tell the producer to start playing the sound
245 		return fSounds[sound - 1]->StartPlaying();
246 	}
247 
248 	fSounds[sound - 1]->Reset();
249 	return EALREADY;
250 }
251 
252 
253 status_t
254 BGameSoundDevice::StopPlaying(gs_id sound)
255 {
256 	if (sound <= 0)
257 		return B_BAD_VALUE;
258 
259 	if (fSounds[sound - 1]->IsPlaying()) {
260 		// Tell the producer to stop play this sound
261 		fSounds[sound - 1]->Reset();
262 		return fSounds[sound - 1]->StopPlaying();
263 	}
264 
265 	return EALREADY;
266 }
267 
268 
269 bool
270 BGameSoundDevice::IsPlaying(gs_id sound)
271 {
272 	if (sound <= 0)
273 		return false;
274 	return fSounds[sound - 1]->IsPlaying();
275 }
276 
277 
278 status_t
279 BGameSoundDevice::GetAttributes(gs_id sound,
280 								gs_attribute * attributes,
281 								size_t attributeCount)
282 {
283 	if (!fSounds[sound - 1])
284 		return B_ERROR;
285 
286 	return fSounds[sound - 1]->GetAttributes(attributes, attributeCount);
287 }
288 
289 
290 status_t
291 BGameSoundDevice::SetAttributes(gs_id sound,
292 								gs_attribute * attributes,
293 								size_t attributeCount)
294 {
295 	if (!fSounds[sound - 1])
296 		return B_ERROR;
297 
298 	return fSounds[sound - 1]->SetAttributes(attributes, attributeCount);
299 }
300 
301 
302 status_t
303 BGameSoundDevice::Connect()
304 {
305 	BMediaRoster* roster = BMediaRoster::Roster();
306 
307 	// create your own audio mixer
308 	// TODO: Don't do this!!! See bug #575
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, &fConnection->producer);
318 	if (err != B_OK)
319 		return err;
320 
321 	// retieve the system's audio mixer
322 	err = roster->GetAudioMixer(&fConnection->consumer);
323 	if (err != B_OK)
324 		return err;
325 
326 	int32 count = 1;
327 	media_input mixerInput;
328 	err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1, &count);
329 	if (err != B_OK)
330 		return err;
331 
332 	count = 1;
333 	media_output mixerOutput;
334 	err = roster->GetFreeOutputsFor(fConnection->producer, &mixerOutput, 1, &count);
335 	if (err != B_OK)
336 		return err;
337 
338 	media_format format(mixerOutput.format);
339 	err = roster->Connect(mixerOutput.source, mixerInput.destination,
340 				 &format, &mixerOutput, &mixerInput);
341 	if (err != B_OK)
342 		return err;
343 
344 	// set the producer's time source to be the "default" time source, which
345 	// the Mixer uses too.
346 	roster->GetTimeSource(&fConnection->timeSource);
347 	roster->SetTimeSourceFor(fConnection->producer.node, fConnection->timeSource.node);
348 
349 	// Start our mixer's time source if need be. Chances are, it won't need to be,
350 	// but if we forget to do this, our mixer might not do anything at all.
351 	BTimeSource* mixerTimeSource = roster->MakeTimeSourceFor(fConnection->producer);
352 	if (!mixerTimeSource)
353 		return B_ERROR;
354 
355 	if (!mixerTimeSource->IsRunning()) {
356 		status_t err = roster->StartNode(mixerTimeSource->Node(), BTimeSource::RealTime());
357 		if (err != B_OK) {
358 			mixerTimeSource->Release();
359 			return err;
360 		}
361 	}
362 
363 	// Start up our mixer
364 	bigtime_t tpNow = mixerTimeSource->Now();
365 	err = roster->StartNode(fConnection->producer, tpNow + 10000);
366 	mixerTimeSource->Release();
367 	if (err != B_OK)
368 		return err;
369 
370 	// the inputs and outputs might have been reassigned during the
371 	// nodes' negotiation of the Connect().  That's why we wait until
372 	// after Connect() finishes to save their contents.
373 	fConnection->format = format;
374 	fConnection->source = mixerOutput.source;
375 	fConnection->destination = mixerInput.destination;
376 
377 	// Set an appropriate run mode for the producer
378 	roster->SetRunModeNode(fConnection->producer, BMediaNode::B_INCREASE_LATENCY);
379 
380 	media_to_gs_format(&fFormat, &format.u.raw_audio);
381 	fIsConnected = true;
382 	return B_OK;
383 }
384 
385 
386 int32
387 BGameSoundDevice::AllocateSound()
388 {
389 	for (int32 i = 0; i < fSoundCount; i++)
390 		if (!fSounds[i])
391 			return i;
392 
393 	// we need to allocate new space for the sound
394 	GameSoundBuffer ** sounds = new GameSoundBuffer*[fSoundCount + kGrowth];
395 	for (int32 i = 0; i < fSoundCount; i++)
396 		sounds[i] = fSounds[i];
397 
398 	for (int32 i = fSoundCount; i < fSoundCount + kGrowth; i++)
399 		sounds[i] = NULL;
400 
401 	// replace the old list
402 	delete [] fSounds;
403 	fSounds = sounds;
404 	fSoundCount += kGrowth;
405 
406 	return fSoundCount - kGrowth;
407 }
408 
409