Files
source/Standard Gaming Platform/soundman.cpp
majcostaandGitHub 6b0c704b19 avoid static initialization order fiasco in soundman.cpp (#80)
when running the cmake Debug build instead of the ja2_vs2019.sln file s_SoundLog is undefined.
the most likely reason for that is that static objects in global scope
have unspecified initialization order. the problem is fixed if the
static SoundLog object is created inside the SoundLog(CHAR8*) function
instead.

also remove the comment whose meaning apparently has been lost to the
sands of time
2023-01-17 23:33:06 -08:00

1900 lines
54 KiB
C++

/*********************************************************************************
* SGP Digital Sound Module
*
* This module handles the playing of digital samples, preloaded or streamed.
*
* Derek Beland, May 28, 1997
*
*********************************************************************************/
#include "builddefines.h"
#include <stdio.h>
#include <string.h>
#include "soundman.h"
#include "FileMan.h"
#include "debug.h"
#include "MemMan.h"
#include "random.h"
#include "fmod.h"
#include "fmod_errors.h"
#include "sgp_logger.h"
// sevenfm
#include "message.h"
#include "Sound Control.h"
//#include "english.h"
//#include "input.h"
#include <ctime>
#include <chrono>
// Uncomment this to disable the startup of sound hardware
//#define SOUND_DISABLE
// global settings
#define SOUND_MAX_CACHED 128 // number of cache slots
// sevenfm: increased number of channels
#define SOUND_MAX_CHANNELS 128
// default memory limit
#define SOUND_DEFAULT_MEMORY (8048*1024)
// size for sample to be double-buffered
#define SOUND_DEFAULT_THRESH (256*8024)
// playing/random value to indicate default
#define SOUND_PARMS_DEFAULT 0xffffffff
// Sound status flags
#define SOUND_CALLBACK 0x00000008
// Max volume
#define MAX_VOLUME (127)
// Initialization parameters
#define AUDIO_BUFFER_LEN 100
#define STREAM_BUFFER_LEN 100
// Lesh modifications
// Sound debug
CHAR8 SndDebugFileName[]="sound.log";
FILE *SndDebug;
// Debug logging
void SoundLog(CHAR8 *strMessage);
void InitLogging();
// Cache system
BOOLEAN SoundInitCache(void);
BOOLEAN SoundShutdownCache(void);
UINT32 SoundLoadSample(STR pFilename);
UINT32 SoundFreeSample(STR pFilename);
UINT32 SoundGetCached(STR pFilename);
UINT32 SoundLoadDisk(STR pFilename);
BOOLEAN SoundCleanCache(void);
UINT32 SoundFreeSampleIndex(UINT32 uiSample);
// Low level
// Init, de-init
BOOLEAN SoundInitHardware(void);
BOOLEAN SoundShutdownHardware(void);
// Playing
UINT32 SoundStartSample(UINT32 uiSample, UINT32 uiChannel, SOUNDPARMS *pParms);
UINT32 SoundStartStream(STR pFilename, UINT32 uiChannel, SOUNDPARMS *pParms);
BOOLEAN SoundPlayStreamed(STR pFilename);
UINT32 SoundStartRandom(UINT32 uiSample);
BOOLEAN SoundRandomShouldPlay(UINT32 uiSample);
// Stopping
BOOLEAN SoundStopIndex(UINT32 uiSound);
// Checks
BOOLEAN SoundSampleIsPlaying(UINT32 uiSample);
BOOLEAN SoundIndexIsPlaying(UINT32 uiSound);
BOOLEAN SoundSampleIsInUse(UINT32 uiSample);
// Volume operations
UINT32 SoundGetVolumeIndex(UINT32 uiChannel);
BOOLEAN SoundSetVolumeIndex(UINT32 uiChannel, UINT32 uiVolume);
// Cleaning
UINT32 SoundGetEmptySample(void);
// Index, ID, other...
UINT32 SoundGetIndexByID(UINT32 uiSoundID);
UINT32 SoundGetFreeChannel(void);
UINT32 SoundGetUniqueID(void);
// Callbacks
void * F_CALLBACKAPI SoundFileOpen (const STR8 pName);
void F_CALLBACKAPI SoundFileClose(void *uiHandle);
INT F_CALLBACKAPI SoundFileRead (void *pBuffer, INT iSize, void *uiHandle);
INT F_CALLBACKAPI SoundFileSeek (void *uiHandle, INT iPos, signed char cMode);
INT F_CALLBACKAPI SoundFileTell (void *uiHandle);
// Global variables
UINT32 guiSoundDefaultVolume = 127;
UINT32 guiSoundMemoryLimit=SOUND_DEFAULT_MEMORY; // Maximum memory used for sounds
UINT32 guiSoundMemoryUsed=0; // Memory currently in use
UINT32 guiSoundCacheThreshold=SOUND_DEFAULT_THRESH; // Double-buffered threshold
BOOLEAN hSoundDriver; // Sound driver ready ?
BOOLEAN fDirectSound=TRUE; // Using Direct Sound
// Local module variables
BOOLEAN fSoundSystemInit=FALSE; // Startup called T/F
BOOLEAN gfEnableStartup=TRUE; // Allow hardware to starup
// Struct definition for sample slots in the cache
// Holds the regular sample data, as well as the
// data for the random samples
typedef struct {
CHAR8 pName[128]; // Sample path
UINT32 uiSize; // Sample size
UINT32 uiFlags; // Status flags
PTR pData; // Pointer to loaded sample
UINT32 uiCacheHits; // Cache hits for this sample
UINT32 uiTimeNext; // Random sound data
UINT32 uiTimeMin, uiTimeMax;
UINT32 uiVolMin, uiVolMax;
UINT32 uiPanMin, uiPanMax;
UINT32 uiPriority; // Priority
UINT32 uiInstances; // Instances of sample sounding at this moment
UINT32 uiMaxInstances; // Max allowable instances of sample
} SAMPLETAG;
// Structure definition for slots in the sound output
// These are used for both the cached and double-buffered
// streams
typedef struct {
UINT32 uiSample; // Sample slot in cache
FSOUND_STREAM* hStream; // Stream fmod handler
UINT32 uiFMODChannel; // Fmod channel
UINT32 uiFlags; // Sample flags
UINT32 uiSoundID; // Sound unique ID
UINT32 uiPriority; // Priority
void (*EOSCallback)(void *);
void *pCallbackData;
UINT32 uiTimeStamp; // Time stamp
BOOLEAN fLooping; // Loop flag
BOOLEAN fStopAtZero; // Stop at zero volume
UINT32 uiFadeVolume;
UINT32 uiFadeRate;
UINT32 uiFadeTime;
} SOUNDTAG;
// Sample cache list for files loaded
SAMPLETAG pSampleList[SOUND_MAX_CACHED];
// Sound channel list for output channels
SOUNDTAG pSoundList[SOUND_MAX_CHANNELS];
//*******************************************************************************
// High Level Interface
//*******************************************************************************
//*******************************************************************************
// SoundEnableSound
//
// Allows or disallows the startup of the sound hardware.
//
// Returns: Nothing.
//
//*******************************************************************************
void SoundEnableSound(BOOLEAN fEnable)
{
gfEnableStartup=fEnable;
}
//*******************************************************************************
// SoundGetDriverHandle
//
// Returns: Pointer to the system level output device module.
//
//*******************************************************************************
void *SoundGetDriverHandle( void )
{
if(fSoundSystemInit)
return(FSOUND_GetOutputHandle());
else
return(NULL);
}
//*******************************************************************************
// InitializeSoundManager
//
// Zeros out the structs for the system info, and initializes the cache.
//
// Returns: TRUE always
//
//*******************************************************************************
BOOLEAN InitializeSoundManager(void)
{
UINT32 uiCount;
InitLogging();
if(fSoundSystemInit)
{
SoundLog("Reopening JA2 sound manager");
ShutdownSoundManager();
}
else
SoundLog("Initialising JA2 sound manager");
SoundLog((CHAR8 *)String(" Using %d channels", SOUND_MAX_CHANNELS));
for(uiCount=0; uiCount < SOUND_MAX_CHANNELS; uiCount++)
{
memset(&pSoundList[uiCount], 0, sizeof(SOUNDTAG));
}
// Setup callbacks
FSOUND_File_SetCallbacks(SoundFileOpen, SoundFileClose, SoundFileRead, SoundFileSeek, SoundFileTell);
#ifndef SOUND_DISABLE
if(gfEnableStartup && SoundInitHardware())
fSoundSystemInit=TRUE;
#endif
SoundInitCache();
SoundLog((CHAR8 *)String(" Sound memory limit = %i", SOUND_DEFAULT_MEMORY));
SoundLog((CHAR8 *)String(" Cache threshold = %i", SOUND_DEFAULT_THRESH));
guiSoundMemoryLimit=SOUND_DEFAULT_MEMORY;
guiSoundMemoryUsed=0;
guiSoundCacheThreshold=SOUND_DEFAULT_THRESH;
return(TRUE);
}
//*******************************************************************************
// ShutdownSoundManager
//
// Silences all currently playing sound, deallocates any memory allocated,
// and releases the sound hardware.
//
//*******************************************************************************
void ShutdownSoundManager(void)
{
SoundLog("Closing sound system...");
SoundStopAll();
SoundShutdownCache();
Sleep(1000);
SoundShutdownHardware();
fSoundSystemInit=FALSE;
SoundLog("JA2 sound manager shutdown");
}
//*******************************************************************************
// SoundPlay
//
// Starts a sample playing. If the sample is not loaded in the cache, it will
// be found and loaded. The pParms structure is used to
// override the attributes of the sample such as playback speed, and to specify
// a volume. Any entry containing SOUND_PARMS_DEFAULT will be set by the system.
//
// Returns: If the sound was started, it returns a sound ID unique to that
// instance of the sound
// If an error occured, SOUND_ERROR will be returned
//
//
// !!Note: Can no longer play streamed files
//
//*******************************************************************************
std::map<std::string, uint64_t, std::less<>> gSoundMap;
uint64_t TimeMS()
{
using namespace std::chrono;
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}
UINT32 SoundPlay(STR pFilename, SOUNDPARMS *pParms)
{
UINT32 uiSample, uiChannel;
if (fSoundSystemInit)
{
if (!SoundPlayStreamed(pFilename))
{
// sevenfm: limit simultaneous sound playing
if (gGameExternalOptions.fLimitSimultaneousSound)
//!_KeyDown(SHIFT))
{
uint64_t curtime = TimeMS();
std::string filename(pFilename);
if (gSoundMap[filename] > curtime)
{
return 0;
}
// set delay for this sound type
gSoundMap[filename] = curtime + 50;
}
if ((uiSample = SoundLoadSample(pFilename)) != NO_SAMPLE)
{
if ((uiChannel = SoundGetFreeChannel()) != SOUND_ERROR)
{
return(SoundStartSample(uiSample, uiChannel, pParms));
}
else
{
SoundLog((CHAR8 *)String("Could not get free channel, uiChannel = %d", uiChannel));
}
}
else
{
SoundLog((CHAR8 *)String("Could not load sample, uiSample = %d", uiSample));
}
}
else
{
//Trying to play a sound which is bigger then the 'guiSoundCacheThreshold'
FastDebugMsg(String("SoundPlay: ERROR: Trying to play %s sound is too large to load into cache, use SoundPlayStreamedFile() instead\n", pFilename));
SoundLog((CHAR8 *)String("SoundPlay: ERROR: Trying to play %s sound is too large to load into cache, use SoundPlayStreamedFile() instead\n", pFilename));
}
}
else
{
SoundLog((CHAR8 *)String("SoundSystemInit FALSE"));
}
return(SOUND_ERROR);
}
void ResetSoundMap(void)
{
gSoundMap.clear();
}
//*******************************************************************************
// SoundPlayStreamedFile
//
// The sample will
// be played as a double-buffered sample. The pParms structure is used to
// override the attributes of the sample such as playback speed, and to specify
// a volume. Any entry containing SOUND_PARMS_DEFAULT will be set by the system.
//
// Returns: If the sound was started, it returns a sound ID unique to that
// instance of the sound
// If an error occured, SOUND_ERROR will be returned
//
//*******************************************************************************
UINT32 SoundPlayStreamedFile( STR pFilename, SOUNDPARMS *pParms )
{
UINT32 uiChannel;
HWFILE hFile;
if( fSoundSystemInit )
{
if((uiChannel=SoundGetFreeChannel())!=SOUND_ERROR)
{
hFile = FileOpen( pFilename, FILE_ACCESS_READ | FILE_OPEN_EXISTING, FALSE );
if( !hFile )
{
SoundLog((CHAR8 *)String(" ERROR in SoundPlayStreamedFile(): Couldnt open '%s'", pFilename ) );
return( SOUND_ERROR );
}
UINT32 uiSize = FileGetSize(hFile);
FileClose(hFile);
if (uiSize == 0)
return ( NO_SAMPLE);
return(SoundStartStream( pFilename, uiChannel, pParms));
}
}
return(SOUND_ERROR);
}
//*******************************************************************************
// SoundPlayRandom
//
// Registers a sample to be played randomly within the specified parameters.
// Parameters are passed in through pParms. Any parameter containing
// SOUND_PARMS_DEFAULT will be set by the system. Only the uiTimeMin entry may
// NOT be defaulted.
//
// * Samples designated "random" are ALWAYS loaded into the cache, and locked
// in place. They are never double-buffered, and this call will fail if they
// cannot be loaded. *
//
// Returns: If successful, it returns the sample index it is loaded to, else
// SOUND_ERROR is returned.
//
//*******************************************************************************
UINT32 SoundPlayRandom(STR pFilename, RANDOMPARMS *pParms)
{
UINT32 uiSample;
if(fSoundSystemInit)
{
if((uiSample=SoundLoadSample(pFilename))!=NO_SAMPLE)
{
// Sample loaded - marking slot
pSampleList[uiSample].uiFlags|=(SAMPLE_RANDOM|SAMPLE_LOCKED);
// Setup time intervals
if(pParms->uiTimeMin==SOUND_PARMS_DEFAULT)
return(SOUND_ERROR);
else
pSampleList[uiSample].uiTimeMin=pParms->uiTimeMin;
if(pParms->uiTimeMax==SOUND_PARMS_DEFAULT)
pSampleList[uiSample].uiTimeMax=pParms->uiTimeMin;
else
pSampleList[uiSample].uiTimeMax=pParms->uiTimeMax;
// Volume
if(pParms->uiVolMin==SOUND_PARMS_DEFAULT)
pSampleList[uiSample].uiVolMin=guiSoundDefaultVolume;
else
pSampleList[uiSample].uiVolMin=pParms->uiVolMin;
if(pParms->uiVolMax==SOUND_PARMS_DEFAULT)
pSampleList[uiSample].uiVolMax=guiSoundDefaultVolume;
else
pSampleList[uiSample].uiVolMax=pParms->uiVolMax;
// Panning
if(pParms->uiPanMin==SOUND_PARMS_DEFAULT)
{
pSampleList[uiSample].uiPanMin=128;
pSampleList[uiSample].uiPanMax=128;
}
else
{
pSampleList[uiSample].uiPanMin=pParms->uiPanMin;
pSampleList[uiSample].uiPanMax=pParms->uiPanMax;
}
// Max instances
if(pParms->uiMaxInstances==SOUND_PARMS_DEFAULT)
pSampleList[uiSample].uiMaxInstances=1;
else
pSampleList[uiSample].uiMaxInstances=pParms->uiMaxInstances;
// Priority
if(pParms->uiPriority==SOUND_PARMS_DEFAULT)
pSampleList[uiSample].uiPriority=PRIORITY_RANDOM;
else
pSampleList[uiSample].uiPriority=pParms->uiPriority;
pSampleList[uiSample].uiInstances=0;
// Time stamp
pSampleList[uiSample].uiTimeNext=GetTickCount()+pSampleList[uiSample].uiTimeMin+Random(pSampleList[uiSample].uiTimeMax-pSampleList[uiSample].uiTimeMin);
return(uiSample);
}
else
{
SoundLog((CHAR8 *)String(" ERROR in SoundPlayRandom(): Couldnt open '%s'", pFilename ) );
}
}
return(SOUND_ERROR);
}
//*******************************************************************************
// SoundIsPlaying
//
// Returns TRUE/FALSE that an instance of a sound is still playing.
//
//*******************************************************************************
BOOLEAN SoundIsPlaying(UINT32 uiSoundID)
{
UINT32 uiSound;
if(fSoundSystemInit)
{
uiSound=SoundGetIndexByID(uiSoundID);
if(uiSound!=NO_SAMPLE)
{
return(SoundIndexIsPlaying(uiSound));
}
}
return(FALSE);
}
//*****************************************************************************************
// SoundIndexIsPlaying
//
// Returns TRUE/FALSE whether a sound channel's sample is currently playing.
//
// Returns BOOLEAN - TRUE = playing, FALSE = stopped or nothing allocated
//
// UINT32 uiSound - Channel number of sound
//
// Created: 2/24/00 Derek Beland
//*****************************************************************************************
BOOLEAN SoundIndexIsPlaying(UINT32 uiSound)
{
if(fSoundSystemInit)
{
if( pSoundList[uiSound].hStream!=NULL )
return(FSOUND_IsPlaying(pSoundList[uiSound].uiFMODChannel));
}
return(FALSE);
}
//*******************************************************************************
// SoundStop
//
// Stops the playing of a sound instance, if still playing.
//
// Returns: TRUE if the sample was actually stopped, FALSE if it could not be
// found, or was not playing.
//
//*******************************************************************************
BOOLEAN SoundStop(UINT32 uiSoundID)
{
UINT32 uiSound;
if(fSoundSystemInit)
{
if(SoundIsPlaying(uiSoundID))
{
uiSound=SoundGetIndexByID(uiSoundID);
if(uiSound!=NO_SAMPLE)
{
SoundStopIndex(uiSound);
return(TRUE);
}
}
}
return(FALSE);
}
//*******************************************************************************
// SoundSetMemoryLimit
//
// Specifies how much memory the sound system is allowed to dynamically
// allocate. Once this limit is reached, the cache code will start dropping the
// least-used samples. You should always set the limit higher by a good margin
// than your actual memory requirements, to give the cache some elbow room.
//
// Returns: TRUE if the limit was set, or FALSE if the memory already used is
// greater than the limit requested.
//
//*******************************************************************************
BOOLEAN SoundSetMemoryLimit(UINT32 uiLimit)
{
if(guiSoundMemoryLimit < guiSoundMemoryUsed)
return(FALSE);
guiSoundMemoryLimit=uiLimit;
return(TRUE);
}
//*******************************************************************************
// SoundGetSystemInfo
//
// Returns information about the capabilities of the hardware. Currently does
// nothing.
//
// Returns: FALSE, always
//
//*******************************************************************************
BOOLEAN SoundGetSystemInfo(void)
{
return(FALSE);
}
//*****************************************************************************************
// SoundSetDefaultVolume
//
// Sets the volume to use when a default is not chosen.
//
// Returns BOOLEAN -
//
// UINT32 uiVolume -
//
// Created: 3/28/00 Derek Beland
//*****************************************************************************************
void SoundSetDefaultVolume(UINT32 uiVolume)
{
guiSoundDefaultVolume=__min(uiVolume, MAX_VOLUME);
}
//*****************************************************************************************
// SoundGetDefaultVolume
//
//
//
// Returns UINT32 -
//
// UINT32 uiVolume -
//
// Created: 3/28/00 Derek Beland
//*****************************************************************************************
UINT32 SoundGetDefaultVolume(void)
{
return(guiSoundDefaultVolume);
}
//*******************************************************************************
// SoundStopAll
//
// Stops all currently playing sounds.
//
// Returns: TRUE, always
//
//*******************************************************************************
BOOLEAN SoundStopAll(void)
{
UINT32 uiCount;
SoundLog(" Stopping all sounds");
if(fSoundSystemInit)
{
for(uiCount=0; uiCount < SOUND_MAX_CHANNELS; uiCount++)
SoundStopIndex(uiCount);
}
return(TRUE);
}
//*******************************************************************************
// SoundSetVolume
//
// Sets the volume on a currently playing sound.
//
// Returns: TRUE if the volume was actually set on the sample, FALSE if the
// sample had already expired or couldn't be found
//
//*******************************************************************************
BOOLEAN SoundSetVolume(UINT32 uiSoundID, UINT32 uiVolume)
{
UINT32 uiSound, uiVolCap;
if(fSoundSystemInit)
{
uiVolCap=__min(uiVolume, MAX_VOLUME);
if((uiSound=SoundGetIndexByID(uiSoundID))!=NO_SAMPLE)
{
pSoundList[uiSound].uiFadeVolume = uiVolume;
return(SoundSetVolumeIndex(uiSound, uiVolume));
}
}
return(FALSE);
}
//*****************************************************************************************
// SoundSetVolumeIndex
//
// Sounds the volume on a sound channel.
//
// Returns BOOLEAN - TRUE if the volume was set
//
// UINT32 uiChannel - Sound channel
// UINT32 uiVolume - New volume 0-127
//
// Created: 3/17/00 Derek Beland
//*****************************************************************************************
BOOLEAN SoundSetVolumeIndex(UINT32 uiChannel, UINT32 uiVolume)
{
UINT32 uiVolCap;
if(fSoundSystemInit)
{
uiVolCap=__min(uiVolume, MAX_VOLUME);
if( pSoundList[uiChannel].hStream!=NULL )
FSOUND_SetVolume(pSoundList[uiChannel].uiFMODChannel, uiVolCap * 2);
return(TRUE);
}
return(FALSE);
}
//*******************************************************************************
// SoundSetPan
//
// Sets the pan on a currently playing sound.
//
// Returns: TRUE if the pan was actually set on the sample, FALSE if the
// sample had already expired or couldn't be found
//
//*******************************************************************************
BOOLEAN SoundSetPan(UINT32 uiSoundID, UINT32 uiPan)
{
UINT32 uiSound, uiPanCap;
if(fSoundSystemInit)
{
uiPanCap=__min(uiPan, 255);
if((uiSound=SoundGetIndexByID(uiSoundID))!=NO_SAMPLE)
{
if( pSoundList[uiSound].hStream!=NULL )
FSOUND_SetPan(pSoundList[uiSound].uiFMODChannel, uiPanCap);
return(TRUE);
}
}
return(FALSE);
}
//*******************************************************************************
// SoundGetVolume
//
// Returns the current volume setting of a sound that is playing. If the sound
// has expired, or could not be found, SOUND_ERROR is returned.
//
//*******************************************************************************
UINT32 SoundGetVolume(UINT32 uiSoundID)
{
UINT32 uiSound;
if(fSoundSystemInit)
{
if((uiSound=SoundGetIndexByID(uiSoundID))!=NO_SAMPLE)
return(SoundGetVolumeIndex(uiSound));
}
return(SOUND_ERROR);
}
//*****************************************************************************************
// SoundGetVolumeIndex
//
// Returns the current volume of a sound channel.
//
// Returns UINT32 - Volume 0-127
//
// UINT32 uiChannel - Channel
//
// Created: 3/17/00 Derek Beland
//*****************************************************************************************
UINT32 SoundGetVolumeIndex(UINT32 uiChannel)
{
if(fSoundSystemInit)
{
if( pSoundList[uiChannel].hStream!=NULL )
return((UINT32)FSOUND_GetVolume(pSoundList[uiChannel].uiFMODChannel) / 2);
}
return(SOUND_ERROR);
}
//*******************************************************************************
// SoundServiceRandom
//
// This function should be polled by the application if random samples are
// used. The time marks on each are checked and if it is time to spawn a new
// instance of the sound, the number already in existance are checked, and if
// there is room, a new one is made and the count updated.
// If random samples are not being used, there is no purpose in polling this
// function.
//
// Returns: TRUE if a new random sound was created, FALSE if nothing was done.
//
//*******************************************************************************
BOOLEAN SoundServiceRandom(void)
{
UINT32 uiCount;
BOOLEAN fRandomSoundWasCreated=FALSE;
for(uiCount=0; uiCount < SOUND_MAX_CACHED; uiCount++)
{
if(!(pSampleList[uiCount].uiFlags&SAMPLE_RANDOM_MANUAL) && SoundRandomShouldPlay(uiCount))
fRandomSoundWasCreated |= SoundStartRandom(uiCount);
}
return(fRandomSoundWasCreated);
}
//*******************************************************************************
// SoundRandomShouldPlay
//
// Determines whether a random sound is ready for playing or not.
//
// Returns: TRUE if a the sample should be played.
//
//*******************************************************************************
BOOLEAN SoundRandomShouldPlay(UINT32 uiSample)
{
if(pSampleList[uiSample].uiFlags&SAMPLE_RANDOM)
if(pSampleList[uiSample].uiTimeNext <= GetTickCount())
if(pSampleList[uiSample].uiInstances < pSampleList[uiSample].uiMaxInstances)
{
return(TRUE);
}
return(FALSE);
}
//*******************************************************************************
// SoundStartRandom
//
// Starts an instance of a random sample.
//
// Returns: TRUE if a new random sound was created, FALSE if nothing was done.
//
//*******************************************************************************
UINT32 SoundStartRandom(UINT32 uiSample)
{
UINT32 uiChannel, uiSoundID;
SOUNDPARMS spParms;
if((uiChannel=SoundGetFreeChannel())!=SOUND_ERROR)
{
memset(&spParms, 0xff, sizeof(SOUNDPARMS));
spParms.uiVolume=pSampleList[uiSample].uiVolMin+Random(pSampleList[uiSample].uiVolMax-pSampleList[uiSample].uiVolMin);
spParms.uiPan=pSampleList[uiSample].uiPanMin+Random(pSampleList[uiSample].uiPanMax-pSampleList[uiSample].uiPanMin);
spParms.uiLoop=1;
spParms.uiPriority=pSampleList[uiSample].uiPriority;
if((uiSoundID=SoundStartSample(uiSample, uiChannel, &spParms))!=SOUND_ERROR)
{
pSampleList[uiSample].uiTimeNext=GetTickCount()+pSampleList[uiSample].uiTimeMin+Random(pSampleList[uiSample].uiTimeMax-pSampleList[uiSample].uiTimeMin);
pSampleList[uiSample].uiInstances++;
//SoundLog((CHAR8 *)String(" SoundPlayRandom(): Sample #%d = '%s'", uiSample, pFilename ) );
return(TRUE);
}
else
SoundLog((CHAR8 *)String(" ERROR in SoundStartRandom(): Sample #%d start error - %s", uiSample, FMOD_ErrorString(FSOUND_GetError())));
}
else
SoundLog(" ERROR in SoundStartRandom(): Failed to get free channel");
return(FALSE);
}
//*******************************************************************************
// SoundStopAllRandom
//
// This function should be polled by the application if random samples are
// used. The time marks on each are checked and if it is time to spawn a new
// instance of the sound, the number already in existance are checked, and if
// there is room, a new one is made and the count updated.
// If random samples are not being used, there is no purpose in polling this
// function.
//
// Returns: TRUE if a new random sound was created, FALSE if nothing was done.
//
//*******************************************************************************
BOOLEAN SoundStopAllRandom(void)
{
UINT32 uiChannel, uiSample;
// Stop all currently playing random sounds
for(uiChannel=0; uiChannel < SOUND_MAX_CHANNELS; uiChannel++)
{
if( pSoundList[uiChannel].hStream!=NULL )
{
uiSample=pSoundList[uiChannel].uiSample;
// if this was a random sample, decrease the iteration count
if (uiSample != -1 && (pSampleList[uiSample].uiFlags & SAMPLE_RANDOM))
{
SoundStopIndex(uiChannel);
}
}
}
// Unlock all random sounds so they can be dumped from the cache, and
// take the random flag off so they won't be serviced/played
for(uiSample=0; uiSample < SOUND_MAX_CACHED; uiSample++)
{
if(pSampleList[uiSample].uiFlags & SAMPLE_RANDOM)
pSampleList[uiSample].uiFlags &= (~(SAMPLE_RANDOM | SAMPLE_LOCKED));
}
return(TRUE);
}
//*******************************************************************************
// SoundServiceStreams
//
// Can be polled in tight loops where sound buffers might starve due to heavy
// hardware use, etc. Streams DO NOT normally need to be serviced manually, but
// in some cases (heavy file loading) it might be desirable.
//
// If you are using the end of sample callbacks, you must call this function
// periodically to check the sample's status.
//
// Returns: TRUE always.
//
//*******************************************************************************
BOOLEAN SoundServiceStreams(void)
{
UINT32 uiCount;
if(fSoundSystemInit)
{
for(uiCount=0; uiCount < SOUND_MAX_CHANNELS; uiCount++)
{
if( (pSoundList[uiCount].hStream!=NULL) && (pSoundList[uiCount].uiSample==-1) )
{
// If a sound has a handle, but isn't playing, stop it and free up the handle
if(!SoundIsPlaying(pSoundList[uiCount].uiSoundID))
{
SoundStopIndex(uiCount);
}
else
{ // Check the volume fades on currently playing sounds
UINT32 uiVolume = SoundGetVolumeIndex(uiCount);
UINT32 uiTime = GetTickCount();
if((uiVolume != pSoundList[uiCount].uiFadeVolume) && (uiTime >= (pSoundList[uiCount].uiFadeTime + pSoundList[uiCount].uiFadeRate)) )
{
if(uiVolume < pSoundList[uiCount].uiFadeVolume)
SoundSetVolumeIndex(uiCount, ++uiVolume);
else if(uiVolume > pSoundList[uiCount].uiFadeVolume)
{
uiVolume--;
if(!uiVolume && pSoundList[uiCount].fStopAtZero)
{
SoundStopIndex(uiCount);
SoundLog((CHAR8 *)String(" SoundServiceStreams(): Stop at zero volume on channel %d", uiCount));
}
else
SoundSetVolumeIndex(uiCount, uiVolume);
}
pSoundList[uiCount].uiFadeTime = uiTime;
}
}
}
}
}
return(TRUE);
}
//*******************************************************************************
// SoundGetPosition
//
// Reports the current time position of the sample.
//
// Note: You should be checking SoundIsPlaying very carefully while
// calling this function.
//
// Returns: The current time of the sample in milliseconds.
//
//*******************************************************************************
UINT32 SoundGetPosition(UINT32 uiSoundID)
{
UINT32 uiSound, uiTime, uiPosition;
if(fSoundSystemInit)
{
if((uiSound=SoundGetIndexByID(uiSoundID))!=NO_SAMPLE)
{
uiTime=GetTickCount();
// check for rollover
if(uiTime < pSoundList[uiSound].uiTimeStamp)
uiPosition=(0-pSoundList[uiSound].uiTimeStamp)+uiTime;
else
uiPosition=(uiTime-pSoundList[uiSound].uiTimeStamp);
return(uiPosition);
}
}
return(0);
}
//*******************************************************************************
// Cacheing Subsystem
//*******************************************************************************
//*******************************************************************************
// SoundInitCache
//
// Zeros out the structures of the sample list.
//
//*******************************************************************************
BOOLEAN SoundInitCache(void)
{
UINT32 uiCount;
SoundLog("Init cache...");
SoundLog((CHAR8 *)String(" Using %d cache slots", SOUND_MAX_CACHED));
for(uiCount=0; uiCount < SOUND_MAX_CACHED; uiCount++)
{
memset(&pSampleList[uiCount], 0, sizeof(SAMPLETAG));
}
return(TRUE);
}
//*******************************************************************************
// SoundShutdownCache
//
// Empties out the cache.
//
// Returns: TRUE, always
//
//*******************************************************************************
BOOLEAN SoundShutdownCache(void)
{
SoundEmptyCache();
SoundLog("Cache shutdown");
return(TRUE);
}
//*******************************************************************************
// SoundSetCacheThreshold
//
// Sets the sound size above which samples will be played double-buffered,
// below which they will be loaded into the cache.
//
// Returns: TRUE, always
//
//*******************************************************************************
BOOLEAN SoundSetCacheThreshhold(UINT32 uiThreshold)
{
if(uiThreshold==0)
guiSoundCacheThreshold=SOUND_DEFAULT_THRESH;
else
guiSoundCacheThreshold=uiThreshold;
return(TRUE);
}
//*******************************************************************************
// SoundEmptyCache
//
// Frees up all samples in the cache.
//
// Returns: TRUE, always
//
//*******************************************************************************
BOOLEAN SoundEmptyCache(void)
{
UINT32 uiCount;
SoundLog("Cleaning cache");
SoundStopAll();
for(uiCount=0; uiCount < SOUND_MAX_CACHED; uiCount++)
SoundFreeSampleIndex(uiCount);
return(TRUE);
}
//*******************************************************************************
// SoundLoadSample
//
// Loads a sample into cache.
//
// Returns: Sample index, if OK. NO_SAMPLE - if error.
//
//*******************************************************************************
UINT32 SoundLoadSample(STR pFilename)
{
UINT32 uiSample=NO_SAMPLE;
if((uiSample=SoundGetCached(pFilename))!=NO_SAMPLE)
return(uiSample);
return(SoundLoadDisk(pFilename));
}
//*******************************************************************************
// SoundLockSample
//
// Locks a sample into cache memory, so the cacheing system won't release it
// when it needs room.
//
// Returns: The sample index if successful, NO_SAMPLE if the file wasn't found
// in the cache.
//
//*******************************************************************************
UINT32 SoundLockSample(STR pFilename)
{
UINT32 uiSample;
if((uiSample=SoundGetCached(pFilename))!=NO_SAMPLE)
{
pSampleList[uiSample].uiFlags|=SAMPLE_LOCKED;
return(uiSample);
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundUnlockSample
//
// Removes the lock on a sample so the cache is free to dump it when necessary.
//
// Returns: The sample index if successful, NO_SAMPLE if the file wasn't found
// in the cache.
//
//*******************************************************************************
UINT32 SoundUnlockSample(STR pFilename)
{
UINT32 uiSample;
if((uiSample=SoundGetCached(pFilename))!=NO_SAMPLE)
{
pSampleList[uiSample].uiFlags&=(~SAMPLE_LOCKED);
return(uiSample);
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundFreeSample
//
// Releases the resources associated with a sample from the cache.
//
// Returns: The sample index if successful, NO_SAMPLE if the file wasn't found
// in the cache.
//
//*******************************************************************************
UINT32 SoundFreeSample(STR pFilename)
{
UINT32 uiSample;
if((uiSample=SoundGetCached(pFilename))!=NO_SAMPLE)
{
if(!SoundSampleIsPlaying(uiSample))
{
SoundFreeSampleIndex(uiSample);
return(uiSample);
}
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundGetCached
//
// Tries to locate a sound by looking at what is currently loaded in the
// cache.
//
// Returns: The sample index if successful, NO_SAMPLE if the file wasn't found
// in the cache.
//
//*******************************************************************************
UINT32 SoundGetCached(STR pFilename)
{
UINT32 uiCount;
for(uiCount=0; uiCount < SOUND_MAX_CACHED; uiCount++)
{
if(_stricmp(pSampleList[uiCount].pName, pFilename)==0)
return(uiCount);
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundLoadDisk
//
// Loads a sound file from disk into the cache, allocating memory and a slot
// for storage.
//
//
// Returns: The sample index if successful, NO_SAMPLE if the file wasn't found
// in the cache.
//
//*******************************************************************************
UINT32 SoundLoadDisk(STR pFilename)
{
HWFILE hFile;
UINT32 uiSize, uiSample;
BOOLEAN fRemoved;
if((hFile=FileOpen(pFilename, FILE_ACCESS_READ, FALSE))!=0)
{
uiSize=FileGetSize(hFile);
if(uiSize == 0)
{
return NO_SAMPLE;
}
// if insufficient memory, start unloading old samples until either
// there's nothing left to unload, or we fit
fRemoved=TRUE;
while(((uiSize + guiSoundMemoryUsed) > guiSoundMemoryLimit) && (fRemoved))
fRemoved=SoundCleanCache();
// if we still don't fit
if((uiSize + guiSoundMemoryUsed) > guiSoundMemoryLimit)
{
SoundLog((CHAR8 *)String(" ERROR in SoundLoadDisk(): trying to play '%s', not enough memory", pFilename ) );
FileClose(hFile);
return(NO_SAMPLE);
}
// if all the sample slots are full, unloading one
if((uiSample=SoundGetEmptySample())==NO_SAMPLE)
{
SoundCleanCache();
uiSample=SoundGetEmptySample();
}
// if we still don't have a sample slot
if(uiSample==NO_SAMPLE)
{
SoundLog((CHAR8 *)String(" ERROR in SoundLoadDisk(): Trying to play '%s', cache slots are full", pFilename ) );
FileClose(hFile);
return(NO_SAMPLE);
}
memset(&pSampleList[uiSample], 0, sizeof(SAMPLETAG));
if((pSampleList[uiSample].pData=MemAlloc(uiSize))==NULL)
{
SoundLog((CHAR8 *)String(" ERROR in SoundLoadDisk(): Trying to play '%s', memory allocation failed", pFilename ) );
FileClose(hFile);
return(NO_SAMPLE);
}
guiSoundMemoryUsed+=uiSize;
FileRead(hFile, pSampleList[uiSample].pData, uiSize, NULL);
FileClose(hFile);
strcpy(pSampleList[uiSample].pName, pFilename);
_strupr(pSampleList[uiSample].pName);
pSampleList[uiSample].uiSize=uiSize;
pSampleList[uiSample].uiFlags|=SAMPLE_ALLOCATED;
return(uiSample);
}
else
{
SoundLog((CHAR8 *)String(" ERROR in SoundLoadDisk(): Failed to open '%s'", pFilename));
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundCleanCache
//
// Removes the least-used sound from the cache to make room.
//
// Returns: TRUE if a sample was freed, FALSE if none
//
//*******************************************************************************
BOOLEAN SoundCleanCache(void)
{
UINT32 uiCount, uiLowestHits=NO_SAMPLE, uiLowestHitsCount=0;
for(uiCount=0; uiCount < SOUND_MAX_CACHED; uiCount++)
{
if((pSampleList[uiCount].uiFlags&SAMPLE_ALLOCATED) &&
!(pSampleList[uiCount].uiFlags&SAMPLE_LOCKED))
{
if((uiLowestHits==NO_SAMPLE) || (uiLowestHitsCount < pSampleList[uiCount].uiCacheHits))
{
if(!SoundSampleIsPlaying(uiCount))
{
uiLowestHits=uiCount;
uiLowestHitsCount=pSampleList[uiCount].uiCacheHits;
}
}
}
}
if(uiLowestHits!=NO_SAMPLE)
{
SoundFreeSampleIndex(uiLowestHits);
return(TRUE);
}
return(FALSE);
}
//*******************************************************************************
// Low Level Interface (Local use only)
//*******************************************************************************
//*******************************************************************************
// SoundSampleIsPlaying
//
// Returns TRUE/FALSE that a sample is currently in use for playing a sound.
//
//*******************************************************************************
BOOLEAN SoundSampleIsPlaying(UINT32 uiSample)
{
UINT32 uiCount;
for(uiCount=0; uiCount < SOUND_MAX_CHANNELS; uiCount++)
{
if(pSoundList[uiCount].uiSample==uiSample)
return(TRUE);
}
return(FALSE);
}
//*******************************************************************************
// SoundGetEmptySample
//
// Returns the slot number of an available sample index.
//
// Returns: A free sample index, or NO_SAMPLE if none are left.
//
//*******************************************************************************
UINT32 SoundGetEmptySample(void)
{
UINT32 uiCount;
for(uiCount=0; uiCount < SOUND_MAX_CACHED; uiCount++)
{
if(!(pSampleList[uiCount].uiFlags&SAMPLE_ALLOCATED))
return(uiCount);
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundFreeSampleIndex
//
// Frees up a sample referred to by it's index slot number.
//
// Returns: Slot number if something was free, NO_SAMPLE otherwise.
//
//*******************************************************************************
UINT32 SoundFreeSampleIndex(UINT32 uiSample)
{
if(pSampleList[uiSample].uiFlags&SAMPLE_ALLOCATED)
{
if(pSampleList[uiSample].pData!=NULL)
{
guiSoundMemoryUsed-=pSampleList[uiSample].uiSize;
MemFree(pSampleList[uiSample].pData);
}
memset(&pSampleList[uiSample], 0, sizeof(SAMPLETAG));
return(uiSample);
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundGetIndexByID
//
// Searches out a sound instance referred to by it's ID number.
//
// Returns: If the instance was found, the slot number. NO_SAMPLE otherwise.
//
//*******************************************************************************
UINT32 SoundGetIndexByID(UINT32 uiSoundID)
{
UINT32 uiCount;
for(uiCount=0; uiCount < SOUND_MAX_CHANNELS; uiCount++)
{
if(pSoundList[uiCount].uiSoundID==uiSoundID)
return(uiCount);
}
return(NO_SAMPLE);
}
//*******************************************************************************
// SoundInitHardware
//
// Initializes the sound hardware through Windows/DirectX. THe highest possible
// mixing rate and capabilities set are searched out and used.
//
// Returns: TRUE if the hardware was initialized, FALSE otherwise.
//
//*******************************************************************************
BOOLEAN SoundInitHardware(void)
{
UINT32 uiCaps;
SoundLog("Init hardware...");
// Try to start up the FMOD Sound System
FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
FSOUND_SetBufferSize(AUDIO_BUFFER_LEN);
SoundLog((CHAR8 *)String(" Using DirectSound driver: %s", FSOUND_GetDriverName(FSOUND_GetDriver())));
SoundLog(" Driver capabilities:");
FSOUND_GetDriverCaps( FSOUND_GetDriver(), &uiCaps );
if ( uiCaps & FSOUND_CAPS_HARDWARE )
SoundLog(" - supports hardware accelerated 3d sound");
if ( uiCaps & FSOUND_CAPS_EAX2 )
SoundLog(" - supports EAX 2 reverb");
if ( uiCaps & FSOUND_CAPS_EAX3 )
SoundLog(" - supports EAX 3 reverb");
if( !FSOUND_Init(44100, SOUND_MAX_CHANNELS, FSOUND_INIT_GLOBALFOCUS|FSOUND_INIT_DONTLATENCYADJUST) )
{
SoundLog((CHAR8 *)String(" ERROR in SoundInitHardware(): %s", FMOD_ErrorString(FSOUND_GetError())));
return(FALSE);
}
SoundLog(" FMOD started");
SoundLog((CHAR8 *)String(" Mixing rate: %d", FSOUND_GetOutputRate()));
// Driver is ready
hSoundDriver = TRUE;
fDirectSound = TRUE;
return(hSoundDriver);
}
//*******************************************************************************
// SoundShutdownHardware
//
// Shuts down the system hardware.
//
// Returns: TRUE always.
//
//*******************************************************************************
BOOLEAN SoundShutdownHardware(void)
{
if(fSoundSystemInit)
FSOUND_Close();
SoundLog(" FMOD closed");
return(TRUE);
}
//*******************************************************************************
// SoundGetFreeChannel
//
// Finds an unused sound channel in the channel list.
//
// Returns: Index of a sound channel if one was found, SOUND_ERROR if not.
//
//*******************************************************************************
UINT32 SoundGetFreeChannel(void)
{
UINT32 uiCount;
for(uiCount=0; uiCount < SOUND_MAX_CHANNELS; uiCount++)
{
if(!SoundIsPlaying(pSoundList[uiCount].uiSoundID))
{
SoundStopIndex(uiCount);
}
if( pSoundList[uiCount].hStream==NULL )
return(uiCount);
}
return(SOUND_ERROR);
}
//*******************************************************************************
// SoundStartSample
//
// Starts up a sample on the specified channel. Override parameters are passed
// in through the structure pointer pParms. Any entry with a value of 0xffffffff
// will be filled in by the system.
//
// Returns: Unique sound ID if successful, SOUND_ERROR if not.
//
//*******************************************************************************
UINT32 SoundStartSample(UINT32 uiSample, UINT32 uiChannel, SOUNDPARMS *pParms)
{
UINT32 uiSoundID;
if(!fSoundSystemInit)
return(SOUND_ERROR);
// Setting up stream buffer
FSOUND_Stream_SetBufferSize(STREAM_BUFFER_LEN);
// Creating stream
pSoundList[uiChannel].hStream=FSOUND_Stream_Open((STR8) pSampleList[uiSample].pData, FSOUND_LOADMEMORY|FSOUND_LOOP_NORMAL|FSOUND_2D, 0, pSampleList[uiSample].uiSize);
if(pSoundList[uiChannel].hStream==NULL)
{
SoundLog((CHAR8 *)String(" ERROR in SoundStartSample(): %s", FMOD_ErrorString(FSOUND_GetError()) ));
return(SOUND_ERROR);
}
// Setup sample params
// Loop
if((pParms!=NULL) && (pParms->uiLoop!=SOUND_PARMS_DEFAULT))
{
// If looping infinitely, lock the sample so it can't be unloaded and mark it as a looping sound
if(pParms->uiLoop==0)
{
pSampleList[uiSample].uiFlags|=SAMPLE_LOCKED;
pSoundList[uiChannel].fLooping=TRUE;
}
else
FSOUND_Stream_SetLoopCount(pSoundList[uiChannel].hStream, pParms->uiLoop-1);
}
// Starting stream in pause
pSoundList[uiChannel].uiFMODChannel = FSOUND_Stream_PlayEx(uiChannel, pSoundList[uiChannel].hStream, NULL, TRUE);
if(pSoundList[uiChannel].uiFMODChannel==-1)
{
FSOUND_Stream_Close(pSoundList[uiChannel].hStream);
SoundLog((CHAR8 *)String(" ERROR in SoundStartSample(): %s", FMOD_ErrorString(FSOUND_GetError()) ));
return(SOUND_ERROR);
}
// Speed and pitchbend don't use
// Volume
if((pParms!=NULL) && (pParms->uiVolume!=SOUND_PARMS_DEFAULT))
FSOUND_SetVolume(pSoundList[uiChannel].uiFMODChannel, pParms->uiVolume * 2);
else
FSOUND_SetVolume(pSoundList[uiChannel].uiFMODChannel, guiSoundDefaultVolume);
// Panning
if((pParms!=NULL) && (pParms->uiPan!=SOUND_PARMS_DEFAULT))
FSOUND_SetPan(pSoundList[uiChannel].uiFMODChannel, pParms->uiPan);
else
FSOUND_SetPan(pSoundList[uiChannel].uiFMODChannel, 128);
// Start sound!
FSOUND_SetPaused(pSoundList[uiChannel].uiFMODChannel, FALSE);
// Priority
if((pParms!=NULL) && (pParms->uiPriority!=SOUND_PARMS_DEFAULT))
pSoundList[uiChannel].uiPriority=pParms->uiPriority;
else
pSoundList[uiChannel].uiPriority=PRIORITY_MAX;
// Callback at end of playback
if((pParms!=NULL) && ((UINT32)pParms->EOSCallback!=SOUND_PARMS_DEFAULT))
{
pSoundList[uiChannel].EOSCallback=pParms->EOSCallback;
pSoundList[uiChannel].pCallbackData=pParms->pCallbackData;
}
else
{
pSoundList[uiChannel].EOSCallback=NULL;
pSoundList[uiChannel].pCallbackData=NULL;
}
// Other stuff...
uiSoundID=SoundGetUniqueID();
pSoundList[uiChannel].uiSoundID=uiSoundID;
pSoundList[uiChannel].uiSample=uiSample;
pSoundList[uiChannel].uiTimeStamp=GetTickCount();
pSoundList[uiChannel].uiFadeVolume = SoundGetVolumeIndex(uiChannel);
pSampleList[uiSample].uiCacheHits++;
return(uiSoundID);
}
//*******************************************************************************
// SoundStartStream
//
// Starts up a stream on the specified channel. Override parameters are passed
// in through the structure pointer pParms. Any entry with a value of 0xffffffff
// will be filled in by the system.
//
// Returns: Unique sound ID if successful, SOUND_ERROR if not.
//
//*******************************************************************************
UINT32 SoundStartStream(STR pFilename, UINT32 uiChannel, SOUNDPARMS *pParms)
{
UINT32 uiSoundID;
if(!fSoundSystemInit)
return(SOUND_ERROR);
// Setting up stream buffer
FSOUND_Stream_SetBufferSize(STREAM_BUFFER_LEN);
// Creating stream
pSoundList[uiChannel].hStream=FSOUND_Stream_Open(pFilename, FSOUND_LOOP_NORMAL|FSOUND_2D, 0, 0);
if(pSoundList[uiChannel].hStream==NULL)
{
SoundLog((CHAR8 *)String(" ERROR in SoundStartStream(): %s ('%s')", FMOD_ErrorString(FSOUND_GetError()), pFilename));
return(SOUND_ERROR);
}
// Setup params
// Loop
if( (pParms!=NULL) && (pParms->uiLoop!=SOUND_PARMS_DEFAULT ) )
{
if(pParms->uiLoop>0)
FSOUND_Stream_SetLoopCount(pSoundList[uiChannel].hStream, pParms->uiLoop-1);
}
// Starting stream in pause
pSoundList[uiChannel].uiFMODChannel = FSOUND_Stream_PlayEx(uiChannel, pSoundList[uiChannel].hStream, NULL, TRUE);
if(pSoundList[uiChannel].uiFMODChannel==-1)
{
FSOUND_Stream_Close(pSoundList[uiChannel].hStream);
SoundLog((CHAR8 *)String(" ERROR in SoundStartStream(): %s ('%s')", FMOD_ErrorString(FSOUND_GetError()), pFilename));
return(SOUND_ERROR);
}
// Speed and pitchbend don't use
// Volume
if((pParms!=NULL) && (pParms->uiVolume!=SOUND_PARMS_DEFAULT))
FSOUND_SetVolume(pSoundList[uiChannel].uiFMODChannel, pParms->uiVolume * 2);
else
FSOUND_SetVolume(pSoundList[uiChannel].uiFMODChannel, guiSoundDefaultVolume);
// Panning
if((pParms!=NULL) && (pParms->uiPan!=SOUND_PARMS_DEFAULT))
FSOUND_SetPan(pSoundList[uiChannel].uiFMODChannel, pParms->uiPan);
// Start sound!
FSOUND_SetPaused(pSoundList[uiChannel].uiFMODChannel, FALSE);
// Get ID
uiSoundID=SoundGetUniqueID();
pSoundList[uiChannel].uiSoundID=uiSoundID;
// Priority
if(pParms)
pSoundList[uiChannel].uiPriority=pParms->uiPriority;
else
pSoundList[uiChannel].uiPriority=SOUND_PARMS_DEFAULT;
// Callback at end of playback
if((pParms!=NULL) && ((UINT32)pParms->EOSCallback!=SOUND_PARMS_DEFAULT))
{
pSoundList[uiChannel].EOSCallback=pParms->EOSCallback;
pSoundList[uiChannel].pCallbackData=pParms->pCallbackData;
}
else
{
pSoundList[uiChannel].EOSCallback=NULL;
pSoundList[uiChannel].pCallbackData=NULL;
}
// Other stuff
pSoundList[uiChannel].uiTimeStamp=GetTickCount();
pSoundList[uiChannel].uiFadeVolume = SoundGetVolumeIndex(uiChannel);
pSoundList[uiChannel].uiSample = -1; // it's streaming directly from file !!!!
return(uiSoundID);
}
// ------------------------
// Callbacks implementation
// ========================
static void * F_CALLBACKAPI SoundFileOpen(const STR8 pName)
{
return((void*)FileOpen(pName, FILE_ACCESS_READ | FILE_OPEN_EXISTING, FALSE));
}
static void F_CALLBACKAPI SoundFileClose(void *uiHandle)
{
FileClose((UINT32)uiHandle);
}
static INT F_CALLBACKAPI SoundFileRead(void *pBuffer, INT iSize, void *uiHandle)
{
UINT32 uiActuallyRead;
FileRead((UINT32)uiHandle, pBuffer, iSize, &uiActuallyRead);
return(uiActuallyRead);
}
static INT F_CALLBACKAPI SoundFileSeek(void *uiHandle, INT iPos, signed char cMode)
{
UINT8 uiHow;
switch(cMode)
{
case SEEK_END:
uiHow = FILE_SEEK_FROM_END;
break;
case SEEK_CUR:
uiHow = FILE_SEEK_FROM_CURRENT;
break;
default:
uiHow = FILE_SEEK_FROM_START;
}
return(!FileSeek((UINT32)uiHandle, iPos, uiHow));
}
static INT F_CALLBACKAPI SoundFileTell(void *uiHandle)
{
return(FileGetPos((UINT32)uiHandle));
}
//*******************************************************************************
// SoundGetUniqueID
//
// Returns a unique ID number with every call. Basically it's just a 32-bit
// static value that is incremented each time.
//
//*******************************************************************************
UINT32 SoundGetUniqueID(void)
{
static UINT32 uiNextID=0;
if(uiNextID==NO_SAMPLE)
uiNextID++;
return(uiNextID++);
}
//*******************************************************************************
// SoundPlayStreamed
//
// Returns TRUE/FALSE whether a sound file should be played as a streamed
// sample, or loaded into the cache. The decision is based on the size of the
// file compared to the guiSoundCacheThreshold.
//
// Returns: TRUE if it should be streamed, FALSE if loaded.
//
//*******************************************************************************
BOOLEAN SoundPlayStreamed(STR pFilename)
{
HWFILE hDisk;
UINT32 uiFilesize;
if((hDisk=FileOpen(pFilename, FILE_ACCESS_READ, FALSE))!=0)
{
uiFilesize=FileGetSize(hDisk);
FileClose(hDisk);
return(uiFilesize >= guiSoundCacheThreshold);
}
return(FALSE);
}
//*******************************************************************************
// SoundStopIndex
//
// Stops a sound referred to by it's slot number. This function is the only
// one that should be deallocating sample handles. The random sounds have to have
// their counters maintained, and using this as the central function ensures
// that they stay in sync.
//
// Returns: TRUE if the sample was stopped, FALSE if it could not be found.
//
//*******************************************************************************
BOOLEAN SoundStopIndex(UINT32 uiChannel)
{
UINT32 uiSample;
if(fSoundSystemInit)
{
if(uiChannel!=NO_SAMPLE)
{
if(pSoundList[uiChannel].hStream!=NULL)
{
FSOUND_Stream_Stop(pSoundList[uiChannel].hStream);
FSOUND_Stream_Close(pSoundList[uiChannel].hStream);
uiSample=pSoundList[uiChannel].uiSample;
// if this was a random sample, decrease the iteration count
if ( (uiSample != -1) && (pSampleList[uiSample].uiFlags&SAMPLE_RANDOM) )
pSampleList[uiSample].uiInstances--;
if(pSoundList[uiChannel].EOSCallback!=NULL)
pSoundList[uiChannel].EOSCallback(pSoundList[uiChannel].pCallbackData);
if(!pSoundList[uiChannel].fLooping && !SoundSampleIsInUse(uiChannel))
SoundRemoveSampleFlags(uiSample, SAMPLE_LOCKED);
memset(&pSoundList[uiChannel], 0, sizeof(SOUNDTAG));
}
return(TRUE);
}
}
return(FALSE);
}
// FUNCTIONS TO SET / RESET SAMPLE FLAGS
void SoundSetSampleFlags( UINT32 uiSample, UINT32 uiFlags )
{
// CHECK FOR VALUE SAMPLE
if((pSampleList[ uiSample ].uiFlags&SAMPLE_ALLOCATED) )
{
// SET
pSampleList[uiSample].uiFlags |= uiFlags;
}
}
void SoundRemoveSampleFlags( UINT32 uiSample, UINT32 uiFlags )
{
// CHECK FOR VALID SAMPLE
if((pSampleList[ uiSample ].uiFlags&SAMPLE_ALLOCATED) )
{
//REMOVE
pSampleList[uiSample].uiFlags &= (~uiFlags);
}
}
//*******************************************************************************
// SoundSampleIsInUse
//
// Returns: TRUE if the sample index is currently being played by the system.
//
//*******************************************************************************
BOOLEAN SoundSampleIsInUse(UINT32 uiSample)
{
UINT32 uiCount;
for(uiCount=0; uiCount < SOUND_MAX_CHANNELS; uiCount++)
{
if((pSoundList[uiCount].uiSample==uiSample) && SoundIsPlaying(uiCount))
return(TRUE);
}
return(FALSE);
}
//*****************************************************************************************
// SoundLog
// Writes string into log file
//
// Returns nothing
//
// Created: 10.12.2005 Lesh
//*****************************************************************************************
void SoundLog(CHAR8 *strMessage)
{
static struct SoundLog {
sgp::Logger_ID id;
SoundLog() {
id = sgp::Logger::instance().createLogger();
sgp::Logger::instance().connectFile(id, SndDebugFileName, true, sgp::Logger::FLUSH_ON_DELETE);
}
} s_SoundLog;
#ifndef USE_VFS
if ((SndDebug = fopen(SndDebugFileName, "a+t")) != NULL)
{
fprintf(SndDebug, "%s\n", strMessage);
fclose(SndDebug);
}
#else
SGP_LOG(s_SoundLog.id, vfs::String::widen(strMessage,strlen(strMessage)));
#endif
}
//*****************************************************************************************
// InitLogging
// Creates empty log file
//
// Returns nothing
//
// Created: 10.12.2005 Lesh
//*****************************************************************************************
void InitLogging()
{
#ifndef USE_VFS
if ((SndDebug = fopen(SndDebugFileName, "wt")) != NULL)
{
fclose(SndDebug);
}
#endif
}