xref: /haiku/src/kits/game/GameSoundDevice.cpp (revision d5cd5d63ff0ad395989db6cf4841a64d5b545d1d)
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 <malloc.h>
30 #include <stdio.h>
31 #include <string.h>
32 
33 #include <List.h>
34 #include <MediaRoster.h>
35 #include <MediaAddOn.h>
36 #include <TimeSource.h>
37 #include <MediaTheme.h>
38 
39 #include "GSUtility.h"
40 #include "GameSoundDevice.h"
41 #include "GameSoundBuffer.h"
42 #include "GameProducer.h"
43 
44 // BGameSoundDevice definitions ------------------------------------
45 const int32 kInitSoundCount = 32;
46 const int32 kGrowth = 16;
47 
48 static int32 deviceCount = 0;
49 static BGameSoundDevice* theDevice = NULL;
50 
51 BGameSoundDevice * GetDefaultDevice()
52 {
53 	if (!theDevice)
54 		theDevice = new BGameSoundDevice();
55 
56 	deviceCount++;
57 	return theDevice;
58 }
59 
60 void ReleaseDevice()
61 {
62 	deviceCount--;
63 
64 	if (deviceCount <= 0)
65 	{
66 		delete theDevice;
67 		theDevice = NULL;
68 	}
69 }
70 
71 // BGameSoundDevice -------------------------------------------------------
72 BGameSoundDevice::BGameSoundDevice()
73 	:   fIsConnected(false),
74 		fSoundCount(kInitSoundCount)
75 {
76 	fConnection = new Connection;
77 	memset(&fFormat, 0, sizeof(gs_audio_format));
78 
79 	fInitError = Connect();
80 
81 	fSounds = new GameSoundBuffer*[kInitSoundCount];
82 	for(int32 i = 0; i < kInitSoundCount; i++)
83 		fSounds[i] = NULL;
84 }
85 
86 
87 BGameSoundDevice::~BGameSoundDevice()
88 {
89 	BMediaRoster* r = BMediaRoster::Roster();
90 
91 	// We need to stop all the sounds before we stop the mixer
92 	for(int32 i = 0; i < fSoundCount; i++)
93 	{
94 		if (fSounds[i]) fSounds[i]->StopPlaying();
95 		delete fSounds[i];
96 	}
97 
98 	if (fIsConnected)
99 	{
100 		// stop the nodes if they are running
101 		r->StopNode(fConnection->producer, 0, true);		// synchronous stop
102 
103 		// Ordinarily we'd stop *all* of the nodes in the chain at this point.  However,
104 		// one of the nodes is the System Mixer, and stopping the Mixer is a Bad Idea (tm).
105 		// So, we just disconnect from it, and release our references to the nodes that
106 		// we're using.  We *are* supposed to do that even for global nodes like the Mixer.
107 		r->Disconnect(fConnection->producer.node, fConnection->source,
108 							fConnection->consumer.node, fConnection->destination);
109 
110 		r->ReleaseNode(fConnection->producer);
111 		r->ReleaseNode(fConnection->consumer);
112 	}
113 
114 	delete [] fSounds;
115 	delete fConnection;
116 }
117 
118 
119 status_t
120 BGameSoundDevice::InitCheck() const
121 {
122 	return fInitError;
123 }
124 
125 
126 const gs_audio_format &
127 BGameSoundDevice::Format() const
128 {
129 	return fFormat;
130 }
131 
132 
133 const gs_audio_format &
134 BGameSoundDevice::Format(gs_id sound) const
135 {
136 	return fSounds[sound-1]->Format();
137 }
138 
139 
140 void
141 BGameSoundDevice::SetInitError(status_t error)
142 {
143 	fInitError = error;
144 }
145 
146 
147 status_t
148 BGameSoundDevice::CreateBuffer(gs_id * sound,
149 								const gs_audio_format * format,
150 								const void * data,
151 								int64 frames)
152 {
153 	if (frames <= 0 || !sound) return B_BAD_VALUE;
154 
155 	status_t err = B_MEDIA_TOO_MANY_BUFFERS;
156 	int32 position = AllocateSound();
157 
158 	if (position >= 0)
159 	{
160 		fSounds[position] = new SimpleSoundBuffer(format, data, frames);
161 		err = fSounds[position]->Connect(&fConnection->producer);
162 	}
163 
164 	*sound = gs_id(position+1);
165 	return err;
166 }
167 
168 
169 status_t
170 BGameSoundDevice::CreateBuffer(gs_id * sound,
171 								const void * object,
172 								const gs_audio_format * format)
173 {
174 	if (!object || !sound) return B_BAD_VALUE;
175 
176 	status_t err = B_MEDIA_TOO_MANY_BUFFERS;
177 	int32 position = AllocateSound();
178 
179 	if (position >= 0)
180 	{
181 		fSounds[position] = new StreamingSoundBuffer(format, object);
182 		err = fSounds[position]->Connect(&fConnection->producer);
183 	}
184 
185 	*sound = gs_id(position+1);
186 	return err;
187 }
188 
189 
190 void
191 BGameSoundDevice::ReleaseBuffer(gs_id sound)
192 {
193 	if (fSounds[sound-1])
194 	{
195 		// We must stop playback befor destroying the sound or else
196 		// we may recieve fatel errors.
197 		fSounds[sound-1]->StopPlaying();
198 
199 		delete fSounds[sound-1];
200 		fSounds[sound-1] = NULL;
201 	}
202 }
203 
204 
205 status_t
206 BGameSoundDevice::Buffer(gs_id sound,
207 						 gs_audio_format * format,
208 						 void * data)
209 {
210 	if (!format) return B_BAD_VALUE;
211 
212 	memcpy(format, &fSounds[sound-1]->Format(), sizeof(gs_audio_format));
213 
214 	if (fSounds[sound-1]->Data())
215 	{
216 		data = malloc(format->buffer_size);
217 		memcpy(data, fSounds[sound-1]->Data(), format->buffer_size);
218 	}
219 	else data = NULL;
220 
221 	return B_OK;
222 }
223 
224 status_t
225 BGameSoundDevice::StartPlaying(gs_id sound)
226 {
227 	status_t error = EALREADY;
228 
229 	if (!fSounds[sound-1]->IsPlaying())
230 	{
231 		// tell the producer to start playing the sound
232 		error = fSounds[sound-1]->StartPlaying();
233 	}
234 	else fSounds[sound-1]->Reset();
235 
236 	return error;
237 }
238 
239 
240 status_t
241 BGameSoundDevice::StopPlaying(gs_id sound)
242 {
243 	status_t error = EALREADY;
244 
245 	if (fSounds[sound-1]->IsPlaying())
246 	{
247 		// Tell the producer to stop play this sound
248 		fSounds[sound-1]->Reset();
249 		error = fSounds[sound-1]->StopPlaying();
250 	}
251 
252 	return error;
253 }
254 
255 
256 bool
257 BGameSoundDevice::IsPlaying(gs_id sound)
258 {
259 	return fSounds[sound-1]->IsPlaying();
260 }
261 
262 
263 status_t
264 BGameSoundDevice::GetAttributes(gs_id sound,
265 								gs_attribute * attributes,
266 								size_t attributeCount)
267 {
268 	if (!fSounds[sound-1]) return B_ERROR;
269 
270 	return fSounds[sound-1]->GetAttributes(attributes, attributeCount);
271 }
272 
273 
274 status_t
275 BGameSoundDevice::SetAttributes(gs_id sound,
276 								gs_attribute * attributes,
277 								size_t attributeCount)
278 {
279 	if (!fSounds[sound-1]) return B_ERROR;
280 
281 	return fSounds[sound-1]->SetAttributes(attributes, attributeCount);
282 }
283 
284 
285 status_t
286 BGameSoundDevice::Connect()
287 {
288 	BMediaRoster* r = BMediaRoster::Roster();
289 	status_t err;
290 
291 	// create your own audio mixer
292 	dormant_node_info mixer_dormant_info;
293 	int32 mixer_count = 1; // for now, we only care about the first  we find.
294 	err = r->GetDormantNodes(&mixer_dormant_info, &mixer_count, 0, 0, 0, B_SYSTEM_MIXER, 0);
295 	if (err != B_OK) return err;
296 
297 	//fMixer = new media_node;
298 	err = r->InstantiateDormantNode(mixer_dormant_info, &fConnection->producer);
299 	if (err != B_OK) return err;
300 
301 	// retieve the system's audio mixer
302 	err = r->GetAudioMixer(&fConnection->consumer);
303 	if (err != B_OK) return err;
304 
305 	int32 count = 1;
306 	media_input mixerInput;
307 	err = r->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1, &count);
308 	if (err != B_OK) return err;
309 
310 	count = 1;
311 	media_output mixerOutput;
312 	err = r->GetFreeOutputsFor(fConnection->producer, &mixerOutput, 1, &count);
313 	if (err != B_OK) return err;
314 
315 	media_format format(mixerOutput.format);
316 	err = r->Connect(mixerOutput.source, mixerInput.destination, &format, &mixerOutput, &mixerInput);
317 	if (err != B_OK) return err;
318 
319 	// set the producer's time source to be the "default" time source, which
320 	// the Mixer uses too.
321 	r->GetTimeSource(&fConnection->timeSource);
322 	r->SetTimeSourceFor(fConnection->producer.node, fConnection->timeSource.node);
323 
324 	// Start our mixer's time source if need be. Chances are, it won't need to be,
325 	// but if we forget to do this, our mixer might not do anything at all.
326 	BTimeSource* mixerTimeSource = r->MakeTimeSourceFor(fConnection->producer);
327 	if (! mixerTimeSource) return B_ERROR;
328 
329 	if (!mixerTimeSource->IsRunning())
330 	{
331 		status_t err = r->StartNode(mixerTimeSource->Node(), BTimeSource::RealTime());
332 		if (err != B_OK)
333 		{
334 			mixerTimeSource->Release();
335 			return err;
336 		}
337 	}
338 
339 	// Start up our mixer
340 	bigtime_t tpNow = mixerTimeSource->Now();
341 	err = r->StartNode(fConnection->producer, tpNow + 10000);
342 	mixerTimeSource->Release();
343 	if (err != B_OK) return err;
344 
345 	// the inputs and outputs might have been reassigned during the
346 	// nodes' negotiation of the Connect().  That's why we wait until
347 	// after Connect() finishes to save their contents.
348 	fConnection->format = format;
349 	fConnection->source = mixerOutput.source;
350 	fConnection->destination = mixerInput.destination;
351 
352 	// Set an appropriate run mode for the producer
353 	r->SetRunModeNode(fConnection->producer, BMediaNode::B_INCREASE_LATENCY);
354 
355 	media_to_gs_format(&fFormat, &format.u.raw_audio);
356 	fIsConnected = true;
357 	return B_OK;
358 }
359 
360 
361 int32
362 BGameSoundDevice::AllocateSound()
363 {
364 	for(int32 i = 0; i < fSoundCount; i++)
365 		if (!fSounds[i])
366 			return i;
367 
368 	// we need to allocate new space for the sound
369 	GameSoundBuffer ** sounds = new GameSoundBuffer*[fSoundCount + kGrowth];
370 	for(int32 i = 0; i < fSoundCount; i++)
371 		sounds[i] = fSounds[i];
372 
373 	for(int32 i	= fSoundCount; i < fSoundCount + kGrowth; i++)
374 		sounds[i] = NULL;
375 
376 	// replace the old list
377 	delete [] fSounds;
378 	fSounds = sounds;
379 	fSoundCount += kGrowth;
380 
381 	return fSoundCount - kGrowth;
382 }
383 
384