xref: /haiku/src/kits/game/GameSoundBuffer.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2002, Haiku
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, int32 min, int32 middle, int32 max>
47 static inline void
48 ApplyMod(T* data, int64 index, float* pan)
49 {
50 	data[index * 2] = clamp<T, min, max>(float(data[index * 2] - middle)
51 		* pan[0] + middle);
52 	data[index * 2 + 1] = clamp<T, min, max>(float(data[index * 2 + 1] - middle)
53 		* pan[1] + middle);
54 }
55 
56 
57 // GameSoundBuffer -------------------------------------------------------
58 GameSoundBuffer::GameSoundBuffer(const gs_audio_format * format)
59 	:
60 	fLooping(false),
61 	fIsConnected(false),
62 	fIsPlaying(false),
63 	fGain(1.0),
64 	fPan(0.0),
65 	fPanLeft(1.0),
66 	fPanRight(1.0),
67 	fGainRamp(NULL),
68 	fPanRamp(NULL)
69 {
70 	fConnection = new Connection;
71 	fNode = new GameProducer(this, format);
72 
73 	fFrameSize = get_sample_size(format->format) * format->channel_count;
74 
75 	fFormat = *format;
76 }
77 
78 
79 // Play must stop before the distructor is called; otherwise, a fatal
80 // error occures if the playback is in a subclass.
81 GameSoundBuffer::~GameSoundBuffer()
82 {
83 	BMediaRoster* roster = BMediaRoster::Roster();
84 
85 	if (fIsConnected) {
86 		// Ordinarily we'd stop *all* of the nodes in the chain at this point.
87 		// However, one of the nodes is the System Mixer, and stopping the Mixer
88 		// is a Bad Idea (tm). So, we just disconnect from it, and release our
89 		// references to the nodes that we're using.  We *are* supposed to do
90 		// that even for global nodes like the Mixer.
91 		roster->Disconnect(fConnection->producer.node, fConnection->source,
92 			fConnection->consumer.node, fConnection->destination);
93 
94 		roster->ReleaseNode(fConnection->producer);
95 		roster->ReleaseNode(fConnection->consumer);
96 	}
97 
98 	delete fGainRamp;
99 	delete fPanRamp;
100 
101 	delete fConnection;
102 	delete fNode;
103 }
104 
105 
106 const gs_audio_format &
107 GameSoundBuffer::Format() const
108 {
109 	return fFormat;
110 }
111 
112 
113 bool
114 GameSoundBuffer::IsLooping() const
115 {
116 	return fLooping;
117 }
118 
119 
120 void
121 GameSoundBuffer::SetLooping(bool looping)
122 {
123 	fLooping = looping;
124 }
125 
126 
127 float
128 GameSoundBuffer::Gain() const
129 {
130 	return fGain;
131 }
132 
133 
134 status_t
135 GameSoundBuffer::SetGain(float gain, bigtime_t duration)
136 {
137 	if (gain < 0.0 || gain > 1.0)
138 		return B_BAD_VALUE;
139 
140 	delete fGainRamp;
141 	fGainRamp = NULL;
142 
143 	if (duration > 100000)
144 		fGainRamp  = InitRamp(&fGain, gain, fFormat.frame_rate, duration);
145 	else
146 		fGain = gain;
147 
148 	return B_OK;
149 }
150 
151 
152 float
153 GameSoundBuffer::Pan() const
154 {
155 	return fPan;
156 }
157 
158 
159 status_t
160 GameSoundBuffer::SetPan(float pan, bigtime_t duration)
161 {
162 	if (pan < -1.0 || pan > 1.0)
163 		return B_BAD_VALUE;
164 
165 	delete fPanRamp;
166 	fPanRamp = NULL;
167 
168 	if (duration < 100000) {
169 		fPan = pan;
170 
171 		if (fPan < 0.0) {
172 			fPanLeft = 1.0;
173 			fPanRight = 1.0 + fPan;
174 		} else {
175 			fPanRight = 1.0;
176 			fPanLeft = 1.0 - fPan;
177 		}
178 	} else
179 		fPanRamp = InitRamp(&fPan, pan, fFormat.frame_rate, duration);
180 
181 	return B_OK;
182 }
183 
184 
185 status_t
186 GameSoundBuffer::GetAttributes(gs_attribute * attributes,
187 	size_t attributeCount)
188 {
189 	for (size_t i = 0; i < attributeCount; i++) {
190 		switch (attributes[i].attribute) {
191 			case B_GS_GAIN:
192 				attributes[i].value = fGain;
193 				if (fGainRamp)
194 					attributes[i].duration = fGainRamp->duration;
195 				break;
196 
197 			case B_GS_PAN:
198 				attributes[i].value = fPan;
199 				if (fPanRamp)
200 					attributes[i].duration = fPanRamp->duration;
201 				break;
202 
203 			case B_GS_LOOPING:
204 				attributes[i].value = (fLooping) ? -1.0 : 0.0;
205 				attributes[i].duration = bigtime_t(0);
206 				break;
207 
208 			default:
209 				attributes[i].value = 0.0;
210 				attributes[i].duration = bigtime_t(0);
211 				break;
212 		}
213 	}
214 
215 	return B_OK;
216 }
217 
218 
219 status_t
220 GameSoundBuffer::SetAttributes(gs_attribute * attributes,
221 	size_t attributeCount)
222 {
223 	status_t error = B_OK;
224 
225 	for (size_t i = 0; i < attributeCount; i++) {
226 		switch (attributes[i].attribute) {
227 			case B_GS_GAIN:
228 				error = SetGain(attributes[i].value, attributes[i].duration);
229 				break;
230 
231 			case B_GS_PAN:
232 				error = SetPan(attributes[i].value, attributes[i].duration);
233 				break;
234 
235 			case B_GS_LOOPING:
236 				fLooping = bool(attributes[i].value);
237 				break;
238 
239 			default:
240 				break;
241 		}
242 	}
243 
244 	return error;
245 }
246 
247 
248 void
249 GameSoundBuffer::Play(void * data, int64 frames)
250 {
251 	// Mh... should we add some locking?
252 	if (!fIsPlaying)
253 		return;
254 
255 	if (fFormat.channel_count == 2) {
256 		float pan[2];
257 		pan[0] = fPanRight * fGain;
258 		pan[1] = fPanLeft * fGain;
259 
260 		FillBuffer(data, frames);
261 
262 		switch (fFormat.format) {
263 			case gs_audio_format::B_GS_U8:
264 			{
265 				for (int64 i = 0; i < frames; i++) {
266 					ApplyMod<uint8, 0, 128, UINT8_MAX>((uint8*)data, i, pan);
267 					UpdateMods();
268 				}
269 
270 				break;
271 			}
272 
273 			case gs_audio_format::B_GS_S16:
274 			{
275 				for (int64 i = 0; i < frames; i++) {
276 					ApplyMod<int16, INT16_MIN, 0, INT16_MAX>((int16*)data, i,
277 						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, INT32_MIN, 0, INT32_MAX>((int32*)data, i,
288 						pan);
289 					UpdateMods();
290 				}
291 
292 				break;
293 			}
294 
295 			case gs_audio_format::B_GS_F:
296 			{
297 				for (int64 i = 0; i < frames; i++) {
298 					ApplyMod<float, -1, 0, 1>((float*)data, i, pan);
299 					UpdateMods();
300 				}
301 
302 				break;
303 			}
304 		}
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 			bytes -= remainder;
516 			buffer += remainder;
517 		}
518 
519 		if (fLooping) {
520 			// restart the sound from the beginning
521 			memcpy(buffer, fBuffer, bytes);
522 			fPosition = bytes;
523 			bytes = 0;
524 		} else {
525 			fPosition = fBufferSize;
526 		}
527 
528 		if (bytes > 0) {
529 			// Fill the rest with silence
530 			int middle = 0;
531 			if (fFormat.format == gs_audio_format::B_GS_U8)
532 				middle = 128;
533 			memset(buffer, middle, bytes);
534 		}
535 	} else {
536 		memcpy(buffer, &fBuffer[fPosition], bytes);
537 		fPosition += bytes;
538 	}
539 }
540 
541 
542 // StreamingSoundBuffer ------------------------------------------------------
543 StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format,
544 	const void * streamHook, size_t inBufferFrameCount, size_t inBufferCount)
545 	:
546 	GameSoundBuffer(format),
547 	fStreamHook(const_cast<void *>(streamHook))
548 {
549 	if (inBufferFrameCount != 0 && inBufferCount  != 0) {
550 		BBufferGroup *bufferGroup
551 			= new BBufferGroup(inBufferFrameCount * fFrameSize, inBufferCount);
552 		fNode->SetBufferGroup(fConnection->source, bufferGroup);
553 	}
554 }
555 
556 
557 StreamingSoundBuffer::~StreamingSoundBuffer()
558 {
559 }
560 
561 
562 void
563 StreamingSoundBuffer::FillBuffer(void * buffer, int64 frames)
564 {
565 	BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook;
566 
567 	size_t bytes = fFrameSize * frames;
568 	object->FillBuffer(buffer, bytes);
569 }
570