1
0
mirror of https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git synced 2025-07-18 19:29:09 +00:00

Initial Commit of CyanWorlds.com Engine Open Source Client/Plugin

This commit is contained in:
jwplatt
2011-03-12 12:34:52 -05:00
commit b970ae4bad
3976 changed files with 1301355 additions and 0 deletions

View File

@ -0,0 +1,220 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plAudioCaps - Utility class to query and detect available audio options.//
// //
//////////////////////////////////////////////////////////////////////////////
#include "HeadSpin.h"
#include "al.h"
#include "alc.h"
#include "plEAXEffects.h"
#include "plAudioCaps.h"
#include <eax.h>
#include <eaxlegacy.h>
#include <DShow.h>
#include "../plStatusLog/plStatusLog.h"
#define MAX_NUM_SOURCES 128
#define kLogMe if( fLog != nil ) fLog->AddLineF(
#define MAX_AUDIOCARD_NAME 256
//////////////////////////////////////////////////////////////////////////////
//// Detector Functions //////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
plAudioCaps plAudioCapsDetector::fCaps;
hsBool plAudioCapsDetector::fGotCaps = false;
plStatusLog *plAudioCapsDetector::fLog = nil;
plAudioCapsDetector::plAudioCapsDetector()
{
}
plAudioCapsDetector::~plAudioCapsDetector()
{
}
//// Detect //////////////////////////////////////////////////////////////////
// Our big function that does all of our work
plAudioCaps &plAudioCapsDetector::Detect( hsBool logIt, hsBool init )
{
// If we already have the device capabilities, just return them
if(fGotCaps) return fCaps;
fCaps.fIsAvailable = true;
ALCdevice * device;
ALCcontext * context;
if(init)
{
device = alcOpenDevice(0);
if(!device)
{
fCaps.fIsAvailable = false;
}
context = alcCreateContext(device, 0);
if(alGetError() != AL_NO_ERROR)
{
fCaps.fIsAvailable = false;
}
alcMakeContextCurrent(context);
if(alGetError() != AL_NO_ERROR)
{
fCaps.fIsAvailable = false;
}
}
EnumerateAudioDevices();
if( logIt )
fLog = plStatusLogMgr::GetInstance().CreateStatusLog( 30, "audioCaps.log" );
else
fLog = nil;
kLogMe 0xff00ff00, "Starting audio caps detection..." );
// find the max number of sources
ALuint sources[MAX_NUM_SOURCES];
ALuint i = 0;
for(; i < MAX_NUM_SOURCES; i++)
{
alGenSources(1, &sources[i]);
if(alGetError() != AL_NO_ERROR)
break;
fCaps.fMaxNumSources++;
}
alDeleteSources(i, sources);
kLogMe 0xffffffff, "Max Number of sources: %d", i);
plStatusLog::AddLineS("audio.log", "Max Number of sources: %d", i);
// Detect EAX support
kLogMe 0xff00ff00, "Attempting to detect EAX support..." );
fCaps.fEAXAvailable = IDetectEAX( );
kLogMe 0xff00ff00, "Audio caps detection COMPLETE." );
delete fLog;
fGotCaps = true; // We've got the device capabilities
if(init)
{
alcMakeContextCurrent(nil);
alcDestroyContext(context);
alcCloseDevice(device);
}
return fCaps;
}
void plAudioCapsDetector::EnumerateAudioDevices()
{
ICreateDevEnum *pDevEnum;
IEnumMoniker *pEnumMon;
IMoniker *pMoniker;
ULONG cFetched;
HRESULT hr;
char audioCardName[MAX_AUDIOCARD_NAME];
short *pShort;
// Enumerate audio devices
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDevEnum);
if(SUCCEEDED(hr))
{
hr = pDevEnum->CreateClassEnumerator(CLSID_AudioRendererCategory, &pEnumMon, 0);
if(SUCCEEDED(hr))
{
while(pEnumMon->Next(1, &pMoniker, &cFetched) == S_OK)
{
if(pMoniker)
{
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
if(SUCCEEDED(hr))
{
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
memset(audioCardName, 0, MAX_AUDIOCARD_NAME);
pShort = varName.piVal;
// copy from wide character array to char array
for(int i = 0; *pShort != 0 && i < MAX_AUDIOCARD_NAME; pShort++, i++)
{
audioCardName[i] = (char)*pShort;
}
if(SUCCEEDED(hr))
{
plStatusLog::AddLineS("audiocaps.log", audioCardName );
}
VariantClear(&varName);
pPropBag->Release();
}
pMoniker->Release();
}
}
pEnumMon->Release();
}
pDevEnum->Release();
}
}
//// IDetectEAX //////////////////////////////////////////////////////////////
// Attempt to actually init the EAX listener.Note that we can potentially do
// this even if we didn't load the EAX Unified driver (we could just be
// running EAX 3.0 native), so you can really just think of the EAX Unified
// init code above as a way of trying to make sure this line here will
// succeed as often as possible.
hsBool plAudioCapsDetector::IDetectEAX( )
{
hsBool gotSupport = true;
if(!alIsExtensionPresent((ALchar *)"EAX4.0")) // is eax 4 supported
{
if(!alIsExtensionPresent((ALchar *) "EAX4.0Emulated")) // is an earlier version of eax supported
{
kLogMe 0xff00ff00, "EAX not supported");
gotSupport = false;
}
else
{
fCaps.fEAXUnified = true;
kLogMe 0xff00ff00, "EAX 4 Emulated supported");
}
}
else
{
kLogMe 0xff00ff00, "EAX 4 available");
}
return gotSupport;
}

View File

@ -0,0 +1,84 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plAudioCaps - Utility class to query and detect available audio options.//
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef _plAudioCaps_h
#define _plAudioCaps_h
#include "hsTypes.h"
#include "hsTemplates.h"
class plAudioCapsDetector;
class plStatusLog;
class plAudioCaps
{
public:
plAudioCaps() { Clear(); }
void Clear( void )
{
fIsAvailable = false;
fEAXAvailable = false;
fEAXUnified = false;
fMaxNumSources = 0;
}
hsBool IsAvailable( void ) const { return fIsAvailable; }
hsBool IsEAXAvailable( void ) const { return fEAXAvailable; }
hsBool UsingEAXUnified( void ) const { return fEAXUnified; }
unsigned GetMaxNumVoices() { return fMaxNumSources; }
protected:
friend class plAudioCapsDetector;
hsBool fIsAvailable, fEAXAvailable, fEAXUnified;
unsigned fMaxNumSources;
};
class plAudioCapsDetector
{
public:
plAudioCapsDetector();
virtual ~plAudioCapsDetector();
static plAudioCaps &Detect( hsBool log = false, hsBool init = false );
protected:
static plStatusLog *fLog;
static plAudioCaps fCaps;
static hsBool fGotCaps;
static hsBool IDetectEAX( );
static void EnumerateAudioDevices();
};
#endif //_plAudioCaps_h

View File

@ -0,0 +1,57 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plAudioCreatable_inc
#define plAudioCreatable_inc
#include "../pnFactory/plCreator.h"
#include "plAudioSystem.h"
REGISTER_CREATABLE( plAudioSystem );
#include "plSound.h"
REGISTER_NONCREATABLE( plSound );
REGISTER_CREATABLE( plSoundVolumeApplicator );
#include "plWin32Sound.h"
#include "plWin32StaticSound.h"
#include "plWin32StreamingSound.h"
#include "plWin32GroupedSound.h"
REGISTER_NONCREATABLE( plWin32Sound );
REGISTER_CREATABLE( plWin32StaticSound );
REGISTER_CREATABLE( plWin32LinkSound );
REGISTER_CREATABLE( plWin32StreamingSound );
REGISTER_CREATABLE( plWin32GroupedSound );
#include "plEAXListenerMod.h"
REGISTER_CREATABLE( plEAXListenerMod );
#include "plAudioReaderCreatable.h"
#endif // plAudioCreatable_inc

View File

@ -0,0 +1,25 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/

View File

@ -0,0 +1,264 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plAudioSystem_h
#define plAudioSystem_h
#include "hsStlUtils.h"
#include "hsWindowHndl.h"
#include "hsTemplates.h"
#include "hsGeometry3.h"
#include "../pnKeyedObject/hsKeyedObject.h"
#define DEFAULT_AUDIO_DEVICE_NAME "Generic Software"
typedef wchar_t WCHAR;
class plSound;
class plSoftSoundNode;
class plgAudioSys;
class plStatusLog;
class plEAXListenerMod;
typedef struct ALCdevice_struct ALCdevice;
typedef struct ALCcontext_struct ALCcontext;
class DeviceDescriptor
{
public:
DeviceDescriptor(const char *name, hsBool supportsEAX):
fDeviceName(name),
fSupportsEAX(supportsEAX)
{
}
const char *GetDeviceName() { return fDeviceName.c_str();}
hsBool SupportsEAX() { return fSupportsEAX; }
private:
std::string fDeviceName;
hsBool fSupportsEAX;
};
class plAudioSystem : public hsKeyedObject
{
public:
plAudioSystem();
~plAudioSystem();
CLASSNAME_REGISTER( plAudioSystem );
GETINTERFACE_ANY( plAudioSystem, hsKeyedObject );
enum
{
kThreadSndRef = 0,
kRefEAXRegion
};
hsBool Init(hsWindowHndl hWnd);
void Shutdown();
void SetActive( hsBool b );
void SetListenerPos(const hsPoint3 pos);
void SetListenerVelocity(const hsVector3 vel);
void SetListenerOrientation(const hsVector3 view, const hsVector3 up);
void SetMaxNumberOfActiveSounds(); // sets the max number of active sounds based on the priority cutoff
void SetDistanceModel(int i);
virtual hsBool MsgReceive(plMessage* msg);
double GetTime();
void NextDebugSound( void );
hsPoint3 GetCurrListenerPos( void ) const { return fCurrListenerPos; }
int GetNumAudioDevices();
const char *GetAudioDeviceName(int index);
hsBool SupportsEAX(const char *deviceName);
void SetFadeLength(float lengthSec);
protected:
friend class plgAudioSys;
ALCdevice * fDevice;
ALCcontext * fContext;
ALCdevice * fCaptureDevice;
plSoftSoundNode *fSoftRegionSounds;
plSoftSoundNode *fActiveSofts;
plStatusLog *fDebugActiveSoundDisplay;
static Int32 fMaxNumSounds, fNumSoundsSlop; // max number of sounds the engine is allowed to audibly play. Different than fMaxNumSources. That is the max number of sounds the audio card can play
plSoftSoundNode *fCurrDebugSound;
hsTArray<plKey> fPendingRegisters;
hsPoint3 fCurrListenerPos;//, fCommittedListenerPos;
hsBool fActive, fUsingEAX, fRestartOnDestruct, fWaitingForShutdown;
__int64 fStartTime;
hsTArray<hsKeyedObject *> fMyRefs;
hsTArray<plEAXListenerMod *> fEAXRegions;
hsPoint3 fLastPos;
hsBool fAvatarPosSet; // used for listener stuff
hsBool fDisplayNumBuffers;
std::vector<DeviceDescriptor> fDeviceList; // list of openal device names
double fStartFade;
float fFadeLength;
unsigned int fMaxNumSources; // max openal sources
double fLastUpdateTimeMs;
void RegisterSoftSound( const plKey soundKey );
void UnregisterSoftSound( const plKey soundKey );
void IUpdateSoftSounds( const hsPoint3 &newPosition );
UInt32 IScaleVolume(float volume);
void IEnumerateDevices();
public:
hsBool fListenerInit;
};
class plgAudioSys
{
public:
enum ASChannel
{
kSoundFX,
kAmbience,
kBgndMusic,
kGUI,
kNPCVoice,
kVoice,
kNumChannels
};
enum DebugFlags
{
kDisableRightSelect = 0x00000001,
kDisableLeftSelect = 0x00000002
};
enum AudioMode
{
kDisabled,
kSoftware,
kHardware,
kHardwarePlusEAX,
};
static void Init(hsWindowHndl hWnd);
static hsBool Hardware() { return fUseHardware; }
static void SetUseHardware(hsBool b);
static void SetActive(hsBool b);
static void SetMuted( hsBool b );
static void EnableEAX( hsBool b );
static hsBool Active() { return fInit; }
static void Shutdown();
static void Activate(hsBool b);
static hsBool IsMuted( void ) { return fMuted; }
static hsWindowHndl hWnd() { return fWnd; }
static plAudioSystem* Sys() { return fSys; }
static void Restart( void );
static hsBool UsingEAX( void ) { return fSys->fUsingEAX; }
static void NextDebugSound( void );
static void SetChannelVolume( ASChannel chan, hsScalar vol );
static hsScalar GetChannelVolume( ASChannel chan );
static void Set2D3DBias( hsScalar bias );
static hsScalar Get2D3Dbias();
static void SetGlobalFadeVolume( hsScalar vol );
static hsScalar GetGlobalFadeVolume( void ) { return fGlobalFadeVolume; }
static void SetDebugFlag( UInt32 flag, hsBool set = true ) { if( set ) fDebugFlags |= flag; else fDebugFlags &= ~flag; }
static hsBool IsDebugFlagSet( UInt32 flag ) { return fDebugFlags & flag; }
static void ClearDebugFlags( void ) { fDebugFlags = 0; }
static hsScalar GetStreamingBufferSize( void ) { return fStreamingBufferSize; }
static void SetStreamingBufferSize( hsScalar size ) { fStreamingBufferSize = size; }
static UInt8 GetPriorityCutoff( void ) { return fPriorityCutoff; }
static void SetPriorityCutoff( UInt8 cut ) { fPriorityCutoff = cut; if(fSys) fSys->SetMaxNumberOfActiveSounds(); }
static hsBool AreExtendedLogsEnabled( void ) { return fEnableExtendedLogs; }
static void EnableExtendedLogs( hsBool e ) { fEnableExtendedLogs = e; }
static hsScalar GetStreamFromRAMCutoff( void ) { return fStreamFromRAMCutoff; }
static void SetStreamFromRAMCutoff( hsScalar c ) { fStreamFromRAMCutoff = c; }
static void SetListenerPos(const hsPoint3 pos);
static void SetListenerVelocity(const hsVector3 vel);
static void SetListenerOrientation(const hsVector3 view, const hsVector3 up);
static void ShowNumBuffers(hsBool b) { if(fSys) fSys->fDisplayNumBuffers = b; }
static void SetAudioMode(AudioMode mode);
static int GetAudioMode();
static hsBool LogStreamingUpdates() { return fLogStreamingUpdates; }
static void SetLogStreamingUpdates(hsBool logUpdates) { fLogStreamingUpdates = logUpdates; }
static void SetDeviceName(const char *device, hsBool restart = false);
static const char *GetDeviceName() { return fDeviceName.c_str(); }
static int GetNumAudioDevices();
static const char *GetAudioDeviceName(int index);
static ALCdevice *GetCaptureDevice();
static hsBool SupportsEAX(const char *deviceName);
static void RegisterSoftSound( const plKey soundKey );
static void UnregisterSoftSound( const plKey soundKey );
static hsBool IsRestarting() {return fRestarting;}
private:
friend class plAudioSystem;
static plAudioSystem* fSys;
static hsBool fInit;
static hsBool fActive;
static hsBool fMuted;
static hsWindowHndl fWnd;
static hsBool fUseHardware;
static hsBool fDelayedActivate;
static hsScalar fChannelVolumes[ kNumChannels ];
static hsScalar fGlobalFadeVolume;
static UInt32 fDebugFlags;
static hsBool fEnableEAX;
static hsScalar fStreamingBufferSize;
static UInt8 fPriorityCutoff;
static hsBool fEnableExtendedLogs;
static hsScalar fStreamFromRAMCutoff;
static hsScalar f2D3DBias;
static hsBool fLogStreamingUpdates;
static std::string fDeviceName;
static hsBool fRestarting;
static hsBool fMutedStateChange;
};
#endif //plAudioSystem_h

View File

@ -0,0 +1,783 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plDSoundBuffer - Simple wrapper class for a DirectSound buffer. //
// Allows us to simplify all the work done behind the //
// scenes in plWin32BufferThread. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "hsThread.h"
#include "plDSoundBuffer.h"
#include "al.h"
#include "plgDispatch.h"
#include "plAudioSystem.h"
#include "../plAudioCore/plAudioCore.h"
#include "../plAudioCore/plAudioFileReader.h"
#include "plEAXEffects.h"
#include "plProfile.h"
#include "../plStatusLog/plStatusLog.h"
#include <dsound.h>
UInt32 plDSoundBuffer::fNumBuffers = 0;
plProfile_CreateCounterNoReset( "Playing", "Sound", SoundPlaying );
plProfile_CreateCounterNoReset( "Allocated", "Sound", NumAllocated );
//// Constructor/Destructor //////////////////////////////////////////////////
plDSoundBuffer::plDSoundBuffer( UInt32 size, plWAVHeader &bufferDesc, hsBool enable3D, hsBool isLooping, hsBool tryStatic, bool streaming )
{
fLooping = isLooping;
fValid = false;
fBufferDesc = nil;
fLockPtr = nil;
fLockLength = 0;
fStreaming = streaming;
buffer = 0;
source = 0;
for(int i = 0; i < STREAMING_BUFFERS; ++i)
{
streamingBuffers[i] = 0;
}
IAllocate( size, bufferDesc, enable3D, tryStatic );
fNumBuffers++;
}
plDSoundBuffer::~plDSoundBuffer()
{
IRelease();
fNumBuffers--;
}
//// IAllocate ///////////////////////////////////////////////////////////////
void plDSoundBuffer::IAllocate( UInt32 size, plWAVHeader &bufferDesc, hsBool enable3D, hsBool tryStatic )
{
// Create a DSound buffer description
fBufferDesc = TRACKED_NEW DSBUFFERDESC;
fBufferDesc->dwSize = sizeof( DSBUFFERDESC );
fBufferDesc->dwBufferBytes = size;
fBufferDesc->dwReserved = 0;
fBufferDesc->lpwfxFormat = TRACKED_NEW WAVEFORMATEX;
fBufferDesc->lpwfxFormat->cbSize = 0;
fBufferDesc->lpwfxFormat->nAvgBytesPerSec = bufferDesc.fAvgBytesPerSec;
fBufferDesc->lpwfxFormat->nBlockAlign = bufferDesc.fBlockAlign;
fBufferDesc->lpwfxFormat->nChannels = bufferDesc.fNumChannels;
fBufferDesc->lpwfxFormat->nSamplesPerSec = bufferDesc.fNumSamplesPerSec;
fBufferDesc->lpwfxFormat->wBitsPerSample = bufferDesc.fBitsPerSample;
fBufferDesc->lpwfxFormat->wFormatTag = bufferDesc.fFormatTag;
// Do we want to try EAX?
if( plgAudioSys::UsingEAX() )
fEAXSource.Init( this );
fValid = true;
plProfile_Inc( NumAllocated );
}
//// IRelease ////////////////////////////////////////////////////////////////
void plDSoundBuffer::IRelease( void )
{
if( IsPlaying() )
Stop();
// Release stuff
fEAXSource.Release();
alSourcei(source, AL_BUFFER, nil);
alDeleteSources(1, &source);
if(buffer)
alDeleteBuffers( 1, &buffer );
else
alDeleteBuffers(STREAMING_BUFFERS, streamingBuffers);
source = 0;
buffer = 0;
alGetError();
memset(streamingBuffers, 0, STREAMING_BUFFERS * sizeof(unsigned));
if( fBufferDesc != nil )
{
delete fBufferDesc->lpwfxFormat;
fBufferDesc->lpwfxFormat = nil;
}
delete fBufferDesc;
fBufferDesc = nil;
fValid = false;
plProfile_Dec( NumAllocated );
}
/*****************************************************************************
*
* OpenAL
*
***/
int plDSoundBuffer::IGetALFormat(unsigned bitsPerSample, unsigned int numChannels)
{
int format = 0;
switch(bitsPerSample)
{
case 8:
format = (numChannels == 1) ? AL_FORMAT_MONO8 : AL_FORMAT_STEREO8;
break;
case 16:
format = (numChannels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
break;
}
return format;
}
bool plDSoundBuffer::FillBuffer(void *data, unsigned bytes, plWAVHeader *header)
{
if(source)
{
alSourcei(source, AL_BUFFER, nil);
alDeleteSources(1, &source);
}
if(buffer)
alDeleteBuffers(1, &buffer);
source = 0;
buffer = 0;
ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels);
ALenum error = alGetError();
alGenBuffers(1, &buffer);
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create sound buffer %d", error);
return false;
}
alBufferData(buffer, format, data, bytes, header->fNumSamplesPerSec );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to fill sound buffer %d", error);
return false;
}
alGenSources(1, &source);
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create audio source %d %d", error, source);
return false;
}
// Just make it quiet for now
SetScalarVolume(0);
alSourcef(source, AL_ROLLOFF_FACTOR, 0.3048);
alGetError();
if( error != AL_NO_ERROR )
{
return false;
}
alSourcei(source, AL_BUFFER, buffer);
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to attach buffer to source %d", error);
return false;
}
return true;
}
//============================================================================
// OpenAL Streaming functions
//============================================================================
// this function is used when restarting the audio system. It is needed to restart a streaming source from where it left off
bool plDSoundBuffer::SetupStreamingSource(plAudioFileReader *stream)
{
unsigned char data[STREAM_BUFFER_SIZE];
unsigned int size;
ALenum error;
alGetError();
int numBuffersToQueue = 0;
// fill buffers with data
for( int i = 0; i < STREAMING_BUFFERS; i++ )
{
size = stream->NumBytesLeft() < STREAM_BUFFER_SIZE ? stream->NumBytesLeft() : STREAM_BUFFER_SIZE;
if(!size)
{
if(IsLooping())
{
stream->SetPosition(0);
}
}
stream->Read(size, data);
numBuffersToQueue++;
alGenBuffers( 1, &streamingBuffers[i] );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create sound buffer %d", error);
return false;
}
ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels);
alBufferData( streamingBuffers[i], format, data, size, fBufferDesc->lpwfxFormat->nSamplesPerSec );
if( (error = alGetError()) != AL_NO_ERROR )
plStatusLog::AddLineS("audio.log", "alBufferData");
}
// Generate AL Source
alGenSources( 1, &source );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create audio source %d %d", error, source);
return false;
}
alSourcei(source, AL_BUFFER, nil);
SetScalarVolume(0);
alSourcef(source, AL_ROLLOFF_FACTOR, 0.3048);
alGetError();
if( error != AL_NO_ERROR )
{
return false;
}
alSourceQueueBuffers( source, numBuffersToQueue, streamingBuffers );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to queue buffers %d", error);
return false;
}
return true;
}
// this function is used when starting up a streaming sound, as opposed to restarting it due to an audio system restart.
bool plDSoundBuffer::SetupStreamingSource(void *data, unsigned bytes)
{
unsigned char bufferData[STREAM_BUFFER_SIZE];
unsigned int size;
ALenum error;
char *pData = (char *)data;
alGetError();
int numBuffersToQueue = 0;
// fill buffers with data
for( int i = 0; i < STREAMING_BUFFERS; i++ )
{
size = bytes < STREAM_BUFFER_SIZE ? bytes : STREAM_BUFFER_SIZE;
if(!size)
break;
MemCopy(bufferData, pData, size);
pData += size;
bytes-= size;
numBuffersToQueue++;
alGenBuffers( 1, &streamingBuffers[i] );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create sound buffer %d", error);
return false;
}
ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels);
alBufferData( streamingBuffers[i], format, bufferData, size, fBufferDesc->lpwfxFormat->nSamplesPerSec );
if( (error = alGetError()) != AL_NO_ERROR )
plStatusLog::AddLineS("audio.log", "alBufferData");
}
// Generate AL Source
alGenSources( 1, &source );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create audio source %d %d", error, source);
return false;
}
alSourcei(source, AL_BUFFER, nil);
SetScalarVolume(0);
alSourcef(source, AL_ROLLOFF_FACTOR, 0.3048);
alGetError();
if( error != AL_NO_ERROR )
{
return false;
}
alSourceQueueBuffers( source, numBuffersToQueue, streamingBuffers );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to queue buffers %d", error);
return false;
}
return true;
}
//============================================================================
int plDSoundBuffer::BuffersProcessed()
{
if(alIsSource(source)==AL_FALSE)
{
plStatusLog::AddLineS("audio.log", "BuffersProcessed, source invalid");
return 0;
}
ALint processed = 0;
alGetSourcei( source, AL_BUFFERS_PROCESSED, &processed );
if(alGetError() != AL_NO_ERROR)
{
plStatusLog::AddLineS("audio.log", "alGetSourcei failed");
}
return processed;
}
//============================================================================
int plDSoundBuffer::BuffersQueued()
{
if(alIsSource(source)==AL_FALSE) return 0;
ALint queued = 0;
alGetSourcei( source, AL_BUFFERS_QUEUED, &queued );
alGetError();
return queued;
}
//============================================================================
bool plDSoundBuffer::StreamingFillBuffer(plAudioFileReader *stream)
{
if(!source)
return false;
ALenum error;
ALuint bufferId;
unsigned char data[STREAM_BUFFER_SIZE];
int buffersProcessed = BuffersProcessed();
hsBool finished = false;
for(int i = 0; i < buffersProcessed; i++)
{
alSourceUnqueueBuffers( source, 1, &bufferId );
if( (error = alGetError()) != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to unqueue buffer %d", error);
return false;
}
if(!finished)
{
if(stream->NumBytesLeft() == 0)
{
// if at anytime we run out of data, and we are looping, reset the data stream and continue to fill buffers
if(IsLooping())
{
stream->SetPosition(0); // we are looping, so reset data stream, and keep filling buffers
}
else
{
finished = true; // no more data, but we could still be playing, so we don't want to stop the sound yet
}
}
if(!finished)
{ unsigned int size = stream->NumBytesLeft() < STREAM_BUFFER_SIZE ? stream->NumBytesLeft() : STREAM_BUFFER_SIZE;
stream->Read(size, data);
ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels);
alBufferData( bufferId, format, data, size, fBufferDesc->lpwfxFormat->nSamplesPerSec );
if( (error = alGetError()) != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to copy data to sound buffer %d", error);
return false;
}
alSourceQueueBuffers( source, 1, &bufferId );
if( (error = alGetError()) != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to queue buffer %d", error);
return false;
}
}
}
}
if(!IsPlaying() && !finished)
{
alSourcePlay(source);
}
alGetError();
return true;
}
/*****************************************************************************
*
* Voice playback functions
*
***/
bool plDSoundBuffer::GetAvailableBufferId(unsigned *bufferId)
{
if(mAvailableBuffers.empty())
{
return false;
}
*bufferId = mAvailableBuffers.front();
mAvailableBuffers.pop_front();
return true;
}
bool plDSoundBuffer::SetupVoiceSource()
{
ALenum error;
alGetError();
// Generate AL Buffers
alGenBuffers( STREAMING_BUFFERS, streamingBuffers );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create sound buffer %d", error);
return false;
}
for( int i = 0; i < STREAMING_BUFFERS; i++ )
{
mAvailableBuffers.push_back(streamingBuffers[i]);
}
// Generate AL Source
alGenSources( 1, &source );
error = alGetError();
if( error != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to create audio source %d %d", error, source);
return false;
}
SetScalarVolume(0);
alSourcef(source, AL_ROLLOFF_FACTOR, 0.3048);
alGetError();
if( error != AL_NO_ERROR )
{
return false;
}
alSourcei(source, AL_BUFFER, nil);
alGetError();
//alSourcei(source, AL_PITCH, 0);
// dont queue any buffers here
return true;
}
//============================================================================
void plDSoundBuffer::UnQueueVoiceBuffers()
{
unsigned buffersProcessed = BuffersProcessed();
if(buffersProcessed)
plStatusLog::AddLineS("audio.log", "unqueuing buffers %d", buffersProcessed);
for(int i = 0; i < buffersProcessed; i++)
{
ALuint unQueued;
alSourceUnqueueBuffers( source, 1, &unQueued );
if(alGetError() == AL_NO_ERROR)
{
mAvailableBuffers.push_back(unQueued);
}
else
{
plStatusLog::AddLineS("audio.log", "Failed to unqueue buffer");
}
}
}
//============================================================================
bool plDSoundBuffer::VoiceFillBuffer(void *data, unsigned bytes, unsigned bufferId)
{
if(!source)
return false;
ALenum error;
unsigned int size = bytes < STREAM_BUFFER_SIZE ? bytes : STREAM_BUFFER_SIZE;
ALenum format = IGetALFormat(fBufferDesc->lpwfxFormat->wBitsPerSample, fBufferDesc->lpwfxFormat->nChannels);
alBufferData( bufferId, format, data, size, fBufferDesc->lpwfxFormat->nSamplesPerSec );
if( (error = alGetError()) != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to copy data to sound buffer %d", error);
return false;
}
alSourceQueueBuffers( source, 1, &bufferId );
if( (error = alGetError()) != AL_NO_ERROR )
{
plStatusLog::AddLineS("audio.log", "Failed to queue buffer %d", error);
return false;
}
if(!IsPlaying())
{
alSourcePlay(source);
}
alGetError();
return true;
}
//// SetLooping //////////////////////////////////////////////////////////////
void plDSoundBuffer::SetLooping( hsBool loop )
{
fLooping = loop;
}
void plDSoundBuffer::SetMinDistance( int dist )
{
alSourcei(source, AL_REFERENCE_DISTANCE, dist);
ALenum error;
if((error = alGetError()) != AL_NO_ERROR)
plStatusLog::AddLineS("audio.log", "Failed to set min distance");
}
void plDSoundBuffer::SetMaxDistance( int dist )
{
alSourcei(source, AL_MAX_DISTANCE, dist);
ALenum error;
if((error = alGetError()) != AL_NO_ERROR)
plStatusLog::AddLineS("audio.log", "Failed to set min distance");
}
//// Play ////////////////////////////////////////////////////////////////////
void plDSoundBuffer::Play( void )
{
if(!source)
return;
ALenum error = alGetError(); // clear error
// we dont want openal to loop our streaming buffers, or the buffer will loop back on itself. We will handle looping in the streaming sound
if(fLooping && !fStreaming)
alSourcei(source, AL_LOOPING, AL_TRUE);
else
alSourcei(source, AL_LOOPING, AL_FALSE);
error = alGetError();
alSourcePlay(source);
error = alGetError();
if(error != AL_NO_ERROR)
plStatusLog::AddLineS("voice.log", "Play failed");
plProfile_Inc( SoundPlaying );
}
//// Stop ////////////////////////////////////////////////////////////////////
void plDSoundBuffer::Stop( void )
{
if(!source)
return;
alSourceStop(source);
alGetError();
plProfile_Dec( SoundPlaying );
}
//============================================================================
void plDSoundBuffer::SetPosition(float x, float y, float z)
{
alSource3f(source, AL_POSITION, x, y, -z); // negate z coord, since openal uses opposite handedness
alGetError();
}
//============================================================================
void plDSoundBuffer::SetOrientation(float x, float y, float z)
{
alSource3f(source, AL_ORIENTATION, x, y, -z); // negate z coord, since openal uses opposite handedness
alGetError();
}
//============================================================================
void plDSoundBuffer::SetVelocity(float x, float y, float z)
{
alSource3f(source, AL_VELOCITY, 0, 0, 0); // no doppler shift
alGetError();
}
//============================================================================
void plDSoundBuffer::SetConeAngles(int inner, int outer)
{
alSourcei(source, AL_CONE_INNER_ANGLE, inner);
alSourcei(source, AL_CONE_OUTER_ANGLE, outer);
alGetError();
}
//============================================================================
void plDSoundBuffer::SetConeOrientation(float x, float y, float z)
{
alSource3f(source, AL_DIRECTION, x, y, -z); // negate z coord, since openal uses opposite handedness
alGetError();
}
//============================================================================
// vol range: -5000 - 0
void plDSoundBuffer::SetConeOutsideVolume(int vol)
{
float volume = (float)vol / 5000.0f + 1.0f; // mb to scalar
alSourcef(source, AL_CONE_OUTER_GAIN, volume);
alGetError();
}
//============================================================================
void plDSoundBuffer::Rewind()
{
alSourceRewind(source);
alGetError();
}
//// IsPlaying ///////////////////////////////////////////////////////////////
hsBool plDSoundBuffer::IsPlaying( void )
{
ALint state = AL_STOPPED;
alGetSourcei(source, AL_SOURCE_STATE, &state);
alGetError();
return state == AL_PLAYING;
}
//// IsEAXAccelerated ////////////////////////////////////////////////////////
hsBool plDSoundBuffer::IsEAXAccelerated( void ) const
{
return fEAXSource.IsValid();
}
//// BytePosToMSecs //////////////////////////////////////////////////////////
UInt32 plDSoundBuffer::BytePosToMSecs( UInt32 bytePos ) const
{
return (UInt32)(bytePos * 1000 / (hsScalar)fBufferDesc->lpwfxFormat->nAvgBytesPerSec);
}
//// GetBufferBytePos ////////////////////////////////////////////////////////
UInt32 plDSoundBuffer::GetBufferBytePos( hsScalar timeInSecs ) const
{
hsAssert( fBufferDesc != nil && fBufferDesc->lpwfxFormat != nil, "Nil buffer description when calling GetBufferBytePos()" );
UInt32 byte = (UInt32)( timeInSecs * (hsScalar)fBufferDesc->lpwfxFormat->nSamplesPerSec );
byte *= fBufferDesc->lpwfxFormat->nBlockAlign;
return byte;
}
//// GetLengthInBytes ////////////////////////////////////////////////////////
UInt32 plDSoundBuffer::GetLengthInBytes( void ) const
{
return (UInt32)fBufferDesc->dwBufferBytes;
}
//// SetEAXSettings //////////////////////////////////////////////////////////
void plDSoundBuffer::SetEAXSettings( plEAXSourceSettings *settings, hsBool force )
{
fEAXSource.SetFrom( settings, source, force );
}
//// GetBlockAlign ///////////////////////////////////////////////////////////
UInt8 plDSoundBuffer::GetBlockAlign( void ) const
{
return ( fBufferDesc != nil && fBufferDesc->lpwfxFormat != nil ) ? fBufferDesc->lpwfxFormat->nBlockAlign : 0;
}
//// SetScalarVolume /////////////////////////////////////////////////////////
// Sets the volume, but on a range from 0 to 1
void plDSoundBuffer::SetScalarVolume( hsScalar volume )
{
if(source)
{
ALenum error;
alSourcef(source, AL_GAIN, volume);
if((error = alGetError()) != AL_NO_ERROR)
plStatusLog::AddLineS("audio.log", "failed to set volume on source %d", error);
}
}
unsigned plDSoundBuffer::GetByteOffset()
{
ALint bytes;
alGetSourcei(source, AL_BYTE_OFFSET, &bytes);
ALenum error = alGetError();
return bytes;
}
float plDSoundBuffer::GetTimeOffsetSec()
{
float time;
alGetSourcef(source, AL_SEC_OFFSET, &time);
ALenum error = alGetError();
return time;
}
void plDSoundBuffer::SetTimeOffsetSec(float seconds)
{
alSourcef(source, AL_SEC_OFFSET, seconds);
ALenum error = alGetError();
}
void plDSoundBuffer::SetTimeOffsetBytes(unsigned bytes)
{
alSourcef(source, AL_BYTE_OFFSET, bytes);
ALenum error = alGetError();
}

View File

@ -0,0 +1,152 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plDSoundBuffer - Simple wrapper class for a DirectSound buffer. //
// Allows us to simplify all the work done behind the //
// scenes in plWin32BufferThread. //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef _plDSoundBuffer_h
#define _plDSoundBuffer_h
#include "hsStlUtils.h"
#include "hsTemplates.h"
#include "plEAXEffects.h"
#define STREAMING_BUFFERS 16
#define STREAM_BUFFER_SIZE 4608*4
//#define VOICE_BUFFERS 4
//#define VOICE_BUFFER_SIZE 4608
class plWAVHeader;
class plAudioFileReader;
typedef struct tWAVEFORMATEX WAVEFORMATEX;
typedef struct _DSBUFFERDESC DSBUFFERDESC;
// Ported to OpenAL from DirectSound May 2006. Idealy the openal sources would be seperate from this class.
// OpenAl sound buffer, and source.
class plDSoundBuffer
{
public:
plDSoundBuffer( UInt32 size, plWAVHeader &bufferDesc, hsBool enable3D, hsBool looping, hsBool tryStatic = false, bool streaming = false );
~plDSoundBuffer();
void Play( void );
void Stop( void );
void Rewind() ;
UInt32 GetLengthInBytes( void ) const;
void SetScalarVolume( hsScalar volume ); // Sets the volume, but on a range from 0 to 1
unsigned GetSource() { return source; }
void SetPosition(float x, float y, float z);
void SetOrientation(float x, float y, float z);
void SetVelocity(float x, float y, float z);
void SetConeAngles(int inner, int outer);
void SetConeOrientation(float x, float y, float z);
void SetConeOutsideVolume(int vol);
void SetLooping( hsBool loop );
void SetMinDistance( int dist);
void SetMaxDistance( int dist );
hsBool IsValid( void ) const { return fValid; }
hsBool IsPlaying( void );
hsBool IsLooping( void ) const { return fLooping; }
hsBool IsEAXAccelerated( void ) const;
bool FillBuffer(void *data, unsigned bytes, plWAVHeader *header);
// Streaming support
bool SetupStreamingSource(plAudioFileReader *stream);
bool SetupStreamingSource(void *data, unsigned bytes);
int BuffersProcessed();
bool StreamingFillBuffer(plAudioFileReader *stream);
bool SetupVoiceSource();
bool VoiceFillBuffer(void *data, unsigned bytes, unsigned buferId);
void UnQueueVoiceBuffers();
unsigned GetByteOffset();
UInt32 GetBufferBytePos( hsScalar timeInSecs ) const;
UInt32 BytePosToMSecs( UInt32 bytePos ) const;
void SetEAXSettings( plEAXSourceSettings *settings, hsBool force = false );
void SetTimeOffsetBytes(unsigned bytes);
UInt8 GetBlockAlign( void ) const;
static UInt32 GetNumBuffers() { return fNumBuffers; }
float GetDefaultMinDistance() { return fDefaultMinDistance; }
bool GetAvailableBufferId(unsigned *bufferId);
unsigned GetNumQueuedBuffers(){ return fNumQueuedBuffers;} // returns the max number of buffers queued on a source
float GetTimeOffsetSec();
void SetTimeOffsetSec(float seconds);
int BuffersQueued();
protected:
enum BufferType
{
kStatic,
kStreaming,
kVoice,
};
BufferType fType;
hsBool fValid, fLooping;
UInt32 fLockLength;
void * fLockPtr;
hsTArray<UInt32> fPosNotifys;
bool fStreaming;
DSBUFFERDESC* fBufferDesc;
unsigned buffer; // used if this is not a streaming buffer
unsigned streamingBuffers[STREAMING_BUFFERS]; // used if this is a streaming buffer
std::list<unsigned> mAvailableBuffers; // used for doing our own buffer management. Specifically voice chat, since we dont want old buffers queued
unsigned source;
unsigned int fStreamingBufferSize;
plEAXSource fEAXSource;
static UInt32 fNumBuffers;
static float fDefaultMinDistance;
unsigned fNumQueuedBuffers;
hsScalar fPrevVolume;
void IAllocate( UInt32 size, plWAVHeader &bufferDesc, hsBool enable3D, hsBool tryStatic );
void IRelease( void );
int IGetALFormat(unsigned bitsPerSample, unsigned int numChannels);
};
#endif //_plDSoundBuffer_h

View File

@ -0,0 +1,679 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plEAXEffects - Various classes and wrappers to support EAX //
// acceleration. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "hsThread.h"
#include "plEAXEffects.h"
#include "../plAudioCore/plAudioCore.h"
#include "plDSoundBuffer.h"
#include "hsTemplates.h"
#include "plEAXListenerMod.h"
#include "hsStream.h"
#include "plAudioSystem.h"
#include <al.h>
#include <dmusici.h>
#include <dxerr9.h>
#include <eax.h>
#include <eax-util.h>
#include <eaxlegacy.h>
#include "../plStatusLog/plStatusLog.h"
#define kDebugLog if( myLog != nil ) myLog->AddLineF(
static EAXGet s_EAXGet;
static EAXSet s_EAXSet;
//// GetInstance /////////////////////////////////////////////////////////////
plEAXListener &plEAXListener::GetInstance( void )
{
static plEAXListener instance;
return instance;
}
//// Constructor/Destructor //////////////////////////////////////////////////
plEAXListener::plEAXListener()
{
fInited = false;
ClearProcessCache();
}
plEAXListener::~plEAXListener()
{
Shutdown();
}
//// Init ////////////////////////////////////////////////////////////////////
hsBool plEAXListener::Init( void )
{
if( fInited )
return true;
if(!alIsExtensionPresent((ALchar *)"EAX4.0")) // is eax 4 supported
{
if(!alIsExtensionPresent((ALchar *) "EAX4.0Emulated")) // is an earlier version of eax supported
{
plStatusLog::AddLineS("audio.log", "EAX not supported");
return false;
}
else
{
plStatusLog::AddLineS("audio.log", "EAX 4 Emulated supported");
}
}
else
{
plStatusLog::AddLineS("audio.log", "EAX 4 available");
}
// EAX is supported
s_EAXGet = (EAXGet)alGetProcAddress((ALchar *)"EAXGet");
s_EAXSet = (EAXSet)alGetProcAddress((ALchar *)"EAXSet");
if(!s_EAXGet || ! s_EAXSet)
{
IFail( "EAX initialization failed", true );
return false;
}
fInited = true;
#if 1
try
{
// Make an EAX call here to prevent problems on WDM driver
unsigned int lRoom = -10000;
SetGlobalEAXProperty(DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ROOM, &lRoom, sizeof( unsigned int ));
}
catch ( ... )
{
plStatusLog::AddLineS("audio.log", "Unable to set EAX Property Set, disabling EAX...");
plgAudioSys::EnableEAX(false);
return false;
}
#endif
ClearProcessCache();
return true;
}
//// Shutdown ////////////////////////////////////////////////////////////////
void plEAXListener::Shutdown( void )
{
if( !fInited )
return;
s_EAXSet = nil;
s_EAXGet = nil;
IRelease();
}
bool plEAXListener::SetGlobalEAXProperty(GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize )
{
if(fInited)
{
return s_EAXSet(&guid, ulProperty, 0, pData, ulDataSize) == AL_NO_ERROR;
}
return false;
}
bool plEAXListener::GetGlobalEAXProperty(GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize)
{
if(fInited)
{
return s_EAXGet(&guid, ulProperty, 0, pData, ulDataSize) == AL_NO_ERROR;
}
return false;
}
bool plEAXSource::SetSourceEAXProperty(unsigned source, GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize)
{
return s_EAXSet(&guid, ulProperty, source, pData, ulDataSize) == AL_NO_ERROR;
}
bool plEAXSource::GetSourceEAXProperty(unsigned source, GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize)
{
return s_EAXGet(&guid, ulProperty, source, pData, ulDataSize) == AL_NO_ERROR;
}
//// IRelease ////////////////////////////////////////////////////////////////
void plEAXListener::IRelease( void )
{
fInited = false;
}
//// IFail ///////////////////////////////////////////////////////////////////
void plEAXListener::IFail( hsBool major )
{
plStatusLog::AddLineS( "audio.log", plStatusLog::kRed,
"ERROR in plEAXListener: Could not set global eax params");
if( major )
IRelease();
}
void plEAXListener::IFail( const char *msg, hsBool major )
{
plStatusLog::AddLineS( "audio.log", plStatusLog::kRed,
"ERROR in plEAXListener: %s", msg );
if( major )
IRelease();
}
//// IMuteProperties /////////////////////////////////////////////////////////
// Mutes the given properties, so if you have some props that you want
// half strength, this function will do it for ya.
void plEAXListener::IMuteProperties( EAXLISTENERPROPERTIES *props, hsScalar percent )
{
// We only mute the room, roomHF and roomLF, since those control the overall effect
// application. All three are a direct linear blend as defined by eax-util.cpp, so
// this should be rather easy
hsScalar invPercent = 1.f - percent;
// The old way, as dictated by EAX sample code...
props->lRoom = (int)( ( (float)EAXLISTENER_MINROOM * invPercent ) + ( (float)props->lRoom * percent ) );
// The new way, as suggested by EAX guys...
// props->lRoom = (int)( 2000.f * log( invPercent ) ) + props->lRoom;
// props->lRoomLF = (int)( ( (float)EAXLISTENER_MINROOMLF * invPercent ) + ( (float)props->lRoomLF * percent ) );
// props->lRoomHF = (int)( ( (float)EAXLISTENER_MINROOMHF * invPercent ) + ( (float)props->lRoomHF * percent ) );
}
//// ClearProcessCache ///////////////////////////////////////////////////////
// Clears the cache settings used to speed up ProcessMods(). Call whenever
// the list of mods changed.
void plEAXListener::ClearProcessCache( void )
{
fLastBigRegion = nil;
fLastModCount = -1;
fLastWasEmpty = false;
fLastSingleStrength = -1.f;
}
//// ProcessMods /////////////////////////////////////////////////////////////
// 9.13.02 mcn - Updated the caching method. Now fLastBigRegion will point
// to a region iff it's the only region from the last pass that had a
// strength > 0. The reason we can't do our trick before is because even if
// we have a region w/ strength 1, another region might power up from 1 and
// thus suddenly alter the total reverb settings. Thus, the only time we
// can wisely skip is if our current big region == fLastBigRegion *and*
// the total strength is the same.
void plEAXListener::ProcessMods( hsTArray<plEAXListenerMod *> &modArray )
{
int i;
float totalStrength;
hsBool firstOne;
plEAXListenerMod *thisBigRegion = nil;
EAXLISTENERPROPERTIES finalProps;
static int oldTime = timeGetTime(); // Get starting time
int newTime;
hsBool bMorphing = false;
static plStatusLog *myLog = nil;
if( myLog == nil && plgAudioSys::AreExtendedLogsEnabled() )
myLog = plStatusLogMgr::GetInstance().CreateStatusLog( 30, "EAX Reverbs", plStatusLog::kFilledBackground | plStatusLog::kDeleteForMe | plStatusLog::kDontWriteFile );
else if( myLog != nil && !plgAudioSys::AreExtendedLogsEnabled() )
{
delete myLog;
myLog = nil;
}
if( myLog != nil )
myLog->Clear();
if( modArray.GetCount() != fLastModCount )
{
kDebugLog "Clearing cache..." );
ClearProcessCache(); // Code path changed, clear the entire cache
fLastModCount = modArray.GetCount();
}
else
{
kDebugLog "" );
}
if( modArray.GetCount() > 0 )
{
kDebugLog "%d regions to calc", modArray.GetCount() );
// Reset and find a new one if applicable
thisBigRegion = nil;
// Accumulate settings from all the active listener regions (shouldn't be too many, we hope)
totalStrength = 0.f;
firstOne = true;
for( i = 0; i < modArray.GetCount(); i++ )
{
float strength = modArray[ i ]->GetStrength();
kDebugLog "%4.2f - %s", strength, modArray[ i ]->GetKey()->GetUoid().GetObjectName() );
if( strength > 0.f )
{
// fLastBigRegion will point to a region iff it's the only region w/ strength > 0
if( totalStrength == 0.f )
thisBigRegion = modArray[ i ];
else
thisBigRegion = nil;
if( firstOne )
{
// First one, just init to it
memcpy( &finalProps, modArray[ i ]->GetListenerProps(), sizeof( finalProps ) );
totalStrength = strength;
firstOne = false;
}
else
{
hsScalar scale = strength / ( totalStrength + strength );
EAX3ListenerInterpolate( &finalProps, modArray[ i ]->GetListenerProps(), scale, &finalProps, false );
totalStrength += strength;
bMorphing = true;
}
if( totalStrength >= 1.f )
break;
}
}
if( firstOne )
{
// No regions of strength > 0, so just make it quiet
kDebugLog "Reverb should be quiet" );
if( fLastWasEmpty )
return;
memcpy( &finalProps, &EAX30_ORIGINAL_PRESETS[ ORIGINAL_GENERIC ], sizeof( EAXLISTENERPROPERTIES ) );
finalProps.lRoom = EAXLISTENER_MINROOM;
// finalProps.lRoomLF = EAXLISTENER_MINROOMLF;
// finalProps.lRoomHF = EAXLISTENER_MINROOMHF;
fLastWasEmpty = true;
fLastBigRegion = nil;
fLastSingleStrength = -1.f;
}
else
{
fLastWasEmpty = false;
if( thisBigRegion == fLastBigRegion && totalStrength == fLastSingleStrength )
// Cached values should be the same, so we can bail at this point
return;
fLastBigRegion = thisBigRegion;
fLastSingleStrength = ( thisBigRegion != nil ) ? totalStrength : -1.f;
if( totalStrength < 1.f )
{
kDebugLog "Total strength < 1; muting result" );
// All of them together is less than full strength, so mute our result
IMuteProperties( &finalProps, totalStrength );
}
}
}
else
{
kDebugLog "No regions at all; disabling reverb" );
// No regions whatsoever, so disable listener props entirely
if( fLastWasEmpty )
return;
memcpy( &finalProps, &EAX30_ORIGINAL_PRESETS[ ORIGINAL_GENERIC ], sizeof( EAXLISTENERPROPERTIES ) );
finalProps.lRoom = EAXLISTENER_MINROOM;
// finalProps.lRoomLF = EAXLISTENER_MINROOMLF;
// finalProps.lRoomHF = EAXLISTENER_MINROOMHF;
fLastWasEmpty = true;
}
// if were morphing between regions, do 10th of a second check, otherwise just let it
// change due to opt out(caching) feature.
if(bMorphing)
{
newTime = timeGetTime();
// Update, at most, ten times per second
if((newTime - oldTime) < 100) return;
oldTime = newTime; // update time
}
//finalProps.flAirAbsorptionHF *= 0.3048f; // Convert to feet
//kDebugLog "** Updating property set **" );
if(!SetGlobalEAXProperty(DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ALLPARAMETERS, &finalProps, sizeof( finalProps )))
{
IFail( false );
}
}
//////////////////////////////////////////////////////////////////////////////
//// Source Settings /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// Constructor/Destructor //////////////////////////////////////////////////
plEAXSourceSettings::plEAXSourceSettings()
{
fDirtyParams = kAll;
Enable( false );
}
plEAXSourceSettings::~plEAXSourceSettings()
{
}
//// Read/Write/Set //////////////////////////////////////////////////////////
void plEAXSourceSettings::Read( hsStream *s )
{
fEnabled = s->ReadBool();
if( fEnabled )
{
fRoom = s->ReadSwap16();
fRoomHF = s->ReadSwap16();
fRoomAuto = s->ReadBool();
fRoomHFAuto = s->ReadBool();
fOutsideVolHF = s->ReadSwap16();
fAirAbsorptionFactor = s->ReadSwapFloat();
fRoomRolloffFactor = s->ReadSwapFloat();
fDopplerFactor = s->ReadSwapFloat();
fRolloffFactor = s->ReadSwapFloat();
fSoftStarts.Read( s );
fSoftEnds.Read( s );
fOcclusionSoftValue = -1.f;
SetOcclusionSoftValue( s->ReadSwapFloat() );
fDirtyParams = kAll;
}
else
Enable( false ); // Force init of params
}
void plEAXSourceSettings::Write( hsStream *s )
{
s->WriteBool( fEnabled );
if( fEnabled )
{
s->WriteSwap16( fRoom );
s->WriteSwap16( fRoomHF );
s->WriteBool( fRoomAuto );
s->WriteBool( fRoomHFAuto );
s->WriteSwap16( fOutsideVolHF );
s->WriteSwapFloat( fAirAbsorptionFactor );
s->WriteSwapFloat( fRoomRolloffFactor );
s->WriteSwapFloat( fDopplerFactor );
s->WriteSwapFloat( fRolloffFactor );
fSoftStarts.Write( s );
fSoftEnds.Write( s );
s->WriteSwapFloat( fOcclusionSoftValue );
}
}
void plEAXSourceSettings::SetRoomParams( Int16 room, Int16 roomHF, hsBool roomAuto, hsBool roomHFAuto )
{
fRoom = room;
fRoomHF = roomHF;
fRoomAuto = roomAuto;
fRoomHFAuto = roomHFAuto;
fDirtyParams |= kRoom;
}
void plEAXSourceSettings::Enable( hsBool e )
{
fEnabled = e;
if( !e )
{
fRoom = EAXBUFFER_MINROOM;
fRoomHF = EAXBUFFER_MINROOMHF;
fRoomAuto = true;
fRoomHFAuto = true;
fOutsideVolHF = 0;
fAirAbsorptionFactor = 1.f;
fRoomRolloffFactor = 0.f;
fDopplerFactor = 0.f;
fRolloffFactor = 0.f;
fOcclusionSoftValue = 0.f;
fSoftStarts.Reset();
fSoftEnds.Reset();
fCurrSoftValues.Reset();
fDirtyParams = kAll;
}
}
void plEAXSourceSettings::SetOutsideVolHF( Int16 vol )
{
fOutsideVolHF = vol;
fDirtyParams |= kOutsideVolHF;
}
void plEAXSourceSettings::SetFactors( hsScalar airAbsorption, hsScalar roomRolloff, hsScalar doppler, hsScalar rolloff )
{
fAirAbsorptionFactor = airAbsorption;
fRoomRolloffFactor = roomRolloff;
fDopplerFactor = doppler;
fRolloffFactor = rolloff;
fDirtyParams |= kFactors;
}
void plEAXSourceSettings::SetOcclusionSoftValue( hsScalar value )
{
if( fOcclusionSoftValue != value )
{
fOcclusionSoftValue = value;
IRecalcSofts( kOcclusion );
fDirtyParams |= kOcclusion;
}
}
void plEAXSourceSettings::IRecalcSofts( UInt8 whichOnes )
{
hsScalar percent, invPercent;
if( whichOnes & kOcclusion )
{
percent = fOcclusionSoftValue;
invPercent = 1.f - percent;
Int16 occ = (Int16)( ( (float)fSoftStarts.GetOcclusion() * invPercent ) + ( (float)fSoftEnds.GetOcclusion() * percent ) );
hsScalar lfRatio = (hsScalar)( ( fSoftStarts.GetOcclusionLFRatio() * invPercent ) + ( fSoftEnds.GetOcclusionLFRatio() * percent ) );
hsScalar roomRatio = (hsScalar)( ( fSoftStarts.GetOcclusionRoomRatio() * invPercent ) + ( fSoftEnds.GetOcclusionRoomRatio() * percent ) );
hsScalar directRatio = (hsScalar)( ( fSoftStarts.GetOcclusionDirectRatio() * invPercent ) + ( fSoftEnds.GetOcclusionDirectRatio() * percent ) );
fCurrSoftValues.SetOcclusion( occ, lfRatio, roomRatio, directRatio );
}
}
//////////////////////////////////////////////////////////////////////////////
//// Source Soft Settings ////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void plEAXSourceSoftSettings::Reset( void )
{
fOcclusion = 0;
fOcclusionLFRatio = 0.25f;
fOcclusionRoomRatio = 1.5f;
fOcclusionDirectRatio = 1.f;
}
void plEAXSourceSoftSettings::Read( hsStream *s )
{
s->ReadSwap( &fOcclusion );
s->ReadSwap( &fOcclusionLFRatio );
s->ReadSwap( &fOcclusionRoomRatio );
s->ReadSwap( &fOcclusionDirectRatio );
}
void plEAXSourceSoftSettings::Write( hsStream *s )
{
s->WriteSwap( fOcclusion );
s->WriteSwap( fOcclusionLFRatio );
s->WriteSwap( fOcclusionRoomRatio );
s->WriteSwap( fOcclusionDirectRatio );
}
void plEAXSourceSoftSettings::SetOcclusion( Int16 occ, hsScalar lfRatio, hsScalar roomRatio, hsScalar directRatio )
{
fOcclusion = occ;
fOcclusionLFRatio = lfRatio;
fOcclusionRoomRatio = roomRatio;
fOcclusionDirectRatio = directRatio;
}
//// Constructor/Destructor //////////////////////////////////////////////////
plEAXSource::plEAXSource()
{
fInit = false;
}
plEAXSource::~plEAXSource()
{
Release();
}
//// Init/Release ////////////////////////////////////////////////////////////
void plEAXSource::Init( plDSoundBuffer *parent )
{
fInit = true;
// Init some default params
plEAXSourceSettings defaultParams;
SetFrom( &defaultParams, parent->GetSource() );
}
void plEAXSource::Release( void )
{
fInit = false;
}
hsBool plEAXSource::IsValid( void ) const
{
return true;
}
//// SetFrom /////////////////////////////////////////////////////////////////
void plEAXSource::SetFrom( plEAXSourceSettings *settings, unsigned source, hsBool force )
{
UInt32 dirtyParams;
if(source == 0 || !fInit)
return;
if( force )
dirtyParams = plEAXSourceSettings::kAll;
else
dirtyParams = settings->fDirtyParams;
// Do the params
if( dirtyParams & plEAXSourceSettings::kRoom )
{
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_ROOM, &settings->fRoom, sizeof(settings->fRoom));
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_ROOMHF, &settings->fRoomHF, sizeof(settings->fRoomHF));
}
if( dirtyParams & plEAXSourceSettings::kOutsideVolHF )
{
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OUTSIDEVOLUMEHF, &settings->fOutsideVolHF, sizeof(settings->fOutsideVolHF));
}
if( dirtyParams & plEAXSourceSettings::kFactors )
{
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_DOPPLERFACTOR, &settings->fDopplerFactor, sizeof(settings->fDopplerFactor));
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_ROLLOFFFACTOR, &settings->fRolloffFactor, sizeof(settings->fRolloffFactor));
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_ROOMROLLOFFFACTOR, &settings->fRoomRolloffFactor, sizeof(settings->fRoomRolloffFactor));
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_AIRABSORPTIONFACTOR, &settings->fAirAbsorptionFactor, sizeof(settings->fAirAbsorptionFactor));
}
if( dirtyParams & plEAXSourceSettings::kOcclusion )
{
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSION, &settings->GetCurrSofts().fOcclusion, sizeof(settings->GetCurrSofts().fOcclusion));
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, &settings->GetCurrSofts().fOcclusionLFRatio, sizeof(settings->GetCurrSofts().fOcclusionLFRatio));
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, &settings->GetCurrSofts().fOcclusionRoomRatio, sizeof(settings->GetCurrSofts().fOcclusionRoomRatio));
SetSourceEAXProperty(source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONDIRECTRATIO, &settings->GetCurrSofts().fOcclusionDirectRatio, sizeof(settings->GetCurrSofts().fOcclusionDirectRatio));
}
settings->ClearDirtyParams();
// Do all the flags in one pass
DWORD flags;
if( GetSourceEAXProperty( source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_FLAGS, &flags, sizeof( DWORD )) )
{
if( settings->GetRoomAuto() )
flags |= EAXBUFFERFLAGS_ROOMAUTO;
else
flags &= ~EAXBUFFERFLAGS_ROOMAUTO;
if( settings->GetRoomHFAuto() )
flags |= EAXBUFFERFLAGS_ROOMHFAUTO;
else
flags &= ~EAXBUFFERFLAGS_ROOMHFAUTO;
if( SetSourceEAXProperty( source, DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_FLAGS, &flags, sizeof( DWORD ) ) )
{
return; // All worked, return here
}
// Flag setting failed somehow
hsAssert( false, "Unable to set EAX buffer flags" );
}
}

View File

@ -0,0 +1,192 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plEAXEffects - Various classes and wrappers to support EAX //
// acceleration. //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef _plEAXEffects_h
#define _plEAXEffects_h
#include "hsTypes.h"
#include "hsTemplates.h"
//// Listener Settings Class Definition ///////////////////////////////////////
class plDSoundBuffer;
class plEAXListenerMod;
typedef struct _EAXREVERBPROPERTIES EAXREVERBPROPERTIES;
class plEAXListener
{
public:
~plEAXListener();
static plEAXListener &GetInstance( void );
hsBool Init( void );
void Shutdown( void );
bool SetGlobalEAXProperty(GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize );
bool GetGlobalEAXProperty(GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize );
void ProcessMods( hsTArray<plEAXListenerMod *> &modArray );
void ClearProcessCache( void );
protected:
plEAXListener();
void IFail( hsBool major );
void IFail( const char *msg, hsBool major );
void IRelease( void );
void IMuteProperties( EAXREVERBPROPERTIES *props, hsScalar percent );
hsBool fInited;
// Cache info
Int32 fLastModCount;
hsBool fLastWasEmpty;
hsScalar fLastSingleStrength;
plEAXListenerMod *fLastBigRegion;
};
//// Soft Buffer Settings Class Definition ////////////////////////////////////
// Used to hold buffer settings that will be attenuated by a soft volume,
// to make the main settings class a bit cleaner
class hsStream;
class plEAXSourceSoftSettings
{
public:
Int16 fOcclusion;
hsScalar fOcclusionLFRatio, fOcclusionRoomRatio, fOcclusionDirectRatio;
void Read( hsStream *s );
void Write( hsStream *s );
void SetOcclusion( Int16 occ, hsScalar lfRatio, hsScalar roomRatio, hsScalar directRatio );
Int16 GetOcclusion( void ) const { return fOcclusion; }
hsScalar GetOcclusionLFRatio( void ) const { return fOcclusionLFRatio; }
hsScalar GetOcclusionRoomRatio( void ) const { return fOcclusionRoomRatio; }
hsScalar GetOcclusionDirectRatio( void ) const { return fOcclusionDirectRatio; }
void Reset( void );
};
//// Buffer Settings Class Definition /////////////////////////////////////////
class plEAXSource;
class plEAXSourceSettings
{
public:
plEAXSourceSettings();
virtual ~plEAXSourceSettings();
void Read( hsStream *s );
void Write( hsStream *s );
void Enable( hsBool e );
hsBool IsEnabled( void ) const { return fEnabled; }
void SetRoomParams( Int16 room, Int16 roomHF, hsBool roomAuto, hsBool roomHFAuto );
Int16 GetRoom( void ) const { return fRoom; }
Int16 GetRoomHF( void ) const { return fRoomHF; }
hsBool GetRoomAuto( void ) const { return fRoomAuto; }
hsBool GetRoomHFAuto( void ) const { return fRoomHFAuto; }
void SetOutsideVolHF( Int16 vol );
Int16 GetOutsideVolHF( void ) const { return fOutsideVolHF; }
void SetFactors( hsScalar airAbsorption, hsScalar roomRolloff, hsScalar doppler, hsScalar rolloff );
hsScalar GetAirAbsorptionFactor( void ) const { return fAirAbsorptionFactor; }
hsScalar GetRoomRolloffFactor( void ) const { return fRoomRolloffFactor; }
hsScalar GetDopplerFactor( void ) const { return fDopplerFactor; }
hsScalar GetRolloffFactor( void ) const { return fRolloffFactor; }
plEAXSourceSoftSettings &GetSoftStarts( void ) { return fSoftStarts; }
plEAXSourceSoftSettings &GetSoftEnds( void ) { return fSoftEnds; }
plEAXSourceSoftSettings &GetCurrSofts( void ) { return fCurrSoftValues; }
void SetOcclusionSoftValue( hsScalar value );
hsScalar GetOcclusionSoftValue( void ) const { return fOcclusionSoftValue; }
void ClearDirtyParams( void ) const { fDirtyParams = 0; }
protected:
friend class plEAXSource;
friend plEAXSourceSoftSettings;
hsBool fEnabled;
Int16 fRoom, fRoomHF;
hsBool fRoomAuto, fRoomHFAuto;
Int16 fOutsideVolHF;
hsScalar fAirAbsorptionFactor, fRoomRolloffFactor, fDopplerFactor, fRolloffFactor;
plEAXSourceSoftSettings fSoftStarts, fSoftEnds, fCurrSoftValues;
hsScalar fOcclusionSoftValue;
mutable UInt32 fDirtyParams;
enum ParamSets
{
kOcclusion = 0x01,
kRoom = 0x02,
kOutsideVolHF = 0x04,
kFactors = 0x08,
kAll = 0xff
};
void IRecalcSofts( UInt8 whichOnes );
};
//// Source Class Definition //////////////////////////////////////////////////
class plEAXSource
{
public:
friend plEAXSourceSettings;
friend plEAXSourceSoftSettings;
plEAXSource();
virtual ~plEAXSource();
void Init( plDSoundBuffer *parent );
void Release( void );
hsBool IsValid( void ) const;
bool SetSourceEAXProperty(unsigned source, GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize);
bool GetSourceEAXProperty(unsigned source, GUID guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize);
void SetFrom( plEAXSourceSettings *settings, unsigned source, hsBool force = false );
private:
hsBool fInit;
};
#endif //_plEAXEffects_h

View File

@ -0,0 +1,228 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plEAXListenerMod //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plEAXListenerMod.h"
#include "../plIntersect/plSoftVolume.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "plAudioSystem.h"
#include "../pnMessage/plAudioSysMsg.h"
#include <eax-util.h>
plEAXListenerMod::plEAXListenerMod()
{
fListenerProps = TRACKED_NEW EAXREVERBPROPERTIES;
fSoftRegion = nil;
fRegistered = false;
fGetsMessages = false;
memcpy( fListenerProps, &REVERB_ORIGINAL_PRESETS[ ORIGINAL_GENERIC ], sizeof( EAXREVERBPROPERTIES ) );
}
plEAXListenerMod::~plEAXListenerMod()
{
// Tell the audio sys we're going away
IUnRegister();
// Unregister for audioSys messages
if( fGetsMessages )
{
plgDispatch::Dispatch()->UnRegisterForExactType( plAudioSysMsg::Index(), GetKey() );
fGetsMessages = false;
}
delete fListenerProps;
}
void plEAXListenerMod::IRegister( void )
{
if( !fGetsMessages )
{
plgDispatch::Dispatch()->RegisterForExactType( plAudioSysMsg::Index(), GetKey() );
fGetsMessages = true;
}
if( fRegistered || GetKey() == nil )
return;
plKey sysKey = hsgResMgr::ResMgr()->FindKey( plUoid( kAudioSystem_KEY ) );
if( sysKey != nil )
{
plGenRefMsg *refMsg = TRACKED_NEW plGenRefMsg( sysKey, plRefMsg::kOnCreate, 0, plAudioSystem::kRefEAXRegion );
hsgResMgr::ResMgr()->AddViaNotify( GetKey(), refMsg, plRefFlags::kPassiveRef );
fRegistered = true;
}
}
void plEAXListenerMod::IUnRegister( void )
{
if( !fRegistered || GetKey() == nil )
return;
plKey sysKey = hsgResMgr::ResMgr()->FindKey( plUoid( kAudioSystem_KEY ) );
if( sysKey != nil && GetKey() != nil )
sysKey->Release( GetKey() );
fRegistered = false;
}
hsBool plEAXListenerMod::IEval( double secs, hsScalar del, UInt32 dirty )
{
IRegister();
return false;
}
hsBool plEAXListenerMod::MsgReceive( plMessage* pMsg )
{
plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef( pMsg );
if( refMsg != nil )
{
switch( refMsg->fType )
{
case kRefSoftRegion:
if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) )
{
fSoftRegion = plSoftVolume::ConvertNoRef( refMsg->GetRef() );
fSoftRegion->SetCheckListener();
}
else if( refMsg->GetContext() & ( plRefMsg::kOnRemove | plRefMsg::kOnDestroy ) )
{
fSoftRegion = nil;
}
break;
}
}
plAudioSysMsg *sysMsg = plAudioSysMsg::ConvertNoRef( pMsg );
if( sysMsg != nil )
{
if( sysMsg->GetAudFlag() == plAudioSysMsg::kActivate )
{
IRegister();
}
else if( sysMsg->GetAudFlag() == plAudioSysMsg::kDeActivate )
{
IUnRegister();
}
return true;
}
return plSingleModifier::MsgReceive( pMsg );
}
void plEAXListenerMod::Read( hsStream* s, hsResMgr* mgr )
{
plSingleModifier::Read( s, mgr );
// Read in the soft region
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, 0, kRefSoftRegion ), plRefFlags::kActiveRef );
// Read the listener params
fListenerProps->ulEnvironment = s->ReadSwap32();
fListenerProps->flEnvironmentSize = s->ReadSwapFloat();
fListenerProps->flEnvironmentDiffusion = s->ReadSwapFloat();
fListenerProps->lRoom = s->ReadSwap32();
fListenerProps->lRoomHF = s->ReadSwap32();
fListenerProps->lRoomLF = s->ReadSwap32();
fListenerProps->flDecayTime = s->ReadSwapFloat();
fListenerProps->flDecayHFRatio = s->ReadSwapFloat();
fListenerProps->flDecayLFRatio = s->ReadSwapFloat();
fListenerProps->lReflections = s->ReadSwap32();
fListenerProps->flReflectionsDelay = s->ReadSwapFloat();
//fListenerProps->vReflectionsPan; // early reflections panning vector
fListenerProps->lReverb = s->ReadSwap32(); // late reverberation level relative to room effect
fListenerProps->flReverbDelay = s->ReadSwapFloat();
//fListenerProps->vReverbPan; // late reverberation panning vector
fListenerProps->flEchoTime = s->ReadSwapFloat();
fListenerProps->flEchoDepth = s->ReadSwapFloat();
fListenerProps->flModulationTime = s->ReadSwapFloat();
fListenerProps->flModulationDepth = s->ReadSwapFloat();
fListenerProps->flAirAbsorptionHF = s->ReadSwapFloat();
fListenerProps->flHFReference = s->ReadSwapFloat();
fListenerProps->flLFReference = s->ReadSwapFloat();
fListenerProps->flRoomRolloffFactor = s->ReadSwapFloat();
fListenerProps->ulFlags = s->ReadSwap32();
// Done reading, time to tell the audio sys we exist
IRegister();
}
void plEAXListenerMod::Write( hsStream* s, hsResMgr* mgr )
{
plSingleModifier::Write( s, mgr );
// Write the soft region key
mgr->WriteKey( s, fSoftRegion );
// Write the listener params
s->WriteSwap32( fListenerProps->ulEnvironment );
s->WriteSwapFloat( fListenerProps->flEnvironmentSize );
s->WriteSwapFloat( fListenerProps->flEnvironmentDiffusion );
s->WriteSwap32( fListenerProps->lRoom );
s->WriteSwap32( fListenerProps->lRoomHF );
s->WriteSwap32( fListenerProps->lRoomLF );
s->WriteSwapFloat( fListenerProps->flDecayTime );
s->WriteSwapFloat( fListenerProps->flDecayHFRatio );
s->WriteSwapFloat( fListenerProps->flDecayLFRatio );
s->WriteSwap32( fListenerProps->lReflections );
s->WriteSwapFloat( fListenerProps->flReflectionsDelay );
//s->WriteSwapFloat( fListenerProps->vReflectionsPan; // early reflections panning vector
s->WriteSwap32( fListenerProps->lReverb ); // late reverberation level relative to room effect
s->WriteSwapFloat( fListenerProps->flReverbDelay );
//s->WriteSwapFloat( fListenerProps->vReverbPan; // late reverberation panning vector
s->WriteSwapFloat( fListenerProps->flEchoTime );
s->WriteSwapFloat( fListenerProps->flEchoDepth );
s->WriteSwapFloat( fListenerProps->flModulationTime );
s->WriteSwapFloat( fListenerProps->flModulationDepth );
s->WriteSwapFloat( fListenerProps->flAirAbsorptionHF );
s->WriteSwapFloat( fListenerProps->flHFReference );
s->WriteSwapFloat( fListenerProps->flLFReference );
s->WriteSwapFloat( fListenerProps->flRoomRolloffFactor );
s->WriteSwap32( fListenerProps->ulFlags );
}
void plEAXListenerMod::SetFromPreset( UInt32 preset )
{
memcpy( fListenerProps, &REVERB_ORIGINAL_PRESETS[ preset ], sizeof( EAXREVERBPROPERTIES ) );
}
float plEAXListenerMod::GetStrength( void )
{
if( fSoftRegion == nil )
return 0.f;
return fSoftRegion->GetListenerStrength();
}

View File

@ -0,0 +1,75 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plEAXListenerMod Header //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef _plEAXListenerMod_h
#define _plEAXListenerMod_h
#include "../pnModifier/plSingleModifier.h"
class plMessage;
class plSoftVolume;
typedef struct _EAXREVERBPROPERTIES EAXREVERBPROPERTIES;
class plEAXListenerMod : public plSingleModifier
{
public:
plEAXListenerMod();
virtual ~plEAXListenerMod();
CLASSNAME_REGISTER( plEAXListenerMod );
GETINTERFACE_ANY( plEAXListenerMod, plSingleModifier );
enum Refs
{
kRefSoftRegion = 0,
};
virtual hsBool MsgReceive( plMessage* pMsg );
virtual void Read( hsStream* s, hsResMgr* mgr );
virtual void Write( hsStream* s, hsResMgr* mgr );
float GetStrength( void );
EAXREVERBPROPERTIES * GetListenerProps( void ) { return fListenerProps; }
void SetFromPreset( UInt32 preset );
protected:
plSoftVolume *fSoftRegion;
EAXREVERBPROPERTIES *fListenerProps;
hsBool fRegistered, fGetsMessages;
void IRegister( void );
void IUnRegister( void );
virtual hsBool IEval( double secs, hsScalar del, UInt32 dirty ); // called only by owner object's Eval()
};
#endif // _plEAXListenerMod_h

View File

@ -0,0 +1,342 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plOGGCodec - Plasma codec support for the OGG/Vorbis file format. //
// //
//// Notes ///////////////////////////////////////////////////////////////////
// //
// 2.7.2003 - Created by mcn. If only life were really this simple... //
// //
//////////////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include "hsTypes.h"
#include "plOGGCodec.h"
#include "hsTimer.h"
#include "../pnNetCommon/plNetApp.h"
plOGGCodec::DecodeFormat plOGGCodec::fDecodeFormat = plOGGCodec::k16bitSigned;
UInt8 plOGGCodec::fDecodeFlags = 0;
//// Constructor/Destructor //////////////////////////////////////////////////
plOGGCodec::plOGGCodec( const char *path, plAudioCore::ChannelSelect whichChan ) : fFileHandle( nil )
{
fOggFile = nil;
IOpen( path, whichChan );
fCurHeaderPos = 0;
fHeadBuf = nil;
}
void plOGGCodec::BuildActualWaveHeader()
{
// Build an actual WAVE header for this ogg
int fmtSize = 16;
short fmt = 1;
int factsize = 4;
int factdata = 0;
int size = fDataSize+48; // size of data with header except for first four bytes
fHeadBuf = (UInt8 *) ALLOC(56);
memcpy(fHeadBuf, "RIFF", 4);
memcpy(fHeadBuf+4, &size, 4);
memcpy(fHeadBuf+8, "WAVE", 4);
memcpy(fHeadBuf+12, "fmt ", 4);
memcpy(fHeadBuf+16, &fmtSize, 4);
memcpy(fHeadBuf+20, &fmt, 2); /* format */
memcpy(fHeadBuf+22, &fHeader.fNumChannels, 2);
memcpy(fHeadBuf+24, &fHeader.fNumSamplesPerSec, 4);
memcpy(fHeadBuf+28, &fHeader.fAvgBytesPerSec, 4);
memcpy(fHeadBuf+32, &fHeader.fBlockAlign, 4);
memcpy(fHeadBuf+34, &fHeader.fBitsPerSample, 2);
memcpy(fHeadBuf+36, "fact", 4);
memcpy(fHeadBuf+40, &factsize, 4);
memcpy(fHeadBuf+44, &factdata, 4);
memcpy(fHeadBuf+48, "data", 4);
memcpy(fHeadBuf+52, &fDataSize, 4);
}
bool plOGGCodec::ReadFromHeader(int numBytes, void *data)
{
if(fCurHeaderPos < 56)
{
memcpy(data, fHeadBuf+fCurHeaderPos, numBytes);
fCurHeaderPos += numBytes;
return true;
}
return false;
}
void plOGGCodec::IOpen( const char *path, plAudioCore::ChannelSelect whichChan )
{
hsAssert( path != nil, "Invalid path specified in plOGGCodec reader" );
// plNetClientApp::StaticDebugMsg("Ogg Open %s, t=%f, start", path, hsTimer::GetSeconds());
strncpy( fFilename, path, sizeof( fFilename ) );
fWhichChannel = whichChan;
/// Open the file as a plain binary stream
fFileHandle = fopen( path, "rb" );
if( fFileHandle != nil )
{
/// Create the OGG data struct
fOggFile = TRACKED_NEW OggVorbis_File;
/// Open the OGG decompressor
if( ov_open( fFileHandle, fOggFile, NULL, 0 ) < 0 )
{
IError( "Unable to open OGG source file" );
return;
}
/// Construct some header info from the ogg info
vorbis_info *vInfo = ov_info( fOggFile, -1 );
fHeader.fFormatTag = 1;
fHeader.fNumChannels = vInfo->channels;
fHeader.fNumSamplesPerSec = vInfo->rate;
// Funny thing about the bits per sample: we get to CHOOSE. Go figure!
fHeader.fBitsPerSample = ( fDecodeFormat == k8bitUnsigned ) ? 8 : 16;
// Why WAV files hold this info when it can be calculated is beyond me...
fHeader.fBlockAlign = ( fHeader.fBitsPerSample * fHeader.fNumChannels ) >> 3;
fHeader.fAvgBytesPerSec = fHeader.fNumSamplesPerSec * fHeader.fBlockAlign;
/// The size in bytes of our PCM data stream
/// Note: OGG sometimes seems to be off by 1 sample, which causes our reads to suck
/// because we end up waiting for 1 more sample than we actually have. So, on the
/// assumption that OGG is just slightly wrong sometimes, we just subtract 1 sample
/// from what it tells us. As Brice put it, who's going to miss 1/40,000'ths of a second?
fDataSize = (UInt32)(( ov_pcm_total( fOggFile, -1 ) - 1 ) * fHeader.fBlockAlign);
/// Channel select
if( fWhichChannel != plAudioCore::kAll )
{
fChannelAdjust = 2;
fChannelOffset = ( fWhichChannel == plAudioCore::kLeft ) ? 0 : 1;
}
else
{
fChannelAdjust = 1;
fChannelOffset = 0;
}
/// Construct our fake header for channel adjustment
fFakeHeader = fHeader;
fFakeHeader.fAvgBytesPerSec /= fChannelAdjust;
fFakeHeader.fNumChannels /= (UInt16)fChannelAdjust;
fFakeHeader.fBlockAlign /= (UInt16)fChannelAdjust;
SetPosition( 0 );
}
// plNetClientApp::StaticDebugMsg("Ogg Open %s, t=%f, end", path, hsTimer::GetSeconds());
}
plOGGCodec::~plOGGCodec()
{
Close();
}
void plOGGCodec::Close( void )
{
// plNetClientApp::StaticDebugMsg("Ogg Close, t=%f, start", hsTimer::GetSeconds());
FREE(fHeadBuf);
fHeadBuf = nil;
if( fOggFile != nil )
{
ov_clear( fOggFile );
DEL(fOggFile);
fOggFile = nil;
}
if( fFileHandle != nil )
{
fclose( fFileHandle );
fFileHandle = nil;
}
// plNetClientApp::StaticDebugMsg("Ogg Close, t=%f, end", hsTimer::GetSeconds());
}
void plOGGCodec::IError( const char *msg )
{
hsAssert( false, msg );
Close();
}
plWAVHeader &plOGGCodec::GetHeader( void )
{
hsAssert( IsValid(), "GetHeader() called on an invalid OGG file" );
return fFakeHeader;
}
float plOGGCodec::GetLengthInSecs( void )
{
hsAssert( IsValid(), "GetLengthInSecs() called on an invalid OGG file" );
// Just query ogg directly...starting to see how cool ogg is yet?
return (float)ov_time_total( fOggFile, -1 );
}
hsBool plOGGCodec::SetPosition( UInt32 numBytes )
{
hsAssert( IsValid(), "GetHeader() called on an invalid OGG file" );
if( !ov_seekable( fOggFile ) )
{
hsAssert( false, "Trying to set position on an unseekable OGG stream!" );
return false;
}
// The numBytes position is in uncompressed space and should be sample-aligned anyway,
// so this should be just fine here.
ogg_int64_t newSample = ( numBytes / (fFakeHeader.fBlockAlign * fChannelAdjust) );
// Now please note how freaking easy it is here to do accurate or fast seeking...
// Also note that if we're doing our channel extraction, we MUST do it the accurate way
if( ( fDecodeFlags & kFastSeeking ) && fChannelAdjust == 1 )
{
if( ov_pcm_seek_page( fOggFile, newSample ) != 0 )
{
IError( "Unable to seek OGG stream" );
return false;
}
}
else
{
if( ov_pcm_seek( fOggFile, newSample ) != 0 )
{
IError( "Unable to seek OGG stream" );
return false;
}
}
return true;
}
hsBool plOGGCodec::Read( UInt32 numBytes, void *buffer )
{
hsAssert( IsValid(), "GetHeader() called on an invalid OGG file" );
// plNetClientApp::StaticDebugMsg("Ogg Read, t=%f, start", hsTimer::GetSeconds());
int bytesPerSample = ( fDecodeFormat == k16bitSigned ) ? 2 : 1;
int isSigned = ( fDecodeFormat == k16bitSigned ) ? 1 : 0;
int currSection;
if( fWhichChannel == plAudioCore::kAll )
{
// Easy, just a straight read
char *uBuffer = (char *)buffer;
while( numBytes > 0 )
{
// Supposedly we should pay attention to currSection in case of bitrate changes,
// but hopefully we'll never have those....
long bytesRead = ov_read( fOggFile, uBuffer, numBytes, 0, bytesPerSample, isSigned, &currSection );
// Since our job is so simple, do some extra error checking
if( bytesRead == OV_HOLE )
{
IError( "Unable to read from OGG file: missing data" );
return false;
}
else if( bytesRead == OV_EBADLINK )
{
IError( "Unable to read from OGG file: corrupt link" );
return false;
}
else if( bytesRead == 0 )
{
IError( "Unable to finish reading from OGG file: end of stream" );
return false;
}
else if( bytesRead < 0 )
{
IError( "Unable to read from OGG file: unknown error" );
return false;
}
numBytes -= bytesRead;
uBuffer += bytesRead;
}
}
else
{
/// Read in 4k chunks and extract
static char trashBuffer[ 4096 ];
long toRead, i, thisRead, sampleSize = fFakeHeader.fBlockAlign;
for( ; numBytes > 0; )
{
/// Read 4k worth of samples
toRead = ( sizeof( trashBuffer ) < numBytes * fChannelAdjust ) ? sizeof( trashBuffer ) : numBytes * fChannelAdjust;
thisRead = ov_read( fOggFile, (char *)trashBuffer, toRead, 0, bytesPerSample, isSigned, &currSection );
if( thisRead < 0 )
return false;
/// Copy every other sample out
int sampleOffset = (fChannelOffset == 1) ? sampleSize : 0;
for (i = 0; i < thisRead; i += sampleSize * 2)
{
memcpy(buffer, &trashBuffer[i + sampleOffset], sampleSize);
buffer = (void*)((UInt8*)buffer + sampleSize);
numBytes -= sampleSize;
}
}
}
// plNetClientApp::StaticDebugMsg("Ogg Read, t=%f, end", hsTimer::GetSeconds());
return true;
}
UInt32 plOGGCodec::NumBytesLeft( void )
{
if(!IsValid())
{
hsAssert( false, "GetHeader() called on an invalid OGG file" );
return 0;
}
return (UInt32)(( fDataSize - ( ov_pcm_tell( fOggFile ) * fHeader.fBlockAlign ) ) / fChannelAdjust);
}

View File

@ -0,0 +1,106 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plOGGCodec - Plasma codec support for the OGG/Vorbis file format. //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef _plOGGCodec_h
#define _plOGGCodec_h
#include "../plAudioCore/plAudioFileReader.h"
//// Class Definition ////////////////////////////////////////////////////////
struct OggVorbis_File;
class plOGGCodec : public plAudioFileReader
{
public:
plOGGCodec( const char *path, plAudioCore::ChannelSelect whichChan = plAudioCore::kAll );
virtual ~plOGGCodec();
enum DecodeFormat
{
k8bitUnsigned,
k16bitSigned
};
enum DecodeFlags
{
kFastSeeking = 0x01
};
virtual plWAVHeader &GetHeader( void );
virtual void Close( void );
virtual UInt32 GetDataSize( void ) { return fDataSize / fChannelAdjust; }
virtual float GetLengthInSecs( void );
virtual hsBool SetPosition( UInt32 numBytes );
virtual hsBool Read( UInt32 numBytes, void *buffer );
virtual UInt32 NumBytesLeft( void );
virtual hsBool IsValid( void ) { return ( fOggFile != nil ) ? true : false; }
static void SetDecodeFormat( DecodeFormat f ) { fDecodeFormat = f; }
static void SetDecodeFlag( UInt8 flag, hsBool on ) { if( on ) fDecodeFlags |= flag; else fDecodeFlags &= ~flag; }
static UInt8 GetDecodeFlags( void ) { return fDecodeFlags; }
void ResetWaveHeaderRef() { fCurHeaderPos = 0; }
void BuildActualWaveHeader();
bool ReadFromHeader(int numBytes, void *data); // read from Actual wave header
protected:
enum
{
kPCMFormatTag = 1
};
char fFilename[ 512 ];
FILE *fFileHandle;
OggVorbis_File *fOggFile;
plWAVHeader fHeader, fFakeHeader;
UInt32 fDataStartPos, fCurrDataPos, fDataSize;
plAudioCore::ChannelSelect fWhichChannel;
UInt32 fChannelAdjust, fChannelOffset;
static DecodeFormat fDecodeFormat;
static UInt8 fDecodeFlags;
UInt8 * fHeadBuf;
int fCurHeaderPos;
void IError( const char *msg );
void IOpen( const char *path, plAudioCore::ChannelSelect whichChan = plAudioCore::kAll );
};
#endif //_plOGGCodec_h

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,396 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plSound.h - Base sound class header //
// //
//// History /////////////////////////////////////////////////////////////////
// //
// 10.12.01 mcn - Added preliminary soft region (volume) support. //
// 7.12.02 mcn - Added EAX support //
// 7.15.02 mcn - Added support for animated volumes //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef plSound_h
#define plSound_h
#include "hsTemplates.h"
#include "hsGeometry3.h"
#include "plEAXEffects.h"
#include "../pnNetCommon/plSynchedObject.h"
#include "../plAvatar/plAGChannel.h"
#include "../plAvatar/plAGApplicator.h"
#include "../plAudioCore/plSoundBuffer.h"
class hsResMgr;
class hsStream;
class plSoundProxy;
class plDrawableSpans;
class hsGMaterial;
class plSoundMsg;
class plSoftVolume;
class plGraphPlate;
struct hsMatrix44;
class plSoundBuffer;
class plSceneObject;
class plSoundVolumeApplicator;
// Set this to 1 to do our own distance attenuation (model doesn't work yet tho)
#define MCN_HACK_OUR_ATTEN 0
#define MAX_INCIDENTALS 4
class plSound : public plSynchedObject
{
friend class plSoundSDLModifier;
friend class plSoundVolumeApplicator;
public:
plSound();
virtual ~plSound();
CLASSNAME_REGISTER( plSound );
GETINTERFACE_ANY( plSound, plSynchedObject );
enum Property
{
kPropIs3DSound = 0x00000001,
kPropDisableLOD = 0x00000002,
kPropLooping = 0x00000004,
kPropAutoStart = 0x00000008,
kPropLocalOnly = 0x00000010, // Disables network synching and triggering
kPropLoadOnlyOnCall = 0x00000020, // Only load and unload when we're told to
kPropFullyDisabled = 0x00000040, // This sound should never play while this is set
// Only plWin32LinkSound uses it. Placed here as a TODO though...
kPropDontFade = 0x00000080,
kPropIncidental = 0x00000100 // Incidental sound, will be played thru the incidental manager
};
enum Type
{
kStartType,
kSoundFX = kStartType, // For now, 3D sounds are always marked as this
kAmbience,
kBackgroundMusic,
kGUISound,
kNPCVoices,
kNumTypes
};
enum Refs
{
kRefSoftVolume = 0,
kRefDataBuffer, // plugins only
kRefParentSceneObject,
kRefSoftOcclusionRegion
};
enum
{
kSoftRegion = 0
};
enum StreamType
{
kNoStream,
kStreamFromRAM,
kStreamFromDisk,
kStreamCompressed
};
class plFadeParams
{
friend class plSound;
public:
enum Type
{
kLinear,
kLogarithmic,
kExponential
};
hsScalar fLengthInSecs; // Time to take to fade
hsScalar fVolStart; // Set one of these two for fade in/out,
hsScalar fVolEnd; // the other becomes the current volume
UInt8 fType;
hsBool fStopWhenDone; // Actually stop the sound once the fade is complete
hsBool fFadeSoftVol; // Fade the soft volume instead of fCurrVolume
plFadeParams() { fLengthInSecs = 0.f; fCurrTime = -1.f; fStopWhenDone = false; fFadeSoftVol = false; fVolStart = fVolEnd = 0.f; fType = kLinear; }
plFadeParams( Type type, hsScalar len, hsScalar start, hsScalar end )
{
fLengthInSecs = len; fVolStart = start; fVolEnd = end; fType = type;
fStopWhenDone = false;
fFadeSoftVol = false;
}
void Read( hsStream *s );
void Write( hsStream *s );
hsScalar InterpValue( void );
protected:
hsScalar fCurrTime; // -1 if we aren't active, else it's how far we're into the animation
};
virtual hsBool LoadSound( hsBool is3D ) = 0;
hsScalar GetVirtualStartTime( void ) const { return (hsScalar)fVirtualStartTime; }
virtual void Play();
void SynchedPlay( unsigned bytes );
void SynchedPlay( hsScalar virtualStartTime );
virtual void Stop();
virtual void FastForwardPlay();
virtual void FastForwardToggle();
virtual void SetMin(const int m); // sets minimum falloff distance
virtual void SetMax(const int m); // sets maximum falloff distance
virtual int GetMin() const;
virtual int GetMax() const;
virtual void SetVolume(const float volume);
virtual float GetVolume(void) const { return fCurrVolume; }
hsScalar GetMaxVolume() { return fMaxVolume; }
virtual hsBool IsPlaying() { return fPlaying; }
void SetTime(double t);
virtual double GetTime( void ) { return 0.f; }
virtual void Activate(hsBool forcePlay = false);
virtual void DeActivate();
virtual void SetLength(double l) { fLength = l; }
virtual void SetMuted( hsBool muted );
virtual hsBool IsMuted( void ) { return fMuted; }
void Disable() { fDistAttenuation = 0; }
virtual plSoundMsg* GetStatus(plSoundMsg* pMsg){return NULL;}
virtual void SetConeOrientation(hsScalar x, hsScalar y, hsScalar z);
virtual void SetOuterVolume( const int v ); // volume for the outer cone (if applicable)
virtual void SetConeAngles( int inner, int outer );
virtual void SetPosition(const hsPoint3 pos);
virtual void SetVelocity(const hsVector3 vel);
virtual hsPoint3 GetPosition() const;
virtual hsVector3 GetVelocity() const;
virtual void Update();
plSoundBuffer * GetDataBuffer( void ) const { return (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded(); }
hsScalar QueryCurrVolume( void ) const; // Returns the current volume, attenuated
const char * GetFileName( void ) const;
virtual double GetLength();
void SetProperty( Property prop, hsBool on ) { if( on ) fProperties |= prop; else fProperties &= ~prop; }
hsBool IsPropertySet( Property prop ) const { return ( fProperties & prop ) ? true : false; }
virtual void RefreshVolume( void );
virtual void SetStartPos(unsigned bytes) = 0;
virtual unsigned GetByteOffset(){return 0;}
virtual float GetActualTimeSec() = 0;
virtual void AddCallbacks(plSoundMsg* pMsg) = 0;
virtual void RemoveCallbacks(plSoundMsg* pMsg) = 0;
virtual UInt8 GetChannelSelect( void ) const { return 0; } // Only defined on Win32Sound right now, should be here tho
virtual void Read(hsStream* s, hsResMgr* mgr);
virtual void Write(hsStream* s, hsResMgr* mgr);
virtual void SetFadeInEffect( plFadeParams::Type type, hsScalar length );
virtual void SetFadeOutEffect( plFadeParams::Type type, hsScalar length );
virtual hsScalar CalcSoftVolume( hsBool enable, hsScalar distToListenerSquared );
virtual void UpdateSoftVolume( hsBool enable, hsBool firstTime = false );
virtual hsBool MsgReceive( plMessage* pMsg );
virtual hsBool DirtySynchState( const char *sdlName = nil, UInt32 sendFlags = 0 ); // call when state has changed
// Tests whether this sound is within range of the given position, not counting soft regions
hsBool IsWithinRange( const hsPoint3 &listenerPos, hsScalar *distSquared );
// Type setting and getting, from the Types enum
void SetType( UInt8 type ) { fType = type; }
UInt8 GetType( void ) const { return fType; }
// Priority stuff
void SetPriority( UInt8 pri ) { fPriority = pri; }
UInt8 GetPriority( void ) const { return fPriority; }
// Visualization
virtual plDrawableSpans* CreateProxy(const hsMatrix44& l2w, hsGMaterial* mat, hsTArray<UInt32>& idx, plDrawableSpans* addTo);
// Forced loading/unloading (for when the audio system's LOD just doesn't cut it)
virtual void ForceLoad( );
virtual void ForceUnload( void );
// Note: ONLY THE AUDIOSYS SHOULD CALL THIS. If you're not the audioSys, get lost.
static void SetCurrDebugPlate( const plKey soundKey );
void RegisterOnAudioSys( void );
void UnregisterOnAudioSys( void );
// Also only for the audio system
hsScalar GetVolumeRank( void );
void ForceUnregisterFromAudioSys( void );
static void SetLoadOnDemand( hsBool activate ) { fLoadOnDemandFlag = activate; }
static void SetLoadFromDiskOnDemand( hsBool activate ) { fLoadFromDiskOnDemand = activate; }
const plEAXSourceSettings &GetEAXSettings( void ) const { return fEAXSettings; }
plEAXSourceSettings &GetEAXSettings( void ) { return fEAXSettings; }
virtual StreamType GetStreamType() const { return kNoStream; }
virtual void FreeSoundData();
protected:
hsBool fPlaying;
hsBool fActive;
double fTime;
int fMaxFalloff;
int fMinFalloff;
hsScalar fCurrVolume;
hsScalar fDesiredVol; // Equal to fCurrVolume except when we're fading or muted
hsScalar fFadedVolume;
hsScalar fMaxVolume;
int fOuterVol;
int fInnerCone;
int fOuterCone;
double fLength;
int fProperties;
UInt8 fType;
UInt8 fPriority;
hsBool fMuted, fFading, fRegisteredForTime, fPlayOnReactivate, fFreeData;
hsBool fNotHighEnoughPriority; // Set whenever the audioSys calls UpdateSoftVolume() with enable=false,
// thus indicating that we slipped off the top 16 most wanted list.
// Do these need to be synched values? They weren't before...
hsVector3 fConeOrientation;
hsPoint3 f3DPosition;
hsVector3 f3DVelocity;
hsBool fPlayWhenLoaded;
double fSynchedStartTimeSec;
// Just around for reference and sending messages upward (synched state)
plSceneObject *fOwningSceneObject;
// EAX Settings storage here
plEAXSourceSettings fEAXSettings;
hsBool fQueued;
plFadeParams fFadeInParams, fFadeOutParams;
plFadeParams fCoolSoftVolumeTrickParams;
plFadeParams *fCurrFadeParams;
plSoftVolume *fSoftRegion;
hsScalar fSoftVolume;
hsScalar fDistAttenuation, fDistToListenerSquared;
double fVirtualStartTime;
hsBool fRegistered;
static unsigned fIncidentalsPlaying;
plSoftVolume *fSoftOcclusionRegion;
plSoundBuffer *fDataBuffer; // Not always around
hsBool fDataBufferLoaded;
plKey fDataBufferKey; // Always around
static plGraphPlate *fDebugPlate;
static plSound *fCurrDebugPlateSound;
static hsBool fLoadOnDemandFlag, fLoadFromDiskOnDemand;
hsBool fLoading;
void IUpdateDebugPlate( void );
void IPrintDbgMessage( const char *msg, hsBool isErr = false );
virtual void ISetActualVolume(const float v) = 0;
virtual void IActuallyStop( void );
virtual hsBool IActuallyPlaying( void ) = 0;
virtual void IActuallyPlay( void ) = 0;
virtual void IFreeBuffers( void ) = 0;
//NOTE: if isIncidental is true the entire sound will be loaded.
virtual plSoundBuffer::ELoadReturnVal IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental = false );
virtual void ISetActualTime( double t ) = 0;
virtual hsBool IActuallyLoaded( void ) = 0;
virtual void IRefreshEAXSettings( hsBool force = false ) = 0;
virtual hsScalar IGetChannelVolume( void ) const;
void ISynchToStartTime( void );
void ISynchedPlay( double virtualStartTime );
void IStartFade( plFadeParams *params, hsScalar offsetIntoFade = 0.f );
void IStopFade( hsBool shuttingDown = false, hsBool SetVolEnd = true);
hsBool IWillBeAbleToPlay( void );
void ISetSoftRegion( plSoftVolume *region );
hsScalar IAttenuateActualVolume( hsScalar volume ) const;
void ISetSoftOcclusionRegion( plSoftVolume *region );
// Override to make sure the buffer is available before the base class is called
virtual void IRefreshParams( void );
virtual bool ILoadDataBuffer( void );
virtual void IUnloadDataBuffer( void );
//virtual void ISetMinDistance( const int m ) = 0;
//virtual void ISetMaxDistance( const int m ) = 0;
//virtual void ISetOuterVolume( const int v ) = 0;
//virtual void ISetConeAngles( int inner, int outer ) = 0;
//virtual void ISetActualConeOrient( hsVector3 &vector ) = 0;
//virtual void ISetVelocity( const hsVector3 vel ) = 0;
//virtual void ISetPosition( const hsPoint3 pos ) = 0;
virtual void IRead( hsStream *s, hsResMgr *mgr );
virtual void IWrite( hsStream *s, hsResMgr *mgr );
};
//// plSoundVolumeApplicator /////////////////////////////////////////////////
// Tiny helper for handling animated volumes
class plSoundVolumeApplicator : public plAGApplicator
{
public:
plSoundVolumeApplicator() { }
plSoundVolumeApplicator( UInt32 index ) { fIndex = index; }
CLASSNAME_REGISTER( plSoundVolumeApplicator );
GETINTERFACE_ANY( plSoundVolumeApplicator, plAGApplicator );
virtual plAGApplicator *CloneWithChannel( plAGChannel *channel );
virtual void Write( hsStream *stream, hsResMgr *mgr );
virtual void Read( hsStream *s, hsResMgr *mgr );
protected:
UInt32 fIndex;
virtual void IApply( const plAGModifier *mod, double time );
};
#endif //plWin32Sound_h

View File

@ -0,0 +1,183 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plSoundEvent //
// //
//// Notes ///////////////////////////////////////////////////////////////////
// //
// 10.30.2001 - Created by mcn. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plSoundEvent.h"
#include "plgDispatch.h"
#include "../pnMessage/plEventCallbackMsg.h"
#include "../pnMessage/plSoundMsg.h"
#include "plSound.h"
plSoundEvent::plSoundEvent( Types type, plSound *owner )
{
fType = type;
fBytePosTime = 0;
fOwner = owner;
fCallbacks.Reset();
fCallbackEndingFlags.Reset();
}
plSoundEvent::plSoundEvent( Types type, UInt32 bytePos, plSound *owner )
{
fType = type;
fBytePosTime = bytePos;
fOwner = owner;
fCallbacks.Reset();
fCallbackEndingFlags.Reset();
}
plSoundEvent::plSoundEvent()
{
fType = kStart;
fBytePosTime = 0;
fOwner = nil;
fCallbacks.Reset();
fCallbackEndingFlags.Reset();
}
plSoundEvent::~plSoundEvent()
{
int i;
for( i = 0; i < fCallbacks.GetCount(); i++ )
hsRefCnt_SafeUnRef( fCallbacks[ i ] );
}
void plSoundEvent::AddCallback( plEventCallbackMsg *msg )
{
hsRefCnt_SafeRef( msg );
fCallbacks.Append( msg );
fCallbackEndingFlags.Append( 0 );
}
hsBool plSoundEvent::RemoveCallback( plEventCallbackMsg *msg )
{
int idx = fCallbacks.Find( msg );
if( idx != fCallbacks.kMissingIndex )
{
hsRefCnt_SafeUnRef( msg );
fCallbacks.Remove( idx );
fCallbackEndingFlags.Remove( idx );
return true;
}
return false;
}
void plSoundEvent::SendCallbacks( void )
{
int j;
plSoundMsg *sMsg;
for( j = fCallbacks.GetCount() - 1; j >= 0; j-- )
{
plEventCallbackMsg *msg = fCallbacks[ j ];
if (!msg->HasBCastFlag(plMessage::kNetPropagate) || !fOwner ||
fOwner->IsLocallyOwned() == plSynchedObject::kYes )
{
/// Do this a bit differently so we can do our MsgSend last
sMsg = nil;
// Ref to make sure the dispatcher doesn't delete it on us
hsRefCnt_SafeRef( msg );
if( msg->fRepeats == 0 && fCallbackEndingFlags[ j ] == 0 )
{
// Note: we get fancy here. We never want to remove the callback directly,
// because the sound won't know about it. So instead, send it a message to
// remove the callback for us
sMsg = TRACKED_NEW plSoundMsg();
sMsg->SetBCastFlag( plMessage::kLocalPropagate, true );
sMsg->AddReceiver( fOwner->GetKey() );
sMsg->SetCmd( plSoundMsg::kRemoveCallbacks );
sMsg->AddCallback( msg );
}
// If this isn't infinite, decrement the number of repeats
if( msg->fRepeats > 0 )
msg->fRepeats--;
// And finally...
if( fCallbackEndingFlags[ j ] == 0 )
{
plgDispatch::MsgSend( msg, true );
}
if( sMsg != nil )
{
plgDispatch::MsgSend( sMsg, true );
fCallbackEndingFlags[ j ] = 0xff; // Our special flag to mean "hey, don't
// process this, just waiting for
// it to die"
}
}
}
}
UInt32 plSoundEvent::GetNumCallbacks( void ) const
{
return fCallbacks.GetCount();
}
int plSoundEvent::GetType( void ) const
{
return (int)fType;
}
void plSoundEvent::SetType( Types type )
{
fType = type;
}
UInt32 plSoundEvent::GetTime( void ) const
{
return fBytePosTime;
}
plSoundEvent::Types plSoundEvent::GetTypeFromCallbackMsg( plEventCallbackMsg *msg )
{
switch( msg->fEvent )
{
case ::kStart: return kStart;
case ::kTime: return kTime;
case ::kStop: return kStop;
case ::kLoop: return kLoop;
}
return kStop;
}

View File

@ -0,0 +1,83 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plSoundEvent - Event node for handling callback thread stuff //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef _plSoundEvent_h
#define _plSoundEvent_h
#include "hsTemplates.h"
class plEventCallbackMsg;
class plSound;
//// plSoundEvent ////////////////////////////////////////////////////////////
// Storage class for an event node.
class plSoundEvent
{
public:
enum Types
{
kStart,
kStop,
kTime,
kLoop
};
plSoundEvent( Types type, plSound *owner );
plSoundEvent( Types type, UInt32 bytePos, plSound *owner );
plSoundEvent();
~plSoundEvent();
void AddCallback( plEventCallbackMsg *msg );
hsBool RemoveCallback( plEventCallbackMsg *msg );
UInt32 GetNumCallbacks( void ) const;
int GetType( void ) const;
void SetType( Types type );
UInt32 GetTime( void ) const;
void SendCallbacks( void );
static Types GetTypeFromCallbackMsg( plEventCallbackMsg *msg );
protected:
Types fType;
UInt32 fBytePosTime;
plSound *fOwner;
hsTArray<plEventCallbackMsg *> fCallbacks;
hsTArray<UInt8> fCallbackEndingFlags;
};
#endif //_plSoundEvent_h

View File

@ -0,0 +1,698 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsTypes.h"
#include "hsWindows.h"
#include "hsTimer.h"
#include "hsResMgr.h"
#include "al.h"
#include "alc.h"
#include "plDSoundBuffer.h"
#include "speex.h"
#include "speex_bits.h"
#include "hsGeometry3.h"
#include "plVoiceChat.h"
#include "plAudioSystem.h"
#include "plgDispatch.h"
#include "../plAudible/plWinAudible.h"
#include "../plNetMessage/plNetMessage.h"
#include "../plPipeline/plPlates.h"
#include "hsConfig.h"
#include "../plAvatar/plAvatarMgr.h"
#include "../plAvatar/plArmatureMod.h"
#include "hsQuat.h"
#include "../plAudioCore/plAudioCore.h"
// DEBUG for printing to the console
#include "../plMessage/plConsoleMsg.h"
#include "../plPipeline/plDebugText.h"
#include "../plStatusLog/plStatusLog.h"
#define MICROPHONE 121
#define TALKING 122
#define NUM_CHANNELS 1
#define VOICE_STOP_MS 2000
#define MAX_DATA_SIZE 1024 * 4 // 4 KB
hsBool plVoiceRecorder::fCompress = true;
hsBool plVoiceRecorder::fRecording = true;
hsBool plVoiceRecorder::fNetVoice = false;
short plVoiceRecorder::fSampleRate = FREQUENCY;
hsScalar plVoiceRecorder::fRecordThreshhold = 200.0f;
hsBool plVoiceRecorder::fShowIcons = true;
hsBool plVoiceRecorder::fMicAlwaysOpen = false;
hsBool plVoicePlayer::fEnabled = true;
plVoiceRecorder::plVoiceRecorder()
{
plPlateManager::Instance().CreatePlate( &fDisabledIcon );
fDisabledIcon->CreateFromResource( MAKEINTRESOURCE( MICROPHONE ) );
fDisabledIcon->SetPosition(-0.90, -0.90);
fDisabledIcon->SetSize(0.0675, 0.09);
fDisabledIcon->SetVisible(false);
plPlateManager::Instance().CreatePlate( &fTalkIcon );
fTalkIcon->CreateFromResource( MAKEINTRESOURCE( TALKING ) );
fTalkIcon->SetPosition(-0.9,-0.9);
fTalkIcon->SetSize(0.0675, 0.09);
fTalkIcon->SetVisible(false);
}
plVoiceRecorder::~plVoiceRecorder()
{
if(fDisabledIcon)
plPlateManager::Instance().DestroyPlate( fDisabledIcon);
fDisabledIcon = nil;
if (fTalkIcon)
plPlateManager::Instance().DestroyPlate( fTalkIcon );
fTalkIcon = nil;
}
void plVoiceRecorder::IncreaseRecordingThreshhold()
{
fRecordThreshhold += (100 * hsTimer::GetDelSysSeconds());
if (fRecordThreshhold >= 10000.0f)
fRecordThreshhold = 10000.0f;
plDebugText &txt = plDebugText::Instance();
char str[256];
sprintf(str, "RecordThreshhold %f\n", fRecordThreshhold);
txt.DrawString(400,300,str);
}
void plVoiceRecorder::DecreaseRecordingThreshhold()
{
fRecordThreshhold -= (100 * hsTimer::GetDelSysSeconds());
if (fRecordThreshhold <= 50.0f)
fRecordThreshhold = 50.0f;
plDebugText &txt = plDebugText::Instance();
char str[256];
sprintf(str, "RecordThreshhold %f\n", fRecordThreshhold);
txt.DrawString(400,300,str);
}
// Set the quality of speex encoder
void plVoiceRecorder::SetQuality(int quality)
{
char str[] = "Voice quality setting out of range. Must be between 1 and 10 inclusive";
if(quality < 1 || quality > 10)
{
plConsoleMsg *cMsg = TRACKED_NEW plConsoleMsg( plConsoleMsg::kAddLine, str );
plgDispatch::MsgSend( cMsg );
return;
}
if(plSpeex::GetInstance()->IsUsingVBR())
{
// Sets average bit rate between 4kb and 13kb
int AverageBitrate = quality * 1000 + 3000;
plSpeex::GetInstance()->SetABR(AverageBitrate);
}
else
{
plSpeex::GetInstance()->SetQuality(quality);
}
}
// toggle variable bit rate
void plVoiceRecorder::SetVBR(bool vbr)
{
plSpeex::GetInstance()->VBR(vbr);
SetQuality(plSpeex::GetInstance()->GetQuality()); // update proper quality param
}
void plVoiceRecorder::SetComplexity(int c)
{
char str[] = "Voice quality setting out of range. Must be between 1 and 10 inclusive";
if(c < 1 || c > 10)
{
plConsoleMsg *cMsg = TRACKED_NEW plConsoleMsg( plConsoleMsg::kAddLine, str );
plgDispatch::MsgSend( cMsg );
return;
}
plSpeex::GetInstance()->SetComplexity((UInt8) c);
}
void plVoiceRecorder::SetENH(hsBool b)
{
plSpeex::GetInstance()->SetENH(b);
}
void plVoiceRecorder::SetMikeOpen(hsBool b)
{
ALCdevice *device = plgAudioSys::GetCaptureDevice();
if (fRecording && device)
{
if (b)
{
alcCaptureStart(device);
}
else
{
alcCaptureStop(device);
}
DrawTalkIcon(b);
fMikeOpen = b;
}
else
{
DrawDisabledIcon(b); // voice recording is unavailable or disabled
}
}
void plVoiceRecorder::DrawDisabledIcon(hsBool b)
{
if (!fDisabledIcon)
{
// at least try and make one here...
plPlateManager::Instance().CreatePlate( &fDisabledIcon );
if (fDisabledIcon)
{
fDisabledIcon->CreateFromResource( MAKEINTRESOURCE( MICROPHONE ) );
fDisabledIcon->SetPosition(-0.90, -0.90);
fDisabledIcon->SetSize(0.0675, 0.09);
fDisabledIcon->SetVisible(false);
}
}
if (fDisabledIcon)
fDisabledIcon->SetVisible(b);
}
void plVoiceRecorder::DrawTalkIcon(hsBool b)
{
if (!fTalkIcon)
{
plPlateManager::Instance().CreatePlate( &fTalkIcon );
if (fTalkIcon)
{ fTalkIcon->CreateFromResource( MAKEINTRESOURCE( TALKING ) );
fTalkIcon->SetPosition(-0.9,-0.9);
fTalkIcon->SetSize(0.0675, 0.09);
fTalkIcon->SetVisible(false);
}
}
if (fTalkIcon)
{
fTalkIcon->SetVisible(b);
}
}
void plVoiceRecorder::Update(double time)
{
if(!fRecording)
return;
int EncoderFrameSize = plSpeex::GetInstance()->GetFrameSize();
if(EncoderFrameSize == -1)
return;
ALCdevice *captureDevice = plgAudioSys::GetCaptureDevice();
if(!captureDevice)
return;
unsigned minSamples = EncoderFrameSize * 10;
ALCint samples;
alcGetIntegerv(captureDevice, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples );
if (samples > 0)
{
if (samples >= minSamples)
{
int numFrames = (int)(samples / EncoderFrameSize); // the number of frames that have been captured
int totalSamples = numFrames * EncoderFrameSize;
// cap uncompressed data
if(totalSamples > MAX_DATA_SIZE)
totalSamples = MAX_DATA_SIZE;
// convert to correct units:
short *buffer = TRACKED_NEW short[totalSamples];
alcCaptureSamples(captureDevice, buffer, totalSamples);
if (!CompressionEnabled())
{
plNetMsgVoice pMsg;
pMsg.SetNetProtocol(kNetProtocolCli2Game);
pMsg.SetVoiceData((char *)buffer, totalSamples * sizeof(short));
// set frame size here;
pMsg.SetPlayerID(plNetClientApp::GetInstance()->GetPlayerID());
//if (false) //plNetClientApp::GetInstance()->GetFlagsBit(plNetClientApp::kEchoVoice))
// pMsg.SetBit(plNetMessage::kEchoBackToSender);
plNetClientApp::GetInstance()->SendMsg(&pMsg);
}
else // use the speex voice compression lib
{
UInt8 *packet = TRACKED_NEW UInt8[totalSamples]; // packet to send encoded data in
int packedLength = 0; // the size of the packet that will be sent
hsRAMStream ram; // ram stream to hold output data from speex
UInt8 numFrames = totalSamples / EncoderFrameSize; // number of frames to be encoded
// encode the data using speex
plSpeex::GetInstance()->Encode(buffer, numFrames, &packedLength, &ram);
if (packedLength)
{
// extract data from ram stream into packet
ram.Rewind();
ram.Read(packedLength, packet);
plNetMsgVoice pMsg;
pMsg.SetNetProtocol(kNetProtocolCli2Game);
pMsg.SetVoiceData((char *)packet, packedLength);
pMsg.SetPlayerID(plNetClientApp::GetInstance()->GetPlayerID());
pMsg.SetFlag(VOICE_ENCODED); // Set encoded flag
pMsg.SetNumFrames(numFrames);
if (plNetClientApp::GetInstance()->GetFlagsBit(plNetClientApp::kEchoVoice))
pMsg.SetBit(plNetMessage::kEchoBackToSender);
plNetClientApp::GetInstance()->SendMsg(&pMsg);
}
delete[] packet;
}
delete[] buffer;
}
else if(!fMikeOpen)
{
short *buffer = TRACKED_NEW short[samples];
// the mike has since closed, and there isn't enough data to meet our minimum, so throw this data out
alcCaptureSamples(captureDevice, buffer, samples);
delete[] buffer;
}
}
}
plVoicePlayer::plVoicePlayer()
{
}
plVoicePlayer::~plVoicePlayer()
{
}
void plVoicePlayer::PlaybackUncompressedVoiceMessage(void* data, unsigned size)
{
if(fEnabled)
{
if(!fSound.IsPlaying())
{
fSound.Play();
}
fSound.AddVoiceData(data, size);
}
}
void plVoicePlayer::PlaybackVoiceMessage(void* data, unsigned size, int numFramesInBuffer)
{
if(fEnabled)
{
int numBytes; // the number of bytes that speex decompressed the data to.
int bufferSize = numFramesInBuffer * plSpeex::GetInstance()->GetFrameSize();
short *nBuff = TRACKED_NEW short[bufferSize];
memset(nBuff, 0, bufferSize);
// Decode the encoded voice data using speex
if(!plSpeex::GetInstance()->Decode((UInt8 *)data, size, numFramesInBuffer, &numBytes, nBuff))
{
delete[] nBuff;
return;
}
BYTE* newBuff;
newBuff = (BYTE*)nBuff; // Convert to byte data
PlaybackUncompressedVoiceMessage(newBuff, numBytes); // playback uncompressed data
delete[] nBuff;
}
}
void plVoicePlayer::SetVelocity(const hsVector3 vel)
{
fSound.SetVelocity(vel);
}
void plVoicePlayer::SetPosition(const hsPoint3 pos)
{
fSound.SetPosition(pos);
}
void plVoicePlayer::SetOrientation(const hsPoint3 pos)
{
fSound.SetConeOrientation(pos.fX, pos.fY, pos.fZ);
}
/*****************************************************************************
*
* plVoiceSound
*
***/
unsigned plVoiceSound::fCount = 0;
plVoiceSound::plVoiceSound()
{
fInnerCone = 90;
fOuterCone = 240;
fOuterVol = -2000;
fMinFalloff = 15;
fMaxFalloff = 75;
fProperties = 0;
fCurrVolume = 1.0;
fDesiredVol = 1.0;
fPriority = 1;
fType = plgAudioSys::kVoice;
fEAXSettings.SetRoomParams(-1200, -100, 0, 0);
fLastUpdate = 0;
char keyName[32];
StrPrintf(keyName, arrsize(keyName), "VoiceSound_%d", fCount);
fCount++;
hsgResMgr::ResMgr()->NewKey(keyName, this, plLocation::kGlobalFixedLoc);
}
plVoiceSound::~plVoiceSound()
{
}
hsBool plVoiceSound::LoadSound( hsBool is3D )
{
if( fFailed )
return false;
if( !plgAudioSys::Active() || fDSoundBuffer )
return false;
if( fPriority > plgAudioSys::GetPriorityCutoff() )
return false; // Don't set the failed flag, just return
plWAVHeader header;
header.fFormatTag = WAVE_FORMAT_PCM;
header.fBitsPerSample = 16;
header.fNumChannels = 1;
header.fNumSamplesPerSec = FREQUENCY;
header.fBlockAlign = header.fNumChannels * header.fBitsPerSample / 2;
header.fAvgBytesPerSec = header.fNumSamplesPerSec * header.fBlockAlign;
fDSoundBuffer = TRACKED_NEW plDSoundBuffer(0, header, true, false, false, true);
if(!fDSoundBuffer)
return false;
fDSoundBuffer->SetupVoiceSource();
IRefreshParams();
IRefreshEAXSettings( true );
fDSoundBuffer->SetScalarVolume(1.0);
return true;
}
void plVoiceSound::Play()
{
fPlaying = true;
if( IWillBeAbleToPlay() )
{
IRefreshParams();
SetVolume( fDesiredVol );
IActuallyPlay();
}
}
void plVoiceSound::IDerivedActuallyPlay( void )
{
if( !fReallyPlaying )
{
fDSoundBuffer->Play();
fReallyPlaying = true;
}
}
void plVoiceSound::AddVoiceData(void *data, unsigned bytes)
{
unsigned size;
unsigned bufferId;
if(!fDSoundBuffer)
{
if(!LoadSound(true))
{
return;
}
}
fDSoundBuffer->UnQueueVoiceBuffers(); // attempt to unque any buffers that have finished
while(bytes > 0)
{
size = bytes < STREAM_BUFFER_SIZE ? bytes : STREAM_BUFFER_SIZE;
if(!fDSoundBuffer->GetAvailableBufferId(&bufferId))
break; // if there isn't any room for the data, it is currently thrown out
fDSoundBuffer->VoiceFillBuffer(data, size, bufferId);
bytes -= size;
}
fLastUpdate = hsTimer::GetMilliSeconds();
}
void plVoiceSound::Update()
{
if(IsPlaying())
{
if((hsTimer::GetMilliSeconds() - fLastUpdate) > VOICE_STOP_MS)
{
Stop(); // terminating case for playback. Wait for x number of milliseconds, and stop.
}
}
}
void plVoiceSound::IRefreshParams()
{
plSound::IRefreshParams();
}
/*****************************************************************************
*
* Speex Voice Encoding/Decoding
*
***/
plSpeex::plSpeex() :
fBits(nil),
fEncoderState(nil),
fDecoderState(nil),
fSampleRate(plVoiceRecorder::GetSampleRate()),
fFrameSize(-1),
fQuality(7),
fVBR(true), // variable bit rate on
fAverageBitrate(8000), // 8kb bitrate
fComplexity(3),
fENH(false),
fInitialized(false)
{
fBits = TRACKED_NEW SpeexBits;
Init(kNarrowband); // if no one initialized us initialize using a narrowband encoder
}
plSpeex::~plSpeex()
{
Shutdown();
delete fBits;
fBits = nil;
}
hsBool plSpeex::Init(Mode mode)
{
int enh = 1;
// setup speex
speex_bits_init(fBits);
fBitsInit = true;
if(mode == kNarrowband)
{
fEncoderState = speex_encoder_init(&speex_nb_mode); // narrowband
fDecoderState = speex_decoder_init(&speex_nb_mode);
}
else if(mode == kWideband)
{
fEncoderState = speex_encoder_init(&speex_wb_mode);
fDecoderState = speex_decoder_init(&speex_wb_mode);
}
speex_encoder_ctl(fEncoderState, SPEEX_GET_FRAME_SIZE, &fFrameSize); // get frame size
speex_encoder_ctl(fEncoderState, SPEEX_SET_COMPLEXITY, &fComplexity); // 3
speex_encoder_ctl(fEncoderState, SPEEX_SET_SAMPLING_RATE, &fSampleRate); // 8 khz
speex_encoder_ctl(fEncoderState, SPEEX_SET_VBR_QUALITY, &fQuality); // 7
speex_encoder_ctl(fEncoderState, SPEEX_SET_VBR, &fVBR); // use variable bit rate
speex_encoder_ctl(fEncoderState, SPEEX_SET_ABR, &fAverageBitrate); // default to 8kb
speex_decoder_ctl(fDecoderState, SPEEX_SET_ENH, &fENH); // perceptual enhancement
fInitialized = true;
return true;
}
hsBool plSpeex::Shutdown()
{
//shutdown speex
if(fDecoderState)
{
speex_decoder_destroy(fDecoderState);
fDecoderState = nil;
}
if(fEncoderState)
{
speex_encoder_destroy(fEncoderState);
fEncoderState = nil;
}
if(fBitsInit)
{
speex_bits_destroy(fBits);
fBitsInit = false;
}
fInitialized = false;
return true;
}
hsBool plSpeex::Encode(short *data, int numFrames, int *packedLength, hsRAMStream *out)
{
*packedLength = 0;
short *pData = data; // pointer to input data
float *input = TRACKED_NEW float[fFrameSize]; // input to speex - used as am intermediate array since speex requires float data
BYTE frameLength; // number of bytes speex compressed frame to
BYTE *frameData = TRACKED_NEW BYTE[fFrameSize]; // holds one frame of encoded data
// encode data
for( int i = 0; i < numFrames; i++ )
{
// convert input data to floats
for( int j = 0; j < fFrameSize; j++ )
{
input[j] = pData[j];
}
speex_bits_reset(fBits); // reset bit structure
// encode data using speex
speex_encode(fEncoderState, input, fBits);
frameLength = speex_bits_write(fBits, (char *)frameData, fFrameSize);
// write data - length and bytes
out->WriteSwap(frameLength);
*packedLength += sizeof(frameLength); // add length of encoded frame
out->Write(frameLength, frameData);
*packedLength += frameLength; // update length
pData += fFrameSize; // move input pointer
}
delete[] frameData;
delete[] input;
return true;
}
hsBool plSpeex::Decode(UInt8 *data, int size, int numFrames, int *numOutputBytes, short *out)
{
if(!fInitialized) return false;
*numOutputBytes = 0;
hsReadOnlyStream stream( size, data );
float *speexOutput = TRACKED_NEW float[fFrameSize]; // holds output from speex
short *pOut = out; // pointer to output short buffer
// create buffer for input data
BYTE *frameData = TRACKED_NEW BYTE[fFrameSize]; // holds the current frames data to be decoded
BYTE frameLen; // holds the length of the current frame being decoded.
// Decode data
for (int i = 0; i < numFrames; i++)
{
stream.ReadSwap( &frameLen ); // read the length of the current frame to be decoded
stream.Read( frameLen, frameData ); // read the data
memset(speexOutput, 0, fFrameSize * sizeof(float));
speex_bits_read_from(fBits, (char *)frameData, frameLen); // give data to speex
speex_decode(fDecoderState, fBits, speexOutput); // decode data
for(int j = 0; j < fFrameSize; j++)
{
pOut[j] = (short)(speexOutput[j]); // convert floats to shorts
}
pOut += fFrameSize;
}
delete[] frameData;
delete[] speexOutput;
*numOutputBytes = (numFrames * fFrameSize) * sizeof(short); // length of decoded voice data(out) in bytes
if(*numOutputBytes == 0)
return false;
return true;
}
// Sets variable bit rate on/off
void plSpeex::VBR(hsBool b)
{
fVBR = b;
speex_encoder_ctl(fEncoderState, SPEEX_SET_VBR, &fVBR);
}
// Sets the average bit rate
void plSpeex::SetABR(UInt32 abr)
{
fAverageBitrate = abr;
speex_encoder_ctl(fEncoderState, SPEEX_SET_ABR, &fAverageBitrate);
}
// Sets the quality of encoding
void plSpeex::SetQuality(UInt32 quality)
{
fQuality = quality;
speex_encoder_ctl(fEncoderState, SPEEX_SET_QUALITY, &fQuality);
}
void plSpeex::SetENH(hsBool b)
{
fENH = b;
speex_decoder_ctl(fDecoderState, SPEEX_SET_ENH, &fENH);
}
void plSpeex::SetComplexity(UInt8 c)
{
fComplexity = c;
speex_encoder_ctl(fEncoderState, SPEEX_SET_COMPLEXITY, &fComplexity);
}

View File

@ -0,0 +1,196 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plVoiceChat_h
#define plVoiceChat_h
#include "hsTemplates.h"
#include "plWin32Sound.h"
#include "hsThread.h"
// voice flags
#define VOICE_ENCODED ( 1 << 0 )
#define VOICE_NARROWBAND ( 1 << 1 )
#define VOICE_ENH ( 1 << 2 )
#define BUFFER_LEN_SECONDS 4
#define FREQUENCY 8000
struct hsVector3;
struct SpeexBits;
class plWinAudible;
class plPlate;
class plStatusLog;
class plSpeex;
typedef struct ALCdevice_struct ALCdevice;
// Sound used for playing back dynamic voice chat data. this allows us to hook voice chat into the audio system
class plVoiceSound : public plWin32Sound
{
public:
plVoiceSound();
~plVoiceSound();
hsBool LoadSound( hsBool is3D );
void AddVoiceData(void *data, unsigned bytes);
void Update();
void Play();
virtual void SetStartPos(unsigned bytes){}
private:
virtual bool ILoadDataBuffer( void ){ return true; }
virtual void IUnloadDataBuffer( void ){}
virtual void IDerivedActuallyPlay( void );
virtual void ISetActualTime( double t ){}
virtual float GetActualTimeSec() { return 0.0f; }
virtual void IRefreshParams( void );
static unsigned fCount;
double fLastUpdate;
};
class plVoicePlayer
{
public:
plVoicePlayer();
~plVoicePlayer();
void PlaybackVoiceMessage(void* data, unsigned size, int numFramesInBuffer);
void PlaybackUncompressedVoiceMessage(void* data, unsigned size);
void SetVelocity(const hsVector3 vel);
void SetPosition(const hsPoint3 pos);
void SetOrientation(const hsPoint3 pos);
void SetTalkIcon(int index, UInt32 str){}
void ClearTalkIcon(){}
plVoiceSound *GetSoundPtr() { return &fSound; }
static void Enable(hsBool enable) { fEnabled = enable; }
private:
plVoiceSound fSound;
static hsBool fEnabled;
};
class plVoiceRecorder
{
public:
plVoiceRecorder();
~plVoiceRecorder();
void Update(double time);
void SetMikeOpen(hsBool b);
void DrawTalkIcon(hsBool b);
void DrawDisabledIcon(hsBool b);
void SetTalkIcon(int index, UInt32 str);
void ClearTalkIcon();
static hsBool RecordingEnabled() { return fRecording; }
static hsBool NetVoiceEnabled() { return fNetVoice; }
static hsBool CompressionEnabled() { return fCompress; }
static void EnablePushToTalk(hsBool b) { fMicAlwaysOpen = !b; }
static void EnableIcons(hsBool b) { fShowIcons = b; }
static void EnableRecording(hsBool b) { fRecording = b; }
static void EnableNetVoice(hsBool b) { fNetVoice = b; }
static void EnableCompression(hsBool b) { fCompress = b; }
static void SetSampleRate(short s) { fSampleRate = s; }
static void SetSquelch(hsScalar f) { fRecordThreshhold = f; }
static void IncreaseRecordingThreshhold();
static void DecreaseRecordingThreshhold();
static void SetQuality(int quality); // sets the quality of encoding
static void SetMode(int mode); // sets nb or wb mode
static void SetVBR(bool vbr);
static void SetComplexity(int c);
static void SetENH(hsBool b);
static short GetSampleRate() { return fSampleRate; }
private:
hsBool fMikeOpen;
hsBool fMikeJustClosed;
static hsBool fMicAlwaysOpen;
static hsBool fShowIcons;
static hsBool fCompress;
static hsBool fNetVoice;
static hsBool fRecording;
static short fSampleRate;
plPlate* fDisabledIcon;
plPlate* fTalkIcon;
static hsScalar fRecordThreshhold;
};
// Speex voice encoder/decoder class
class plSpeex
{
public:
~plSpeex();
enum Mode
{
kNarrowband,
kWideband,
kUltraWideband
};
static plSpeex *GetInstance()
{
static plSpeex instance;
return &instance;
}
hsBool Init(Mode mode);
hsBool Shutdown();
hsBool Encode(short *data, int numFrames, int *packedLength, hsRAMStream *out);
hsBool Decode(UInt8 *data, int size, int numFrames, int *numOutputBytes, short *out);
int GetFrameSize() { return fFrameSize; }
void VBR(hsBool b); // turn variable bit rate on/off
void SetVBR(UInt32 vbr); // Set variable bit rate quality
void ABR(hsBool b); // turn average bit rate on/off
void SetABR(UInt32 abr); // Set average bit rate quality
void SetQuality(UInt32 quality); // Set encoder quality
hsBool IsUsingVBR() { return fVBR; }
int GetQuality() { return fQuality; }
void SetENH(hsBool b);
void SetComplexity(UInt8 c);
hsBool Initialized() { return fInitialized; }
private:
plSpeex();
SpeexBits* fBits; // main speex structure
hsBool fBitsInit;
void* fEncoderState;
void* fDecoderState;
int fSampleRate;
int fFrameSize; // frame size from speex - 160 for nb
int fQuality; // 0-10 speex encode quality
hsBool fVBR; // toggle variable bit rate
int fAverageBitrate; // n-bits per second
UInt8 fComplexity; // 1-10 sets cpu resources allowed for encoder
hsBool fENH; // perceptual enhancement
hsBool fInitialized;
};
#endif //plVoiceChat_h

View File

@ -0,0 +1,210 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plWAVClipBuffer - Helper class for writing out WAV data in a buffered //
// manner, with support for clipping off the specified //
// amount at the end, but without knowing beforehand //
// exactly how much data we'll have. //
// //
// The algorithm goes something like this: we keep two buffers, both the //
// size of the amount we want to clip. We then start filling in the first //
// buffer, overflowing into the second buffer and wrapping back to the //
// first again in a circular fashion. When we fill up one buffer and are //
// about to advance to the next, we write that next buffer out. Why? //
// Because we know that, even if we got no more data in, we have enough //
// data in the first buffer to clip out the amount we want, so the other //
// half (which will have older data, being a circular buffer) can be //
// written out safely. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plWAVClipBuffer.h"
#include "hsStream.h"
#include "hsUtils.h"
#include "plWavFile.h"
//// Constructor/Destructor //////////////////////////////////////////////////
plWAVClipBuffer::plWAVClipBuffer( UInt32 clipSize, CWaveFile *outFile )
{
fBuffers[ 0 ] = fBuffers[ 1 ] = nil;
fFlushCalled = true;
Init( clipSize, outFile );
}
plWAVClipBuffer::~plWAVClipBuffer()
{
IShutdown();
}
//// Init & IShutdown ////////////////////////////////////////////////////////
void plWAVClipBuffer::Init( UInt32 clipSize, CWaveFile *outFile )
{
IShutdown();
if( clipSize > 0 )
{
fBuffers[ 0 ] = TRACKED_NEW UInt8[ clipSize ];
fBuffers[ 1 ] = TRACKED_NEW UInt8[ clipSize ];
memset( fBuffers[ 0 ], 0, clipSize );
memset( fBuffers[ 1 ], 0, clipSize );
}
fWhichBuffer = 0;
fBufferSize = clipSize;
fCursor = 0;
fFirstFlip = true;
fOutFile = outFile;
fFlushCalled = false;
}
void plWAVClipBuffer::IShutdown( void )
{
hsAssert( fFlushCalled, "WAVClipBuffer shut down without flushing it!!!" );
delete [] fBuffers[ 0 ];
delete [] fBuffers[ 1 ];
}
//// WriteData ///////////////////////////////////////////////////////////////
// The main workhorse; call this to add data to the buffer.
hsBool plWAVClipBuffer::WriteData( UInt32 size, UInt8 *data )
{
while( size > 0 )
{
UInt32 toWrite = fBufferSize - fCursor;
if( size < toWrite )
{
// Just write, haven't filled a buffer yet
memcpy( fBuffers[ fWhichBuffer ] + fCursor, data, size );
data += size;
fCursor += size;
return true; // All done!
}
// Fill up to the end of a buffer, then flip
memcpy( fBuffers[ fWhichBuffer ] + fCursor, data, toWrite );
data += toWrite;
fCursor += toWrite;
size -= toWrite;
// Flip now...
fWhichBuffer = 1 - fWhichBuffer;
fCursor = 0;
// Now we can write out this buffer, since it'll be old data and
// we have enough in the other buffer to clip with. The *only*
// time we don't want to do this is the first time we flip, since
// at that point, the buffer we just flipped to hasn't been filled yet.
// (Every time afterwards, we'll always be flipping to a buffer with old
// data).
if( fFirstFlip )
fFirstFlip = false;
else
{
// Write it out before we overwrite it!
UINT written;
HRESULT hr = fOutFile->Write( fBufferSize, fBuffers[ fWhichBuffer ], &written );
if( FAILED( hr ) )
{
hsAssert( false, "ERROR writing WMA stream to WAV file" );
return false;
}
else if( written != fBufferSize )
{
hsAssert( false, "Unable to write all of WMA stream to WAV file" );
return false;
}
}
}
// Cleanly got here, so just return success
return true;
}
//// Flush ///////////////////////////////////////////////////////////////////
// Writes out the remaining data, minus our clip value (which is fBufferSize)
// So here's our situation: at this point, one of two things could be true:
//
// 1) We haven't received enough data to clip by, at which point we don't
// write any more and bail (this will be true if fFirstFlip is still true)
//
// 2) Our cursor is at 0, which means we have one filled buffer that hasn't been
// written out and our current buffer is empty. At this point, we discard the
// filled buffer (which is precisely the length we want to clip by) and we're done.
//
// 3) The buffer we're on should be partially filled, while the other one is older
// data. So, we want to write out the older data and the partial buffer all the way,
// except for the clip size. Since we can therefore never write out any data in the
// partial buffer (since that count will always be less than the clip size and thus be
// the second half of what we clip), we simply figure out how much of the other one we
// clip and write out the rest.
hsBool plWAVClipBuffer::Flush( void )
{
fFlushCalled = true;
if( fFirstFlip )
return false; // We failed--not enough data to clip with
if( fCursor == 0 )
{
// Our current buffer is empty, so the other buffer is precisely what we clip.
// So just discard and return successfully
return true;
}
// The hard case--we always discard the partial buffer we're on, so figure out
// how much we want to save of the other buffer. The math is:
// Partial buffer amount we're clipping = fCursor
// Amount of other buffer we're clipping = fBufferSize - fCursor
// Amount of other buffer we're writing = fBufferSize - ( fBufferSize - fCursor ) = fCursor
// Go figure :)
UInt32 toWrite = fCursor;
UINT written;
HRESULT hr = fOutFile->Write( toWrite, fBuffers[ 1 - fWhichBuffer ], &written );
if( FAILED( hr ) )
{
hsAssert( false, "ERROR writing WMA stream to WAV file" );
return false;
}
else if( written != toWrite )
{
hsAssert( false, "Unable to write all of WMA stream to WAV file" );
return false;
}
// All done!
return true;
}

View File

@ -0,0 +1,78 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plWAVClipBuffer - Helper class for writing out WAV data in a buffered //
// manner, with support for clipping off the specified //
// amount at the end, but without knowing beforehand //
// exactly how much data we'll have. //
// //
// The algorithm goes something like this: we keep two buffers, both the //
// size of the amount we want to clip. We then start filling in the first //
// buffer, overflowing into the second buffer and wrapping back to the //
// first again in a circular fashion. When we fill up one buffer and are //
// about to advance to the next, we write that next buffer out. Why? //
// Because we know that, even if we got no more data in, we have enough //
// data in the first buffer to clip out the amount we want, so the other //
// half (which will have older data, being a circular buffer) can be //
// written out safely. //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef _plWAVClipBuffer_h
#define _plWAVClipBuffer_h
//// Class Definition ////////////////////////////////////////////////////////
class CWaveFile;
class plWAVClipBuffer
{
public:
plWAVClipBuffer( UInt32 clipSize, CWaveFile *outFile );
~plWAVClipBuffer();
// Inits the buffer. Can re-init if you wish
void Init( UInt32 clipSize, CWaveFile *outFile );
// Writes/adds data to the buffer
hsBool WriteData( UInt32 size, UInt8 *data );
// Call at the end, flushes the buffer and performs the clipping
hsBool Flush( void );
protected:
UInt8 *fBuffers[ 2 ];
UInt8 fWhichBuffer; // 0 or 1
UInt32 fCursor, fBufferSize;
hsBool fFirstFlip, fFlushCalled;
CWaveFile *fOutFile;
void IShutdown( void );
};
#endif //_plWAVClipBuffer_h

View File

@ -0,0 +1,114 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plWavFile_H
#define plWavFile_H
#define WAVEFILE_READ 1
#define WAVEFILE_WRITE 2
#include "hsTypes.h"
#include "hsWindows.h"
#include "hsStlUtils.h"
#include <mmsystem.h>
#include "../plAudioCore/plAudioFileReader.h"
struct plSoundMarker
{
char *fName;
double fOffset; // in Secs
plSoundMarker () { fName = NULL;fOffset = 0.0; }
};
//-----------------------------------------------------------------------------
// Name: class CWaveFile
// Desc: Encapsulates reading or writing sound data to or from a wave file
//-----------------------------------------------------------------------------
class CWaveFile : public plAudioFileReader
{
public:
CWaveFile();
~CWaveFile();
HRESULT Open(const char *strFileName, WAVEFORMATEX* pwfx, DWORD dwFlags );
HRESULT OpenFromMemory( BYTE* pbData, ULONG ulDataSize, WAVEFORMATEX* pwfx, DWORD dwFlags );
HRESULT Read( BYTE* pBuffer, DWORD dwSizeToRead, DWORD* pdwSizeRead );
HRESULT AdvanceWithoutRead( DWORD dwSizeToRead, DWORD* pdwSizeRead );
HRESULT Write( UINT nSizeToWrite, BYTE* pbData, UINT* pnSizeWrote );
DWORD GetSize();
HRESULT ResetFile();
WAVEFORMATEX* GetFormat() { return m_pwfx; };
DWORD GetNumMarkers() { return fMarkers.size() ; };
plSoundMarker *GetSoundMarker(int i) { return fMarkers[i]; }
// Overloads for plAudioFileReader
CWaveFile( const char *path, plAudioCore::ChannelSelect whichChan );
virtual hsBool OpenForWriting( const char *path, plWAVHeader &header );
virtual plWAVHeader &GetHeader( void );
virtual void Close( void );
virtual UInt32 GetDataSize( void );
virtual float GetLengthInSecs( void );
virtual hsBool SetPosition( UInt32 numBytes );
virtual hsBool Read( UInt32 numBytes, void *buffer );
virtual UInt32 NumBytesLeft( void );
virtual UInt32 Write( UInt32 bytes, void *buffer );
virtual hsBool IsValid( void );
WAVEFORMATEX* m_pwfx; // Pointer to WAVEFORMATEX structure
HMMIO m_hmmio; // MM I/O handle for the WAVE
MMCKINFO m_ck; // Multimedia RIFF chunk
MMCKINFO m_ckRiff; // Use in opening a WAVE file
DWORD m_dwSize; // The size of the wave file
MMIOINFO m_mmioinfoOut;
DWORD m_dwFlags;
BOOL m_bIsReadingFromMemory;
BYTE* m_pbData;
BYTE* m_pbDataCur;
ULONG m_ulDataSize;
plWAVHeader fHeader;
std::vector<plSoundMarker*> fMarkers;
double fSecsPerSample;
protected:
HRESULT ReadMMIO();
HRESULT WriteMMIO( WAVEFORMATEX *pwfxDest );
HRESULT IClose();
};
#endif // plWavFile_H

View File

@ -0,0 +1,393 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plWin32GroupedSound - Grouped version of a static sound. Lots of short //
// sounds stored in the buffer, all share the same //
// DSound playback buffer and only one plays at a //
// time. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plWin32GroupedSound.h"
#include "plDSoundBuffer.h"
#include "plAudioSystem.h"
#include "../plAudioCore/plSoundBuffer.h"
#include "../plAudioCore/plSoundDeswizzler.h"
#include "plgDispatch.h"
#include "../pnMessage/plSoundMsg.h"
#include "../plStatusLog/plStatusLog.h"
#include "plProfile.h"
#include "hsResMgr.h"
plProfile_Extern( MemSounds );
plProfile_Extern( StaticSndShoveTime );
plProfile_Extern( StaticSwizzleTime );
/////////////////////////////////////////////////////////////////////////////////////////////////
plWin32GroupedSound::plWin32GroupedSound()
{
fCurrentSound = 0;
}
plWin32GroupedSound::~plWin32GroupedSound()
{
DeActivate();
}
void plWin32GroupedSound::SetPositionArray( UInt16 numSounds, UInt32 *posArray, hsScalar *volumeArray )
{
UInt16 i;
fStartPositions.SetCountAndZero( numSounds );
fVolumes.SetCountAndZero( numSounds );
for( i = 0; i < numSounds; i++ )
{
fStartPositions[ i ] = posArray[ i ];
fVolumes[ i ] = volumeArray[ i ];
}
}
//// IRead/IWrite ////////////////////////////////////////////////////////////
void plWin32GroupedSound::IRead( hsStream *s, hsResMgr *mgr )
{
plWin32StaticSound::IRead( s, mgr );
UInt16 i, n = s->ReadSwap16();
fStartPositions.SetCountAndZero( n );
fVolumes.SetCountAndZero( n );
for( i = 0; i < n; i++ )
{
fStartPositions[ i ] = s->ReadSwap32();
fVolumes[ i ] = s->ReadSwapScalar();
}
}
void plWin32GroupedSound::IWrite( hsStream *s, hsResMgr *mgr )
{
plWin32StaticSound::IWrite( s, mgr );
s->WriteSwap16( fStartPositions.GetCount() );
UInt16 i;
for( i = 0; i < fStartPositions.GetCount(); i++ )
{
s->WriteSwap32( fStartPositions[ i ] );
s->WriteSwapScalar( fVolumes[ i ] );
}
}
//// LoadSound ///////////////////////////////////////////////////////////////
hsBool plWin32GroupedSound::LoadSound( hsBool is3D )
{
if( fFailed )
return false;
if( fPriority > plgAudioSys::GetPriorityCutoff() )
return false; // Don't set the failed flag, just return
if( !plgAudioSys::Active() || fDSoundBuffer != nil )
return false;
// Debug flag #1
if( fChannelSelect > 0 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableRightSelect ) )
{
// Force a fail
fFailed = true;
return false;
}
// We need it to be resident to read in
plSoundBuffer::ELoadReturnVal retVal = IPreLoadBuffer(true);
plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();
if(!buffer)
{
return plSoundBuffer::kError;
}
if( retVal == plSoundBuffer::kPending) // we are still reading data.
{
return true;
}
// We need it to be resident to read in
if( retVal == plSoundBuffer::kError)
{
char str[ 256 ];
sprintf( str, "Unable to open .wav file %s", fDataBufferKey ? fDataBufferKey->GetName() : "nil");
IPrintDbgMessage( str, true );
fFailed = true;
return false;
}
SetProperty( kPropIs3DSound, is3D );
plWAVHeader header = buffer->GetHeader();
// Debug flag #2
if( fChannelSelect == 0 && header.fNumChannels > 1 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableLeftSelect ) )
{
// Force a fail
fFailed = true;
return false;
}
// Calculate the maximum size for our buffer. This will be the length of the longest sound we're going to
// have to play.
UInt16 i;
UInt32 maxSoundSize, len;
for( i = 1, maxSoundSize = 0; i < fStartPositions.GetCount(); i++ )
{
len = fStartPositions[ i ] - fStartPositions[ i - 1 ];
if( len > maxSoundSize )
maxSoundSize = len;
}
len = buffer->GetDataLength() - fStartPositions[ fStartPositions.GetCount() - 1 ];
if( len > maxSoundSize )
maxSoundSize = len;
// Based on that, allocate our buffer
UInt32 bufferSize = maxSoundSize - ( maxSoundSize % header.fBlockAlign );
if( header.fNumChannels > 1 && is3D )
{
// We can only do a single channel of 3D sound. So copy over one (later)
bufferSize /= header.fNumChannels;
header.fBlockAlign /= header.fNumChannels;
header.fAvgBytesPerSec /= header.fNumChannels;
header.fNumChannels = 1;
}
fNumDestChannels = (UInt8)(header.fNumChannels);
fNumDestBytesPerSample = (UInt8)(header.fBlockAlign);
// Create our DSound buffer (or rather, the wrapper around it)
fDSoundBuffer = TRACKED_NEW plDSoundBuffer( bufferSize, header, is3D, IsPropertySet( kPropLooping ), true );
if( !fDSoundBuffer->IsValid() )
{
char str[256];
sprintf(str, "Can't create sound buffer for %s.wav. This could happen if the wav file is a stereo file. Stereo files are not supported on 3D sounds. If the file is not stereo then please report this error.", GetFileName());
IPrintDbgMessage( str, true );
fFailed = true;
delete fDSoundBuffer;
fDSoundBuffer = nil;
return false;
}
IRefreshEAXSettings( true );
// Fill the buffer with whatever our current sound is.
IFillCurrentSound( 0 );
// Logging
char str[ 256 ];
sprintf( str, " Grouped %s %s allocated (%d msec).", buffer->GetFileName() != nil ? "file" : "buffer",
buffer->GetFileName() != nil ? buffer->GetFileName() : buffer->GetKey()->GetUoid().GetObjectName(),
//fDSoundBuffer->IsHardwareAccelerated() ? "hardware" : "software",
//fDSoundBuffer->IsStaticVoice() ? "static" : "dynamic",
#ifdef PL_PROFILE_ENABLED
gProfileVarStaticSndShoveTime.GetValue() );
#else
0 );
#endif
IPrintDbgMessage( str );
if( GetKey() != nil && GetKeyName() != nil && strstr( GetKeyName(), "Footstep" ) != nil )
;
else
plStatusLog::AddLineS( "audioTimes.log", "%s (%s)", str, GetKey() ? GetKeyName() : "unkeyed" );
fTotalBytes = bufferSize;
plProfile_NewMem( MemSounds, fTotalBytes );
// All done!
// if( fLoadFromDiskOnDemand )
// IUnloadDataBuffer();
FreeSoundData();
return true;
}
//// GetSoundLength //////////////////////////////////////////////////////////
// Gets the length (in seconds) of the given sound index from the group.
hsScalar plWin32GroupedSound::GetSoundLength( Int16 soundIndex )
{
plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();
if(buffer)
{
return (hsScalar)IGetSoundByteLength( soundIndex ) / buffer->GetHeader().fAvgBytesPerSec;
}
return 0;
}
//// IGetSoundByteLength /////////////////////////////////////////////////////
// Byte version of above.
UInt32 plWin32GroupedSound::IGetSoundByteLength( Int16 soundIndex )
{
if( soundIndex == fStartPositions.GetCount() - 1 )
return ((plSoundBuffer *)fDataBufferKey->ObjectIsLoaded())->GetDataLength() - fStartPositions[ soundIndex ];
else
return fStartPositions[ soundIndex + 1 ] - fStartPositions[ soundIndex ];
}
//// IGetDataPointer/Length //////////////////////////////////////////////////
// Abstracting a few things here for the incidentalMgr
void *plWin32GroupedSound::IGetDataPointer( void ) const
{
return ( fDataBufferKey->ObjectIsLoaded() ) ? (void *)( (UInt8 *)((plSoundBuffer *)fDataBufferKey->ObjectIsLoaded())->GetData() + fStartPositions[ fCurrentSound ] ) : nil;
}
UInt32 plWin32GroupedSound::IGetDataLength( void ) const
{
return ( fDataBufferKey->ObjectIsLoaded() ) ? fCurrentSoundLength : 0;
}
//// IFillCurrentSound ///////////////////////////////////////////////////////
// Fills the DSoundBuffer with data from the current sound from our sound
// group, optionally switching what our current sound is.
void plWin32GroupedSound::IFillCurrentSound( Int16 newCurrent /*= -1*/ )
{
//void *dataPtr;
//UInt32 dataLength;
if( !fDSoundBuffer && plgAudioSys::Active() )
LoadSound( IsPropertySet( kPropIs3DSound ) );
plProfile_BeginTiming( StaticSndShoveTime );
// Make sure we're stopped first. Don't want to be filling while we're playing
Stop();
if( newCurrent != -1 )
{
fCurrentSound = (UInt16)newCurrent;
if( fCurrentSound >= fStartPositions.GetCount() )
{
// Invalid index!
hsAssert( false, "Invalid index in plWin32GroupedSound::IFillCurrentSound()" );
fCurrentSound = -1;
return;
}
// Set our length based on the current sound
fCurrentSoundLength = IGetSoundByteLength( fCurrentSound );
if( fDataBufferKey->ObjectIsLoaded() )
SetLength( fCurrentSoundLength / ((plSoundBuffer *)fDataBufferKey->ObjectIsLoaded())->GetHeader().fAvgBytesPerSec );
// Update our volume as well
SetVolume( fVolumes[ fCurrentSound ] );
}
if( fDSoundBuffer != nil )
{
/// Lock our buffer
//fDSoundBuffer->Lock( dataLength, dataPtr );
/// Copy or de-swizzle?
//if( fDataBuffer->GetHeader().fNumChannels == fNumDestChannels )
{
// Just copy
//memcpy( dataPtr, (Byte *)fDataBuffer->GetData() + fStartPositions[ fCurrentSound ], fCurrentSoundLength );
//dataPtr = (Byte *)dataPtr + fCurrentSoundLength;
//dataLength -= fCurrentSoundLength;
}
//else
{
// We're extracting a single channel of sound into our sound buffer, so it isn't a straight copy...
/*plProfile_BeginTiming( StaticSwizzleTime );
plSoundDeswizzler deswiz( (Byte *)fDataBuffer->GetData() + fStartPositions[ fCurrentSound ], fCurrentSoundLength,
(UInt8)(fDataBuffer->GetHeader().fNumChannels), fNumDestBytesPerSample );
deswiz.Extract( fChannelSelect, dataPtr );
dataPtr = (Byte *)dataPtr + fCurrentSoundLength / fDataBuffer->GetHeader().fNumChannels;
dataLength -= fCurrentSoundLength / fDataBuffer->GetHeader().fNumChannels;
plProfile_EndTiming( StaticSwizzleTime );*/
}
/// Fill the remaining part with empty space
//memset( dataPtr, 0, dataLength );
/// Finally, unlock!
//fDSoundBuffer->Unlock();
}
/// All done!
plProfile_EndTiming( StaticSndShoveTime );
}
void plWin32GroupedSound::IDerivedActuallyPlay( void )
{
// Ensure there's a stop notify for us
if( !fReallyPlaying )
{
fDSoundBuffer->Play();
fReallyPlaying = true;
}
plSoundEvent *event = IFindEvent( plSoundEvent::kStart );
if( event != nil )
event->SendCallbacks();
}
hsBool plWin32GroupedSound::MsgReceive( plMessage* pMsg )
{
plSoundMsg *soundMsg = plSoundMsg::ConvertNoRef( pMsg );
if( soundMsg != nil && soundMsg->Cmd( plSoundMsg::kSelectFromGroup ) )
{
IFillCurrentSound( soundMsg->fIndex );
return true;
}
else if( soundMsg != nil && soundMsg->Cmd( plSoundMsg::kPlay ) )
{
Play();
//plIncidentalMgr::GetInstance()->Play( this, plIncidentalMgr::kNormal );
return true;
}
return plWin32StaticSound::MsgReceive( pMsg );
}

View File

@ -0,0 +1,83 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plWin32GroupedSound - Grouped version of a static sound. Lots of short //
// sounds stored in the buffer, all share the same //
// DSound playback buffer and only one plays at a //
// time. //
// //
//////////////////////////////////////////////////////////////////////////////
#ifndef plWin32GroupedSound_h
#define plWin32GroupedSound_h
#include "plWin32StaticSound.h"
class hsResMgr;
class plDSoundBuffer;
class plEventCallbackMsg;
#include "plSoundEvent.h"
class plWin32GroupedSound : public plWin32StaticSound
{
public:
plWin32GroupedSound();
~plWin32GroupedSound();
CLASSNAME_REGISTER( plWin32GroupedSound );
GETINTERFACE_ANY( plWin32GroupedSound, plWin32StaticSound );
virtual hsBool LoadSound( hsBool is3D );
virtual hsBool MsgReceive( plMessage *pMsg );
void SetPositionArray( UInt16 numSounds, UInt32 *posArray, hsScalar *volumeArray );
hsScalar GetSoundLength( Int16 soundIndex );
virtual double GetLength() { return GetSoundLength( fCurrentSound ); }
protected:
UInt16 fCurrentSound;
UInt32 fCurrentSoundLength;
hsTArray<UInt32> fStartPositions; // In bytes
hsTArray<hsScalar> fVolumes;
// Some extra handy info for us
UInt8 fNumDestChannels, fNumDestBytesPerSample;
virtual void IDerivedActuallyPlay( void );
virtual void IRead( hsStream *s, hsResMgr *mgr );
virtual void IWrite( hsStream *s, hsResMgr *mgr );
UInt32 IGetSoundByteLength( Int16 soundIndex );
void IFillCurrentSound( Int16 newCurrent = -1 );
// Abstracting a few things here for the incidentalMgr
virtual void * IGetDataPointer( void ) const;
virtual UInt32 IGetDataLength( void ) const;
};
#endif //plWin32GroupedSound_h

View File

@ -0,0 +1,430 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include <hvdi.h>
#include <direct.h>
#include "HeadSpin.h"
#include "hsGeometry3.h"
#include "hsTimer.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "plProfile.h"
#include "plWin32Sound.h"
#include "plAudioSystem.h"
#include "plDSoundBuffer.h"
#include "plWavFile.h"
#include "../plAudible/plWinAudible.h"
#include "../plNetMessage/plNetMessage.h"
#include "../pnNetCommon/plNetApp.h"
#include "../pnMessage/plSoundMsg.h"
#include "../pnMessage/plEventCallbackMsg.h"
#include "../plPipeline/plPlates.h"
#include "../plStatusLog/plStatusLog.h"
plProfile_CreateMemCounter("Sounds", "Memory", MemSounds);
plProfile_Extern(SoundPlaying);
plWin32Sound::plWin32Sound() :
fFailed(false),
fPositionInited(false),
fAwaitingPosition(false),
fTotalBytes(0),
fReallyPlaying(false),
fChannelSelect(0),
fDSoundBuffer(nil)
{
}
plWin32Sound::~plWin32Sound()
{
}
void plWin32Sound::Activate( hsBool forcePlay )
{
if( fFailed )
return;
plSound::Activate( forcePlay );
}
void plWin32Sound::DeActivate()
{
plSound::DeActivate();
IFreeBuffers();
}
void plWin32Sound::IFreeBuffers( void )
{
if( fDSoundBuffer != nil )
{
delete fDSoundBuffer;
fDSoundBuffer = nil;
plProfile_DelMem(MemSounds, fTotalBytes);
fTotalBytes = 0;
}
fPositionInited = false;
fAwaitingPosition = false;
}
void plWin32Sound::Update()
{
plSound::Update();
}
void plWin32Sound::IActuallyPlay( void )
{
//plSound::Play();
if (!fDSoundBuffer && plgAudioSys::Active())
LoadSound( IsPropertySet( kPropIs3DSound ) );
if(!fLoading )
{
if (fDSoundBuffer && plgAudioSys::Active() )
{
// Sometimes base/derived classes can be annoying
IDerivedActuallyPlay();
RefreshVolume();
}
else
{
// If we can't load (for ex., if audio is off), then we act like we played and then stopped
// really fast. Thus, we need to send *all* of our callbacks off immediately and then Stop().
UInt32 i;
for( i = 0; i < fSoundEvents.GetCount(); i++ )
{
fSoundEvents[ i ]->SendCallbacks();
}
// Now stop, 'cause we played really really really really fast
fPlaying = false;
fPlayOnReactivate = IsPropertySet( kPropLooping );
IActuallyStop();
}
}
}
void plWin32Sound::IActuallyStop()
{
if( fReallyPlaying )
{
if( fDSoundBuffer != nil )
{
if(IsPropertySet(kPropIncidental))
{
--fIncidentalsPlaying;
}
fDSoundBuffer->Stop();
plStatusLog::AddLineS("impacts.log", "Stopping %s", GetKeyName());
}
fReallyPlaying = false;
}
else
{
if( fDSoundBuffer != nil && fDSoundBuffer->IsPlaying() )
{
plStatusLog::AddLineS( "audio.log", 0xffff0000, "WARNING: BUFFER FLAGGED AS STOPPED BUT NOT STOPPED - %s", GetKey() ? GetKeyName() : nil );
fDSoundBuffer->Stop();
}
}
// send callbacks
plSoundEvent *event = IFindEvent( plSoundEvent::kStop );
if( event != nil )
{
event->SendCallbacks();
}
plSound::IActuallyStop();
}
plSoundMsg* plWin32Sound::GetStatus(plSoundMsg* pMsg)
{
plSoundMsg* pReply = TRACKED_NEW plSoundMsg;
pReply->AddReceiver( pMsg->GetSender() );
pReply->SetCmd(plSoundMsg::kStatusReply);
pReply->fLoop = IsPropertySet( kPropLooping );
pReply->fPlaying = IsPlaying();
return pReply;
}
void plWin32Sound::SetMin( const int m )
{
plSound::SetMin(m);
if(fDSoundBuffer)
{
fDSoundBuffer->SetMinDistance(m);
}
}
void plWin32Sound::SetMax( const int m )
{
plSound::SetMax(m);
if( fDSoundBuffer )
{
fDSoundBuffer->SetMaxDistance( m );
}
}
void plWin32Sound::SetOuterVolume( const int v )
{
plSound::SetOuterVolume(v);
if(fDSoundBuffer)
{
fDSoundBuffer->SetConeOutsideVolume(v);
}
}
void plWin32Sound::SetConeAngles( int inner, int outer )
{
plSound::SetConeAngles(inner, outer);
if(fDSoundBuffer)
{
fDSoundBuffer->SetConeAngles(inner, outer);
}
}
void plWin32Sound::SetConeOrientation( hsScalar x, hsScalar y, hsScalar z )
{
plSound::SetConeOrientation(x, y, z);
if(fDSoundBuffer)
{
fDSoundBuffer->SetConeOrientation(x, z, y);
}
}
void plWin32Sound::SetVelocity( const hsVector3 vel )
{
plSound::SetVelocity(vel);
if( fDSoundBuffer)
fDSoundBuffer->SetVelocity(vel.fX, vel.fZ, vel.fY);
}
void plWin32Sound::SetPosition( const hsPoint3 pos )
{
plSound::SetPosition(pos);
if(fDSoundBuffer)
{
// in openal sounds that are mono are played as positional. Since gui's may be positioned way off in space, the sound may not be audible.
// doing this allows us to play mono gui sounds and still hear them, since this attaches the sound to the listener.
if(fType == kGUISound)
{
hsPoint3 listenerPos = plgAudioSys::Sys()->GetCurrListenerPos();
fDSoundBuffer->SetPosition(listenerPos.fX, listenerPos.fZ, listenerPos.fY);
}
else
{
fDSoundBuffer->SetPosition(pos.fX, pos.fZ, pos.fY);
}
}
fPositionInited = true;
if( fAwaitingPosition )
{
// If this is set, then we tried to set the volume before the position. Since
// this results in some ghastly sound popping, we wait until we set the position
// (here), and then call our volume again
RefreshVolume();
fAwaitingPosition = false;
}
}
void plWin32Sound::ISetActualVolume(const float volume)
{
float vol = IAttenuateActualVolume( volume ) * IGetChannelVolume();
if( fDSoundBuffer )
{
if( fPositionInited || !IsPropertySet( kPropIs3DSound ) )
{
fDSoundBuffer->SetScalarVolume( vol );
}
else
{
// If position isn't inited, we don't want to set the volume yet,
// so set this flag so we know to set the volume once we DO get the position
fAwaitingPosition = true;
}
}
IUpdateDebugPlate(); // Byte me.
}
//////////////////////////////////////////////////////////////
// The base class will make sure all our params are correct and up-to-date,
// but before it does its work, we have to make sure our buffer is ready to
// be updated.
void plWin32Sound::IRefreshParams( void )
{
if (!fDSoundBuffer && plgAudioSys::Active())
LoadSound( IsPropertySet( kPropIs3DSound ) );
else
{
// There is a gap between the ds buffer stopping and the sound being marked as stopped.
// If the sound is asked to play again during this time frame it will never actually be
// played because it is still marked as playing. Not only that, but we'll lose a hardware voice too.
// This will fix that by starting it up again.
if(plgAudioSys::Active())
fDSoundBuffer->Play();
}
plSound::IRefreshParams();
}
void plWin32Sound::IRefreshEAXSettings( hsBool force )
{
if( fDSoundBuffer != nil )
fDSoundBuffer->SetEAXSettings( &GetEAXSettings(), force );
}
void plWin32Sound::IAddCallback( plEventCallbackMsg *pMsg )
{
plSoundEvent::Types type = plSoundEvent::GetTypeFromCallbackMsg( pMsg );
plSoundEvent *event;
if( type == plSoundEvent::kTime )
{
UInt32 byteTime = ( fDSoundBuffer != nil ) ? fDSoundBuffer->GetBufferBytePos( pMsg->fEventTime ) : 0;
event = IFindEvent( type, byteTime );
if( event == nil )
{
// Add a new sound event for this guy
event = TRACKED_NEW plSoundEvent( type, byteTime, this );
//fDSoundBuffer->AddPosNotify( byteTime );
fSoundEvents.Append( event );
}
}
else
{
event = IFindEvent( type );
if( event == nil )
{
// Add a new sound event for this guy
event = TRACKED_NEW plSoundEvent( type, this );
fSoundEvents.Append( event );
}
}
event->AddCallback( pMsg );
}
void plWin32Sound::IRemoveCallback( plEventCallbackMsg *pMsg )
{
plSoundEvent::Types type = plSoundEvent::GetTypeFromCallbackMsg( pMsg );
for(int i = 0; i < fSoundEvents.GetCount(); ++i)
{
if( fSoundEvents[ i ]->GetType() == type )
{
if( fSoundEvents[ i ]->RemoveCallback( pMsg ) )
{
if( fSoundEvents[ i ]->GetNumCallbacks() == 0 )
{
//if( fSoundEvents[ i ]->GetType() == plSoundEvent::kTime )
//fDSoundBuffer->RemovePosNotify( fSoundEvents[ i ]->GetTime() );
delete fSoundEvents[ i ];
fSoundEvents.Remove( i );
}
break;
}
}
}
}
plSoundEvent *plWin32Sound::IFindEvent( plSoundEvent::Types type, UInt32 bytePos )
{
for(int i = 0; i < fSoundEvents.GetCount(); ++i )
{
if( fSoundEvents[ i ]->GetType() == type )
{
if( type != plSoundEvent::kTime || bytePos == fSoundEvents[ i ]->GetTime() )
return fSoundEvents[ i ];
}
}
return nil;
}
void plWin32Sound::RemoveCallbacks(plSoundMsg* pSoundMsg)
{
for(int i = 0; i < pSoundMsg->GetNumCallbacks(); ++i )
IRemoveCallback( pSoundMsg->GetEventCallback( i ) );
}
void plWin32Sound::AddCallbacks(plSoundMsg* pSoundMsg)
{
for(int i = 0; i < pSoundMsg->GetNumCallbacks(); ++i )
IAddCallback( pSoundMsg->GetEventCallback( i ) );
}
hsBool plWin32Sound::MsgReceive( plMessage* pMsg )
{
plEventCallbackMsg *e = plEventCallbackMsg::ConvertNoRef( pMsg );
if( e != nil )
{
if( e->fEvent == kStop )
{
fPlaying = false;
fPlayOnReactivate = false; // Just to make sure...
// Do we have an ending fade?
if( fFadeOutParams.fLengthInSecs > 0 )
{
IStartFade( &fFadeOutParams );
plSoundEvent *event = IFindEvent( plSoundEvent::kStop );
if( event != nil )
event->SendCallbacks();
}
else
{
if( fFading )
IStopFade();
fCurrVolume = 0.f;
this->ISetActualVolume( fCurrVolume );
}
this->IActuallyStop();
return true;
}
}
return plSound::MsgReceive( pMsg );
}
void plWin32Sound::IRead( hsStream *s, hsResMgr *mgr )
{
plSound::IRead( s, mgr );
fChannelSelect = s->ReadByte();
}
void plWin32Sound::IWrite( hsStream *s, hsResMgr *mgr )
{
plSound::IWrite( s, mgr );
s->WriteByte( fChannelSelect );
}

View File

@ -0,0 +1,120 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plWin32Sound_h
#define plWin32Sound_h
#include "hsTemplates.h"
#include "plSound.h"
#include "hsThread.h"
#include "plSoundEvent.h"
#define NUM_MAX_HANDLES 16
#define REPEAT_INFINITE 0xffffffff
struct hsVector3;
class hsResMgr;
class plEventCallbackMsg;
class plSoundMsg;
class DSoundCallbackHandle;
class plSoundEvent;
class plDSoundBuffer;
class plWin32Sound : public plSound
{
public:
plWin32Sound();
virtual ~plWin32Sound();
CLASSNAME_REGISTER( plWin32Sound );
GETINTERFACE_ANY( plWin32Sound, plSound );
virtual void Activate(hsBool forcePlay = false);
virtual void DeActivate();
virtual void AddCallbacks(plSoundMsg* pMsg);
virtual void RemoveCallbacks(plSoundMsg* pMsg);
virtual plSoundMsg* GetStatus(plSoundMsg* pMsg);
virtual hsBool MsgReceive(plMessage* pMsg);
virtual void Update();
virtual void SetMin(const int m); // sets minimum falloff distance
virtual void SetMax(const int m); // sets maximum falloff distance
virtual void SetConeOrientation(hsScalar x, hsScalar y, hsScalar z);
virtual void SetOuterVolume( const int v ); // volume for the outer cone (if applicable)
virtual void SetConeAngles( int inner, int outer );
virtual void SetPosition(const hsPoint3 pos);
virtual void SetVelocity(const hsVector3 vel);
enum ChannelSelect
{
kLeftChannel,
kRightChannel
};
// Selects a channel source from a multi-channel (stereo) file. Ignored if source is mono
void SetChannelSelect( ChannelSelect source ) { fChannelSelect = (UInt8)source; }
virtual UInt8 GetChannelSelect( void ) const { return fChannelSelect; }
protected:
plDSoundBuffer * fDSoundBuffer;
hsBool fFailed;
hsBool fPositionInited, fAwaitingPosition;
hsBool fReallyPlaying;
UInt32 fTotalBytes;
hsBool fWasPlaying;
UInt8 fChannelSelect; // For selecting a mono channel from a stereo file
hsTArray<plSoundEvent *> fSoundEvents;
virtual void ISetActualVolume(const float v);
virtual void IActuallyStop( void );
virtual hsBool IActuallyPlaying( void ) { return fReallyPlaying; }
virtual void IActuallyPlay( void );
virtual void IFreeBuffers( void );
virtual hsBool IActuallyLoaded( void ) { return ( fDSoundBuffer != nil ) ? true : false; }
// Override to make sure the buffer is available before the base class is called
virtual void IRefreshParams( void );
virtual void IDerivedActuallyPlay( void ) = 0;
virtual void IAddCallback( plEventCallbackMsg *pMsg );
virtual void IRemoveCallback( plEventCallbackMsg *pMsg );
plSoundEvent *IFindEvent( plSoundEvent::Types type, UInt32 bytePos = 0 );
virtual void IRead( hsStream *s, hsResMgr *mgr );
virtual void IWrite( hsStream *s, hsResMgr *mgr );
virtual void IRefreshEAXSettings( hsBool force = false );
};
#endif //plWin32Sound_h

View File

@ -0,0 +1,327 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "HeadSpin.h"
#include "hsGeometry3.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "plProfile.h"
#include "plWin32StaticSound.h"
#include "plWin32Sound.h"
#include "plDSoundBuffer.h"
#include "plAudioSystem.h"
#include "../plAudioCore/plSoundBuffer.h"
#include "../plAudioCore/plSoundDeswizzler.h"
#include "../pnMessage/plEventCallbackMsg.h"
#include "../pnMessage/plAudioSysMsg.h"
#include "../plMessage/plLinkToAgeMsg.h"
#include "../plMessage/plAvatarMsg.h"
#include "../plPipeline/plPlates.h"
#include "../plStatusLog/plStatusLog.h"
plProfile_Extern(MemSounds);
plProfile_CreateAsynchTimer( "Static Shove Time", "Sound", StaticSndShoveTime );
plProfile_CreateAsynchTimer( "Static Swizzle Time", "Sound", StaticSwizzleTime );
plWin32StaticSound::plWin32StaticSound()
{
}
plWin32StaticSound::~plWin32StaticSound()
{
DeActivate();
IUnloadDataBuffer();
}
void plWin32StaticSound::Activate( hsBool forcePlay )
{
plWin32Sound::Activate( forcePlay );
}
void plWin32StaticSound::DeActivate()
{
plWin32Sound::DeActivate();
}
hsBool plWin32StaticSound::LoadSound( hsBool is3D )
{
if (fFailed)
return false;
if( fPriority > plgAudioSys::GetPriorityCutoff() )
return false; // Don't set the failed flag, just return
if (plgAudioSys::Active() && !fDSoundBuffer)
{
// Debug flag #1
if( fChannelSelect > 0 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableRightSelect ) )
{
// Force a fail
fFailed = true;
return false;
}
// We need it to be resident to read in
plSoundBuffer::ELoadReturnVal retVal = IPreLoadBuffer(true);
plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();
if(!buffer)
{
return plSoundBuffer::kError;
}
if( retVal == plSoundBuffer::kPending) // we are still reading data.
{
return true;
}
if( retVal == plSoundBuffer::kError )
{
char str[ 256 ];
sprintf( str, "Unable to open .wav file %s", fDataBufferKey ? fDataBufferKey->GetName() : "nil");
IPrintDbgMessage( str, true );
fFailed = true;
return false;
}
SetProperty( kPropIs3DSound, is3D );
plWAVHeader header = buffer->GetHeader();
// Debug flag #2
if( fChannelSelect == 0 && header.fNumChannels > 1 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableLeftSelect ) )
{
// Force a fail
fFailed = true;
return false;
}
UInt32 bufferSize = buffer->GetDataLength();
if( header.fNumChannels > 1 && is3D )
{
// We can only do a single channel of 3D sound. So copy over one (later)
bufferSize /= header.fNumChannels;
header.fBlockAlign /= header.fNumChannels;
header.fAvgBytesPerSec /= header.fNumChannels;
header.fNumChannels = 1;
}
hsBool tryStatic = true;
// If we want FX, we can't use a static voice, but EAX doesn't fit under that limitation :)
if( 0 )
tryStatic = false;
// Create our DSound buffer (or rather, the wrapper around it)
fDSoundBuffer = TRACKED_NEW plDSoundBuffer( bufferSize, header, is3D, IsPropertySet( kPropLooping ), tryStatic );
if( !fDSoundBuffer->IsValid() )
{
char str[256];
sprintf(str, "Can't create sound buffer for %s.wav. This could happen if the wav file is a stereo file. Stereo files are not supported on 3D sounds. If the file is not stereo then please report this error.", GetFileName());
IPrintDbgMessage( str, true );
fFailed = true;
delete fDSoundBuffer;
fDSoundBuffer = nil;
return false;
}
plProfile_BeginTiming( StaticSndShoveTime );
if(!fDSoundBuffer->FillBuffer(buffer->GetData(), buffer->GetDataLength(), &header))
{
delete fDSoundBuffer;
fDSoundBuffer = nil;
plStatusLog::AddLineS("audio.log", "Could not play static sound, no voices left %s", GetKeyName());
return false;
}
plProfile_EndTiming( StaticSndShoveTime );
IRefreshEAXSettings( true );
fTotalBytes = bufferSize;
plProfile_NewMem(MemSounds, fTotalBytes);
// get pertinent info
hsScalar length = (hsScalar)bufferSize / (hsScalar)header.fAvgBytesPerSec;
SetLength(length);
if( fLoadFromDiskOnDemand && !IsPropertySet( kPropLoadOnlyOnCall ) )
FreeSoundData();
return true;
}
return false;
}
void plWin32StaticSound::Update()
{
plWin32Sound::Update();
if(fDSoundBuffer)
{
if(fPlaying) // we think we are playing
{
if(!fDSoundBuffer->IsPlaying()) // are we actually playing
{
Stop();
}
}
}
}
void plWin32StaticSound::IDerivedActuallyPlay( void )
{
// Ensure there's a stop notify for us
if( !fReallyPlaying )
{
for(;;)
{
if(IsPropertySet(kPropIncidental))
{
if(fIncidentalsPlaying >= MAX_INCIDENTALS)
break;
++fIncidentalsPlaying;
}
fDSoundBuffer->Play();
fReallyPlaying = true;
break;
}
}
plSoundEvent *event = IFindEvent( plSoundEvent::kStart );
if( event != nil )
event->SendCallbacks();
}
float plWin32StaticSound::GetActualTimeSec()
{
if(fDSoundBuffer)
return fDSoundBuffer->GetTimeOffsetSec();
return 0.0f;
}
void plWin32StaticSound::ISetActualTime(double t)
{
if( !fDSoundBuffer && plgAudioSys::Active())
LoadSound( IsPropertySet( kPropIs3DSound ) );
if( fDSoundBuffer )
{
if(!t)
fDSoundBuffer->SetTimeOffsetSec((float)t);
}
}
hsBool plWin32StaticSound::MsgReceive( plMessage* pMsg )
{
return plWin32Sound::MsgReceive( pMsg );
}
void plWin32StaticSound::IRemoveCallback( plEventCallbackMsg *pCBMsg )
{
plWin32Sound::IRemoveCallback( pCBMsg );
}
void plWin32StaticSound::IAddCallback( plEventCallbackMsg *pCBMsg )
{
if( plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStop &&
plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStart )
{
hsAssert( false, "Static sounds only support start and stop callbacks at this time." );
return;
}
plWin32Sound::IAddCallback( pCBMsg );
}
plWin32LinkSound::plWin32LinkSound()
{
SetLocalOnly(true); // linking sounds already synch at a higher level
SetProperty( kPropDontFade, true );
}
void plWin32LinkSound::Read(hsStream* s, hsResMgr* mgr)
{
plWin32StaticSound::Read(s, mgr);
plgDispatch::Dispatch()->RegisterForExactType(plLinkEffectBCMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plAvatarStealthModeMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plPseudoLinkEffectMsg::Index(), GetKey());
SetLocalOnly(true); // linking sounds already synch at a higher level
SetProperty( kPropDontFade, true );
}
void plWin32LinkSound::Write(hsStream* s, hsResMgr* mgr)
{
plWin32StaticSound::Write(s, mgr);
}
hsBool plWin32LinkSound::MsgReceive( plMessage* pMsg )
{
plLinkEffectBCMsg *msg = plLinkEffectBCMsg::ConvertNoRef( pMsg );
if( msg != nil && !msg->HasLinkFlag(plLinkEffectBCMsg::kMute))
{
if (msg->fLinkKey->GetUoid().GetClonePlayerID() == GetKey()->GetUoid().GetClonePlayerID())
{
if (!IsPropertySet(kPropFullyDisabled))
{
ISetActualTime(0);
Play();
//Activate(true);
}
}
return true;
}
plPseudoLinkEffectMsg *psmsg = plPseudoLinkEffectMsg::ConvertNoRef( pMsg );
if( psmsg != nil)
{
if (psmsg->fAvatarKey->GetUoid().GetClonePlayerID() == GetKey()->GetUoid().GetClonePlayerID())
{
if (!IsPropertySet(kPropFullyDisabled))
{
ISetActualTime(0);
//Play();
Activate(true);
}
}
return true;
}
plAvatarStealthModeMsg *sMsg = plAvatarStealthModeMsg::ConvertNoRef(pMsg);
if (sMsg)
{
if (sMsg->GetSender()->GetUoid().GetClonePlayerID() == GetKey()->GetUoid().GetClonePlayerID())
{
SetProperty(kPropFullyDisabled, (sMsg->fMode == plAvatarStealthModeMsg::kStealthCloaked));
plNetApp::StaticDebugMsg("plWin32LinkSound: rcvd avatarStealth msg, cloaked=%d", sMsg->fMode == plAvatarStealthModeMsg::kStealthCloaked);
}
return true;
}
return plWin32StaticSound::MsgReceive( pMsg );
}

View File

@ -0,0 +1,80 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plWin32StaticSound_h
#define plWin32StaticSound_h
#include "plWin32Sound.h"
class hsResMgr;
class plDSoundBuffer;
class plEventCallbackMsg;
#include "plSoundEvent.h"
class plWin32StaticSound : public plWin32Sound
{
public:
plWin32StaticSound();
~plWin32StaticSound();
CLASSNAME_REGISTER( plWin32StaticSound );
GETINTERFACE_ANY( plWin32StaticSound, plWin32Sound );
virtual void Activate( hsBool forcePlay = false );
virtual void DeActivate();
virtual hsBool LoadSound( hsBool is3D );
virtual void Update();
virtual hsBool MsgReceive(plMessage* pMsg);
virtual void SetStartPos(unsigned bytes){}
protected:
hsBool fRegisteredOnThread;
virtual void IDerivedActuallyPlay( void );
virtual void ISetActualTime( double t );
virtual float GetActualTimeSec();
virtual void IAddCallback( plEventCallbackMsg *pCBMsg );
virtual void IRemoveCallback( plEventCallbackMsg *pCBMsg );
};
// Same as a plWin32StaticSound, except this registers for a plLinkEffectBCMsg to play the sound on linking.
class plWin32LinkSound : public plWin32StaticSound
{
public:
plWin32LinkSound();
~plWin32LinkSound() { }
CLASSNAME_REGISTER( plWin32LinkSound );
GETINTERFACE_ANY( plWin32LinkSound, plWin32StaticSound );
virtual void Read(hsStream* s, hsResMgr* mgr);
virtual void Write(hsStream* s, hsResMgr* mgr);
virtual hsBool MsgReceive(plMessage* pMsg);
};
#endif //plWin32StaticSound_h

View File

@ -0,0 +1,493 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include <direct.h>
#include "HeadSpin.h"
#include "hsTimer.h"
#include "hsGeometry3.h"
#include "plgDispatch.h"
#include "plProfile.h"
#include "plWin32Sound.h"
#include "plWin32StreamingSound.h"
#include "plDSoundBuffer.h"
#include "plAudioSystem.h"
#include "../plAudioCore/plAudioFileReader.h"
#include "../plAudioCore/plSoundBuffer.h"
#include "../plAudioCore/plSoundDeswizzler.h"
#include "../pnMessage/plSoundMsg.h"
#include "../pnMessage/plEventCallbackMsg.h"
#include "../plStatusLog/plStatusLog.h"
#define STREAMING_UPDATE_MS 200
plProfile_Extern(MemSounds);
plProfile_CreateAsynchTimer( "Stream Shove Time", "Sound", StreamSndShoveTime );
plProfile_CreateAsynchTimer( "Stream Swizzle Time", "Sound", StreamSwizzleTime );
plProfile_Extern( SoundLoadTime );
plWin32StreamingSound::plWin32StreamingSound() :
fDataStream(nil),
fBlankBufferFillCounter(0),
fDeswizzler(nil),
fStreamType(kNoStream),
fLastStreamingUpdate(0),
fStopping(false),
fPlayWhenStopped(false),
fStartPos(0)
{
fBufferLengthInSecs = plgAudioSys::GetStreamingBufferSize();
}
plWin32StreamingSound::~plWin32StreamingSound()
{
/// Call before we delete our dataStream
DeActivate();
IUnloadDataBuffer();
delete fDataStream;
fDataStream = nil;
fSrcFilename[ 0 ] = 0;
delete fDeswizzler;
}
void plWin32StreamingSound::DeActivate()
{
plWin32Sound::DeActivate();
}
// Change the filename used by this streaming sound, so we can play a different file
void plWin32StreamingSound::SetFilename(const char *filename, bool isCompressed)
{
fNewFilename = filename;
fIsCompressed = isCompressed;
fFailed = false; // just in case the last sound failed to load turn this off so it can try to load the new sound
}
//////////////////////////////////////////////////////////////
// Override, 'cause we don't want to actually LOAD the sound for streaming,
// just make sure it's decompressed and such and ready to stream.
plSoundBuffer::ELoadReturnVal plWin32StreamingSound::IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental /* = false */ )
{
if(fPlayWhenStopped)
return plSoundBuffer::kPending;
hsBool sfxPath = fNewFilename.size() ? false : true;
if( fDataStream != nil && fNewFilename.size() == 0)
return plSoundBuffer::kSuccess; // Already loaded
if(!ILoadDataBuffer())
{
return plSoundBuffer::kError;
}
plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();
if(!buffer)
return plSoundBuffer::kError;
// The databuffer also needs to know if the source is compressed or not
if(fNewFilename.length())
{
buffer->SetFileName(fNewFilename.c_str());
buffer->SetFlag(plSoundBuffer::kStreamCompressed, fIsCompressed);
fNewFilename.clear();
if(fReallyPlaying)
{
fPlayWhenStopped = true;
return plSoundBuffer::kPending;
}
}
if( buffer->IsValid() )
{
plAudioFileReader::StreamType type = plAudioFileReader::kStreamNative;
fStreamType = kStreamCompressed;
if(!buffer->HasFlag(plSoundBuffer::kStreamCompressed))
{
if(buffer->GetDataLengthInSecs() > plgAudioSys::GetStreamFromRAMCutoff( ))
{
fStreamType = kStreamFromDisk;
type = plAudioFileReader::kStreamWAV;
}
else
{
fStreamType = kStreamFromRAM;
type = plAudioFileReader::kStreamRAM;
}
}
if(!fStartPos)
{
if(buffer->AsyncLoad(type, isIncidental ? 0 : STREAMING_BUFFERS * STREAM_BUFFER_SIZE ) == plSoundBuffer::kPending)
{
fPlayWhenLoaded = playWhenLoaded;
fLoading = true;
return plSoundBuffer::kPending;
}
}
char str[ 256 ];
strncpy( fSrcFilename, buffer->GetFileName(), sizeof( fSrcFilename ) );
bool streamCompressed = (buffer->HasFlag(plSoundBuffer::kStreamCompressed) != 0);
delete fDataStream;
fDataStream = buffer->GetAudioReader();
if(!fDataStream)
{
plAudioCore::ChannelSelect select = buffer->GetReaderSelect();
bool streamCompressed = (buffer->HasFlag(plSoundBuffer::kStreamCompressed) != 0);
/// Open da file
CHAR strPath[ MAX_PATH ];
_getcwd(strPath, MAX_PATH);
if(sfxPath)
strcat( strPath, "\\sfx\\" );
else
{
// if we've changing the filename don't append 'sfx', just append a '\' since a path to the folder is expected
strcat( strPath, "\\");
}
strcat( strPath, fSrcFilename );
fDataStream = plAudioFileReader::CreateReader(strPath, select,type);
}
if( fDataStream == nil || !fDataStream->IsValid() )
{
delete fDataStream;
fDataStream = nil;
return plSoundBuffer::kError;
}
sprintf( str, " Readied file %s for streaming", fSrcFilename );
IPrintDbgMessage( str );
// dont free sound data until we have a chance to use it in load sound
return fDataStream ? plSoundBuffer::kSuccess : plSoundBuffer::kError;
}
plStatusLog::AddLineS("audio.log", "EnsureLoadable failed for streaming sound %d", fDataBufferKey->GetName());
return plSoundBuffer::kError;
}
void plWin32StreamingSound::IFreeBuffers( void )
{
plWin32Sound::IFreeBuffers();
if( fLoadFromDiskOnDemand && !IsPropertySet( kPropLoadOnlyOnCall ) )
{
// if the audio system has just restarted, dont delete the datastream. This way we can pick up where we left off instead of restarting the sound.
if(!plgAudioSys::IsRestarting())
{
// we are deleting the stream, we must release the sound data.
FreeSoundData();
delete fDataStream;
fDataStream = nil;
}
fSrcFilename[ 0 ] = 0;
}
}
///////////////////////////////////////////////////////////////////
// Overload from plSound. Basically sets up the streaming file and fills the
// first half of our buffer. We'll fill the rest of the buffer as we get
// notifications for such.
hsBool plWin32StreamingSound::LoadSound( hsBool is3D )
{
if( fFailed )
return false;
if( !plgAudioSys::Active() || fDSoundBuffer )
return false;
if( fPriority > plgAudioSys::GetPriorityCutoff() )
return false; // Don't set the failed flag, just return
// Debug flag #1
if( is3D && fChannelSelect > 0 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableRightSelect ) )
{
// Force a fail
fFailed = true;
return false;
}
plSoundBuffer::ELoadReturnVal retVal = IPreLoadBuffer(true);
if(retVal == plSoundBuffer::kPending)
return true;
if( retVal == plSoundBuffer::kError )
{
char str[ 256 ];
sprintf( str, "Unable to open streaming source %s", fDataBufferKey->GetName() );
IPrintDbgMessage( str, true );
fFailed = true;
return false;
}
SetProperty( kPropIs3DSound, is3D );
plWAVHeader header = fDataStream->GetHeader();
UInt32 bufferSize = (UInt32)(fBufferLengthInSecs * header.fAvgBytesPerSec);
// Debug flag #2
if( is3D && fChannelSelect == 0 && header.fNumChannels > 1 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableLeftSelect ) )
{
// Force a fail
fFailed = true;
return false;
}
if( header.fNumChannels > 1 && is3D )
{
// We can only do a single channel of 3D sound. So copy over one (later)
bufferSize /= header.fNumChannels;
header.fBlockAlign /= header.fNumChannels;
header.fAvgBytesPerSec /= header.fNumChannels;
header.fNumChannels = 1;
}
// Actually create the buffer now (always looping)
fDSoundBuffer = TRACKED_NEW plDSoundBuffer( bufferSize, header, is3D, IsPropertySet(kPropLooping), false, true );
if( !fDSoundBuffer->IsValid() )
{
fDataStream->Close();
delete fDataStream;
fDataStream = nil;
delete fDSoundBuffer;
fDSoundBuffer = nil;
char str[256];
sprintf(str, "Can't create sound buffer for %s.wav. This could happen if the wav file is a stereo file. Stereo files are not supported on 3D sounds. If the file is not stereo then please report this error.", GetFileName());
IPrintDbgMessage( str, true );
fFailed = true;
return false;
}
fTotalBytes = (UInt32)bufferSize;
plProfile_NewMem(MemSounds, fTotalBytes);
plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();
if(!buffer)
return false;
bool setupSource = true;
if(!buffer->GetData() || fStartPos)
{
if(fStartPos && fStartPos <= fDataStream->NumBytesLeft())
{
fDataStream->SetPosition(fStartPos);
plStatusLog::AddLineS("syncaudio.log", "startpos %d", fStartPos);
}
// if we get here we are not starting from the beginning of the sound. We still have an audio loaded and need to pick up where we left off
if(!fDSoundBuffer->SetupStreamingSource(fDataStream))
{
setupSource = false;
}
}
else
{
// this sound is starting from the beginning. Get the data and start it.
if(!fDSoundBuffer->SetupStreamingSource(buffer->GetData(), buffer->GetAsyncLoadLength()))
{
setupSource = false;
}
}
if(!setupSource)
{
fDataStream->Close();
delete fDataStream;
fDataStream = nil;
delete fDSoundBuffer;
fDSoundBuffer = nil;
plStatusLog::AddLineS("audio.log", "Could not play streaming sound, no voices left %s", GetKeyName());
return false;
}
FreeSoundData();
IRefreshEAXSettings( true );
// Debug info
char str[ 256 ];
sprintf( str, " Streaming %s.", fSrcFilename);
IPrintDbgMessage( str );
plStatusLog::AddLineS( "audioTimes.log", 0xffffffff, "Streaming %4.2f secs of %s", fDataStream->GetLengthInSecs(), GetKey()->GetUoid().GetObjectName() );
// Get pertinent info
SetLength( (hsScalar)fDataStream->GetLengthInSecs() );
// Set up our deswizzler, if necessary
delete fDeswizzler;
if( fDataStream->GetHeader().fNumChannels != header.fNumChannels )
fDeswizzler = TRACKED_NEW plSoundDeswizzler( (UInt32)(fBufferLengthInSecs * fDataStream->GetHeader().fAvgBytesPerSec),
(UInt8)(fDataStream->GetHeader().fNumChannels),
header.fBitsPerSample / 8 );
else
fDeswizzler = nil;
// LEAVE THE WAV FILE OPEN! (We *are* streaming, after all :)
return true;
}
void plWin32StreamingSound::IStreamUpdate()
{
if(!fReallyPlaying)
return;
if(hsTimer::GetMilliSeconds() - fLastStreamingUpdate < STREAMING_UPDATE_MS) // filter out update requests so we aren't doing this more that we need to
return;
plProfile_BeginTiming( StreamSndShoveTime );
if(fDSoundBuffer)
{
if(fDSoundBuffer->BuffersQueued() == 0 && fDataStream->NumBytesLeft() == 0 && !fDSoundBuffer->IsLooping())
{
// If we are fading out it's possible that we will hit this multiple times causing this sound to try and fade out multiple times, never allowing it to actually stop
if(!fStopping)
{
fStopping = true;
Stop();
plProfile_EndTiming( StreamSndShoveTime );
}
return;
}
if(!fDSoundBuffer->StreamingFillBuffer(fDataStream))
{
plStatusLog::AddLineS("audio.log", "%s Streaming buffer fill failed", GetKeyName());
}
}
plProfile_EndTiming( StreamSndShoveTime );
}
void plWin32StreamingSound::Update()
{
plWin32Sound::Update();
IStreamUpdate();
}
void plWin32StreamingSound::IDerivedActuallyPlay( void )
{
fStopping = false;
if( !fReallyPlaying )
{
for(;;)
{
if(fSynchedStartTimeSec)
{
// if we are synching to another sound this is our latency time
fDSoundBuffer->SetTimeOffsetSec((float)(hsTimer::GetSeconds() - fSynchedStartTimeSec));
fSynchedStartTimeSec = 0;
}
if(IsPropertySet(kPropIncidental))
{
if(fIncidentalsPlaying >= MAX_INCIDENTALS)
break;
++fIncidentalsPlaying;
}
fDSoundBuffer->Play();
fReallyPlaying = true;
break;
}
}
/// Send start callbacks
plSoundEvent *event = IFindEvent( plSoundEvent::kStart );
if( event != nil )
event->SendCallbacks();
}
void plWin32StreamingSound::IActuallyStop()
{
fStopping = false;
plWin32Sound::IActuallyStop();
if(fPlayWhenStopped)
{
fPlayWhenStopped = false;
Play();
}
}
unsigned plWin32StreamingSound::GetByteOffset()
{
if(fDataStream && fDSoundBuffer)
{
unsigned bytesQueued = fDSoundBuffer->BuffersQueued() * STREAM_BUFFER_SIZE;
unsigned offset = fDSoundBuffer->GetByteOffset();
long byteoffset = ((fDataStream->GetDataSize() - fDataStream->NumBytesLeft()) - bytesQueued) + offset;
return byteoffset < 0 ? fDataStream->GetDataSize() - abs(byteoffset) : byteoffset;
}
return 0;
}
float plWin32StreamingSound::GetActualTimeSec()
{
if(fDataStream && fDSoundBuffer)
return fDSoundBuffer->BytePosToMSecs(fDataStream->NumBytesLeft()) / 1000.0f;
return 0.0f;
}
void plWin32StreamingSound::SetStartPos(unsigned bytes)
{
fStartPos = bytes;
}
void plWin32StreamingSound::ISetActualTime( double t )
{
//fStartTimeSec = 0;
if(fDataStream && fDSoundBuffer)
fDataStream->SetPosition(fDSoundBuffer->GetBufferBytePos((float)t));
//else
// fStartTimeSec = t;
}
hsBool plWin32StreamingSound::MsgReceive( plMessage* pMsg )
{
return plWin32Sound::MsgReceive( pMsg );
}
void plWin32StreamingSound::IRemoveCallback( plEventCallbackMsg *pCBMsg )
{
plWin32Sound::IRemoveCallback( pCBMsg );
}
void plWin32StreamingSound::IAddCallback( plEventCallbackMsg *pCBMsg )
{
if( plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStop &&
plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStart )
{
hsAssert( false, "Streaming sounds only support start and stop callbacks at this time." );
return;
}
plWin32Sound::IAddCallback( pCBMsg );
}

View File

@ -0,0 +1,90 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plWin32StreamingSound_h
#define plWin32StreamingSound_h
#include "plWin32Sound.h"
#include "../pnUtils/pnUtils.h"
class plDSoundBuffer;
class DSoundCallbackHandle;
class plAudioFileReader;
class plStreamingSoundThread;
enum CallbackHandleType;
class plSoundDeswizzler;
class plWin32StreamingSound : public plWin32Sound
{
public:
plWin32StreamingSound();
~plWin32StreamingSound();
CLASSNAME_REGISTER( plWin32StreamingSound );
GETINTERFACE_ANY( plWin32StreamingSound, plWin32Sound );
virtual void DeActivate();
virtual hsBool LoadSound( hsBool is3D );
virtual float GetActualTimeSec();
virtual unsigned GetByteOffset();
virtual StreamType GetStreamType() const { return fStreamType; }
virtual void SetFilename(const char *filename, bool isCompressed);
virtual void Update(); // temp
void StreamUpdate();
virtual hsBool MsgReceive( plMessage *pMsg );
protected:
hsScalar fTimeAtBufferStart;
plAudioFileReader *fDataStream;
hsScalar fBufferLengthInSecs;
UInt8 fBlankBufferFillCounter;
plSoundDeswizzler *fDeswizzler;
char fSrcFilename[ 256 ];
StreamType fStreamType;
bool fIsCompressed; // this applies only to the new sound file specified in fNewFilename, so we can play both ogg's and wav's
std::string fNewFilename; // allow the filename to be changed so we can play from a different source.
// ultimately this filename will be given to fDataBuffer, but since it's not always around we'll store it here
hsBool fStopping;
double fLastStreamingUpdate;
bool fPlayWhenStopped;
unsigned fStartPos;
hsScalar IGetTimeAtBufferStart( void ) { return fTimeAtBufferStart; }
virtual void SetStartPos(unsigned bytes);
virtual void IDerivedActuallyPlay( void );
void IActuallyStop();
virtual void ISetActualTime( double t );
virtual void IAddCallback( plEventCallbackMsg *pMsg );
virtual void IRemoveCallback( plEventCallbackMsg *pMsg );
virtual void IFreeBuffers( void );
void IStreamUpdate();
virtual plSoundBuffer::ELoadReturnVal IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental = false );
};
#endif //plWin32StreamingSound_h

View File

@ -0,0 +1,361 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plWinMicLevel - Annoying class to deal with the annoying problem of //
// setting the microphone recording volume in Windows. //
// Yeah, you'd THINK there'd be some easier way... //
// //
//// Notes ///////////////////////////////////////////////////////////////////
// //
// 5.8.2001 - Created by mcn. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plWinMicLevel.h"
#include "hsWindows.h"
#include <mmsystem.h>
//// Our Local Static Data ///////////////////////////////////////////////////
int sNumMixers = 0;
HMIXER sMixerHandle = nil;
MIXERCAPS sMixerCaps;
DWORD sMinValue = 0, sMaxValue = 0;
DWORD sVolControlID = 0;
//// Local Static Helpers ////////////////////////////////////////////////////
hsBool IGetMuxMicVolumeControl( void );
hsBool IGetBaseMicVolumeControl( void );
hsBool IGetControlValue( DWORD &value );
hsBool ISetControlValue( DWORD value );
MIXERLINE *IGetLineByType( DWORD type );
MIXERLINE *IGetLineByID( DWORD id );
MIXERCONTROL *IGetControlByType( MIXERLINE *line, DWORD type );
MIXERLINE *IGetMixerSubLineByType( MIXERCONTROL *mux, DWORD type );
//// The Publics /////////////////////////////////////////////////////////////
hsScalar plWinMicLevel::GetLevel( void )
{
if( !CanSetLevel() )
return -1;
DWORD rawValue;
if( !IGetControlValue( rawValue ) )
return -1;
return (hsScalar)( rawValue - sMinValue ) / (hsScalar)( sMaxValue - sMinValue );
}
void plWinMicLevel::SetLevel( hsScalar level )
{
if( !CanSetLevel() )
return;
DWORD rawValue = (DWORD)(( level * ( sMaxValue - sMinValue ) ) + sMinValue);
ISetControlValue( rawValue );
}
hsBool plWinMicLevel::CanSetLevel( void )
{
// Just to init
plWinMicLevel &instance = IGetInstance();
return ( sMixerHandle != nil ) ? true : false;
}
//// Protected Init Stuff ////////////////////////////////////////////////////
plWinMicLevel &plWinMicLevel::IGetInstance( void )
{
static plWinMicLevel sInstance;
return sInstance;
}
plWinMicLevel::plWinMicLevel()
{
sMixerHandle = nil;
memset( &sMixerCaps, 0, sizeof( sMixerCaps ) );
// Get the number of mixers in the system
sNumMixers = ::mixerGetNumDevs();
// So long as we have one, open the first one
if( sNumMixers == 0 )
return;
if( ::mixerOpen( &sMixerHandle, 0,
nil, // Window handle to receive callback messages
nil, MIXER_OBJECTF_MIXER ) != MMSYSERR_NOERROR )
{
sMixerHandle = nil; // Just to be sure
return;
}
if( ::mixerGetDevCaps( (UINT)sMixerHandle, &sMixerCaps, sizeof( sMixerCaps ) ) != MMSYSERR_NOERROR )
{
// Oh well, who cares
}
// Try to get the Mux/mixer-based mic volume control first, since that seems to work better/more often/at all
if( !IGetMuxMicVolumeControl() )
{
// Failed, so try getting the volume control from the base mic-in line
if( !IGetBaseMicVolumeControl() )
{
IShutdown();
return;
}
}
}
plWinMicLevel::~plWinMicLevel()
{
IShutdown();
}
void plWinMicLevel::IShutdown( void )
{
if( sMixerHandle != nil )
::mixerClose( sMixerHandle );
sMixerHandle = nil;
}
//// IGetMuxMicVolumeControl /////////////////////////////////////////////////
// Tries to get the volume control of the microphone subline of the MUX or
// mixer control of the WaveIn destination line of the audio system (whew!)
// Note: testing indcates that this works but the direct SRC_MICROPHONE
// doesn't, hence we try this one first.
hsBool IGetMuxMicVolumeControl( void )
{
if( sMixerHandle == nil )
return false;
// Get the WaveIn destination line
MIXERLINE *waveInLine = IGetLineByType( MIXERLINE_COMPONENTTYPE_DST_WAVEIN );
if( waveInLine == nil )
return false;
// Get the mixer or MUX controller from the line
MIXERCONTROL *control = IGetControlByType( waveInLine, MIXERCONTROL_CONTROLTYPE_MIXER );
if( control == nil )
control = IGetControlByType( waveInLine, MIXERCONTROL_CONTROLTYPE_MUX );
if( control == nil )
return false;
// Get the microphone sub-component
// Note: this eventually calls IGetLineByType(), which destroys the waveInLine pointer we had before
MIXERLINE *micLine = IGetMixerSubLineByType( control, MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE );
if( micLine == nil )
return false;
// Get the volume subcontroller
MIXERCONTROL *micVolCtrl = IGetControlByType( micLine, MIXERCONTROL_CONTROLTYPE_VOLUME );
if( micVolCtrl == nil )
return false;
// Found it! store our values
char *dbgLineName = micLine->szName;
char *dbgControlName = micVolCtrl->szName;
sMinValue = micVolCtrl->Bounds.dwMinimum;
sMaxValue = micVolCtrl->Bounds.dwMaximum;
sVolControlID = micVolCtrl->dwControlID;
return true;
}
//// IGetBaseMicVolumeControl ////////////////////////////////////////////////
// Tries to get the volume control of the mic-in line. See
// IGetMuxMicVolumeControl for why we don't do this one first.
hsBool IGetBaseMicVolumeControl( void )
{
if( sMixerHandle == nil )
return false;
// Get the mic source line
MIXERLINE *micLine = IGetLineByType( MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE );
if( micLine == nil )
return false;
// Get the volume subcontroller
MIXERCONTROL *micVolCtrl = IGetControlByType( micLine, MIXERCONTROL_CONTROLTYPE_VOLUME );
if( micVolCtrl == nil )
return false;
// Found it! store our values
char *dbgLineName = micLine->szName;
char *dbgControlName = micVolCtrl->szName;
sMinValue = micVolCtrl->Bounds.dwMinimum;
sMaxValue = micVolCtrl->Bounds.dwMaximum;
sVolControlID = micVolCtrl->dwControlID;
return true;
}
//// IGetControlValue ////////////////////////////////////////////////////////
// Gets the raw value of the current volume control.
hsBool IGetControlValue( DWORD &value )
{
if( sMixerHandle == nil )
return false;
MIXERCONTROLDETAILS_UNSIGNED mxcdVolume;
MIXERCONTROLDETAILS mxcd;
mxcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mxcd.dwControlID = sVolControlID;
mxcd.cChannels = 1;
mxcd.cMultipleItems = 0;
mxcd.cbDetails = sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mxcd.paDetails = &mxcdVolume;
if( ::mixerGetControlDetails( (HMIXEROBJ)sMixerHandle, &mxcd,
MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE ) != MMSYSERR_NOERROR )
return false;
value = mxcdVolume.dwValue;
return true;
}
//// ISetControlValue ////////////////////////////////////////////////////////
// Sets the raw value of the current volume control.
hsBool ISetControlValue( DWORD value )
{
if( sMixerHandle == nil )
return false;
MIXERCONTROLDETAILS_UNSIGNED mxcdVolume = { value };
MIXERCONTROLDETAILS mxcd;
mxcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mxcd.dwControlID = sVolControlID;
mxcd.cChannels = 1;
mxcd.cMultipleItems = 0;
mxcd.cbDetails = sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mxcd.paDetails = &mxcdVolume;
if( ::mixerSetControlDetails( (HMIXEROBJ)sMixerHandle, &mxcd,
MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ) != MMSYSERR_NOERROR )
return false;
return true;
}
//// Helper Functions ////////////////////////////////////////////////////////
MIXERLINE *IGetLineByType( DWORD type )
{
static MIXERLINE mxl;
mxl.cbStruct = sizeof( MIXERLINE );
mxl.dwComponentType = type;
if( ::mixerGetLineInfo( (HMIXEROBJ)sMixerHandle, &mxl, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ) != MMSYSERR_NOERROR )
return nil;
return &mxl;
}
MIXERLINE *IGetLineByID( DWORD id )
{
static MIXERLINE mxl;
mxl.cbStruct = sizeof( MIXERLINE );
mxl.dwLineID = id;
if( ::mixerGetLineInfo( (HMIXEROBJ)sMixerHandle, &mxl, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_LINEID ) != MMSYSERR_NOERROR )
return nil;
return &mxl;
}
MIXERCONTROL *IGetControlByType( MIXERLINE *line, DWORD type )
{
static MIXERCONTROL mxc;
MIXERLINECONTROLS mxlc;
mxlc.cbStruct = sizeof( MIXERLINECONTROLS );
mxlc.dwLineID = line->dwLineID;
mxlc.dwControlType = type;
mxlc.cControls = 1;
mxlc.cbmxctrl = sizeof( MIXERCONTROL );
mxlc.pamxctrl = &mxc;
if( ::mixerGetLineControls( (HMIXEROBJ)sMixerHandle, &mxlc, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ) != MMSYSERR_NOERROR )
return nil;
return &mxc;
}
MIXERLINE *IGetMixerSubLineByType( MIXERCONTROL *mux, DWORD type )
{
// A mixer or MUX is really a combination of MORE lines. And beautifully, you can't
// just ask for a single one off of it, you have to ask for them all and search through yourself
MIXERCONTROLDETAILS_LISTTEXT *lineInfo = TRACKED_NEW MIXERCONTROLDETAILS_LISTTEXT[ mux->cMultipleItems ];
if( lineInfo == nil )
return nil;
MIXERCONTROLDETAILS details;
details.cbStruct = sizeof( MIXERCONTROLDETAILS );
details.dwControlID = mux->dwControlID;
details.cChannels = 1;
details.cMultipleItems = mux->cMultipleItems;
details.cbDetails = sizeof( MIXERCONTROLDETAILS_LISTTEXT );
details.paDetails = lineInfo;
if( ::mixerGetControlDetails( (HMIXEROBJ)sMixerHandle, &details, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_LISTTEXT ) != MMSYSERR_NOERROR )
{
delete [] lineInfo;
return nil;
}
// Loop through and find the one with the right component type. But of course it doesn't give us that offhand...
for( unsigned int i = 0; i < mux->cMultipleItems; i++ )
{
MIXERLINE *line = IGetLineByID( lineInfo[ i ].dwParam1 );
if( line->dwComponentType == type )
{
delete [] lineInfo;
return line;
}
}
delete [] lineInfo;
return nil;
}

View File

@ -0,0 +1,64 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef _plWinMicLevel_h
#define _plWinMicLevel_h
//////////////////////////////////////////////////////////////////////////////
// //
// plWinMicLevel - Annoying class to deal with the annoying problem of //
// setting the microphone recording volume in Windows. //
// Yeah, you'd THINK there'd be some easier way... //
// //
//// Notes ///////////////////////////////////////////////////////////////////
// //
// 5.8.2001 - Created by mcn. //
// //
//////////////////////////////////////////////////////////////////////////////
//// Class Definition ////////////////////////////////////////////////////////
class plWinMicLevel
{
public:
~plWinMicLevel();
// Gets the microphone volume, range 0-1, -1 if error
static hsScalar GetLevel( void );
// Sets the microphone volume, range 0-1
static void SetLevel( hsScalar level );
// Returns whether we can set the level
static hsBool CanSetLevel( void );
protected:
plWinMicLevel(); // Protected constructor for IGetInstance. Just to init some stuff
static plWinMicLevel &IGetInstance( void );
void IShutdown( void );
};
#endif // _plWinMicLevel_h