#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"
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(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;
}
GameSoundBuffer::~GameSoundBuffer()
{
BMediaRoster* roster = BMediaRoster::Roster();
if (fIsConnected) {
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)
{
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) {
FillBuffer(data, frames);
} else
debugger("Invalid number of channels.");
}
void
GameSoundBuffer::UpdateMods()
{
if (fGainRamp) {
if (ChangeRamp(fGainRamp)) {
delete fGainRamp;
fGainRamp = NULL;
}
}
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;
err = roster->GetNodeFor(fNode->Node().node, &fConnection->producer);
if (err != B_OK)
return err;
fConnection->consumer = *consumer;
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;
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;
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;
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);
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);
Reset();
fIsPlaying = false;
return B_OK;
}
bool
GameSoundBuffer::IsPlaying()
{
return fIsPlaying;
}
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) {
size_t remainder = fBufferSize - fPosition;
memcpy(buffer, &fBuffer[fPosition], remainder);
bytes -= remainder;
buffer += remainder;
}
if (fLooping) {
memcpy(buffer, fBuffer, bytes);
fPosition = bytes;
bytes = 0;
} else {
fPosition = fBufferSize;
}
if (bytes > 0) {
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(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);
}