Files
fs2open.github.com/code/sound/audiostr.cpp
asarium 2dc2295a59 Make FFmpeg an optional requirement
This does some refactoring to make it possible to cimpile and run FSO
without needing to link to FFmpeg. Since that eliminates support for
cutscenes or sound this is mostly useful for running a standalone server
which has no need for this anyway.

This reduces the image size of the standalone Docker image to only 20MB
and also resolves any licensing issues that might have existed.
2020-02-29 17:24:56 +01:00

1189 lines
27 KiB
C++

#ifdef _WIN32
#define VC_EXTRALEAN
#ifndef STRICT
#define STRICT
#endif
#include <windows.h>
#include <mmsystem.h>
#endif
#include "cfile/cfile.h"
#include "globalincs/pstypes.h"
#include "io/timer.h"
#include "sound/audiostr.h"
#include "sound/ds.h"
#include "sound/IAudioFile.h"
#include "sound/sound.h"
#include "sound/openal.h"
#ifdef WITH_FFMPEG
#include "sound/ffmpeg/FFmpegWaveFile.h"
#endif
#define MAX_STREAM_BUFFERS 4
// status
#define ASF_FREE 0
#define ASF_USED 1
// constants
#define BIGBUF_SIZE 176400
ubyte *Wavedata_load_buffer = NULL; // buffer used for cueing audiostreams
ubyte *Wavedata_service_buffer = NULL; // buffer used for servicing audiostreams
SDL_mutex* Global_service_lock;
typedef bool (*TIMERCALLBACK)(ptr_u);
#define COMPRESSED_BUFFER_SIZE 176400
ubyte *Compressed_buffer = NULL; // Used to load in compressed data during a cueing interval
ubyte *Compressed_service_buffer = NULL; // Used to read in compressed data during a service interval
// Globalize the list of audio extensions for use in several sound related files
const char *audio_ext_list[] = { ".ogg", ".wav" };
const int NUM_AUDIO_EXT = sizeof(audio_ext_list) / sizeof(char*);
static std::unique_ptr<sound::IAudioFile> openAudioFile(const char* fileName, bool keep_ext) {
#ifdef WITH_FFMPEG
{
std::unique_ptr<sound::IAudioFile> audio_file(new sound::ffmpeg::FFmpegWaveFile());
// Open given file
if (audio_file->Open(fileName, keep_ext)) {
return audio_file;
}
}
#endif
return nullptr;
}
int Audiostream_inited = 0;
class Timer
{
public:
void constructor();
void destructor();
bool Create (uint nPeriod, uint nRes, ptr_u dwUser, TIMERCALLBACK pfnCallback);
protected:
static uint TimeProc(uint interval, void *param);
TIMERCALLBACK m_pfnCallback;
ptr_u m_dwUser;
uint m_nPeriod;
uint m_nRes;
SDL_TimerID m_nIDTimer;
};
class AudioStream
{
public:
AudioStream ();
~AudioStream ();
bool Create (char *pszFilename);
bool Destroy ();
void Play (float volume, int looping);
bool Is_Playing(){ return m_fPlaying; }
bool Is_Paused(){ return m_bIsPaused; }
bool Is_Past_Limit() { return m_bPastLimit; }
void Stop (int paused = 0);
void Stop_and_Rewind ();
void Fade_and_Destroy ();
void Fade_and_Stop();
void Set_Volume(float vol);
float Get_Volume();
void Init_Data();
void Set_Sample_Cutoff(uint sample_cutoff);
void Set_Default_Volume(float vol) { m_lDefaultVolume = vol; }
float Get_Default_Volume() { return m_lDefaultVolume; }
uint Get_Samples_Committed();
int Is_looping() { return m_bLooping; }
int status;
int type;
bool paused_via_sexp_or_script;
protected:
void Cue ();
bool WriteWaveData (uint cbSize, uint *num_bytes_written, int service = 1);
uint GetMaxWriteSize ();
bool ServiceBuffer ();
static bool TimerCallback (ptr_u dwUser);
bool PlaybackDone();
ALuint m_source_id; // name of openAL source
ALuint m_buffer_ids[MAX_STREAM_BUFFERS]; // names of buffers
Timer m_timer; // ptr to Timer object
std::unique_ptr<sound::IAudioFile> m_pwavefile; // ptr to WaveFile object
sound::AudioFileProperties m_fileProps;
bool m_fCued; // semaphore (stream cued)
bool m_fPlaying; // semaphore (stream playing)
uint m_cbBufOffset; // last write position
uint m_cbBufSize; // size of sound buffer in bytes
uint m_nBufService; // service interval in msec
uint m_nTimeStarted; // time (in system time) playback started
bool m_bLooping; // whether or not to loop playback
bool m_bFade; // fade out music
bool m_bDestroy_when_faded;
float m_lVolume; // volume of stream ( 0 -> 1 )
float m_lCutoffVolume;
bool m_bIsPaused; // stream is stopped, but not rewinded
bool m_bReadingDone; // no more bytes to be read from disk, still have remaining buffer to play
uint m_fade_timer_id; // timestamp so we know when to start fade
uint m_finished_id; // timestamp so we know when we've played #bytes required
bool m_bPastLimit; // flag to show we've played past the number of bytes requred
float m_lDefaultVolume;
size_t m_total_uncompressed_bytes_read;
size_t m_max_uncompressed_bytes_to_read;
SDL_mutex* write_lock;
};
// Timer class implementation
//
////////////////////////////////////////////////////////////
// constructor
void Timer::constructor(void)
{
m_nIDTimer = 0;
}
// Destructor
void Timer::destructor(void)
{
if (m_nIDTimer) {
SDL_RemoveTimer(m_nIDTimer);
m_nIDTimer = 0;
}
}
// Create
bool Timer::Create (uint nPeriod, uint nRes, ptr_u dwUser, TIMERCALLBACK pfnCallback)
{
bool bRtn = true; // assume success
Assert(pfnCallback);
Assert(nPeriod > 10);
Assert(nPeriod >= nRes);
m_nPeriod = nPeriod;
m_nRes = nRes;
m_dwUser = dwUser;
m_pfnCallback = pfnCallback;
if ((m_nIDTimer = SDL_AddTimer(m_nPeriod, TimeProc, (void*)this)) == 0) {
bRtn = false;
}
return (bRtn);
}
// Timer proc for multimedia timer callback set with timeSetTime().
//
// Calls procedure specified when Timer object was created. The
// dwUser parameter contains "this" pointer for associated Timer object.
//
uint Timer::TimeProc(uint interval, void *dwUser)
{
// dwUser contains ptr to Timer object
Timer * ptimer = (Timer *) dwUser;
// Call user-specified callback and pass back user specified data
(ptimer->m_pfnCallback) (ptimer->m_dwUser);
if (ptimer->m_nPeriod) {
return interval;
} else {
SDL_RemoveTimer(ptimer->m_nIDTimer);
ptimer->m_nIDTimer = 0;
return 0;
}
}
//
// AudioStream class implementation
//
////////////////////////////////////////////////////////////
// The following constants are the defaults for our streaming buffer operation.
const ushort DefBufferServiceInterval = 250; // default buffer service interval in msec
// Constructor
AudioStream::AudioStream (void) : m_total_uncompressed_bytes_read(0), m_max_uncompressed_bytes_to_read(0)
{
write_lock = SDL_CreateMutex();
}
// Destructor
AudioStream::~AudioStream (void)
{
SDL_DestroyMutex( write_lock );
}
void AudioStream::Init_Data ()
{
m_bLooping = 0;
m_bFade = false;
m_fade_timer_id = 0;
m_finished_id = 0;
m_bPastLimit = false;
m_bDestroy_when_faded = false;
m_lVolume = 1.0f;
m_lCutoffVolume = 0.0f;
m_bIsPaused = false;
m_bReadingDone = false;
m_pwavefile = nullptr;
m_fPlaying = m_fCued = false;
m_cbBufOffset = 0;
m_cbBufSize = 0;
m_nBufService = DefBufferServiceInterval;
m_nTimeStarted = 0;
memset(m_buffer_ids, 0, sizeof(m_buffer_ids));
m_source_id = 0;
m_total_uncompressed_bytes_read = 0;
m_max_uncompressed_bytes_to_read = std::numeric_limits<size_t>::max();
}
// Create
bool AudioStream::Create (char *pszFilename)
{
bool fRtn = true; // assume success
Assert(pszFilename);
Init_Data();
if (pszFilename) {
// make 100% sure we got a good filename
if ( !strlen(pszFilename) )
return false;
// Create a new WaveFile object and open it
m_pwavefile = openAudioFile(pszFilename, (type == ASF_EVENTMUSIC));
if (m_pwavefile) {
m_fileProps = m_pwavefile->getFileProperties();
m_cbBufSize = (m_fileProps.sample_rate * m_fileProps.bytes_per_sample * m_fileProps.num_channels) >> 2;
// make sure that we are a multiple of the frame size
m_cbBufSize -= (m_cbBufSize % (m_fileProps.bytes_per_sample * m_fileProps.num_channels));
m_cbBufSize += (m_cbBufSize % 12) << 1;
// if the requested buffer size is too big then cap it
m_cbBufSize = (m_cbBufSize > BIGBUF_SIZE) ? BIGBUF_SIZE : m_cbBufSize;
// nprintf(("SOUND", "SOUND => Stream buffer created using %d bytes\n", m_cbBufSize));
OpenAL_ErrorCheck( alGenSources(1, &m_source_id), { fRtn = false; goto ErrorExit; } );
OpenAL_ErrorCheck( alGenBuffers(MAX_STREAM_BUFFERS, m_buffer_ids), { fRtn = false; goto ErrorExit; } );
OpenAL_ErrorPrint( alSourcef(m_source_id, AL_ROLLOFF_FACTOR, 1.0f) );
OpenAL_ErrorPrint( alSourcei(m_source_id, AL_SOURCE_RELATIVE, AL_TRUE) );
OpenAL_ErrorPrint( alSource3f(m_source_id, AL_POSITION, 0.0f, 0.0f, 0.0f) );
OpenAL_ErrorPrint( alSource3f(m_source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f) );
OpenAL_ErrorPrint( alSourcef(m_source_id, AL_GAIN, 1.0f) );
OpenAL_ErrorPrint( alSourcef(m_source_id, AL_PITCH, 1.0f) );
// maybe set EFX
if ( (type == ASF_SOUNDFX) && ds_eax_is_inited() ) {
extern ALuint AL_EFX_aux_id;
OpenAL_ErrorPrint( alSource3i(m_source_id, AL_AUXILIARY_SEND_FILTER, AL_EFX_aux_id, 0, AL_FILTER_NULL) );
}
Snd_sram += (m_cbBufSize * MAX_STREAM_BUFFERS);
}
else {
// Error, unable to create WaveFile object
nprintf(("Sound", "SOUND => Failed to open wave file %s\n\r", pszFilename));
fRtn = false;
}
}
else {
// Error, passed invalid parms
fRtn = false;
}
ErrorExit:
if ( (fRtn == false) && (m_pwavefile) ) {
mprintf(("AUDIOSTR => ErrorExit for ::Create() on wave file: %s\n", pszFilename));
if (m_source_id)
OpenAL_ErrorPrint( alDeleteSources(1, &m_source_id) );
m_pwavefile = nullptr;
}
return (fRtn);
}
// Destroy
bool AudioStream::Destroy (void)
{
bool fRtn = true;
ALint buffers_processed = 0;
SDL_LockMutex(write_lock);
// Stop playback
Stop ();
OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &buffers_processed) );
while (buffers_processed) {
ALuint buffer_id = 0;
OpenAL_ErrorPrint( alSourceUnqueueBuffers(m_source_id, 1, &buffer_id) );
buffers_processed--;
}
// Release sound sources and buffers
OpenAL_ErrorPrint( alDeleteSources(1, &m_source_id) );
OpenAL_ErrorPrint( alDeleteBuffers(MAX_STREAM_BUFFERS, m_buffer_ids) );
Snd_sram -= (m_cbBufSize * MAX_STREAM_BUFFERS);
// Delete WaveFile object
m_pwavefile = nullptr;
status = ASF_FREE;
SDL_UnlockMutex(write_lock);
return fRtn;
}
// WriteWaveData
//
// Writes wave data to sound buffer. This is a helper method used by Create and
// ServiceBuffer; it's not exposed to users of the AudioStream class.
bool AudioStream::WriteWaveData (uint size, uint *num_bytes_written, int service)
{
bool fRtn = true;
ubyte *uncompressed_wave_data;
*num_bytes_written = 0;
if ( size == 0 || m_bReadingDone ) {
return fRtn;
}
if ( (m_buffer_ids[0] == 0) || !m_pwavefile ) {
return fRtn;
}
if ( service ) {
SDL_LockMutex(Global_service_lock);
}
if ( service ) {
uncompressed_wave_data = Wavedata_service_buffer;
} else {
uncompressed_wave_data = Wavedata_load_buffer;
}
int num_bytes_read = 0;
const auto alFormat = openal_get_format(m_fileProps.bytes_per_sample * 8, m_fileProps.num_channels);
if ( !service ) {
for (int ib = 0; ib < MAX_STREAM_BUFFERS; ib++) {
num_bytes_read = m_pwavefile->Read(uncompressed_wave_data, m_cbBufSize);
// if looping then maybe reset wavefile and keep going
if ( (num_bytes_read < 0) && m_bLooping) {
m_pwavefile->Cue();
m_total_uncompressed_bytes_read = 0;
num_bytes_read = m_pwavefile->Read(uncompressed_wave_data, m_cbBufSize);
}
if (num_bytes_read < 0) {
m_bReadingDone = 1;
break;
} else if (num_bytes_read > 0) {
OpenAL_ErrorCheck( alBufferData(m_buffer_ids[ib], alFormat, uncompressed_wave_data, num_bytes_read, m_fileProps.sample_rate), { fRtn = false; goto ErrorExit; } );
OpenAL_ErrorCheck( alSourceQueueBuffers(m_source_id, 1, &m_buffer_ids[ib]), { fRtn = false; goto ErrorExit; } );
*num_bytes_written += num_bytes_read;
}
}
} else {
ALint buffers_processed = 0;
OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &buffers_processed) );
while (buffers_processed) {
ALuint buffer_id = 0;
OpenAL_ErrorPrint( alSourceUnqueueBuffers(m_source_id, 1, &buffer_id) );
num_bytes_read = m_pwavefile->Read(uncompressed_wave_data, m_cbBufSize);
// if looping then maybe reset wavefile and keep going
if ( (num_bytes_read < 0) && m_bLooping) {
m_pwavefile->Cue();
m_total_uncompressed_bytes_read = 0;
num_bytes_read = m_pwavefile->Read(uncompressed_wave_data, m_cbBufSize);
}
if (num_bytes_read < 0) {
m_bReadingDone = 1;
} else if (num_bytes_read > 0) {
OpenAL_ErrorPrint( alBufferData(buffer_id, alFormat, uncompressed_wave_data, num_bytes_read, m_fileProps.sample_rate) );
OpenAL_ErrorPrint( alSourceQueueBuffers(m_source_id, 1, &buffer_id) );
*num_bytes_written += num_bytes_read;
}
buffers_processed--;
}
}
ErrorExit:
m_total_uncompressed_bytes_read += *num_bytes_written;
if ( service ) {
SDL_UnlockMutex(Global_service_lock);
}
return (fRtn);
}
// GetMaxWriteSize
//
// Helper function to calculate max size of sound buffer write operation, i.e. how much
// free space there is in buffer.
uint AudioStream::GetMaxWriteSize (void)
{
uint dwMaxSize = m_cbBufSize;
ALint n, q;
OpenAL_ErrorCheck( alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &n), return 0 );
OpenAL_ErrorCheck( alGetSourcei(m_source_id, AL_BUFFERS_QUEUED, &q), return 0 );
if (!n && (q >= MAX_STREAM_BUFFERS)) //all buffers queued
dwMaxSize = 0;
// nprintf(("Alan","Max write size: %d\n", dwMaxSize));
return (dwMaxSize);
}
#define VOLUME_ATTENUATION_BEFORE_CUTOFF 0.03f
#define VOLUME_ATTENUATION 0.65f
bool AudioStream::ServiceBuffer (void)
{
float vol;
bool fRtn = true;
if ( status != ASF_USED )
return false;
SDL_LockMutex( write_lock );
// status may have changed, so lets check once again
if ( status != ASF_USED ){
SDL_UnlockMutex( write_lock );
return false;
}
if ( m_bFade == true ) {
if ( m_lCutoffVolume == 0.0f ) {
vol = Get_Volume();
// nprintf(("Alan","Volume is: %d\n",vol));
m_lCutoffVolume = vol * VOLUME_ATTENUATION_BEFORE_CUTOFF;
}
vol = Get_Volume() * VOLUME_ATTENUATION;
// nprintf(("Alan","Volume is now: %d\n",vol));
Set_Volume(vol);
// nprintf(("Sound","SOUND => Volume for stream sound is %d\n",vol));
// nprintf(("Alan","Cuttoff Volume is: %d\n",m_lCutoffVolume));
if ( vol < m_lCutoffVolume ) {
m_bFade = false;
m_lCutoffVolume = 0.0f;
if ( m_bDestroy_when_faded == true ) {
SDL_UnlockMutex( write_lock );
Destroy();
// Reset reentrancy semaphore
return false;
} else {
Stop_and_Rewind();
// Reset reentrancy semaphore
SDL_UnlockMutex( write_lock );
return true;
}
}
}
// All of sound not played yet, send more data to buffer
uint dwFreeSpace = GetMaxWriteSize ();
// Determine free space in sound buffer
if (dwFreeSpace) {
// Some wave data remains, but not enough to fill free space
// Send wave data to buffer, fill remainder of free space with silence
uint num_bytes_written;
if (WriteWaveData (dwFreeSpace, &num_bytes_written) == true) {
// nprintf(("Alan","Num bytes written: %d\n", num_bytes_written));
if ( m_total_uncompressed_bytes_read >= m_max_uncompressed_bytes_to_read ) {
m_fade_timer_id = timer_get_milliseconds() + 1700; // start fading 1.7 seconds from now
m_finished_id = timer_get_milliseconds() + 2000; // 2 seconds left to play out buffer
m_max_uncompressed_bytes_to_read = std::numeric_limits<uint>::max();
}
if ( (m_fade_timer_id>0) && ((uint)timer_get_milliseconds() > m_fade_timer_id) ) {
m_fade_timer_id = 0;
Fade_and_Stop();
}
if ( (m_finished_id>0) && ((uint)timer_get_milliseconds() > m_finished_id) ) {
m_finished_id = 0;
m_bPastLimit = true;
}
if ( PlaybackDone() ) {
if ( m_bDestroy_when_faded == true ) {
SDL_UnlockMutex( write_lock );
Destroy();
// Reset reentrancy semaphore
return false;
}
// All of sound has played, stop playback or loop again
if ( m_bLooping && !m_bFade) {
Play(m_lVolume, m_bLooping);
} else {
Stop_and_Rewind();
}
}
}
else {
// Error writing wave data
fRtn = false;
Int3();
}
}
SDL_UnlockMutex( write_lock );
return (fRtn);
}
// Cue
void AudioStream::Cue (void)
{
uint num_bytes_written;
if (!m_fCued) {
m_bFade = false;
m_fade_timer_id = 0;
m_finished_id = 0;
m_bPastLimit = false;
m_lVolume = 1.0f;
m_lCutoffVolume = 0.0f;
m_bDestroy_when_faded = false;
// Reset buffer ptr
m_cbBufOffset = 0;
// Reset file ptr, etc
m_pwavefile->Cue ();
// Unqueue all buffers
ALint buffers_processed = 0;
OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &buffers_processed) );
while (buffers_processed) {
ALuint buffer_id = 0;
OpenAL_ErrorPrint( alSourceUnqueueBuffers(m_source_id, 1, &buffer_id) );
buffers_processed--;
}
// Fill buffer with wave data
WriteWaveData (m_cbBufSize, &num_bytes_written, 0);
m_fCued = true;
// Init some of our data
m_total_uncompressed_bytes_read = 0;
m_max_uncompressed_bytes_to_read = std::numeric_limits<uint>::max();
}
}
// Play
void AudioStream::Play (float volume, int looping)
{
if (m_buffer_ids[0] != 0) {
// If playing, stop
if (m_fPlaying) {
if ( m_bIsPaused == false)
Stop_and_Rewind();
}
// loop flag must be set before Cue()!
if ( looping )
m_bLooping = 1;
else
m_bLooping = 0;
// Cue for playback if necessary
if ( !m_fCued )
Cue ();
OpenAL_ErrorPrint( alSourcePlay(m_source_id) );
m_nTimeStarted = timer_get_milliseconds();
Set_Volume(volume);
// Kick off timer to service buffer
m_timer.constructor();
m_timer.Create (m_nBufService, m_nBufService, ptr_u (this), TimerCallback);
// Playback begun, no longer cued
m_fPlaying = true;
m_bIsPaused = false;
}
}
// Timer callback for Timer object created by ::Play method.
bool AudioStream::TimerCallback (ptr_u dwUser)
{
// dwUser contains ptr to AudioStream object
AudioStream * pas = (AudioStream *) dwUser;
return (pas->ServiceBuffer ());
}
void AudioStream::Set_Sample_Cutoff(unsigned int sample_cutoff)
{
if ( m_pwavefile == NULL )
return;
m_max_uncompressed_bytes_to_read = (sample_cutoff * m_fileProps.bytes_per_sample);
}
uint AudioStream::Get_Samples_Committed(void)
{
if ( m_pwavefile == NULL )
return 0;
return (uint) (m_total_uncompressed_bytes_read / m_fileProps.bytes_per_sample);
}
/** Have stream fade out and be destroyed when inaudabile.
If stream is already done or never started just destroy it now.
*/
void AudioStream::Fade_and_Destroy (void)
{
if (!m_fPlaying || PlaybackDone())
{
Destroy();
}
else
{
m_bFade = true;
m_bDestroy_when_faded = true;
}
}
// Fade_and_Destroy
void AudioStream::Fade_and_Stop (void)
{
m_bFade = true;
m_bDestroy_when_faded = false;
}
// Stop
void AudioStream::Stop(int paused)
{
if (m_fPlaying) {
if (paused) {
OpenAL_ErrorPrint( alSourcePause(m_source_id) );
} else {
OpenAL_ErrorPrint( alSourceStop(m_source_id) );
}
m_fPlaying = false;
m_bIsPaused = (paused != 0);
// Delete Timer object
m_timer.destructor();
}
}
// Stop_and_Rewind
void AudioStream::Stop_and_Rewind (void)
{
if (m_fPlaying) {
// Stop playback
OpenAL_ErrorPrint( alSourceStop(m_source_id) );
// Delete Timer object
m_timer.destructor();
m_fPlaying = false;
m_bIsPaused = false;
}
// Unqueue all buffers
ALint buffers_processed = 0;
OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &buffers_processed) );
while (buffers_processed) {
ALuint buffer_id = 0;
OpenAL_ErrorPrint( alSourceUnqueueBuffers(m_source_id, 1, &buffer_id) );
buffers_processed--;
}
m_fCued = false; // this will cause wave file to start from beginning
m_bReadingDone = false;
}
// Set_Volume
void AudioStream::Set_Volume(float vol)
{
CAP(vol, 0.0f, 1.0f);
OpenAL_ErrorPrint( alSourcef(m_source_id, AL_GAIN, vol) );
m_lVolume = vol;
}
// Set_Volume
float AudioStream::Get_Volume()
{
return m_lVolume;
}
bool AudioStream::PlaybackDone()
{
ALint state = 0;
OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_SOURCE_STATE, &state) );
if (m_bReadingDone && (state != AL_PLAYING))
return true;
else
return false;
}
AudioStream Audio_streams[MAX_AUDIO_STREAMS];
void audiostream_init()
{
int i;
if ( Audiostream_inited == 1 )
return;
// Allocate memory for the buffer which holds the uncompressed wave data that is streamed from the
// disk during a load/cue
if ( Wavedata_load_buffer == NULL ) {
Wavedata_load_buffer = (ubyte*)vm_malloc(BIGBUF_SIZE);
Assert(Wavedata_load_buffer != NULL);
}
// Allocate memory for the buffer which holds the uncompressed wave data that is streamed from the
// disk during a service interval
if ( Wavedata_service_buffer == NULL ) {
Wavedata_service_buffer = (ubyte*)vm_malloc(BIGBUF_SIZE);
Assert(Wavedata_service_buffer != NULL);
}
// Allocate memory for the buffer which holds the compressed wave data that is read from the hard disk
if ( Compressed_buffer == NULL ) {
Compressed_buffer = (ubyte*)vm_malloc(COMPRESSED_BUFFER_SIZE);
Assert(Compressed_buffer != NULL);
}
if ( Compressed_service_buffer == NULL ) {
Compressed_service_buffer = (ubyte*)vm_malloc(COMPRESSED_BUFFER_SIZE);
Assert(Compressed_service_buffer != NULL);
}
for ( i = 0; i < MAX_AUDIO_STREAMS; i++ ) {
Audio_streams[i].Init_Data();
Audio_streams[i].status = ASF_FREE;
Audio_streams[i].type = ASF_NONE;
Audio_streams[i].paused_via_sexp_or_script = false;
}
SDL_InitSubSystem(SDL_INIT_TIMER);
Global_service_lock = SDL_CreateMutex();
Audiostream_inited = 1;
}
// Close down the audiostream system. Must call audiostream_init() before any audiostream functions can
// be used.
void audiostream_close()
{
if ( Audiostream_inited == 0 )
return;
int i;
for ( i = 0; i < MAX_AUDIO_STREAMS; i++ ) {
if ( Audio_streams[i].status == ASF_USED ) {
Audio_streams[i].status = ASF_FREE;
Audio_streams[i].Destroy();
}
}
// free global buffers
if ( Wavedata_load_buffer ) {
vm_free(Wavedata_load_buffer);
Wavedata_load_buffer = NULL;
}
if ( Wavedata_service_buffer ) {
vm_free(Wavedata_service_buffer);
Wavedata_service_buffer = NULL;
}
if ( Compressed_buffer ) {
vm_free(Compressed_buffer);
Compressed_buffer = NULL;
}
if ( Compressed_service_buffer ) {
vm_free(Compressed_service_buffer);
Compressed_service_buffer = NULL;
}
SDL_DestroyMutex( Global_service_lock );
Audiostream_inited = 0;
}
// Open a digital sound file for streaming
//
// input: filename => disk filename of sound file
// type => what type of audio stream do we want to open:
// ASF_SOUNDFX
// ASF_EVENTMUSIC
// ASF_MENUMUSIC
// ASF_VOICE
//
// returns: success => handle to identify streaming sound
// failure => -1
int audiostream_open( const char *filename, int type )
{
int i, rc;
char fname[MAX_FILENAME_LEN];
if ( !Audiostream_inited || !snd_is_inited() )
return -1;
for (i = 0; i < MAX_AUDIO_STREAMS; i++) {
if (Audio_streams[i].status == ASF_FREE) {
Audio_streams[i].status = ASF_USED;
Audio_streams[i].type = type;
break;
}
}
if (i == MAX_AUDIO_STREAMS) {
nprintf(("Sound", "SOUND => No more audio streams available!\n"));
return -1;
}
// copy filename, since we might modify it
strcpy_s(fname, filename);
switch (type)
{
case ASF_VOICE:
case ASF_SOUNDFX:
case ASF_MENUMUSIC:
{
// go ahead and strip off file extension
char *p = strrchr(fname, '.');
if ( p && (strlen(p) > 2) )
(*p) = 0;
break;
}
case ASF_EVENTMUSIC:
break;
default:
Int3();
return -1;
}
rc = Audio_streams[i].Create(fname);
if ( rc == 0 ) {
Audio_streams[i].status = ASF_FREE;
return -1;
} else {
return i;
}
}
void audiostream_close_file(int i, bool fade)
{
if (!Audiostream_inited)
return;
if ( i == -1 )
return;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if ( Audio_streams[i].status == ASF_USED ) {
if ( fade )
Audio_streams[i].Fade_and_Destroy();
else
Audio_streams[i].Destroy();
}
}
void audiostream_close_all(bool fade)
{
int i;
for ( i = 0; i < MAX_AUDIO_STREAMS; i++ ) {
if ( Audio_streams[i].status == ASF_FREE )
continue;
audiostream_close_file(i, fade);
}
}
void audiostream_play(int i, float volume, int looping)
{
if (!Audiostream_inited)
return;
if ( i == -1 )
return;
Assert(looping >= 0);
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if (volume == -1.0f) {
volume = Audio_streams[i].Get_Default_Volume();
}
Assert(volume >= 0.0f && volume <= 1.0f );
CAP(volume, 0.0f, 1.0f);
Assert( Audio_streams[i].status == ASF_USED );
Audio_streams[i].Set_Default_Volume(volume);
Audio_streams[i].Play(volume, looping);
}
// use as buffer service function
int audiostream_is_playing(int i)
{
if ( i == -1 )
return 0;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if ( Audio_streams[i].status != ASF_USED )
return 0;
return (int)Audio_streams[i].Is_Playing();
}
void audiostream_stop(int i, int rewind, int paused)
{
if (!Audiostream_inited)
return;
if ( i == -1 )
return;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
Assert( Audio_streams[i].status == ASF_USED );
if ( rewind )
Audio_streams[i].Stop_and_Rewind();
else
Audio_streams[i].Stop(paused);
}
void audiostream_set_volume_all(float volume, int type)
{
int i;
for ( i = 0; i < MAX_AUDIO_STREAMS; i++ ) {
if ( Audio_streams[i].status == ASF_FREE )
continue;
if ( (Audio_streams[i].type == type) || ((Audio_streams[i].type == ASF_MENUMUSIC) && (type == ASF_EVENTMUSIC)) ) {
Audio_streams[i].Set_Volume(volume);
}
}
}
void audiostream_set_volume(int i, float volume)
{
if ( i == -1 )
return;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
Assert( volume >= 0.0f && volume <= 1.0f);
if ( Audio_streams[i].status == ASF_FREE )
return;
Audio_streams[i].Set_Volume(volume);
}
int audiostream_is_paused(int i)
{
if ( i == -1 )
return 0;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if ( Audio_streams[i].status == ASF_FREE )
return -1;
return (int) Audio_streams[i].Is_Paused();
}
void audiostream_set_sample_cutoff(int i, uint cutoff)
{
if ( i == -1 )
return;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
Assert( cutoff > 0 );
if ( Audio_streams[i].status == ASF_FREE )
return;
Audio_streams[i].Set_Sample_Cutoff(cutoff);
}
uint audiostream_get_samples_committed(int i)
{
if ( i == -1 )
return 0;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if ( Audio_streams[i].status == ASF_FREE )
return 0;
return Audio_streams[i].Get_Samples_Committed();
}
int audiostream_done_reading(int i)
{
if ( i == -1 )
return 0;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if ( Audio_streams[i].status == ASF_FREE )
return 0;
return Audio_streams[i].Is_Past_Limit();
}
int audiostream_is_inited()
{
return Audiostream_inited;
}
void audiostream_pause(int i, bool via_sexp_or_script)
{
if ( i == -1 )
return;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if ( Audio_streams[i].status == ASF_FREE )
return;
if ( audiostream_is_playing(i) == (int)true )
audiostream_stop(i, 0, 1);
if (via_sexp_or_script)
Audio_streams[i].paused_via_sexp_or_script = true;
}
void audiostream_unpause(int i, bool via_sexp_or_script)
{
if ( i == -1 )
return;
Assert( i >= 0 && i < MAX_AUDIO_STREAMS );
if ( Audio_streams[i].status == ASF_FREE )
return;
if ( audiostream_is_paused(i) == (int)true ) {
audiostream_play(i, Audio_streams[i].Get_Volume(), Audio_streams[i].Is_looping());
}
if (via_sexp_or_script)
Audio_streams[i].paused_via_sexp_or_script = false;
}
void audiostream_pause_all(bool via_sexp_or_script)
{
int i;
for ( i = 0; i < MAX_AUDIO_STREAMS; i++ ) {
if ( Audio_streams[i].status == ASF_FREE )
continue;
audiostream_pause(i, via_sexp_or_script);
}
}
void audiostream_unpause_all(bool via_sexp_or_script)
{
int i;
for ( i = 0; i < MAX_AUDIO_STREAMS; i++ ) {
if ( Audio_streams[i].status == ASF_FREE )
continue;
// if we explicitly paused this and we are not explicitly unpausing, skip this stream
if ( Audio_streams[i].paused_via_sexp_or_script && !via_sexp_or_script )
continue;
audiostream_unpause(i, via_sexp_or_script);
}
}