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