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