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 ----------------------------------------
47inline void
48ApplyMod(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
55inline void
56ApplyMod(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
63inline void
64ApplyMod(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
71inline void
72ApplyMod(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 -------------------------------------------------------
80GameSoundBuffer::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.
103GameSoundBuffer::~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
128const gs_audio_format &
129GameSoundBuffer::Format() const
130{
131	return fFormat;
132}
133
134
135bool
136GameSoundBuffer::IsLooping() const
137{
138	return fLooping;
139}
140
141
142void
143GameSoundBuffer::SetLooping(bool looping)
144{
145	fLooping = looping;
146}
147
148
149float
150GameSoundBuffer::Gain() const
151{
152	return fGain;
153}
154
155
156status_t
157GameSoundBuffer::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
174float
175GameSoundBuffer::Pan() const
176{
177	return fPan;
178}
179
180
181status_t
182GameSoundBuffer::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
207status_t
208GameSoundBuffer::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
241status_t
242GameSoundBuffer::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
270void
271GameSoundBuffer::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
327void
328GameSoundBuffer::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
356void
357GameSoundBuffer::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
374status_t
375GameSoundBuffer::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
438status_t
439GameSoundBuffer::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
463status_t
464GameSoundBuffer::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
480bool
481GameSoundBuffer::IsPlaying()
482{
483	return fIsPlaying;
484}
485
486
487// SimpleSoundBuffer ------------------------------------------------------
488SimpleSoundBuffer::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
501SimpleSoundBuffer::~SimpleSoundBuffer()
502{
503	delete [] fBuffer;
504}
505
506
507void
508SimpleSoundBuffer::Reset()
509{
510	GameSoundBuffer::Reset();
511	fPosition = 0;
512}
513
514
515void
516SimpleSoundBuffer::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 ------------------------------------------------------
544StreamingSoundBuffer::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
558StreamingSoundBuffer::~StreamingSoundBuffer()
559{
560}
561
562
563void
564StreamingSoundBuffer::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