//------------------------------------------------------------------------------ // Copyright (c) 2001-2002, OpenBeOS // // 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 #include #include #include #include #include #include #include "GameProducer.h" #include "GameSoundBuffer.h" #include "GameSoundDevice.h" #include "StreamingGameSound.h" #include "GSUtility.h" // Sound Buffer Utility functions ---------------------------------------- inline void ApplyMod(uint8 * data, uint8 * buffer, int64 index, float gain, float * pan) { data[index*2] += uint8(float(buffer[index*2]) * gain * pan[0]); data[index*2+1] += uint8(float(buffer[index*2+1]) * gain * pan[1]); } inline void ApplyMod(int16 * data, int16 * buffer, int32 index, float gain, float * pan) { data[index*2] = int16(float(buffer[index*2]) * gain * pan[0]); data[index*2+1] = int16(float(buffer[index*2+1]) * gain * pan[1]); } inline void ApplyMod(int32 * data, int32 * buffer, int32 index, float gain, float * pan) { data[index*2] += int32(float(buffer[index*2]) * gain * pan[0]); data[index*2+1] += int32(float(buffer[index*2+1]) * gain * pan[1]); } inline void ApplyMod(float * data, float * buffer, int32 index, float gain, float * pan) { data[index*2] += buffer[index*2] * gain * pan[0]; data[index*2+1] += buffer[index*2+1] * gain * pan[1]; } // 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; memcpy(&fFormat, format, sizeof(gs_audio_format)); } // Play must stop before the distructor is called; otherwise, a fatel // error occures if the playback is in a subclass. GameSoundBuffer::~GameSoundBuffer() { BMediaRoster* r = 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. r->Disconnect(fConnection->producer.node, fConnection->source, fConnection->consumer.node, fConnection->destination); r->ReleaseNode(fConnection->producer); r->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; if (fGainRamp) delete fGainRamp; 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; if (fPanRamp) delete fPanRamp; 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 = fGainRamp->duration; case B_GS_LOOPING: attributes[i].value = (fLooping) ? -1.0 : 0.0; attributes[i].duration = bigtime_t(0); default: attributes[i].value = 0.0; attributes[i].duration = bigtime_t(0); } } 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); } } return error; } void GameSoundBuffer::Play(void * data, int64 frames) { float * pan = new float[1]; pan[0] = fPanRight; pan[1] = fPanLeft; char * buffer = new char[fFrameSize * frames]; FillBuffer(buffer, frames); switch(fFormat.format) { case gs_audio_format::B_GS_U8: { for(int64 i = 0; i < frames; i++) { ApplyMod((uint8*)data, (uint8*)buffer, i, fGain, pan); UpdateMods(); } break; } case gs_audio_format::B_GS_S16: { for(int64 i = 0; i < frames; i++) { ApplyMod((int16*)data, (int16*)buffer, i, fGain, pan); UpdateMods(); } break; } case gs_audio_format::B_GS_S32: { for(int64 i = 0; i < frames; i++) { ApplyMod((int32*)data, (int32*)buffer, i, fGain, pan); UpdateMods(); } break; } case gs_audio_format::B_GS_F: { for(int64 i = 0; i < frames; i++) { ApplyMod((float*)data, (float*)buffer, i, fGain, pan); UpdateMods(); } break; } } delete [] buffer; delete [] pan; } 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* r = BMediaRoster::Roster(); status_t err; err = r->RegisterNode(fNode); if (err != B_OK) return err; // make sure the Media Roster knows that we're using the node r->GetNodeFor(fNode->Node().node, &fConnection->producer); // connect to the mixer fConnection->consumer = *consumer; if (err != B_OK) return err; // set the producer's time source to be the "default" time source, which // the Mixer uses too. r->GetTimeSource(&fConnection->timeSource); r->SetTimeSourceFor(fConnection->producer.node, fConnection->timeSource.node); // got the nodes; now we find the endpoints of the connection media_input mixerInput; media_output soundOutput; int32 count = 1; err = r->GetFreeOutputsFor(fConnection->producer, &soundOutput, 1, &count); if (err != B_OK) return err; count = 1; err = r->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 = r->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* r = BMediaRoster::Roster(); BTimeSource* ts = r->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; r->GetLatencyFor(fConnection->producer, &latency); r->StartNode(fConnection->producer, ts->Now() + latency); ts->Release(); fIsPlaying = true; return B_OK; } status_t GameSoundBuffer::StopPlaying() { if (!fIsPlaying) return EALREADY; BMediaRoster* r = BMediaRoster::Roster(); r->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) { fBuffer = new char[frames * fFrameSize]; fBufferSize = frames * fFrameSize; memcpy(fBuffer, data, fBufferSize); } 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); if (fLooping) { // restart the sound from the begging memcpy(&buffer[remainder], fBuffer, bytes - remainder); fPosition = bytes - remainder; } else fPosition = fBufferSize; } else memset(data, 0, bytes); // there is nothing left to play } else { memcpy(buffer, &fBuffer[fPosition], bytes); fPosition += bytes; } } // StreamingSoundBuffer ------------------------------------------------------ StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format, const void * streamHook) : GameSoundBuffer(format) { fStreamHook = (void*)streamHook; } StreamingSoundBuffer::~StreamingSoundBuffer() { } void StreamingSoundBuffer::FillBuffer(void * buffer, int64 frames) { BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook; size_t bytes = fFrameSize * frames; object->FillBuffer(buffer, bytes); }