//-----------------------------------------------------------------------------------
//
// Torque Network Library - ZAP example multiplayer vector graphics space game
// Copyright (C) 2004 GarageGames.com, Inc.
// For more information see http://www.opentnl.org
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// For use in products that are not compatible with the terms of the GNU
// General Public License, alternative licensing options are available
// from GarageGames.com.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//------------------------------------------------------------------------------------
#include "sfx.h"
#include "tnl.h"
#include "tnlLog.h"
#include "tnlRandom.h"
#if !defined (ZAP_DEDICATED) && !defined (TNL_OS_XBOX)
#include "alInclude.h"
using namespace TNL;
namespace Zap
{
static SFXProfile gSFXProfiles[] = {
// Utility sounds
{ "phaser.wav", true, 1.0f, false, 0, 0 },
{ "phaser.wav", false, 0.45f, false, 150, 600 },
// Weapon noises
{ "phaser.wav", false, 0.45f, false, 150, 600 },
{ "phaser_impact.wav", false, 0.7f, false, 150, 600 },
{ "bounce.wav", false, 0.45f, false, 150, 600 },
{ "bounce_impact.wav", false, 0.7f, false, 150, 600 },
{ "triple.wav", false, 0.45f, false, 150, 600 },
{ "triple_impact.wav", false, 0.7f, false, 150, 600 },
{ "turret.wav", false, 0.45f, false, 150, 600 },
{ "turret_impact.wav", false, 0.7f, false, 150, 600 },
{ "grenade.wav", false, 0.9f, false, 300, 600 },
{ "mine_deploy.wav", false, 0.4f, false, 150, 600 },
{ "mine_arm.wav", false, 0.7f, false, 400, 600 },
{ "mine_explode.wav", false, 0.8f, false, 300, 800 },
// Ship noises
{ "ship_explode.wav", false, 1.0, false, 300, 1000 },
{ "ship_heal.wav", false, 1.0, false, 300, 1000 },
{ "ship_turbo.wav", false, 0.15f, true, 150, 500 },
{ "bounce_wall.wav", false, 0.7f, false, 150, 600 },
{ "bounce_obj.wav", false, 0.7f, false, 150, 600 },
{ "bounce_shield.wav", false, 0.7f, false, 150, 600 },
{ "ship_shield.wav", false, 0.15f, true, 150, 500 },
{ "ship_sensor.wav", false, 0.15f, true, 150, 500 },
{ "ship_repair.wav", false, 0.15f, true, 150, 500 },
{ "ship_cloak.wav", false, 0.15f, true, 150, 500 },
// Flag noises
{ "flag_capture.wav", true, 0.45f, false, 0, 0 },
{ "flag_drop.wav", true, 0.45f, false, 0, 0 },
{ "flag_return.wav", true, 0.45f, false, 0, 0 },
{ "flag_snatch.wav", true, 0.45f, false, 0, 0 },
// Teleport noises
{ "teleport_in.wav", false, 1.0, false, 200, 500 },
{ "teleport_out.wav", false, 1.0, false, 200, 500 },
// Forcefield noises
{ "forcefield_up.wav", false, 0.7f, false, 150, 600 },
{ "forcefield_down.wav", false, 0.7f, false, 150, 600 },
// UI noises
{ "boop.wav", true, 0.4f, false, 150, 600 },
{ "comm_up.wav", true, 0.4f, false, 150, 600 },
{ "comm_down.wav", true, 0.4f, false, 150, 600 },
{ "boop.wav", true, 0.25f, false, 150, 600 },
{ NULL, false, 0, false, 0, 0 },
};
static ALCdevice *gDevice = NULL;
static ALCcontext *gContext = NULL;
static bool gSFXValid = false;
enum {
NumSources = 16,
};
static ALuint gSources[NumSources];
Point SFXObject::mListenerPosition;
Point SFXObject::mListenerVelocity;
F32 SFXObject::mMaxDistance = 500;
static ALuint gBuffers[NumSFXBuffers];
static Vector<ALuint> gVoiceFreeBuffers;
static Vector<SFXHandle> gPlayList;
SFXObject::SFXObject(U32 profileIndex, ByteBufferPtr ib, F32 gain, Point position, Point velocity)
{
mSFXIndex = profileIndex;
mProfile = gSFXProfiles + profileIndex;
mGain = gain;
mPosition = position;
mVelocity = velocity;
mSourceIndex = -1;
mPriority = 0;
mInitialBuffer = ib;
}
RefPtr<SFXObject> SFXObject::play(U32 profileIndex, F32 gain)
{
RefPtr<SFXObject> ret = new SFXObject(profileIndex, NULL, gain, Point(), Point());
ret->play();
return ret;
}
RefPtr<SFXObject> SFXObject::play(U32 profileIndex, Point position, Point velocity, F32 gain)
{
RefPtr<SFXObject> ret = new SFXObject(profileIndex, NULL, gain, position, velocity);
ret->play();
return ret;
}
RefPtr<SFXObject> SFXObject::playRecordedBuffer(ByteBufferPtr p, F32 gain)
{
RefPtr<SFXObject> ret = new SFXObject(0, p, gain, Point(), Point());
ret->play();
return ret;
}
SFXObject::~SFXObject()
{
}
void SFXObject::updateGain()
{
ALuint source = gSources[mSourceIndex];
F32 gain = mGain;
if(!mProfile->isRelative)
{
F32 distance = (mListenerPosition - mPosition).len();
if(distance > mProfile->fullGainDistance)
{
if(distance < mProfile->zeroGainDistance)
gain *= 1 - (distance - mProfile->fullGainDistance) /
(mProfile->zeroGainDistance - mProfile->fullGainDistance);
else
gain = 0.0f;
}
else
gain = 1.0f;
}
else
gain = 1.0f;
alSourcef(source, AL_GAIN, gain * mProfile->gainScale);
}
void SFXObject::updateMovementParams()
{
ALuint source = gSources[mSourceIndex];
if(mProfile->isRelative)
{
alSourcei(source, AL_SOURCE_RELATIVE, true);
alSource3f(source, AL_POSITION, 0, 0, 0);
//alSource3f(source, AL_VELOCITY, 0, 0, 0);
}
else
{
alSourcei(source, AL_SOURCE_RELATIVE, false);
alSource3f(source, AL_POSITION, mPosition.x, mPosition.y, 0);
//alSource3f(source, AL_VELOCITY, mVelocity.x, mVelocity.y, 0);
}
}
static void unqueueBuffers(S32 sourceIndex)
{
// free up any played buffers from this source.
if(sourceIndex != -1)
{
ALint processed;
alGetError();
alGetSourcei(gSources[sourceIndex], AL_BUFFERS_PROCESSED, &processed);
while(processed)
{
ALuint buffer;
alSourceUnqueueBuffers(gSources[sourceIndex], 1, &buffer);
if(alGetError() != AL_NO_ERROR)
return;
//logprintf("unqueued buffer %d\n", buffer);
processed--;
// ok, this is a lame solution - but the way OpenAL should work is...
// you should only be able to unqueue buffers that you queued - duh!
// otherwise it's a bitch to manage sources that can either be streamed
// or already loaded.
U32 i;
for(i = 0 ; i < NumSFXBuffers; i++)
if(buffer == gBuffers[i])
break;
if(i == NumSFXBuffers)
gVoiceFreeBuffers.push_back(buffer);
}
}
}
void SFXObject::queueBuffer(ByteBufferPtr p)
{
if(!gSFXValid)
return;
if(!p->getBufferSize())
return;
mInitialBuffer = p;
if(mSourceIndex != -1)
{
if(!gVoiceFreeBuffers.size())
return;
ALuint source = gSources[mSourceIndex];
ALuint buffer = gVoiceFreeBuffers.first();
gVoiceFreeBuffers.pop_front();
S16 max = 0;
S16 *ptr = (S16 *) p->getBuffer();
U32 count = p->getBufferSize() / 2;
while(count--)
{
if(max < *ptr)
max = *ptr;
ptr++;
}
//logprintf("queued buffer %d - %d max %d len\n", buffer, max, mInitialBuffer->getBufferSize());
alBufferData(buffer, AL_FORMAT_MONO16, mInitialBuffer->getBuffer(),
mInitialBuffer->getBufferSize(), 8000);
alSourceQueueBuffers(source, 1, &buffer);
ALint state;
alGetSourcei(mSourceIndex, AL_SOURCE_STATE, &state);
if(state == AL_STOPPED)
alSourcePlay(mSourceIndex);
}
else
play();
}
void SFXObject::playOnSource()
{
ALuint source = gSources[mSourceIndex];
alSourceStop(source);
unqueueBuffers(mSourceIndex);
if(mInitialBuffer.isValid())
{
if(!gVoiceFreeBuffers.size())
return;
ALuint buffer = gVoiceFreeBuffers.first();
gVoiceFreeBuffers.pop_front();
alBufferData(buffer, AL_FORMAT_MONO16, mInitialBuffer->getBuffer(),
mInitialBuffer->getBufferSize(), 8000);
alSourceQueueBuffers(source, 1, &buffer);
}
else
alSourcei(source, AL_BUFFER, gBuffers[mSFXIndex]);
alSourcei(source, AL_LOOPING, mProfile->isLooping);
#ifdef TNL_OS_MAC_OSX
// This is a workaround for the broken OS X implementation of AL_NONE distance model.
alSourcef(source, AL_REFERENCE_DISTANCE,9000);
alSourcef(source, AL_ROLLOFF_FACTOR,1);
alSourcef(source, AL_MAX_DISTANCE, 10000);
#endif
updateMovementParams();
updateGain();
alSourcePlay(source);
}
void SFXObject::setGain(F32 gain)
{
if(!gSFXValid)
return;
mGain = gain;
if(mSourceIndex != -1)
updateGain();
}
void SFXObject::setMovementParams(Point position, Point velocity)
{
if(!gSFXValid)
return;
mPosition = position;
mVelocity = velocity;
if(mSourceIndex != -1)
updateMovementParams();
}
void SFXObject::play()
{
if(!gSFXValid)
return;
if(mSourceIndex != -1)
return;
else
{
// see if it's on the play list:
for(S32 i = 0; i < gPlayList.size(); i++)
if(this == gPlayList[i].getPointer())
return;
gPlayList.push_back(this);
}
}
void SFXObject::stop()
{
if(!gSFXValid)
return;
// remove from the play list, if this sound is playing:
if(mSourceIndex != -1)
{
alSourceStop(gSources[mSourceIndex]);
mSourceIndex = -1;
}
for(S32 i = 0; i < gPlayList.size(); i++)
{
if(gPlayList[i].getPointer() == this)
{
gPlayList.erase(i);
return;
}
}
}
void SFXObject::init()
{
ALint error;
gDevice = alcOpenDevice((ALubyte *) "DirectSound3D");
if(!gDevice)
{
logprintf("Failed to intitialize OpenAL.");
return;
}
static int contextData[][2] =
{
{ALC_FREQUENCY, 11025},
{0,0} // Indicate end of list...
};
gContext = alcCreateContext(gDevice, (ALCint*)contextData);
alcMakeContextCurrent(gContext);
error = alGetError();
alGenBuffers(NumSFXBuffers, gBuffers);
gVoiceFreeBuffers.setSize(32);
alGenBuffers(32, gVoiceFreeBuffers.address());
error = alGetError();
alDistanceModel(AL_NONE);
error = alGetError();
// load up all the sound buffers
//if(error != AL_NO_ERROR)
// return;
alGenSources(NumSources, gSources);
for(U32 i = 0; i < NumSFXBuffers; i++)
{
if(!gSFXProfiles[i].fileName)
break;
ALsizei size,freq;
ALenum format;
ALvoid *data;
ALboolean loop;
char fileBuffer[256];
dSprintf(fileBuffer, sizeof(fileBuffer), "sfx/%s", gSFXProfiles[i].fileName);
#ifdef TNL_OS_MAC_OSX
alutLoadWAVFile((ALbyte *) fileBuffer, &format, &data, &size, &freq);
#else
alutLoadWAVFile((ALbyte *) fileBuffer, &format, &data, &size, &freq, &loop);
#endif
if(alGetError() != AL_NO_ERROR)
{
logprintf("Failure (1) loading sound file '%s'", gSFXProfiles[i].fileName);
return;
}
alBufferData(gBuffers[i], format, data, size, freq);
alutUnloadWAV(format, data, size, freq);
if(alGetError() != AL_NO_ERROR)
{
logprintf("Failure (2) loading sound file '%s'", gSFXProfiles[i].fileName);
return;
}
}
gSFXValid = true;
}
void SFXObject::process()
{
if(!gSFXValid)
return;
// ok, so we have a list of currently "playing" sounds, which is
// unbounded in length, but only the top NumSources actually have sources
// associtated with them. Sounds are prioritized on a 0-1 scale
// based on type and distance.
// Each time through the main loop, newly played sounds are placed
// on the process list. When SFXProcess is called, any finished sounds
// are retired from the list, and then it prioritizes and sorts all
// the remaining sounds. For any sounds from 0 to NumSources that don't
// have a current source, they grab one of the sources not used by the other
// top sounds. At this point, any sound that is not looping, and is
// not in the active top list is retired.
// ok, look through all the currently playing sources and see which
// ones need to be retired:
bool sourceActive[NumSources];
for(S32 i = 0; i < NumSources; i++)
{
ALint state;
unqueueBuffers(i);
alGetSourcei(gSources[i], AL_SOURCE_STATE, &state);
sourceActive[i] = state != AL_STOPPED && state != AL_INITIAL;
}
for(S32 i = 0; i < gPlayList.size(); )
{
SFXHandle &s = gPlayList[i];
if(s->mSourceIndex != -1 && !sourceActive[s->mSourceIndex])
{
// this sound was playing; now it is stopped,
// so remove it from the list.
s->mSourceIndex = -1;
gPlayList.erase_fast(i);
}
else
{
// compute a priority for this sound.
if(!s->mProfile->isRelative)
s->mPriority = (500 - (s->mPosition - mListenerPosition).len()) / 500.0f;
else
s->mPriority = 1.0;
i++;
}
}
// now, bubble sort all the sounds up the list:
// we choose bubble sort, because the list should
// have a lot of frame-to-frame coherency, making the
// sort most often O(n)
for(S32 i = 1; i < gPlayList.size(); i++)
{
F32 priority = gPlayList[i]->mPriority;
for(S32 j = i - 1; j >= 0; j--)
{
if(priority > gPlayList[j]->mPriority)
{
SFXHandle temp = gPlayList[j];
gPlayList[j] = gPlayList[j+1];
gPlayList[j+1] = temp;
}
}
}
// last, release any sources and get rid of non-looping sounds
// outside our max sound limit
for(S32 i = NumSources; i < gPlayList.size(); )
{
SFXHandle &s = gPlayList[i];
if(s->mSourceIndex != -1)
{
sourceActive[s->mSourceIndex] = false;
s->mSourceIndex = -1;
}
if(!s->mProfile->isLooping)
gPlayList.erase_fast(i);
else
i++;
}
// now assign sources to all our sounds that need them:
S32 firstFree = 0;
S32 max = NumSources;
if(max > gPlayList.size())
max = gPlayList.size();
for(S32 i = 0; i < max; i++)
{
SFXHandle &s = gPlayList[i];
if(s->mSourceIndex == -1)
{
while(sourceActive[firstFree])
firstFree++;
s->mSourceIndex = firstFree;
sourceActive[firstFree] = true;
s->playOnSource();
}
else
{
// for other sources, just attenuate the gain.
s->updateGain();
}
}
}
void SFXObject::setListenerParams(Point pos, Point velocity)
{
if(!gSFXValid)
return;
mListenerPosition = pos;
mListenerVelocity = velocity;
alListener3f(AL_POSITION, pos.x, pos.y, -mMaxDistance/2);
}
void SFXObject::shutdown()
{
if(!gSFXValid)
return;
alDeleteBuffers(NumSFXBuffers, gBuffers);
alcMakeContextCurrent(NULL);
alcDestroyContext(gContext);
alcCloseDevice(gDevice);
}
};
#elif defined (ZAP_DEDICATED)
using namespace TNL;
namespace Zap
{
Point SFXObject::mListenerPosition;
Point SFXObject::mListenerVelocity;
F32 SFXObject::mMaxDistance = 500;
SFXObject::SFXObject(U32 profileIndex, ByteBufferPtr ib, F32 gain, Point position, Point velocity)
{
}
RefPtr<SFXObject> SFXObject::play(U32 profileIndex, F32 gain)
{
return new SFXObject(0,NULL,0,Point(0,0), Point(0,0));
}
RefPtr<SFXObject> SFXObject::play(U32 profileIndex, Point position, Point velocity, F32 gain)
{
return new SFXObject(0,NULL,0,Point(0,0), Point(0,0));
}
RefPtr<SFXObject> SFXObject::playRecordedBuffer(ByteBufferPtr p, F32 gain)
{
return new SFXObject(0,NULL,0,Point(0,0), Point(0,0));
}
void SFXObject::queueBuffer(ByteBufferPtr p)
{
}
SFXObject::~SFXObject()
{
}
void SFXObject::updateGain()
{
}
void SFXObject::updateMovementParams()
{
}
void SFXObject::playOnSource()
{
}
void SFXObject::setGain(F32 gain)
{
}
void SFXObject::setMovementParams(Point position, Point velocity)
{
}
void SFXObject::play()
{
}
void SFXObject::stop()
{
}
void SFXObject::init()
{
logprintf("No OpenAL support on this platform.");
}
void SFXObject::process()
{
}
void SFXObject::setListenerParams(Point pos, Point velocity)
{
}
void SFXObject::shutdown()
{
};
};
#endif
#ifdef TNL_OS_WIN32
#include <dsound.h>
#include <stdio.h>
namespace Zap
{
extern bool gIsCrazyBot;
static LPDIRECTSOUNDCAPTURE8 capture = NULL;
static LPDIRECTSOUNDCAPTUREBUFFER captureBuffer = NULL;
static bool recording = false;
static bool captureInit = false;
enum {
BufferBytes = 16000,
};
static U32 lastReadOffset = 0;
bool SFXObject::startRecording()
{
if(recording)
return true;
if(!captureInit)
{
captureInit = true;
DirectSoundCaptureCreate8(NULL, &capture, NULL);
}
if(!capture)
return false;
if(!captureBuffer)
{
HRESULT hr;
DSCBUFFERDESC dscbd;
// wFormatTag, nChannels, nSamplesPerSec, mAvgBytesPerSec,
// nBlockAlign, wBitsPerSample, cbSize
WAVEFORMATEX wfx = { WAVE_FORMAT_PCM, 1, 8000, 16000, 2, 16, 0};
dscbd.dwSize = sizeof(DSCBUFFERDESC);
dscbd.dwFlags = 0;
dscbd.dwBufferBytes = BufferBytes;
dscbd.dwReserved = 0;
dscbd.lpwfxFormat = &wfx;
dscbd.dwFXCount = 0;
dscbd.lpDSCFXDesc = NULL;
if (FAILED(hr = capture->CreateCaptureBuffer(&dscbd, &captureBuffer, NULL)))
{
captureBuffer = NULL;
return false;
}
}
recording = true;
lastReadOffset = 0;
captureBuffer->Start(DSCBSTART_LOOPING);
return true;
}
void SFXObject::captureSamples(ByteBufferPtr buffer)
{
if(!recording)
{
return;
}
else
{
DWORD capturePosition;
DWORD readPosition;
captureBuffer->GetCurrentPosition(&capturePosition, &readPosition);
S32 byteCount = readPosition - lastReadOffset;
if(byteCount < 0)
byteCount += BufferBytes;
void *buf1;
void *buf2;
DWORD count1;
DWORD count2;
//printf("Capturing samples... %d ... %d\n", lastReadOffset, readPosition);
if(!byteCount)
return;
captureBuffer->Lock(lastReadOffset, byteCount, &buf1, &count1, &buf2, &count2, 0);
U32 sizeAdd = count1 + count2;
U32 start = buffer->getBufferSize();
buffer->resize(start + sizeAdd);
memcpy(buffer->getBuffer() + start, buf1, count1);
if(count2)
memcpy(buffer->getBuffer() + start + count1, buf2, count2);
captureBuffer->Unlock(buf1, count1, buf2, count2);
// Write our own random noise in...
if(gIsCrazyBot)
{
S16 *buff = (S16*)buffer->getBuffer() + start/2;
static U32 synthCount = 0;
static U16 synthGoal = 100;
static U16 curSynth = 0;
for(U32 i=0; i<sizeAdd/2; i++)
{
if(synthCount == 0)
{
if(Random::readI(0, 16) < 4)
curSynth = Random::readI(0, 65535);
synthGoal = Random::readI(0, 32000);
synthCount = 25;
}
synthCount--;
curSynth = (U32)((U32)curSynth+(U32)curSynth+(U32)synthGoal)/3;
buff[i] = curSynth;
}
}
lastReadOffset += sizeAdd;
lastReadOffset %= BufferBytes;
}
}
void SFXObject::stopRecording()
{
if(recording)
{
recording = false;
if(!captureBuffer)
return;
captureBuffer->Stop();
if(captureBuffer)
captureBuffer->Release();
captureBuffer = NULL;
}
}
};
#else
namespace Zap
{
bool SFXObject::startRecording()
{
return false;
}
void SFXObject::captureSamples(ByteBufferPtr buffer)
{
}
void SFXObject::stopRecording()
{
}
};
#endif
syntax highlighted by Code2HTML, v. 0.9.1