xref: /haiku/src/kits/game/GameSoundBuffer.cpp (revision fb6ee7844d0d31441c04674bf6501776868d8e00)
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 // Sound Buffer Utility functions ----------------------------------------
46 template<typename T, int middle = 0>
47 static inline void
48 ApplyMod(T* data, int64 index, float* pan)
49 {
50 	data[index * 2] = T(float(data[index * 2] - middle) * pan[0] + middle);
51 	data[index * 2 + 1] = T(float(data[index * 2 + 1] - middle) * pan[1]
52 							+ middle);
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 		FillBuffer(data, frames);
260 
261 		switch (fFormat.format) {
262 			case gs_audio_format::B_GS_U8:
263 			{
264 				for (int64 i = 0; i < frames; i++) {
265 					ApplyMod<uint8, 128>((uint8*)data, i, pan);
266 					UpdateMods();
267 				}
268 
269 				break;
270 			}
271 
272 			case gs_audio_format::B_GS_S16:
273 			{
274 				for (int64 i = 0; i < frames; i++) {
275 					ApplyMod<int16>((int16*)data, i, pan);
276 					UpdateMods();
277 				}
278 
279 				break;
280 			}
281 
282 			case gs_audio_format::B_GS_S32:
283 			{
284 				for (int64 i = 0; i < frames; i++) {
285 					ApplyMod<int32>((int32*)data, i, pan);
286 					UpdateMods();
287 				}
288 
289 				break;
290 			}
291 
292 			case gs_audio_format::B_GS_F:
293 			{
294 				for (int64 i = 0; i < frames; i++) {
295 					ApplyMod<float>((float*)data, i, pan);
296 					UpdateMods();
297 				}
298 
299 				break;
300 			}
301 		}
302 	} else if (fFormat.channel_count == 1) {
303 		// FIXME the output should be stereo, and we could pan mono sounds
304 		// here. But currently the output has the same number of channels as
305 		// the sound and we can't do this.
306 		// FIXME also, we don't handle the gain here.
307 		FillBuffer(data, frames);
308 	} else
309 		debugger("Invalid number of channels.");
310 
311 }
312 
313 
314 void
315 GameSoundBuffer::UpdateMods()
316 {
317 	// adjust the gain if needed
318 	if (fGainRamp) {
319 		if (ChangeRamp(fGainRamp)) {
320 			delete fGainRamp;
321 			fGainRamp = NULL;
322 		}
323 	}
324 
325 	// adjust the ramp if needed
326 	if (fPanRamp) {
327 		if (ChangeRamp(fPanRamp)) {
328 			delete fPanRamp;
329 			fPanRamp = NULL;
330 		} else {
331 			if (fPan < 0.0) {
332 				fPanLeft = 1.0;
333 				fPanRight = 1.0 + fPan;
334 			} else {
335 				fPanRight = 1.0;
336 				fPanLeft = 1.0 - fPan;
337 			}
338 		}
339 	}
340 }
341 
342 
343 void
344 GameSoundBuffer::Reset()
345 {
346 	fGain = 1.0;
347 	delete fGainRamp;
348 	fGainRamp = NULL;
349 
350 	fPan = 0.0;
351 	fPanLeft = 1.0;
352 	fPanRight = 1.0;
353 
354 	delete fPanRamp;
355 	fPanRamp = NULL;
356 
357 	fLooping = false;
358 }
359 
360 
361 status_t
362 GameSoundBuffer::Connect(media_node * consumer)
363 {
364 	BMediaRoster* roster = BMediaRoster::Roster();
365 	status_t err = roster->RegisterNode(fNode);
366 
367 	if (err != B_OK)
368 		return err;
369 
370 	// make sure the Media Roster knows that we're using the node
371 	err = roster->GetNodeFor(fNode->Node().node, &fConnection->producer);
372 
373 	if (err != B_OK)
374 		return err;
375 
376 	// connect to the mixer
377 	fConnection->consumer = *consumer;
378 
379 	// set the producer's time source to be the "default" time source, which
380 	// the Mixer uses too.
381 	err = roster->GetTimeSource(&fConnection->timeSource);
382 	if (err != B_OK)
383 		return err;
384 
385 	err = roster->SetTimeSourceFor(fConnection->producer.node,
386 		fConnection->timeSource.node);
387 	if (err != B_OK)
388 		return err;
389 	// got the nodes; now we find the endpoints of the connection
390 	media_input mixerInput;
391 	media_output soundOutput;
392 	int32 count = 1;
393 	err = roster->GetFreeOutputsFor(fConnection->producer, &soundOutput, 1,
394 		&count);
395 
396 	if (err != B_OK)
397 		return err;
398 	count = 1;
399 	err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1,
400 		&count);
401 	if (err != B_OK)
402 		return err;
403 
404 	// got the endpoints; now we connect it!
405 	media_format format;
406 	format.type = B_MEDIA_RAW_AUDIO;
407 	format.u.raw_audio = media_raw_audio_format::wildcard;
408 	err = roster->Connect(soundOutput.source, mixerInput.destination, &format,
409 		&soundOutput, &mixerInput);
410 	if (err != B_OK)
411 		return err;
412 
413 	// the inputs and outputs might have been reassigned during the
414 	// nodes' negotiation of the Connect().  That's why we wait until
415 	// after Connect() finishes to save their contents.
416 	fConnection->format = format;
417 	fConnection->source = soundOutput.source;
418 	fConnection->destination = mixerInput.destination;
419 
420 	fIsConnected = true;
421 	return B_OK;
422 }
423 
424 
425 status_t
426 GameSoundBuffer::StartPlaying()
427 {
428 	if (fIsPlaying)
429 		return EALREADY;
430 
431 	BMediaRoster* roster = BMediaRoster::Roster();
432 	BTimeSource* source = roster->MakeTimeSourceFor(fConnection->producer);
433 
434 	// make sure we give the producer enough time to run buffers through
435 	// the node chain, otherwise it'll start up already late
436 	bigtime_t latency = 0;
437 	status_t status = roster->GetLatencyFor(fConnection->producer, &latency);
438 	if (status == B_OK) {
439 		status = roster->StartNode(fConnection->producer,
440 			source->Now() + latency);
441 	}
442 	source->Release();
443 
444 	fIsPlaying = true;
445 
446 	return status;
447 }
448 
449 
450 status_t
451 GameSoundBuffer::StopPlaying()
452 {
453 	if (!fIsPlaying)
454 		return EALREADY;
455 
456 	BMediaRoster* roster = BMediaRoster::Roster();
457 	roster->StopNode(fConnection->producer, 0, true);
458 		// synchronous stop
459 
460 	Reset();
461 	fIsPlaying = false;
462 
463 	return B_OK;
464 }
465 
466 
467 bool
468 GameSoundBuffer::IsPlaying()
469 {
470 	return fIsPlaying;
471 }
472 
473 
474 // SimpleSoundBuffer ------------------------------------------------------
475 SimpleSoundBuffer::SimpleSoundBuffer(const gs_audio_format * format,
476 	const void * data, int64 frames)
477 	:
478 	GameSoundBuffer(format),
479 	fPosition(0)
480 {
481 	fBufferSize = frames * fFrameSize;
482 	fBuffer = (char*)data;
483 }
484 
485 
486 SimpleSoundBuffer::~SimpleSoundBuffer()
487 {
488 	delete [] fBuffer;
489 }
490 
491 
492 void
493 SimpleSoundBuffer::Reset()
494 {
495 	GameSoundBuffer::Reset();
496 	fPosition = 0;
497 }
498 
499 
500 void
501 SimpleSoundBuffer::FillBuffer(void * data, int64 frames)
502 {
503 	char * buffer = (char*)data;
504 	size_t bytes = fFrameSize * frames;
505 
506 	if (fPosition + bytes >= fBufferSize) {
507 		if (fPosition < fBufferSize) {
508 			// copy the remaining frames
509 			size_t remainder = fBufferSize - fPosition;
510 			memcpy(buffer, &fBuffer[fPosition], remainder);
511 
512 			bytes -= remainder;
513 			buffer += remainder;
514 		}
515 
516 		if (fLooping) {
517 			// restart the sound from the beginning
518 			memcpy(buffer, fBuffer, bytes);
519 			fPosition = bytes;
520 			bytes = 0;
521 		} else {
522 			fPosition = fBufferSize;
523 		}
524 
525 		if (bytes > 0) {
526 			// Fill the rest with silence
527 			int middle = 0;
528 			if (fFormat.format == gs_audio_format::B_GS_U8)
529 				middle = 128;
530 			memset(buffer, middle, bytes);
531 		}
532 	} else {
533 		memcpy(buffer, &fBuffer[fPosition], bytes);
534 		fPosition += bytes;
535 	}
536 }
537 
538 
539 // StreamingSoundBuffer ------------------------------------------------------
540 StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format,
541 	const void * streamHook, size_t inBufferFrameCount, size_t inBufferCount)
542 	:
543 	GameSoundBuffer(format),
544 	fStreamHook(const_cast<void *>(streamHook))
545 {
546 	if (inBufferFrameCount != 0 && inBufferCount  != 0) {
547 		BBufferGroup *bufferGroup
548 			= new BBufferGroup(inBufferFrameCount * fFrameSize, inBufferCount);
549 		fNode->SetBufferGroup(fConnection->source, bufferGroup);
550 	}
551 }
552 
553 
554 StreamingSoundBuffer::~StreamingSoundBuffer()
555 {
556 }
557 
558 
559 void
560 StreamingSoundBuffer::FillBuffer(void * buffer, int64 frames)
561 {
562 	BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook;
563 
564 	size_t bytes = fFrameSize * frames;
565 	object->FillBuffer(buffer, bytes);
566 }
567