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