text: //------------------------------------------------------------------------------
  //	Copyright (c) 2001-2002, Haiku
  //
  //	Permission is hereby granted, free of charge, to any person obtaining a
  //	copy of this software and associated documentation files (the "Software"),
  //	to deal in the Software without restriction, including without limitation
  //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
  //	and/or sell copies of the Software, and to permit persons to whom the
  //	Software is furnished to do so, subject to the following conditions:
  //
  //	The above copyright notice and this permission notice shall be included in
  //	all copies or substantial portions of the Software.
  //
  //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  //	DEALINGS IN THE SOFTWARE.
  //
  //	File Name:		GameSoundBuffer.h
  //	Author:			Christopher ML Zumwalt May (zummy@users.sf.net)
  //	Description:	Interface to a single sound, managed by the GameSoundDevice.
  //------------------------------------------------------------------------------
  
  
  #include "GameSoundBuffer.h"
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  
  #include <MediaRoster.h>
  #include <MediaAddOn.h>
  #include <MediaTheme.h>
  #include <TimeSource.h>
  #include <BufferGroup.h>
  
  #include "GameProducer.h"
  #include "GameSoundDevice.h"
  #include "StreamingGameSound.h"
  #include "GSUtility.h"
  
  // Sound Buffer Utility functions ----------------------------------------
  template<typename T, int32 min, int32 middle, int32 max>
  static inline void
  ApplyMod(T* data, int64 index, float* pan)
  {
  	data[index * 2] = clamp<T, min, max>(float(data[index * 2] - middle)
  		* pan[0] + middle);
  	data[index * 2 + 1] = clamp<T, min, max>(float(data[index * 2 + 1] - middle)
  		* pan[1] + middle);
  }
  
  
  // GameSoundBuffer -------------------------------------------------------
  GameSoundBuffer::GameSoundBuffer(const gs_audio_format * format)
  	:
  	fLooping(false),
  	fIsConnected(false),
  	fIsPlaying(false),
  	fGain(1.0),
  	fPan(0.0),
  	fPanLeft(1.0),
  	fPanRight(1.0),
  	fGainRamp(NULL),
  	fPanRamp(NULL)
  {
  	fConnection = new Connection;
  	fNode = new GameProducer(this, format);
  
  	fFrameSize = get_sample_size(format->format) * format->channel_count;
  
  	fFormat = *format;
  }
  
  
  // Play must stop before the distructor is called; otherwise, a fatal
  // error occures if the playback is in a subclass.
  GameSoundBuffer::~GameSoundBuffer()
  {
  	BMediaRoster* roster = BMediaRoster::Roster();
  
  	if (fIsConnected) {
  		// Ordinarily we'd stop *all* of the nodes in the chain at this point.
  		// However, one of the nodes is the System Mixer, and stopping the Mixer
  		// is a Bad Idea (tm). So, we just disconnect from it, and release our
  		// references to the nodes that we're using.  We *are* supposed to do
  		// that even for global nodes like the Mixer.
  		roster->Disconnect(fConnection->producer.node, fConnection->source,
  			fConnection->consumer.node, fConnection->destination);
  
  		roster->ReleaseNode(fConnection->producer);
  		roster->ReleaseNode(fConnection->consumer);
  	}
  
  	delete fGainRamp;
  	delete fPanRamp;
  
  	delete fConnection;
  	delete fNode;
  }
  
  
  const gs_audio_format &
  GameSoundBuffer::Format() const
  {
  	return fFormat;
  }
  
  
  bool
  GameSoundBuffer::IsLooping() const
  {
  	return fLooping;
  }
  
  
  void
  GameSoundBuffer::SetLooping(bool looping)
  {
  	fLooping = looping;
  }
  
  
  float
  GameSoundBuffer::Gain() const
  {
  	return fGain;
  }
  
  
  status_t
  GameSoundBuffer::SetGain(float gain, bigtime_t duration)
  {
  	if (gain < 0.0 || gain > 1.0)
  		return B_BAD_VALUE;
  
  	delete fGainRamp;
  	fGainRamp = NULL;
  
  	if (duration > 100000)
  		fGainRamp  = InitRamp(&fGain, gain, fFormat.frame_rate, duration);
  	else
  		fGain = gain;
  
  	return B_OK;
  }
  
  
  float
  GameSoundBuffer::Pan() const
  {
  	return fPan;
  }
  
  
  status_t
  GameSoundBuffer::SetPan(float pan, bigtime_t duration)
  {
  	if (pan < -1.0 || pan > 1.0)
  		return B_BAD_VALUE;
  
  	delete fPanRamp;
  	fPanRamp = NULL;
  
  	if (duration < 100000) {
  		fPan = pan;
  
  		if (fPan < 0.0) {
  			fPanLeft = 1.0;
  			fPanRight = 1.0 + fPan;
  		} else {
  			fPanRight = 1.0;
  			fPanLeft = 1.0 - fPan;
  		}
  	} else
  		fPanRamp = InitRamp(&fPan, pan, fFormat.frame_rate, duration);
  
  	return B_OK;
  }
  
  
  status_t
  GameSoundBuffer::GetAttributes(gs_attribute * attributes,
  	size_t attributeCount)
  {
  	for (size_t i = 0; i < attributeCount; i++) {
  		switch (attributes[i].attribute) {
  			case B_GS_GAIN:
  				attributes[i].value = fGain;
  				if (fGainRamp)
  					attributes[i].duration = fGainRamp->duration;
  				break;
  
  			case B_GS_PAN:
  				attributes[i].value = fPan;
  				if (fPanRamp)
  					attributes[i].duration = fPanRamp->duration;
  				break;
  
  			case B_GS_LOOPING:
  				attributes[i].value = (fLooping) ? -1.0 : 0.0;
  				attributes[i].duration = bigtime_t(0);
  				break;
  
  			default:
  				attributes[i].value = 0.0;
  				attributes[i].duration = bigtime_t(0);
  				break;
  		}
  	}
  
  	return B_OK;
  }
  
  
  status_t
  GameSoundBuffer::SetAttributes(gs_attribute * attributes,
  	size_t attributeCount)
  {
  	status_t error = B_OK;
  
  	for (size_t i = 0; i < attributeCount; i++) {
  		switch (attributes[i].attribute) {
  			case B_GS_GAIN:
  				error = SetGain(attributes[i].value, attributes[i].duration);
  				break;
  
  			case B_GS_PAN:
  				error = SetPan(attributes[i].value, attributes[i].duration);
  				break;
  
  			case B_GS_LOOPING:
  				fLooping = bool(attributes[i].value);
  				break;
  
  			default:
  				break;
  		}
  	}
  
  	return error;
  }
  
  
  void
  GameSoundBuffer::Play(void * data, int64 frames)
  {
  	// Mh... should we add some locking?
  	if (!fIsPlaying)
  		return;
  
  	if (fFormat.channel_count == 2) {
  		float pan[2];
  		pan[0] = fPanRight * fGain;
  		pan[1] = fPanLeft * fGain;
  
  		FillBuffer(data, frames);
  
  		switch (fFormat.format) {
  			case gs_audio_format::B_GS_U8:
  			{
  				for (int64 i = 0; i < frames; i++) {
  					ApplyMod<uint8, 0, 128, UINT8_MAX>((uint8*)data, i, pan);
  					UpdateMods();
  				}
  
  				break;
  			}
  
  			case gs_audio_format::B_GS_S16:
  			{
  				for (int64 i = 0; i < frames; i++) {
  					ApplyMod<int16, INT16_MIN, 0, INT16_MAX>((int16*)data, i,
  						pan);
  					UpdateMods();
  				}
  
  				break;
  			}
  
  			case gs_audio_format::B_GS_S32:
  			{
  				for (int64 i = 0; i < frames; i++) {
  					ApplyMod<int32, INT32_MIN, 0, INT32_MAX>((int32*)data, i,
  						pan);
  					UpdateMods();
  				}
  
  				break;
  			}
  
  			case gs_audio_format::B_GS_F:
  			{
  				for (int64 i = 0; i < frames; i++) {
  					ApplyMod<float, -1, 0, 1>((float*)data, i, pan);
  					UpdateMods();
  				}
  
  				break;
  			}
  		}
  	} else if (fFormat.channel_count == 1) {
  		// FIXME the output should be stereo, and we could pan mono sounds
  		// here. But currently the output has the same number of channels as
  		// the sound and we can't do this.
  		// FIXME also, we don't handle the gain here.
  		FillBuffer(data, frames);
  	} else
  		debugger("Invalid number of channels.");
  
  }
  
  
  void
  GameSoundBuffer::UpdateMods()
  {
  	// adjust the gain if needed
  	if (fGainRamp) {
  		if (ChangeRamp(fGainRamp)) {
  			delete fGainRamp;
  			fGainRamp = NULL;
  		}
  	}
  
  	// adjust the ramp if needed
  	if (fPanRamp) {
  		if (ChangeRamp(fPanRamp)) {
  			delete fPanRamp;
  			fPanRamp = NULL;
  		} else {
  			if (fPan < 0.0) {
  				fPanLeft = 1.0;
  				fPanRight = 1.0 + fPan;
  			} else {
  				fPanRight = 1.0;
  				fPanLeft = 1.0 - fPan;
  			}
  		}
  	}
  }
  
  
  void
  GameSoundBuffer::Reset()
  {
  	fGain = 1.0;
  	delete fGainRamp;
  	fGainRamp = NULL;
  
  	fPan = 0.0;
  	fPanLeft = 1.0;
  	fPanRight = 1.0;
  
  	delete fPanRamp;
  	fPanRamp = NULL;
  
  	fLooping = false;
  }
  
  
  status_t
  GameSoundBuffer::Connect(media_node * consumer)
  {
  	BMediaRoster* roster = BMediaRoster::Roster();
  	status_t err = roster->RegisterNode(fNode);
  
  	if (err != B_OK)
  		return err;
  
  	// make sure the Media Roster knows that we're using the node
  	err = roster->GetNodeFor(fNode->Node().node, &fConnection->producer);
  
  	if (err != B_OK)
  		return err;
  
  	// connect to the mixer
  	fConnection->consumer = *consumer;
  
  	// set the producer's time source to be the "default" time source, which
  	// the Mixer uses too.
  	err = roster->GetTimeSource(&fConnection->timeSource);
  	if (err != B_OK)
  		return err;
  
  	err = roster->SetTimeSourceFor(fConnection->producer.node,
  		fConnection->timeSource.node);
  	if (err != B_OK)
  		return err;
  	// got the nodes; now we find the endpoints of the connection
  	media_input mixerInput;
  	media_output soundOutput;
  	int32 count = 1;
  	err = roster->GetFreeOutputsFor(fConnection->producer, &soundOutput, 1,
  		&count);
  
  	if (err != B_OK)
  		return err;
  	count = 1;
  	err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1,
  		&count);
  	if (err != B_OK)
  		return err;
  
  	// got the endpoints; now we connect it!
  	media_format format;
  	format.type = B_MEDIA_RAW_AUDIO;
  	format.u.raw_audio = media_raw_audio_format::wildcard;
  	err = roster->Connect(soundOutput.source, mixerInput.destination, &format,
  		&soundOutput, &mixerInput);
  	if (err != B_OK)
  		return err;
  
  	// the inputs and outputs might have been reassigned during the
  	// nodes' negotiation of the Connect().  That's why we wait until
  	// after Connect() finishes to save their contents.
  	fConnection->format = format;
  	fConnection->source = soundOutput.source;
  	fConnection->destination = mixerInput.destination;
  
  	fIsConnected = true;
  	return B_OK;
  }
  
  
  status_t
  GameSoundBuffer::StartPlaying()
  {
  	if (fIsPlaying)
  		return EALREADY;
  
  	BMediaRoster* roster = BMediaRoster::Roster();
  	BTimeSource* source = roster->MakeTimeSourceFor(fConnection->producer);
  
  	// make sure we give the producer enough time to run buffers through
  	// the node chain, otherwise it'll start up already late
  	bigtime_t latency = 0;
  	status_t status = roster->GetLatencyFor(fConnection->producer, &latency);
  	if (status == B_OK) {
  		status = roster->StartNode(fConnection->producer,
  			source->Now() + latency);
  	}
  	source->Release();
  
  	fIsPlaying = true;
  
  	return status;
  }
  
  
  status_t
  GameSoundBuffer::StopPlaying()
  {
  	if (!fIsPlaying)
  		return EALREADY;
  
  	BMediaRoster* roster = BMediaRoster::Roster();
  	roster->StopNode(fConnection->producer, 0, true);
  		// synchronous stop
  
  	Reset();
  	fIsPlaying = false;
  
  	return B_OK;
  }
  
  
  bool
  GameSoundBuffer::IsPlaying()
  {
  	return fIsPlaying;
  }
  
  
  // SimpleSoundBuffer ------------------------------------------------------
  SimpleSoundBuffer::SimpleSoundBuffer(const gs_audio_format * format,
  	const void * data, int64 frames)
  	:
  	GameSoundBuffer(format),
  	fPosition(0)
  {
  	fBufferSize = frames * fFrameSize;
  	fBuffer = (char*)data;
  }
  
  
  SimpleSoundBuffer::~SimpleSoundBuffer()
  {
  	delete [] fBuffer;
  }
  
  
  void
  SimpleSoundBuffer::Reset()
  {
  	GameSoundBuffer::Reset();
  	fPosition = 0;
  }
  
  
  void
  SimpleSoundBuffer::FillBuffer(void * data, int64 frames)
  {
  	char * buffer = (char*)data;
  	size_t bytes = fFrameSize * frames;
  
  	if (fPosition + bytes >= fBufferSize) {
  		if (fPosition < fBufferSize) {
  			// copy the remaining frames
  			size_t remainder = fBufferSize - fPosition;
  			memcpy(buffer, &fBuffer[fPosition], remainder);
  
  			bytes -= remainder;
  			buffer += remainder;
  		}
  
  		if (fLooping) {
  			// restart the sound from the beginning
  			memcpy(buffer, fBuffer, bytes);
  			fPosition = bytes;
  			bytes = 0;
  		} else {
  			fPosition = fBufferSize;
  		}
  
  		if (bytes > 0) {
  			// Fill the rest with silence
  			int middle = 0;
  			if (fFormat.format == gs_audio_format::B_GS_U8)
  				middle = 128;
  			memset(buffer, middle, bytes);
  		}
  	} else {
  		memcpy(buffer, &fBuffer[fPosition], bytes);
  		fPosition += bytes;
  	}
  }
  
  
  // StreamingSoundBuffer ------------------------------------------------------
  StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format,
  	const void * streamHook, size_t inBufferFrameCount, size_t inBufferCount)
  	:
  	GameSoundBuffer(format),
  	fStreamHook(const_cast<void *>(streamHook))
  {
  	if (inBufferFrameCount != 0 && inBufferCount  != 0) {
  		BBufferGroup *bufferGroup
  			= new BBufferGroup(inBufferFrameCount * fFrameSize, inBufferCount);
  		fNode->SetBufferGroup(fConnection->source, bufferGroup);
  	}
  }
  
  
  StreamingSoundBuffer::~StreamingSoundBuffer()
  {
  }
  
  
  void
  StreamingSoundBuffer::FillBuffer(void * buffer, int64 frames)
  {
  	BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook;
  
  	size_t bytes = fFrameSize * frames;
  	object->FillBuffer(buffer, bytes);
  }
  