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