xref: /haiku/src/kits/game/GameSoundBuffer.cpp (revision a5a3b2d9a3d95cbae71eaf371708c73a1780ac0d)
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:		GameSoundBuffer.h
23 //	Author:			Christopher ML Zumwalt May (zummy@users.sf.net)
24 //	Description:	Interface to a single sound, managed by the GameSoundDevice.
25 //------------------------------------------------------------------------------
26 
27 
28 #include "GameSoundBuffer.h"
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include <MediaRoster.h>
35 #include <MediaAddOn.h>
36 #include <MediaTheme.h>
37 #include <TimeSource.h>
38 #include <BufferGroup.h>
39 
40 #include "GameProducer.h"
41 #include "GameSoundDevice.h"
42 #include "StreamingGameSound.h"
43 #include "GSUtility.h"
44 
45 
46 // Sound Buffer Utility functions ----------------------------------------
47 template<typename T>
48 static inline void
49 ApplyMod(T* data, T* buffer, int64 index, float * pan)
50 {
51 	data[index * 2] += T(float(buffer[index * 2]) * pan[0]);
52 	data[index * 2 + 1] += T(float(buffer[index * 2 + 1]) * pan[1]);
53 }
54 
55 
56 // GameSoundBuffer -------------------------------------------------------
57 GameSoundBuffer::GameSoundBuffer(const gs_audio_format * format)
58 	:
59 	fLooping(false),
60 	fIsConnected(false),
61 	fIsPlaying(false),
62 	fGain(1.0),
63 	fPan(0.0),
64 	fPanLeft(1.0),
65 	fPanRight(1.0),
66 	fGainRamp(NULL),
67 	fPanRamp(NULL)
68 {
69 	fConnection = new Connection;
70 	fNode = new GameProducer(this, format);
71 
72 	fFrameSize = get_sample_size(format->format) * format->channel_count;
73 
74 	fFormat = *format;
75 }
76 
77 
78 // Play must stop before the distructor is called; otherwise, a fatal
79 // error occures if the playback is in a subclass.
80 GameSoundBuffer::~GameSoundBuffer()
81 {
82 	BMediaRoster* roster = BMediaRoster::Roster();
83 
84 	if (fIsConnected) {
85 		// Ordinarily we'd stop *all* of the nodes in the chain at this point.
86 		// However, one of the nodes is the System Mixer, and stopping the Mixer
87 		// is a Bad Idea (tm). So, we just disconnect from it, and release our
88 		// references to the nodes that we're using.  We *are* supposed to do
89 		// that even for global nodes like the Mixer.
90 		roster->Disconnect(fConnection->producer.node, fConnection->source,
91 			fConnection->consumer.node, fConnection->destination);
92 
93 		roster->ReleaseNode(fConnection->producer);
94 		roster->ReleaseNode(fConnection->consumer);
95 	}
96 
97 	delete fGainRamp;
98 	delete fPanRamp;
99 
100 	delete fConnection;
101 	delete fNode;
102 }
103 
104 
105 const gs_audio_format &
106 GameSoundBuffer::Format() const
107 {
108 	return fFormat;
109 }
110 
111 
112 bool
113 GameSoundBuffer::IsLooping() const
114 {
115 	return fLooping;
116 }
117 
118 
119 void
120 GameSoundBuffer::SetLooping(bool looping)
121 {
122 	fLooping = looping;
123 }
124 
125 
126 float
127 GameSoundBuffer::Gain() const
128 {
129 	return fGain;
130 }
131 
132 
133 status_t
134 GameSoundBuffer::SetGain(float gain, bigtime_t duration)
135 {
136 	if (gain < 0.0 || gain > 1.0)
137 		return B_BAD_VALUE;
138 
139 	delete fGainRamp;
140 	fGainRamp = NULL;
141 
142 	if (duration > 100000)
143 		fGainRamp  = InitRamp(&fGain, gain, fFormat.frame_rate, duration);
144 	else
145 		fGain = gain;
146 
147 	return B_OK;
148 }
149 
150 
151 float
152 GameSoundBuffer::Pan() const
153 {
154 	return fPan;
155 }
156 
157 
158 status_t
159 GameSoundBuffer::SetPan(float pan, bigtime_t duration)
160 {
161 	if (pan < -1.0 || pan > 1.0)
162 		return B_BAD_VALUE;
163 
164 	delete fPanRamp;
165 	fPanRamp = NULL;
166 
167 	if (duration < 100000) {
168 		fPan = pan;
169 
170 		if (fPan < 0.0) {
171 			fPanLeft = 1.0;
172 			fPanRight = 1.0 + fPan;
173 		} else {
174 			fPanRight = 1.0;
175 			fPanLeft = 1.0 - fPan;
176 		}
177 	} else
178 		fPanRamp = InitRamp(&fPan, pan, fFormat.frame_rate, duration);
179 
180 	return B_OK;
181 }
182 
183 
184 status_t
185 GameSoundBuffer::GetAttributes(gs_attribute * attributes,
186 	size_t attributeCount)
187 {
188 	for (size_t i = 0; i < attributeCount; i++) {
189 		switch (attributes[i].attribute) {
190 			case B_GS_GAIN:
191 				attributes[i].value = fGain;
192 				if (fGainRamp)
193 					attributes[i].duration = fGainRamp->duration;
194 				break;
195 
196 			case B_GS_PAN:
197 				attributes[i].value = fPan;
198 				if (fPanRamp)
199 					attributes[i].duration = fPanRamp->duration;
200 				break;
201 
202 			case B_GS_LOOPING:
203 				attributes[i].value = (fLooping) ? -1.0 : 0.0;
204 				attributes[i].duration = bigtime_t(0);
205 				break;
206 
207 			default:
208 				attributes[i].value = 0.0;
209 				attributes[i].duration = bigtime_t(0);
210 				break;
211 		}
212 	}
213 
214 	return B_OK;
215 }
216 
217 
218 status_t
219 GameSoundBuffer::SetAttributes(gs_attribute * attributes,
220 	size_t attributeCount)
221 {
222 	status_t error = B_OK;
223 
224 	for (size_t i = 0; i < attributeCount; i++) {
225 		switch (attributes[i].attribute) {
226 			case B_GS_GAIN:
227 				error = SetGain(attributes[i].value, attributes[i].duration);
228 				break;
229 
230 			case B_GS_PAN:
231 				error = SetPan(attributes[i].value, attributes[i].duration);
232 				break;
233 
234 			case B_GS_LOOPING:
235 				fLooping = bool(attributes[i].value);
236 				break;
237 
238 			default:
239 				break;
240 		}
241 	}
242 
243 	return error;
244 }
245 
246 
247 void
248 GameSoundBuffer::Play(void * data, int64 frames)
249 {
250 	// Mh... should we add some locking?
251 	if (!fIsPlaying)
252 		return;
253 
254 	if (fFormat.channel_count == 2) {
255 		float pan[2];
256 		pan[0] = fPanRight * fGain;
257 		pan[1] = fPanLeft * fGain;
258 
259 		char * buffer = new char[fFrameSize * frames];
260 
261 		FillBuffer(buffer, frames);
262 
263 		switch (fFormat.format) {
264 			case gs_audio_format::B_GS_U8:
265 			{
266 				for (int64 i = 0; i < frames; i++) {
267 					ApplyMod((uint8*)data, (uint8*)buffer, i, pan);
268 					UpdateMods();
269 				}
270 
271 				break;
272 			}
273 
274 			case gs_audio_format::B_GS_S16:
275 			{
276 				for (int64 i = 0; i < frames; i++) {
277 					ApplyMod((int16*)data, (int16*)buffer, i, pan);
278 					UpdateMods();
279 				}
280 
281 				break;
282 			}
283 
284 			case gs_audio_format::B_GS_S32:
285 			{
286 				for (int64 i = 0; i < frames; i++) {
287 					ApplyMod((int32*)data, (int32*)buffer, i, pan);
288 					UpdateMods();
289 				}
290 
291 				break;
292 			}
293 
294 			case gs_audio_format::B_GS_F:
295 			{
296 				for (int64 i = 0; i < frames; i++) {
297 					ApplyMod((float*)data, (float*)buffer, i, pan);
298 					UpdateMods();
299 				}
300 
301 				break;
302 			}
303 		}
304 		delete[] buffer;
305 	} else if (fFormat.channel_count == 1) {
306 		// FIXME the output should be stereo, and we could pan mono sounds
307 		// here. But currently the output has the same number of channels as
308 		// the sound and we can't do this.
309 		// FIXME also, we don't handle the gain here.
310 		FillBuffer(data, frames);
311 	} else
312 		debugger("Invalid number of channels.");
313 
314 }
315 
316 
317 void
318 GameSoundBuffer::UpdateMods()
319 {
320 	// adjust the gain if needed
321 	if (fGainRamp) {
322 		if (ChangeRamp(fGainRamp)) {
323 			delete fGainRamp;
324 			fGainRamp = NULL;
325 		}
326 	}
327 
328 	// adjust the ramp if needed
329 	if (fPanRamp) {
330 		if (ChangeRamp(fPanRamp)) {
331 			delete fPanRamp;
332 			fPanRamp = NULL;
333 		} else {
334 			if (fPan < 0.0) {
335 				fPanLeft = 1.0;
336 				fPanRight = 1.0 + fPan;
337 			} else {
338 				fPanRight = 1.0;
339 				fPanLeft = 1.0 - fPan;
340 			}
341 		}
342 	}
343 }
344 
345 
346 void
347 GameSoundBuffer::Reset()
348 {
349 	fGain = 1.0;
350 	delete fGainRamp;
351 	fGainRamp = NULL;
352 
353 	fPan = 0.0;
354 	fPanLeft = 1.0;
355 	fPanRight = 1.0;
356 
357 	delete fPanRamp;
358 	fPanRamp = NULL;
359 
360 	fLooping = false;
361 }
362 
363 
364 status_t
365 GameSoundBuffer::Connect(media_node * consumer)
366 {
367 	BMediaRoster* roster = BMediaRoster::Roster();
368 	status_t err = roster->RegisterNode(fNode);
369 
370 	if (err != B_OK)
371 		return err;
372 
373 	// make sure the Media Roster knows that we're using the node
374 	err = roster->GetNodeFor(fNode->Node().node, &fConnection->producer);
375 
376 	if (err != B_OK)
377 		return err;
378 
379 	// connect to the mixer
380 	fConnection->consumer = *consumer;
381 
382 	// set the producer's time source to be the "default" time source, which
383 	// the Mixer uses too.
384 	err = roster->GetTimeSource(&fConnection->timeSource);
385 	if (err != B_OK)
386 		return err;
387 
388 	err = roster->SetTimeSourceFor(fConnection->producer.node,
389 		fConnection->timeSource.node);
390 	if (err != B_OK)
391 		return err;
392 	// got the nodes; now we find the endpoints of the connection
393 	media_input mixerInput;
394 	media_output soundOutput;
395 	int32 count = 1;
396 	err = roster->GetFreeOutputsFor(fConnection->producer, &soundOutput, 1,
397 		&count);
398 
399 	if (err != B_OK)
400 		return err;
401 	count = 1;
402 	err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1,
403 		&count);
404 	if (err != B_OK)
405 		return err;
406 
407 	// got the endpoints; now we connect it!
408 	media_format format;
409 	format.type = B_MEDIA_RAW_AUDIO;
410 	format.u.raw_audio = media_raw_audio_format::wildcard;
411 	err = roster->Connect(soundOutput.source, mixerInput.destination, &format,
412 		&soundOutput, &mixerInput);
413 	if (err != B_OK)
414 		return err;
415 
416 	// the inputs and outputs might have been reassigned during the
417 	// nodes' negotiation of the Connect().  That's why we wait until
418 	// after Connect() finishes to save their contents.
419 	fConnection->format = format;
420 	fConnection->source = soundOutput.source;
421 	fConnection->destination = mixerInput.destination;
422 
423 	fIsConnected = true;
424 	return B_OK;
425 }
426 
427 
428 status_t
429 GameSoundBuffer::StartPlaying()
430 {
431 	if (fIsPlaying)
432 		return EALREADY;
433 
434 	BMediaRoster* roster = BMediaRoster::Roster();
435 	BTimeSource* source = roster->MakeTimeSourceFor(fConnection->producer);
436 
437 	// make sure we give the producer enough time to run buffers through
438 	// the node chain, otherwise it'll start up already late
439 	bigtime_t latency = 0;
440 	status_t status = roster->GetLatencyFor(fConnection->producer, &latency);
441 	if (status == B_OK) {
442 		status = roster->StartNode(fConnection->producer,
443 			source->Now() + latency);
444 	}
445 	source->Release();
446 
447 	fIsPlaying = true;
448 
449 	return status;
450 }
451 
452 
453 status_t
454 GameSoundBuffer::StopPlaying()
455 {
456 	if (!fIsPlaying)
457 		return EALREADY;
458 
459 	BMediaRoster* roster = BMediaRoster::Roster();
460 	roster->StopNode(fConnection->producer, 0, true);
461 		// synchronous stop
462 
463 	Reset();
464 	fIsPlaying = false;
465 
466 	return B_OK;
467 }
468 
469 
470 bool
471 GameSoundBuffer::IsPlaying()
472 {
473 	return fIsPlaying;
474 }
475 
476 
477 // SimpleSoundBuffer ------------------------------------------------------
478 SimpleSoundBuffer::SimpleSoundBuffer(const gs_audio_format * format,
479 	const void * data, int64 frames)
480 	:
481 	GameSoundBuffer(format),
482 	fPosition(0)
483 {
484 	fBufferSize = frames * fFrameSize;
485 	fBuffer = (char*)data;
486 }
487 
488 
489 SimpleSoundBuffer::~SimpleSoundBuffer()
490 {
491 	delete [] fBuffer;
492 }
493 
494 
495 void
496 SimpleSoundBuffer::Reset()
497 {
498 	GameSoundBuffer::Reset();
499 	fPosition = 0;
500 }
501 
502 
503 void
504 SimpleSoundBuffer::FillBuffer(void * data, int64 frames)
505 {
506 	char * buffer = (char*)data;
507 	size_t bytes = fFrameSize * frames;
508 
509 	if (fPosition + bytes >= fBufferSize) {
510 		if (fPosition < fBufferSize) {
511 			// copy the remaining frames
512 			size_t remainder = fBufferSize - fPosition;
513 			memcpy(buffer, &fBuffer[fPosition], remainder);
514 
515 			if (fLooping) {
516 				// restart the sound from the begging
517 				memcpy(&buffer[remainder], fBuffer, bytes - remainder);
518 				fPosition = bytes - remainder;
519 			} else
520 				fPosition = fBufferSize;
521 		} else
522 			memset(data, 0, bytes);
523 			// there is nothing left to play
524 	} else {
525 		memcpy(buffer, &fBuffer[fPosition], bytes);
526 		fPosition += bytes;
527 	}
528 }
529 
530 
531 // StreamingSoundBuffer ------------------------------------------------------
532 StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format,
533 	const void * streamHook, size_t inBufferFrameCount, size_t inBufferCount)
534 	:
535 	GameSoundBuffer(format),
536 	fStreamHook(const_cast<void *>(streamHook))
537 {
538 	if (inBufferFrameCount != 0 && inBufferCount  != 0) {
539 		BBufferGroup *bufferGroup
540 			= new BBufferGroup(inBufferFrameCount * fFrameSize, inBufferCount);
541 		fNode->SetBufferGroup(fConnection->source, bufferGroup);
542 	}
543 }
544 
545 
546 StreamingSoundBuffer::~StreamingSoundBuffer()
547 {
548 }
549 
550 
551 void
552 StreamingSoundBuffer::FillBuffer(void * buffer, int64 frames)
553 {
554 	BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook;
555 
556 	size_t bytes = fFrameSize * frames;
557 	object->FillBuffer(buffer, bytes);
558 }
559