Files
fs2open.github.com/code/sound/audiostr.cpp
T
asarium 7e77b5a67c Merge branch 'master' into antipodes
Conflicts:
	.gitignore
	code/Makefile.am
	code/ai/ai.h
	code/ai/ai_profiles.cpp
	code/ai/ai_profiles.h
	code/ai/aibig.cpp
	code/ai/aicode.cpp
	code/ai/aiturret.cpp
	code/asteroid/asteroid.cpp
	code/asteroid/asteroid.h
	code/bmpman/bm_internal.h
	code/bmpman/bmpman.cpp
	code/bmpman/bmpman.h
	code/camera/camera.cpp
	code/camera/camera.h
	code/cfile/cfile.cpp
	code/cmdline/cmdline.cpp
	code/cmdline/cmdline.h
	code/controlconfig/controlsconfig.h
	code/controlconfig/controlsconfigcommon.cpp
	code/cutscene/movie.cpp
	code/cutscene/oggplayer.cpp
	code/debugconsole/consoleparse.cpp
	code/fred2/campaigneditordlg.cpp
	code/fred2/fred.cpp
	code/fred2/fred.rc
	code/fred2/missionsave.cpp
	code/fred2/resource.h
	code/fred2/sexp_tree.cpp
	code/fred2/sexp_tree.h
	code/fred2/shipflagsdlg.cpp
	code/fred2/shipflagsdlg.h
	code/fred2/shiptexturesdlg.cpp
	code/fred2/voiceactingmanager.cpp
	code/freespace2/freespace.cpp
	code/freespace2/freespace.rc
	code/gamehelp/contexthelp.cpp
	code/globalincs/alphacolors.cpp
	code/globalincs/alphacolors.h
	code/globalincs/version.h
	code/globalincs/vmallocator.h
	code/globalincs/windebug.cpp
	code/graphics/2d.cpp
	code/graphics/font.cpp
	code/graphics/generic.cpp
	code/graphics/generic.h
	code/graphics/grinternal.h
	code/graphics/gropengl.cpp
	code/graphics/gropengldraw.cpp
	code/graphics/gropenglextension.h
	code/graphics/gropenglpostprocessing.cpp
	code/graphics/gropenglstate.cpp
	code/graphics/gropenglstate.h
	code/graphics/gropengltexture.cpp
	code/graphics/gropengltnl.cpp
	code/hud/hud.cpp
	code/hud/hud.h
	code/hud/hudconfig.cpp
	code/hud/hudlock.cpp
	code/hud/hudmessage.cpp
	code/hud/hudparse.cpp
	code/hud/hudreticle.cpp
	code/hud/hudreticle.h
	code/hud/hudshield.cpp
	code/hud/hudsquadmsg.cpp
	code/hud/hudtarget.cpp
	code/hud/hudtarget.h
	code/hud/hudtargetbox.cpp
	code/inetfile/cftp.cpp
	code/inetfile/chttpget.cpp
	code/io/joy.cpp
	code/io/joy_ff.cpp
	code/io/key.cpp
	code/io/keycontrol.cpp
	code/io/mouse.cpp
	code/io/timer.cpp
	code/localization/localize.cpp
	code/localization/localize.h
	code/math/fvi.cpp
	code/math/vecmat.cpp
	code/menuui/credits.cpp
	code/menuui/mainhallmenu.cpp
	code/menuui/mainhallmenu.h
	code/menuui/optionsmenumulti.cpp
	code/menuui/playermenu.cpp
	code/mission/missionbriefcommon.cpp
	code/mission/missioncampaign.cpp
	code/mission/missiongoals.h
	code/mission/missionmessage.cpp
	code/mission/missionparse.cpp
	code/mission/missionparse.h
	code/missionui/missionbrief.cpp
	code/missionui/missioncmdbrief.cpp
	code/missionui/missiondebrief.cpp
	code/missionui/missionscreencommon.cpp
	code/missionui/missionshipchoice.cpp
	code/missionui/missionweaponchoice.cpp
	code/missionui/redalert.cpp
	code/missionui/redalert.h
	code/mod_table/mod_table.cpp
	code/mod_table/mod_table.h
	code/model/model.h
	code/model/modelcollide.cpp
	code/model/modelinterp.cpp
	code/model/modelread.cpp
	code/network/multi.cpp
	code/network/multi_dogfight.cpp
	code/network/multi_ingame.cpp
	code/network/multi_log.cpp
	code/network/multi_rate.cpp
	code/network/multiteamselect.cpp
	code/network/multiui.cpp
	code/network/multiutil.cpp
	code/network/stand_gui-unix.cpp
	code/object/collideshipship.cpp
	code/object/collideshipweapon.cpp
	code/object/object.cpp
	code/object/object.h
	code/object/objectsnd.cpp
	code/object/objectsort.cpp
	code/osapi/osapi.cpp
	code/osapi/osapi_unix.cpp
	code/osapi/outwnd.h
	code/parse/generic_log.cpp
	code/parse/lua.cpp
	code/parse/parselo.cpp
	code/parse/parselo.h
	code/parse/scripting.cpp
	code/parse/scripting.h
	code/parse/sexp.cpp
	code/parse/sexp.h
	code/physics/physics.cpp
	code/pilotfile/csg.cpp
	code/pilotfile/csg_convert.cpp
	code/pilotfile/pilotfile.cpp
	code/pilotfile/pilotfile_convert.h
	code/playerman/playercontrol.cpp
	code/radar/radardradis.cpp
	code/radar/radardradis.h
	code/radar/radarsetup.cpp
	code/radar/radarsetup.h
	code/ship/ship.cpp
	code/ship/ship.h
	code/ship/shipfx.cpp
	code/ship/shiphit.cpp
	code/sound/audiostr.cpp
	code/sound/ds.cpp
	code/sound/openal.cpp
	code/sound/rtvoice.cpp
	code/sound/sound.cpp
	code/sound/voicerec.cpp
	code/stats/medals.cpp
	code/stats/medals.h
	code/stats/scoring.cpp
	code/ui/ui.h
	code/weapon/beam.cpp
	code/weapon/weapon.h
	code/weapon/weapons.cpp
	code/wxfred2/aboutbox.cpp
	code/wxfred2/campaigneditor.cpp
	code/wxfred2/campaigneditor.h
	code/wxfred2/commandbriefingeditor.cpp
	code/wxfred2/debriefingeditor.cpp
	code/wxfred2/fredframe.cpp
	code/wxfred2/fredframe.h
	code/wxfred2/missionspecseditor.cpp
	code/wxfred2/missionspecseditor.h
	code/wxfred2/voiceactingmanagereditor.cpp
	code/wxfred2/wxfred.xrc
	code/wxfred2/wxfred2.cpp
	code/wxfred2/wxfred2.h
	configure.ac
	fs2open.Doxyfile
	jansson/LICENSE
	jansson/dump.c
	jansson/hashtable.c
	jansson/hashtable.h
	jansson/jansson.h
	jansson/jansson_config.h
	jansson/jansson_private.h
	jansson/load.c
	jansson/memory.c
	jansson/pack_unpack.c
	jansson/strbuffer.c
	jansson/strbuffer.h
	jansson/strconv.c
	jansson/utf.c
	jansson/utf.h
	jansson/value.c
	libpng/png.c
	libpng/png.h
	libpng/pngconf.h
	libpng/pngdebug.h
	libpng/pngerror.c
	libpng/pngget.c
	libpng/pnginfo.h
	libpng/pnglibconf.h
	libpng/pngmem.c
	libpng/pngpread.c
	libpng/pngpriv.h
	libpng/pngread.c
	libpng/pngrio.c
	libpng/pngrtran.c
	libpng/pngrutil.c
	libpng/pngset.c
	libpng/pngstruct.h
	libpng/pngtrans.c
	libpng/pngwio.c
	libpng/pngwrite.c
	libpng/pngwtran.c
	libpng/pngwutil.c
	projects/Eclipse/Freespace2/.project
	projects/MSVC_2005/Fred2.vcproj
	projects/MSVC_2005/Freespace2.sln
	projects/MSVC_2005/Freespace2.vcproj
	projects/MSVC_2005/code.vcproj
	projects/MSVC_2005/wxFRED2.vcproj
	projects/MSVC_2008/Fred2.vcproj
	projects/MSVC_2008/Freespace2.doxproj
	projects/MSVC_2008/Freespace2.vcproj
	projects/MSVC_2008/code.vcproj
	projects/MSVC_2010/Fred2.vcxproj
	projects/MSVC_2010/Freespace2.vcxproj
	projects/MSVC_2010/code.vcxproj
	projects/MSVC_2010/code.vcxproj.filters
	projects/MSVC_2010/wxFRED2.vcxproj
	projects/MSVC_2011/Fred2.vcxproj
	projects/MSVC_2011/Freespace2.vcxproj
	projects/MSVC_2011/code.vcxproj
	projects/MSVC_2011/code.vcxproj.filters
	projects/MSVC_2011/wxFRED2.vcxproj
	projects/MSVC_2013/Fred2.vcxproj
	projects/MSVC_2013/Freespace2.vcxproj
	projects/MSVC_2013/code.vcxproj
	projects/MSVC_2013/code.vcxproj.filters
	projects/MSVC_2013/libjpeg.vcxproj
	projects/MSVC_2013/liblua.vcxproj
	projects/MSVC_2013/libpng.vcxproj
	projects/MSVC_2013/wxFRED2.vcxproj
	projects/MSVC_2013/zlib.vcxproj
	projects/MSVC_6/Fred2.dsp
	projects/MSVC_6/Freespace2.dsp
	projects/MSVC_6/code.dsp
	projects/MSVC_6/wxFRED2.dsp
	projects/Xcode/English.lproj/InfoPlist.strings
	projects/Xcode/FS2_Open.xcodeproj/project.pbxproj
	projects/Xcode4/English.lproj/InfoPlist.strings
	projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
	projects/Xcode4/Frameworks.tgz
	projects/codeblocks/Freespace2/Freespace2.cbp
	zlib/adler32.c
	zlib/compress.c
	zlib/crc32.c
	zlib/crc32.h
	zlib/deflate.c
	zlib/deflate.h
	zlib/infback.c
	zlib/inffast.c
	zlib/inffixed.h
	zlib/inflate.c
	zlib/inftrees.c
	zlib/trees.c
	zlib/uncompr.c
	zlib/zconf.h
	zlib/zlib.h
	zlib/zutil.c
	zlib/zutil.h
2015-04-25 16:48:43 +02:00

1982 lines
47 KiB
C++

#ifdef _WIN32
#define VC_EXTRALEAN
#define STRICT
#include <windows.h>
#include <mmsystem.h>
#endif
#define NEED_STRHDL // for STRHTL struct in audiostr.h
#include "globalincs/pstypes.h"
#include "sound/openal.h"
#include "sound/audiostr.h"
#include "sound/ds.h"
#include "sound/acm.h"
#include "cfile/cfile.h"
#include "sound/sound.h"
#include "sound/ogg/ogg.h"
#include "io/timer.h"
#define THREADED
#include "osapi/osapi.h"
#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
#define AS_HIGHEST_MAX 999999999 // max uncompressed filesize supported is 999 meg
// 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*);
int Audiostream_inited = 0;
static int dbg_print_ogg_error(const char *filename, int rc)
{
int fatal = 0;
char err_msg[100];
memset( &err_msg, 0, sizeof(err_msg) );
Assert( filename != NULL );
switch (rc) {
case OV_FALSE:
strncpy(err_msg, "A false status was returned", 99);
// should this be fatal?
break;
case OV_EOF:
strncpy(err_msg, "End-of-file reached", 99);
fatal = 1;
break;
case OV_HOLE:
strncpy(err_msg, "Data interruption (hole)", 99);
// special handling
break;
case OV_EREAD:
strncpy(err_msg, "Media read error", 99);
fatal = 1;
break;
case OV_EFAULT:
strncpy(err_msg, "Internal logic fault", 99);
fatal = 1;
break;
case OV_EIMPL:
strncpy(err_msg, "Attempted to use a feature that's not supported", 99);
fatal = 1;
break;
case OV_EINVAL:
strncpy(err_msg, "Invalid argument value", 99);
// doesn't appear to be fatal
break;
case OV_ENOTVORBIS:
strncpy(err_msg, "File contains non-Vorbis data, or is not a Vorbis file", 99);
fatal = 1;
break;
case OV_EBADHEADER:
strncpy(err_msg, "Invalid bitstream header", 99);
fatal = 1;
break;
case OV_EVERSION:
strncpy(err_msg, "Vorbis version mismatch", 99);
fatal = 1;
break;
case OV_ENOTAUDIO:
strncpy(err_msg, "Submitted data is not audio", 99);
fatal = 1;
break;
case OV_EBADPACKET:
strncpy(err_msg, "An invalid packet was submitted", 99);
// is this fatal?
break;
case OV_EBADLINK:
strncpy(err_msg, "Invalid stream section supplied, or corrupt link", 99);
fatal = 1; // is this really fatal or does the lib compensate?
break;
case OV_ENOSEEK:
strncpy(err_msg, "Bitstream is not seekable", 99);
fatal = 1;
break;
default:
strncpy(err_msg, "Unknown error occurred", 99);
fatal = 1; // assume fatal
break;
}
// only dump fatal errors, everything else should be handled silently by default
if (fatal)
mprintf(("OGG ERROR: \"%s\" in %s\n", err_msg, filename));
// else
// nprintf(("OGGISH", "OGG ERROR: \"%s\" in %s\n", err_msg, filename));
return fatal;
}
static int audiostr_read_uint(HMMIO rw, uint *i)
{
int rc = mmioRead( rw, (char *)i, sizeof(uint) );
if (rc != sizeof(uint))
return 0;
*i = INTEL_INT(*i); //-V570
return 1;
}
static int audiostr_read_word(HMMIO rw, WORD *i)
{
int rc = mmioRead( rw, (char *)i, sizeof(WORD) );
if (rc != sizeof(WORD))
return 0;
*i = INTEL_SHORT(*i); //-V570
return 1;
}
static int audiostr_read_dword(HMMIO rw, DWORD *i)
{
int rc = mmioRead( rw, (char *)i, sizeof(DWORD) );
if (rc != sizeof(DWORD))
return 0;
*i = INTEL_INT(*i); //-V570
return 1;
}
class Timer
{
public:
void constructor(void);
void destructor(void);
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 WaveFile
{
public:
void Init(void);
void Close(void);
bool Open (char *pszFilename, bool keep_ext = true);
bool Cue (void);
int Read (ubyte *pbDest, uint cbSize, int service=1);
uint GetNumBytesRemaining (void) { return (m_nDataSize - m_nBytesPlayed); }
uint GetUncompressedAvgDataRate (void) { return (m_nUncompressedAvgDataRate); }
uint GetDataSize (void) { return (m_nDataSize); }
uint GetNumBytesPlayed (void) { return (m_nBytesPlayed); }
ALenum GetALFormat() { return (m_al_format); }
WAVEFORMATEX m_wfmt; // format of wave file used by Direct Sound
WAVEFORMATEX *m_pwfmt_original; // foramt of wave file from actual wave source
uint m_total_uncompressed_bytes_read;
uint m_max_uncompressed_bytes_to_read;
uint m_bits_per_sample_uncompressed;
protected:
uint m_data_offset; // number of bytes to actual wave data
int m_data_bytes_left;
uint m_wave_format; // format of wave source (ie WAVE_FORMAT_PCM, WAVE_FORMAT_ADPCM)
uint m_nBlockAlign; // wave data block alignment spec
uint m_nUncompressedAvgDataRate; // average wave data rate
uint m_nDataSize; // size of data chunk
uint m_nBytesPlayed; // offset into data chunk
bool m_abort_next_read;
ALenum m_al_format;
STRHDL m_snd_info;
void *m_hStream;
int m_hStream_open;
WAVEFORMATEX m_wfxDest;
char m_wFilename[MAX_FILENAME_LEN];
};
class AudioStream
{
public:
AudioStream (void);
~AudioStream (void);
bool Create (char *pszFilename);
bool Destroy (void);
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);
void Fade_and_Destroy (void);
void Fade_and_Stop(void);
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(void);
int Is_looping() { return m_bLooping; }
int status;
int type;
bool paused_via_sexp_or_script;
ushort m_bits_per_sample_uncompressed;
protected:
void Cue (void);
bool WriteWaveData (uint cbSize, uint *num_bytes_written, int service = 1);
uint GetMaxWriteSize (void);
bool ServiceBuffer (void);
static bool TimerCallback (ptr_u dwUser);
bool PlaybackDone(void);
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
WaveFile *m_pwavefile; // ptr to WaveFile object
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;
SDL_mutex* write_lock;
};
// Timer class implementation
//
////////////////////////////////////////////////////////////
// constructor
void Timer::constructor(void)
{
m_nIDTimer = NULL;
}
// Destructor
void Timer::destructor(void)
{
if (m_nIDTimer) {
SDL_RemoveTimer(m_nIDTimer);
m_nIDTimer = NULL;
}
}
// 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)) == NULL) {
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 = NULL;
return 0;
}
}
// WaveFile class implementation
//
////////////////////////////////////////////////////////////
// Constructor
void WaveFile::Init(void)
{
// Init data members
m_data_offset = 0;
m_snd_info.cfp = NULL;
m_snd_info.true_offset = 0;
m_snd_info.size = 0;
m_pwfmt_original = NULL;
m_nBlockAlign= 0;
m_nUncompressedAvgDataRate = 0;
m_nDataSize = 0;
m_nBytesPlayed = 0;
m_total_uncompressed_bytes_read = 0;
m_max_uncompressed_bytes_to_read = AS_HIGHEST_MAX;
m_al_format = AL_FORMAT_MONO8;
memset(&m_wFilename, 0, MAX_FILENAME_LEN);
m_hStream_open = 0;
m_abort_next_read = false;
}
// Destructor
void WaveFile::Close(void)
{
// Free memory
if (m_pwfmt_original) {
vm_free(m_pwfmt_original);
m_pwfmt_original = NULL;
}
if ( m_hStream_open ) {
ACM_stream_close((void*)m_hStream);
m_hStream_open = 0;
}
// Close file
if (m_snd_info.cfp) {
if (m_wave_format == OGG_FORMAT_VORBIS)
ov_clear(&m_snd_info.vorbis_file);
mmioClose( m_snd_info.cfp, 0 );
m_snd_info.cfp = NULL;
m_snd_info.true_offset = 0;
m_snd_info.size = 0;
}
}
// -- from parselo.cpp --
extern const char *stristr(const char *str, const char *substr);
// Open
bool WaveFile::Open(char *pszFilename, bool keep_ext)
{
int rc = -1;
WORD cbExtra = 0;
bool fRtn = true; // assume success
PCMWAVEFORMAT pcmwf;
int FileSize, FileOffset;
char fullpath[MAX_PATH];
char filename[MAX_FILENAME_LEN];
m_total_uncompressed_bytes_read = 0;
m_max_uncompressed_bytes_to_read = AS_HIGHEST_MAX;
// NOTE: we assume that the extension has already been stripped off if it was supposed to be!!
strcpy_s( filename, pszFilename );
// if we are supposed to load the file as passed...
if (keep_ext) {
for (int i = 0; i < NUM_AUDIO_EXT; i++) {
if ( stristr(pszFilename, audio_ext_list[i]) ) {
rc = i;
break;
}
}
// not a supported extension format ... somebody screwed up their tbls :)
if (rc < 0)
goto OPEN_ERROR;
cf_find_file_location(pszFilename, CF_TYPE_ANY, sizeof(fullpath) - 1, fullpath, &FileSize, &FileOffset);
}
// ... otherwise we just find the best match
else {
rc = cf_find_file_location_ext(filename, NUM_AUDIO_EXT, audio_ext_list, CF_TYPE_ANY, sizeof(fullpath) - 1, fullpath, &FileSize, &FileOffset);
}
if (rc < 0) {
goto OPEN_ERROR;
} else {
// set proper filename for later use (assumes that it doesn't already have an extension)
strcat_s( filename, audio_ext_list[rc] );
}
m_snd_info.cfp = mmioOpen( fullpath, NULL, MMIO_ALLOCBUF | MMIO_READ );
if (m_snd_info.cfp == NULL)
goto OPEN_ERROR;
m_snd_info.true_offset = FileOffset;
m_snd_info.size = FileSize;
// if in a VP then position the stream at the start of the file
if (FileOffset > 0)
mmioSeek( m_snd_info.cfp, FileOffset, SEEK_SET );
// if Ogg Vorbis...
if (rc == 0) {
if ( ov_open_callbacks(&m_snd_info, &m_snd_info.vorbis_file, NULL, 0, mmio_callbacks) == 0 ) {
// got an Ogg Vorbis, so lets read the info in
ov_info(&m_snd_info.vorbis_file, -1);
// we only support one logical bitstream
if ( ov_streams(&m_snd_info.vorbis_file) != 1 ) {
mprintf(("AUDIOSTR => OGG reading error: We don't handle bitstream changes!\n"));
goto OPEN_ERROR;
}
m_wave_format = OGG_FORMAT_VORBIS;
m_wfmt.wFormatTag = WAVE_FORMAT_PCM;
m_wfmt.nChannels = (WORD) m_snd_info.vorbis_file.vi->channels;
m_wfmt.nSamplesPerSec = m_snd_info.vorbis_file.vi->rate;
switch (Ds_sound_quality) {
case DS_SQ_HIGH:
m_wfmt.wBitsPerSample = Ds_float_supported ? 32 : 16;
break;
case DS_SQ_MEDIUM:
m_wfmt.wBitsPerSample = 16;
break;
default:
m_wfmt.wBitsPerSample = 8;
break;
}
m_wfmt.cbSize = 0;
m_wfmt.nBlockAlign = (ushort)(( m_wfmt.nChannels * m_wfmt.wBitsPerSample ) / 8);
m_wfmt.nAvgBytesPerSec = m_wfmt.nSamplesPerSec * m_wfmt.nBlockAlign;
m_nBlockAlign = m_wfmt.nBlockAlign;
m_nUncompressedAvgDataRate = m_wfmt.nAvgBytesPerSec;
// location of start of file in VP
m_data_offset = 0;
m_nDataSize = m_data_bytes_left = ((int)ov_pcm_total(&m_snd_info.vorbis_file, -1) * m_wfmt.nBlockAlign);
} else {
mprintf(("AUDIOSTR => OGG reading error: Not a valid Vorbis file!\n"));
}
}
// if Wave...
else if (rc == 1) {
bool done = false;
// Skip the "RIFF" tag and file size (8 bytes)
// Skip the "WAVE" tag (4 bytes)
mmioSeek( m_snd_info.cfp, 12+FileOffset, SEEK_SET );
// Now read RIFF tags until the end of file
uint tag, size, next_chunk;
while ( !done ) {
if ( !audiostr_read_uint(m_snd_info.cfp, &tag) )
break;
if ( !audiostr_read_uint(m_snd_info.cfp, &size) )
break;
next_chunk = mmioSeek(m_snd_info.cfp, 0, SEEK_CUR );
next_chunk += size;
switch (tag)
{
case 0x20746d66: // The 'fmt ' tag
{
audiostr_read_word(m_snd_info.cfp, &pcmwf.wf.wFormatTag);
audiostr_read_word(m_snd_info.cfp, &pcmwf.wf.nChannels);
audiostr_read_dword(m_snd_info.cfp, &pcmwf.wf.nSamplesPerSec);
audiostr_read_dword(m_snd_info.cfp, &pcmwf.wf.nAvgBytesPerSec);
audiostr_read_word(m_snd_info.cfp, &pcmwf.wf.nBlockAlign);
audiostr_read_word(m_snd_info.cfp, &pcmwf.wBitsPerSample);
if (pcmwf.wf.wFormatTag == WAVE_FORMAT_ADPCM)
audiostr_read_word(m_snd_info.cfp, &cbExtra);
// Allocate memory for WAVEFORMATEX structure + extra bytes
if ( (m_pwfmt_original = (WAVEFORMATEX *) vm_malloc(sizeof(WAVEFORMATEX)+cbExtra)) != NULL ) {
Assert(m_pwfmt_original != NULL);
// Copy bytes from temporary format structure
memcpy (m_pwfmt_original, &pcmwf, sizeof(pcmwf));
m_pwfmt_original->cbSize = cbExtra;
// Read those extra bytes, append to WAVEFORMATEX structure
if (cbExtra != 0)
mmioRead( m_snd_info.cfp, ((char *)(m_pwfmt_original) + sizeof(WAVEFORMATEX)), cbExtra );
} else {
Int3(); // malloc failed
goto OPEN_ERROR;
}
break;
}
case 0x61746164: // the 'data' tag
{
m_nDataSize = size; // This is size of data chunk. Compressed if ADPCM.
m_data_bytes_left = size;
m_data_offset = mmioSeek( m_snd_info.cfp, 0, SEEK_CUR );
done = true;
break;
}
default: // unknown, skip it
break;
} // end switch
mmioSeek( m_snd_info.cfp, next_chunk, SEEK_SET );
}
// make sure that we did good
if ( !done || (m_pwfmt_original == NULL) )
goto OPEN_ERROR;
// At this stage, examine source format, and set up WAVEFORATEX structure for DirectSound.
// Since DirectSound only supports PCM, force this structure to be PCM compliant. We will
// need to convert data on the fly later if our souce is not PCM
switch (m_pwfmt_original->wFormatTag) {
case WAVE_FORMAT_PCM:
m_wave_format = WAVE_FORMAT_PCM;
m_wfmt.wBitsPerSample = m_pwfmt_original->wBitsPerSample;
break;
case WAVE_FORMAT_ADPCM:
m_wave_format = WAVE_FORMAT_ADPCM;
m_wfmt.wBitsPerSample = (Ds_sound_quality) ? 16: 8;
m_bits_per_sample_uncompressed = m_wfmt.wBitsPerSample;
break;
case WAVE_FORMAT_IEEE_FLOAT: {
m_wave_format = WAVE_FORMAT_IEEE_FLOAT;
switch (Ds_sound_quality) {
case DS_SQ_HIGH:
m_wfmt.wBitsPerSample = (Ds_float_supported) ? 32 : 16;
break;
case DS_SQ_MEDIUM:
m_wfmt.wBitsPerSample = 16;
break;
default:
m_wfmt.wBitsPerSample = 8;
break;
}
break;
}
default:
nprintf(("SOUND", "SOUND => Not supporting %d format for playing wave files\n", m_pwfmt_original->wFormatTag));
goto OPEN_ERROR;
break;
} // end switch
// Set up the WAVEFORMATEX structure to have the right PCM characteristics
m_wfmt.wFormatTag = WAVE_FORMAT_PCM;
m_wfmt.nChannels = m_pwfmt_original->nChannels;
m_wfmt.nSamplesPerSec = m_pwfmt_original->nSamplesPerSec;
m_wfmt.cbSize = 0;
m_wfmt.nBlockAlign = (ushort)(( m_wfmt.nChannels * m_wfmt.wBitsPerSample ) / 8);
m_wfmt.nAvgBytesPerSec = m_wfmt.nBlockAlign * m_wfmt.nSamplesPerSec;
// Init some member data from format chunk
m_nBlockAlign = m_pwfmt_original->nBlockAlign;
m_nUncompressedAvgDataRate = m_wfmt.nAvgBytesPerSec;
Assert( (m_wfmt.nChannels == 1) || (m_wfmt.nChannels == 2) );
}
// something unkown???
else {
Int3();
}
m_al_format = openal_get_format(m_wfmt.wBitsPerSample, m_wfmt.nChannels);
if (m_al_format != AL_INVALID_VALUE) {
// Cue for streaming
Cue();
goto OPEN_DONE;
}
OPEN_ERROR:
// Handle all errors here
nprintf(("SOUND","SOUND ==> Could not open wave file %s for streaming\n", filename));
fRtn = false;
if (m_snd_info.cfp != NULL) {
// Close file
mmioClose( m_snd_info.cfp, 0 );
m_snd_info.cfp = NULL;
m_snd_info.true_offset = 0;
m_snd_info.size = 0;
}
if (m_pwfmt_original) {
vm_free(m_pwfmt_original);
m_pwfmt_original = NULL;
}
OPEN_DONE:
strncpy(m_wFilename, filename, MAX_FILENAME_LEN-1);
if (fRtn)
nprintf(("SOUND", "AUDIOSTR => Successfully opened: %s\n", filename));
return (fRtn);
}
// Cue
//
// Set the file pointer to the start of wave data
//
bool WaveFile::Cue (void)
{
bool fRtn = true; // assume success
int rval;
m_total_uncompressed_bytes_read = 0;
m_max_uncompressed_bytes_to_read = AS_HIGHEST_MAX;
if (m_wave_format == OGG_FORMAT_VORBIS) {
rval = (int)ov_raw_seek(&m_snd_info.vorbis_file, m_data_offset);
} else {
rval = mmioSeek( m_snd_info.cfp, m_data_offset, SEEK_SET );
}
if ( rval == -1 ) {
fRtn = false;
}
m_data_bytes_left = m_nDataSize;
m_abort_next_read = false;
return fRtn;
}
// Read
//
// Returns number of bytes actually read.
//
// Returns -1 if there is nothing more to be read. This function can return 0, since
// sometimes the amount of bytes requested is too small for the ACM decompression to
// locate a suitable block
int WaveFile::Read(ubyte *pbDest, uint cbSize, int service)
{
void *dest_buf=NULL, *uncompressed_wave_data;
int rc, uncompressed_bytes_written, section, last_section = -1, byte_order = 0;
uint src_bytes_used, convert_len, num_bytes_desired=0, num_bytes_read;
// nprintf(("Alan","Reqeusted: %d\n", cbSize));
#if BYTE_ORDER == BIG_ENDIAN
byte_order = 1;
#endif
if ( service ) {
uncompressed_wave_data = Wavedata_service_buffer;
} else {
uncompressed_wave_data = Wavedata_load_buffer;
}
switch ( m_wave_format ) {
case WAVE_FORMAT_PCM:
num_bytes_desired = cbSize;
dest_buf = pbDest;
break;
case WAVE_FORMAT_ADPCM: {
if ( !m_hStream_open ) {
if ( !ACM_stream_open(m_pwfmt_original, &m_wfxDest, (void**)&m_hStream, m_bits_per_sample_uncompressed) ) {
m_hStream_open = 1;
} else {
Int3();
}
}
num_bytes_desired = cbSize;
if ( service ) {
dest_buf = Compressed_service_buffer;
} else {
dest_buf = Compressed_buffer;
}
if ( num_bytes_desired <= 0 ) {
num_bytes_desired = 0;
// nprintf(("Alan","No bytes required for ADPCM time interval\n"));
} else {
num_bytes_desired = ACM_query_source_size((void*)m_hStream, cbSize);
// nprintf(("Alan","Num bytes desired: %d\n", num_bytes_desired));
}
break;
}
case OGG_FORMAT_VORBIS:
num_bytes_desired = cbSize;
dest_buf = pbDest;
break;
case WAVE_FORMAT_IEEE_FLOAT: {
num_bytes_desired = cbSize;
if (m_wfmt.wBitsPerSample == 32) {
dest_buf = pbDest;
} else {
if (service) {
dest_buf = Compressed_service_buffer;
} else {
dest_buf = Compressed_buffer;
}
}
break;
}
default:
nprintf(("SOUND", "SOUND => Not supporting %d format for playing wave files\n", m_wave_format));
Int3();
break;
} // end switch
num_bytes_read = 0;
convert_len = 0;
src_bytes_used = 0;
// read data from disk
if ( m_data_bytes_left <= 0 ) {
num_bytes_read = 0;
uncompressed_bytes_written = 0;
return -1;
}
if ( (m_data_bytes_left > 0) && (num_bytes_desired > 0) ) {
int actual_read = 0;
if ( num_bytes_desired <= (uint)m_data_bytes_left ) {
num_bytes_read = num_bytes_desired;
}
else {
num_bytes_read = m_data_bytes_left;
}
// OGG reading is special
if ( m_wave_format == OGG_FORMAT_VORBIS ) {
int sign = (m_wfmt.wBitsPerSample == 8) ? 0 : 1;
int sample_size = sizeof(float) * m_wfmt.nChannels;
while ( !m_abort_next_read && ((uint)actual_read < num_bytes_read)) {
float **pcm = NULL;
if (m_wfmt.wBitsPerSample == 32) {
rc = ov_read_float(&m_snd_info.vorbis_file, &pcm, (num_bytes_read - actual_read) / sample_size, &section);
} else {
rc = ov_read(&m_snd_info.vorbis_file, (char *)dest_buf + actual_read, num_bytes_read - actual_read, byte_order, m_wfmt.wBitsPerSample / 8, sign, &section);
}
// fail if the bitstream changes, shouldn't get this far if that's the case though
if ((last_section != -1) && (last_section != section)) {
mprintf(("AUDIOSTR => OGG reading error: We don't handle bitstream changes!\n"));
goto READ_ERROR;
}
if ( rc > 0 ) {
if (m_wfmt.wBitsPerSample == 32) {
float *out_p = (float*)((ubyte*)dest_buf + actual_read);
for (int i = 0; i < rc; i++) {
for (int j = 0; j < m_wfmt.nChannels; j++) {
*out_p++ = pcm[j][i];
}
}
actual_read += (rc * m_wfmt.nBlockAlign);
} else {
actual_read += rc;
}
last_section = section;
} else if ( rc == 0 ) {
break;
} else if ( rc < 0 ) {
if ( dbg_print_ogg_error(m_wFilename, rc) ) {
// must be a fatal error
goto READ_ERROR;
} else {
// not fatal, just continue on
break;
}
}
}
}
// IEEE FLOAT is special too, downsampling can give short buffers
else if (m_wave_format == WAVE_FORMAT_IEEE_FLOAT) {
while ( !m_abort_next_read && ((uint)actual_read < num_bytes_read) ) {
rc = mmioRead(m_snd_info.cfp, (char *)dest_buf, num_bytes_read);
if (rc <= 0) {
break;
}
#if BYTE_ORDER == BIG_ENDIAN
// need to byte-swap before any later conversions
float *swap_tmp;
for (int i = 0; i < rc; i += sizeof(float)) {
swap_tmp = (float *)((ubyte*)dest_buf + i);
*swap_tmp = INTEL_FLOAT(swap_tmp);
}
#endif
if (m_wfmt.wBitsPerSample == 32) {
actual_read = rc;
} else if (m_wfmt.wBitsPerSample == 16) {
float *in_p = (float*)dest_buf;
short *out_p = (short*)((ubyte*)uncompressed_wave_data + actual_read);
int end = rc / sizeof(float);
for (int i = 0; i < end; i++) {
int i_val = (int)(in_p[i] * 32767.0f + 0.5f);
CLAMP(i_val, -32768, 32767);
*out_p++ = (short)i_val;
}
actual_read += (rc >> 1);
} else {
Assert( m_wfmt.wBitsPerSample == 8 );
float *in_p = (float*)dest_buf;
ubyte *out_p = (ubyte*)((ubyte*)uncompressed_wave_data + actual_read);
int end = num_bytes_read / sizeof(float);
for (int i = 0; i < end; i++) {
int i_val = (int)(in_p[i] * 127.0f + 0.5f) + 128;
CLAMP(i_val, 0, 255);
*out_p++ = (ubyte)i_val;
}
actual_read += (rc >> 2);
}
}
}
// standard WAVE reading
else {
actual_read = mmioRead( m_snd_info.cfp, (char *)dest_buf, num_bytes_read );
}
if ( (actual_read <= 0) || (m_abort_next_read) ) {
num_bytes_read = 0;
uncompressed_bytes_written = 0;
return -1;
}
if ( num_bytes_desired >= (uint)m_data_bytes_left ) {
m_abort_next_read = 1;
}
num_bytes_read = actual_read;
}
// convert data if necessary, to PCM
if ( m_wave_format == WAVE_FORMAT_ADPCM ) {
if ( num_bytes_read > 0 ) {
rc = ACM_convert((void*)m_hStream, (ubyte*)dest_buf, num_bytes_read, (ubyte*)uncompressed_wave_data, BIGBUF_SIZE, &convert_len, &src_bytes_used);
if ( rc == -1 ) {
goto READ_ERROR;
} else if ( convert_len == 0 ) {
if (num_bytes_read < m_nBlockAlign) {
mprintf(("AUDIOSTR => Warning: Short read detected in ACM decode of '%s'!!\n", m_wFilename));
} else {
Int3();
}
}
}
Assert(src_bytes_used <= num_bytes_read);
if ( src_bytes_used < num_bytes_read ) {
// seek back file pointer to reposition before unused source data
mmioSeek( m_snd_info.cfp, src_bytes_used - num_bytes_read, SEEK_CUR );
}
// Adjust number of bytes left
m_data_bytes_left -= src_bytes_used;
m_nBytesPlayed += src_bytes_used;
uncompressed_bytes_written = convert_len;
// Successful read, keep running total of number of data bytes read
goto READ_DONE;
}
else {
// Successful read, keep running total of number of data bytes read
// Adjust number of bytes left
m_data_bytes_left -= num_bytes_read;
m_nBytesPlayed += num_bytes_read;
uncompressed_bytes_written = num_bytes_read;
#if BYTE_ORDER == BIG_ENDIAN
if ( m_wave_format == WAVE_FORMAT_PCM ) {
// swap 16-bit sound data
if (m_wfmt.wBitsPerSample == 16) {
ushort *swap_tmp;
for (uint i=0; i<uncompressed_bytes_written; i=i+2) {
swap_tmp = (ushort*)((ubyte*)dest_buf + i);
*swap_tmp = INTEL_SHORT(*swap_tmp);
}
}
}
#endif
goto READ_DONE;
}
READ_ERROR:
num_bytes_read = 0;
uncompressed_bytes_written = 0;
READ_DONE:
m_total_uncompressed_bytes_read += uncompressed_bytes_written;
// nprintf(("Alan","Read: %d\n", uncompressed_bytes_written));
return (uncompressed_bytes_written);
}
//
// 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)
{
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 = NULL;
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;
}
// 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
m_pwavefile = (WaveFile *)vm_malloc(sizeof(WaveFile));
Assert(m_pwavefile);
if (m_pwavefile) {
// Call constructor
m_pwavefile->Init();
// Open given file
m_pwavefile->m_bits_per_sample_uncompressed = m_bits_per_sample_uncompressed;
if ( m_pwavefile->Open(pszFilename, (type == ASF_EVENTMUSIC)) ) {
m_cbBufSize = m_pwavefile->m_wfmt.nAvgBytesPerSec >> 2;
// make sure that we are a multiple of the frame size
m_cbBufSize -= (m_cbBufSize % m_pwavefile->m_wfmt.nBlockAlign);
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) );
}
// Cue for playback
Cue();
Snd_sram += (m_cbBufSize * MAX_STREAM_BUFFERS);
}
else {
// Error opening file
nprintf(("SOUND", "SOUND => Failed to open wave file: %s\n\r", pszFilename));
fRtn = false;
}
}
else {
// Error, unable to create WaveFile object
nprintf(("Sound", "SOUND => Failed to create WaveFile object %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->Close();
vm_free(m_pwavefile);
m_pwavefile = NULL;
}
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
if (m_pwavefile) {
m_pwavefile->Close();
vm_free(m_pwavefile);
m_pwavefile = NULL;
}
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;
if ( !service ) {
for (int ib = 0; ib < MAX_STREAM_BUFFERS; ib++) {
num_bytes_read = m_pwavefile->Read(uncompressed_wave_data, m_cbBufSize, service);
if (num_bytes_read < 0) {
m_bReadingDone = 1;
} else if (num_bytes_read > 0) {
OpenAL_ErrorCheck( alBufferData(m_buffer_ids[ib], m_pwavefile->GetALFormat(), uncompressed_wave_data, num_bytes_read, m_pwavefile->m_wfmt.nSamplesPerSec), { 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, service);
if (num_bytes_read < 0) {
m_bReadingDone = 1;
} else if (num_bytes_read > 0) {
OpenAL_ErrorPrint( alBufferData(buffer_id, m_pwavefile->GetALFormat(), uncompressed_wave_data, num_bytes_read, m_pwavefile->m_wfmt.nSamplesPerSec) );
OpenAL_ErrorPrint( alSourceQueueBuffers(m_source_id, 1, &buffer_id) );
*num_bytes_written += num_bytes_read;
}
buffers_processed--;
}
}
ErrorExit:
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_pwavefile->m_total_uncompressed_bytes_read >= m_pwavefile->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_pwavefile->m_max_uncompressed_bytes_to_read = AS_HIGHEST_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;
}
}
// 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();
}
// Cue for playback if necessary
if ( !m_fCued )
Cue ();
if ( looping )
m_bLooping = 1;
else
m_bLooping = 0;
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_pwavefile->m_max_uncompressed_bytes_to_read = ((sample_cutoff * m_pwavefile->m_wfmt.wBitsPerSample) / 8);
}
uint AudioStream::Get_Samples_Committed(void)
{
if ( m_pwavefile == NULL )
return 0;
return ((m_pwavefile->m_total_uncompressed_bytes_read * 8) / m_pwavefile->m_wfmt.wBitsPerSample);
}
/** 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( 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);
// we always uncompress to 16 bits
Audio_streams[i].m_bits_per_sample_uncompressed = 16;
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, int 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(int 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);
}
}