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