#ifdef _WIN32 #define VC_EXTRALEAN #ifndef STRICT #define STRICT #endif #include #include #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" #include "gamesnd/eventmusic.h" #ifdef WITH_FFMPEG #include "sound/ffmpeg/FFmpegWaveFile.h" #endif constexpr size_t MAX_STREAM_BUFFERS = 8; // 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 openAudioFile(const char* fileName, bool keep_ext) { #ifdef WITH_FFMPEG { std::unique_ptr audio_file(new sound::ffmpeg::FFmpegWaveFile()); // Open given file if (audio_file->Open(fileName, keep_ext)) { return audio_file; } } #endif return nullptr; } static std::unique_ptr openAudioMem(const uint8_t* snddata, size_t snd_len) { #ifdef WITH_FFMPEG { std::unique_ptr audio_file(new sound::ffmpeg::FFmpegWaveFile()); // Open given in-memory file if (audio_file->OpenMem(snddata, snd_len)) { 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(void *dwUser, SDL_TimerID timerID, Uint32 interval); 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 CreateMem (const uint8_t* snddata, size_t snd_len); 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(); double Get_Duration(); 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: bool prepareOpened(const char *filename); 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 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(void *dwUser, SDL_TimerID /* timerID */, Uint32 interval) { // 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::max(); } bool AudioStream::prepareOpened(const char *filename) { bool fRtn = true; 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); ErrorExit: if ( (fRtn == false) && (m_pwavefile) ) { mprintf(("AUDIOSTR => ErrorExit for ::prepareOpened() on wave file: %s\n", filename)); if (m_source_id) OpenAL_ErrorPrint( alDeleteSources(1, &m_source_id) ); m_pwavefile = nullptr; } return fRtn; } // Create bool AudioStream::Create (char *pszFilename) { Assert(pszFilename); Init_Data(); if ( ! pszFilename ) return false; // 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) { return prepareOpened(pszFilename); } else { // Error, unable to create WaveFile object nprintf(("Sound", "SOUND => Failed to open wave file %s\n", pszFilename)); return false; } } bool AudioStream::CreateMem (const uint8_t* snddata, size_t snd_len) { Init_Data(); // Create a new WaveFile object and open it m_pwavefile = openAudioMem(snddata, snd_len); if (m_pwavefile) { return prepareOpened("in-memory"); } else { // Error, unable to create WaveFile object nprintf(("Sound", "SOUND => Failed to open in-memory wave file \n")); return false; } } // 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 (auto &buffer_id : m_buffer_ids) { 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(buffer_id, alFormat, uncompressed_wave_data, num_bytes_read, m_fileProps.sample_rate), { fRtn = false; goto ErrorExit; } ); OpenAL_ErrorCheck( alSourceQueueBuffers(m_source_id, 1, &buffer_id), { 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 >= sz2i(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::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; } // Recover from buffer underrun: if the source stopped because the queue drained // between service ticks, OpenAL will not auto-resume even after WriteWaveData // queues more buffers. We have to call alSourcePlay again ourselves. ALint state = AL_PLAYING; OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_SOURCE_STATE, &state) ); if (state == AL_STOPPED && m_fPlaying && !m_bReadingDone) { ALint queued = 0; OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_BUFFERS_QUEUED, &queued) ); if (queued > 0) { nprintf(("Sound", "SOUND => Audiostream underrun, restarting playback\n")); OpenAL_ErrorPrint( alSourcePlay(m_source_id) ); } } 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::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; } double AudioStream::Get_Duration() { return m_fileProps.duration; } 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; } 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; } static int audiostream_use_next_free( int type ) { if ( !Audiostream_inited || !snd_is_inited() ) return -1; int i; for (i = 0; i < MAX_AUDIO_STREAMS; i++) if (Audio_streams[i].status == ASF_FREE) break; if (i == MAX_AUDIO_STREAMS) { nprintf(("Sound", "SOUND => No more audio streams available!\n")); return -1; } Audio_streams[i].status = ASF_USED; Audio_streams[i].type = type; switch (type) { case ASF_SOUNDFX: // As in: sound.cpp:590 Audio_streams[i].Set_Default_Volume(Master_sound_volume * aav_effect_volume); break; case ASF_EVENTMUSIC: // As in: sexp.cpp:11562 Audio_streams[i].Set_Default_Volume(Master_event_music_volume * aav_music_volume); break; case ASF_MENUMUSIC: // As in: mainhallmenu.cpp:1170 Audio_streams[i].Set_Default_Volume(Master_event_music_volume); break; case ASF_VOICE: // As in: sound.cpp:590 Audio_streams[i].Set_Default_Volume(Master_voice_volume * aav_voice_volume); break; default: Audio_streams[i].status = ASF_FREE; return -1; } return i; } // 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 = audiostream_use_next_free(type); if ( i == -1 ) return -1; char fname[MAX_FILENAME_LEN]; // 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: Audio_streams[i].status = ASF_FREE; return -1; } int rc = Audio_streams[i].Create(fname); if ( rc == 0 ) { Audio_streams[i].status = ASF_FREE; return -1; } else { return i; } } // Open wave file contents previously loaded into memory for streaming // //input: snddata => reference of an in-memory file // snd_len => length of loaded 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_mem( const uint8_t* snddata, size_t snd_len, int type ) { int i = audiostream_use_next_free(type); if ( i == -1 ) return -1; int rc = Audio_streams[i].CreateMem(snddata, snd_len); 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(); } double audiostream_get_duration(int i) { if (i == -1) return 0; Assert(i >= 0 && i < MAX_AUDIO_STREAMS); if (Audio_streams[i].status == ASF_FREE) return -1; return Audio_streams[i].Get_Duration(); } 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); } }