#include "precompiled.h" #include "res/res.h" #include "res/snd.h" #include #ifdef __APPLE__ # include #else # include # include # include #endif // Linux OpenAL puts the Ogg Vorbis extension enums in alexttypes.h #ifdef OS_LINUX # include #endif // for DLL-load hack in alc_init #ifdef _WIN32 #include "sysdep/win/win_internal.h" #endif #ifdef _MSC_VER #pragma comment(lib, "openal32.lib") #pragma comment(lib, "alut.lib") #endif #include "ogghack.h" #include "timer.h" static bool al_initialized = false; static const char* alc_dev_name = "MMSYSTEM"; //static const char* alc_dev_name = 0; // default: use OpenAL default device. // rationale for "buffer" handle and separate handle for each sound instance: // - need access to sound instances to fade them out / loop for an unknown period // - access via handle for safety // - don't want to reload sound data every time => need one central instance // that owns the data // - want to support normal reload mechanism (for consistency if not necessity) // - could hack something via h_find / if so, create new handle with fn_key = 0, // but that would break reloading and is dodgy. we will create a new handle // type instead. static void al_check(const char* caller = "unknown") { assert(al_initialized); ALenum err = alGetError(); if(err == AL_NO_ERROR) return; const char* str = (const char*)alGetString(err); debug_out("openal error: %s; called from %s", str, caller); debug_warn("OpenAL error"); } /////////////////////////////////////////////////////////////////////////////// // // listener: owns position/orientation and master gain. // if they're set before al_initialized, we pass the saved values to // OpenAL immediately after init (instead of waiting until next update). // /////////////////////////////////////////////////////////////////////////////// static float al_listener_gain = 1.0; static float al_listener_pos[3]; static float al_listener_orientation[6]; // float view_direction[3], up_vector[3]; passed directly to OpenAL // also called from al_init. static void al_listener_latch() { if(al_initialized) { alListenerf(AL_GAIN, al_listener_gain); alListenerfv(AL_POSITION, al_listener_pos); alListenerfv(AL_ORIENTATION, al_listener_orientation); al_check("al_listener_latch"); } } int snd_set_master_gain(float gain) { if(gain < 0) { debug_warn("snd_set_master_gain: gain < 0"); return ERR_INVALID_PARAM; } al_listener_gain = gain; al_listener_latch(); // position will get sent too. this isn't called often, so we don't care. return 0; } static void al_listener_set_pos(const float pos[3], const float dir[3], const float up[3]) { int i; for(i = 0; i < 3; i++) al_listener_pos[i] = pos[i]; for(i = 0; i < 3; i++) al_listener_orientation[i] = dir[i]; for(i = 0; i < 3; i++) al_listener_orientation[3+i] = up[i]; al_listener_latch(); } static float al_listener_dist_2(const float point[3]) { const float dx = al_listener_pos[0] - point[0]; const float dy = al_listener_pos[1] - point[1]; const float dz = al_listener_pos[2] - point[2]; return dx*dx + dy*dy + dz*dz; } /////////////////////////////////////////////////////////////////////////////// // // AL buffer suballocator: allocates buffers as needed (alGenBuffers is fast). // this interface is a bit more convenient than the OpenAL routines, and we // verify that all buffers have been freed at exit. // /////////////////////////////////////////////////////////////////////////////// static int al_bufs_outstanding; static ALuint al_buf_alloc(ALvoid* data, ALsizei size, ALenum al_fmt, ALsizei al_freq) { ALuint al_buf; alGenBuffers(1, &al_buf); alBufferData(al_buf, al_fmt, data, size, al_freq); al_check("al_buf_alloc"); al_bufs_outstanding++; return al_buf; } static void al_buf_free(ALuint al_buf) { assert(alIsBuffer(al_buf)); alDeleteBuffers(1, &al_buf); al_check("al_buf_free"); al_bufs_outstanding--; } // called from al_shutdown. static void al_buf_shutdown() { assert(al_bufs_outstanding == 0); } /////////////////////////////////////////////////////////////////////////////// // // AL source suballocator: allocate all available sources up-front and // pass them out as needed (alGenSources is quite slow, taking 3..5 ms per // source returned). also responsible for enforcing user-specified limit // on total number of sources (to reduce mixing cost on low-end systems). // /////////////////////////////////////////////////////////////////////////////// static const int AL_SRC_MAX = 64; // regardless of sound card caps, we won't use more than this // (64 is just overkill). static ALuint al_srcs[AL_SRC_MAX]; // FIFO stack of sources (first allocated is #0) static int al_src_allocated; // number of valid sources in al_srcs[] (set by al_src_init) static int al_src_used = 0; // number of sources currently in use static int al_src_cap = AL_SRC_MAX; // user-set limit on how many sources may be used // called from al_init. static void al_src_init() { // grab as many sources as possible and count how many we get. for(int i = 0; i < AL_SRC_MAX; i++) { alGenSources(1, &al_srcs[i]); // we've reached the limit, no more are available. if(alGetError() != AL_NO_ERROR) break; assert(alIsSource(al_srcs[i])); al_src_allocated++; } // limit user's cap to what we actually got. if(al_src_cap > al_src_allocated) al_src_cap = al_src_allocated; // make sure we got the minimum guaranteed by OpenAL. assert(al_src_allocated >= 16); } // release all sources on freelist (currently stack). // all sources must have been returned to us via al_src_free. // called from al_shutdown. static void al_src_shutdown() { assert(al_src_used == 0); alDeleteSources(al_src_allocated, al_srcs); al_check("al_src_shutdown"); } static ALuint al_src_alloc() { // no more to give if(al_src_used >= al_src_cap) return 0; ALuint al_src = al_srcs[al_src_used++]; return al_src; } static void al_src_free(ALuint al_src) { assert(alIsSource(al_src)); al_srcs[--al_src_used] = al_src; assert(0 <= al_src_used && al_src_used < al_src_allocated); // don't compare against cap - it might have been // decreased to less than were in use. } int snd_set_max_src(int cap) { // non-positive - bogus. if(cap <= 0) { debug_warn("snd_set_max_src: cap <= 0"); return -1; } // either cap is legit (less than what we allocated in al_src_init), // or al_src_init wasn't called yet. note: we accept anything in the // second case, as al_src_init will sanity-check al_src_cap. if(!al_src_allocated || cap < al_src_allocated) { al_src_cap = cap; return 0; } // user is requesting a cap higher than what we actually allocated. // that's fine (not an error), but we won't set the cap, since it // determines how many sources may be returned. else return -1; } /////////////////////////////////////////////////////////////////////////////// // // OpenAL init / shutdown // /////////////////////////////////////////////////////////////////////////////// // called as late as possible, i.e. the first time sound/music is played // (either from module init there, or from the play routine itself). // this delays library load, leading to faster perceived app startup. // no harm if called more than once. static ALCcontext* alc_ctx; static ALCdevice* alc_dev; static void alc_shutdown() { alcMakeContextCurrent(0); alcDestroyContext(alc_ctx); alcCloseDevice(alc_dev); } static int alc_init() { int ret = 0; // HACK: OpenAL loads and unloads these DLLs several times on Windows. // we hold a reference to prevent the actual unload, thus speeding up // sound startup by 100..400 ms. everything works ATM; // hopefully, OpenAL doesn't rely on them actually being unloaded. #ifdef _WIN32 HMODULE dlls[3]; dlls[0] = LoadLibrary("wrap_oal.dll"); dlls[1] = LoadLibrary("setupapi.dll"); dlls[2] = LoadLibrary("wdmaud.drv"); #endif alc_dev = alcOpenDevice((ALubyte*)alc_dev_name); if(alc_dev) { alc_ctx = alcCreateContext(alc_dev, 0); // no attrlist needed if(alc_ctx) alcMakeContextCurrent(alc_ctx); } ALCenum err = alcGetError(alc_dev); if(err != ALC_NO_ERROR || !alc_dev || !alc_ctx) { debug_out("alc_init failed. alc_dev=%p alc_ctx=%p err=%d\n", alc_dev, alc_ctx, err); ret = -1; } // release DLL references, so BoundsChecker doesn't complain at exit #ifdef _WIN32 for(int i = 0; i < ARRAY_SIZE(dlls); i++) if(dlls[i] != INVALID_HANDLE_VALUE) FreeLibrary(dlls[i]); #endif return ret; } // called from each sound_open, and from snd_dev_set static int al_init() { // only take action on first call, OR after snd_dev_set calls us again if(al_initialized) return 0; al_initialized = true; CHECK_ERR(alc_init()); al_src_init(); // can't fail al_listener_latch(); // can't fail return 0; } static void al_shutdown() { if(al_initialized) { al_src_shutdown(); al_buf_shutdown(); alc_shutdown(); al_initialized = false; } } // if OpenAL hasn't been initialized yet, we only remember the device // name, which will be set when alc_init is later called; otherwise, // OpenAL is reinitialized to use the desired device. // (this is to speed up the common case of retrieving a device name from // config files and setting it; OpenAL doesn't have to be loaded until // sounds are actually played). // return 0 to indicate success, or the status returned while initializing // OpenAL. static int al_reinit() { if(!al_initialized) return 0; al_shutdown(); // was already using another device; now re-init // (stops all currently playing sounds) return al_init(); } /////////////////////////////////////////////////////////////////////////////// // // device enumeration: list all devices and allow the user to choose one, // in case the default device has problems. // /////////////////////////////////////////////////////////////////////////////// static const char* devs; // set by snd_dev_prepare_enum; used by snd_dev_next. // consists of back-to-back C strings, terminated by an extra '\0'. // (this is taken straight from OpenAL; dox say this format may change). // prepare to enumerate all device names (this resets the list returned by // snd_dev_next). return 0 on success, otherwise -1 (only if the requisite // OpenAL extension isn't available). on failure, a "cannot enum device" // message should be presented to the user, and snd_dev_set need not be // called; OpenAL will use its default device. // may be called each time the device list is needed. int snd_dev_prepare_enum() { if(alcIsExtensionPresent(0, (ALubyte*)"ALC_ENUMERATION_EXT") != AL_TRUE) return -1; devs = (const char*)alcGetString(0, ALC_DEVICE_SPECIFIER); return 0; } // return the next device name, or 0 if all have been returned. // do not call unless snd_dev_prepare_enum succeeded! // not thread-safe! (static data from snd_dev_prepare_enum is used) const char* snd_dev_next() { if(!*devs) return 0; const char* dev = devs; devs += strlen(dev)+1; return dev; } // tell OpenAL to use the specified device (0 to revert to default) in future. // // if OpenAL hasn't been initialized yet, we only remember the device // name, which will be set when snd_init is later called; otherwise, // OpenAL is reinitialized to use the desired device (thus stopping all // active sounds). we go to this trouble to speed up perceived load times: // OpenAL doesn't need to be loaded until sounds are actually played. // // return 0 on success, or the status returned while re-initializing OpenAL. int snd_dev_set(const char* new_alc_dev_name) { // requesting a specific device if(new_alc_dev_name) { // already using that device - done if(alc_dev_name && !strcmp(alc_dev_name, new_alc_dev_name)) return 0; // store name (need to copy it, since we snd_init later, // and it must then still be valid) static char buf[32]; strncpy(buf, new_alc_dev_name, 32-1); alc_dev_name = buf; } // requesting default device else { // already using default device - done if(alc_dev_name == 0) return 0; alc_dev_name = 0; } return al_reinit(); } // rationale: could make a case for a separate IO layer, but there's a // problem: IOs need to be discarded after their data has been processed. // if the IO layer is separate, we'd either need a callback from // io_complete, passing the buffer to OpenAL, or mark IO slots as // "discardable", so that they are freed the next io_issue. // both are ugly; we instead integrate IO into the sound data code. // IOs are passed to OpenAL and the discarded immediately. // // having one IO-queue per sound data object is no problem: // we suballocate buffers, and don't need centralized scheduling // (there will be <= 2 streams active at once). // one stream apiece for music and voiceover (narration during tutorial). // allowing more is possible, but would be inefficent due to seek overhead. // set this limit to catch questionable usage (e.g. streaming normal sounds). static const int MAX_STREAMS = 2; static const int MAX_IOS = 4; static const int TOTAL_IOS = MAX_STREAMS * MAX_IOS; static const size_t RAW_BUF_SIZE = 32*KB; /////////////////////////////////////////////////////////////////////////////// // // I/O buffer suballocator for streamed sounds, to avoid // frequent alloc/frees and therefore heap fragmentation. // /////////////////////////////////////////////////////////////////////////////// // note: snd_shutdown is called after h_mgr_shutdown, // so all mem_alloc-ed blocks will already have been freed. // we don't want our alloc to show up as a leak, // so we use malloc and do the alignment ourselves. static const size_t TOTAL_BUF_SIZE = TOTAL_IOS*RAW_BUF_SIZE; // (not including padding for alignment) static void* io_bufs_raw; // raw, unaligned memory for all buffers (from malloc) static void* io_buf_freelist; // list of free buffers. start of buffer holds pointer to next in list. static void io_buf_free(void* p) { assert(io_bufs_raw <= p && p <= (char*)io_bufs_raw+TOTAL_BUF_SIZE); *(void**)p = io_buf_freelist; io_buf_freelist = p; } // called from first io_buf_alloc. static void io_buf_init() { // allocate 1 big aligned block for all buffers const size_t align = 4*KB; io_bufs_raw = malloc(TOTAL_BUF_SIZE + align-1); // .. failed; io_buf_alloc calls will return 0 if(!io_bufs_raw) return; void* bufs = (void*)round_up((uintptr_t)io_bufs_raw, align); // build freelist char* p = (char*)bufs; for(int i = 0; i < TOTAL_IOS; i++) { io_buf_free(p); p += RAW_BUF_SIZE; } } static void* io_buf_alloc() { ONCE(io_buf_init()); void* buf = io_buf_freelist; // note: we have to bail now; can't update io_buf_freelist. if(!buf) { if(!io_bufs_raw) debug_warn("io_buf_alloc: not enough memory to allocate buffer pool"); else debug_warn("io_buf_alloc: max #streams exceeded"); return 0; } io_buf_freelist = *(void**)io_buf_freelist; return buf; } // no-op if io_buf_alloc was never called. // called by snd_shutdown. static void io_buf_shutdown() { free(io_bufs_raw); } /////////////////////////////////////////////////////////////////////////////// // // sound data provider // /////////////////////////////////////////////////////////////////////////////// struct SndData { bool stream; // clip ALuint al_buf; // stream Handle hf; ALenum al_fmt; ALsizei al_freq; Handle ios[MAX_IOS]; int active_ios; void* o; }; H_TYPE_DEFINE(SndData); // called from SndData_reload and snd_data_get_buf. static int stream_issue(SndData* sd, Handle hf) { if(sd->active_ios >= MAX_IOS) return 0; void* buf = io_buf_alloc(); if(!buf) return ERR_NO_MEM; Handle h = vfs_start_io(hf, RAW_BUF_SIZE, buf); CHECK_ERR(h); sd->ios[sd->active_ios++] = h; return 0; } static void SndData_init(SndData* sd, va_list args) { sd->stream = va_arg(args, bool); } static void SndData_dtor(SndData* sd) { debug_out("snd_data dtor\n"); if(sd->stream) { vfs_close(sd->hf); } else { al_buf_free(sd->al_buf); } } static int SndData_reload(SndData* sd, const char* fn, Handle) { // // detect sound format by checking file extension // enum FileType { FT_WAV, FT_OGG } file_type; const char* ext = strrchr(fn, '.'); // .. OGG (data will be passed directly to OpenAL) if(ext && !stricmp(ext, ".ogg")) { // first use of OGG: check if OpenAL extension is available. // note: this is required! OpenAL does its init here. /* static int ogg_supported = -1; if(ogg_supported == -1) ogg_supported = alIsExtensionPresent((ALubyte*)"AL_EXT_vorbis")? 1 : 0; if(!ogg_supported) return -1; */ sd->al_fmt = AL_FORMAT_VORBIS_EXT; sd->al_freq = 0; file_type = FT_OGG; } // .. WAV else if(ext && !stricmp(ext, ".wav")) file_type = FT_WAV; // .. unknown extension else return -1; if(sd->stream) { // refuse to stream anything that cannot be passed directly to OpenAL - // we'd have to extract the audio data ourselves (not worth it). if(file_type != FT_OGG) return -1; sd->hf = vfs_open(fn); CHECK_ERR(sd->hf); for(int i = 0; i < MAX_IOS; i++) CHECK_ERR(stream_issue(sd, sd->hf)); } // clip else { void* file; size_t file_size; CHECK_ERR(vfs_load(fn, file, file_size)); ALvoid* al_data = file; ALsizei al_size = (ALsizei)file_size; if(file_type == FT_WAV) { ALbyte* memory = (ALbyte*)file; ALboolean al_loop; // unused alutLoadWAVMemory(memory, &sd->al_fmt, &al_data, &al_size, &sd->al_freq, &al_loop); } std::vector data; if(file_type == FT_OGG) { sd->o = ogg_create(); ogg_give_raw(sd->o, file, file_size); ogg_open(sd->o, sd->al_fmt, sd->al_freq); size_t datasize=0; size_t bytes_read; do { const size_t bufsize = 32*KB; char buf[bufsize]; bytes_read = ogg_read(sd->o, buf, bufsize); data.resize(data.size() + bytes_read); for(size_t i = 0; i < bytes_read; i++) data[datasize+i] = buf[i]; datasize += bytes_read; } while(bytes_read > 0); al_data = &data[0]; al_size = (ALsizei)datasize; } sd->al_buf = al_buf_alloc(al_data, al_size, sd->al_fmt, sd->al_freq); mem_free(file); } return 0; } // open and return a handle to a sound file's data static Handle snd_data_load(const char* const fn, const bool stream) { // make sure we don't reload a stream object uint flags = 0; if(stream) flags = RES_UNIQUE; return h_alloc(H_SndData, fn, flags, stream); } // close the sound file data and set hsd to 0. static int snd_data_free(Handle& hsd) { return h_free(hsd, H_SndData); } enum BufRet { // buffer has been returned; barring errors, more will be available. BUF_OK = 0, // this was the last buffer we will return (end of file reached). BUF_EOF = 1, // no buffer returned - still streaming in ATM. call again later. BUF_AGAIN = 2, // anything else: negative error code }; static int snd_data_get_buf(Handle hsd, ALuint& al_buf) { // in case H_DEREF fails al_buf = 0; H_DEREF(hsd, SndData, sd); // clip: just return buffer (which was created in snd_data_load) if(sd->al_buf) { al_buf = sd->al_buf; return BUF_EOF; } // // stream: check if IO finished, issue next, return the completed buffer // assert(sd->active_ios); // has it finished? if not, bail Handle hio = sd->ios[0]; int is_complete = vfs_io_complete(hio); CHECK_ERR(is_complete); if(is_complete == 0) return BUF_AGAIN; // get its buffer void* data; size_t size; CHECK_ERR(vfs_wait_io(hio, data, size)); // returns immediately, since vfs_io_complete == 1 al_buf = al_buf_alloc(data, (ALsizei)size, sd->al_fmt, sd->al_freq); // free IO slot int err = vfs_discard_io(hio); if(err < 0) { al_buf_free(al_buf); return err; } // we implement the required 'circular queue' as a stack; // have to shift all items after this one down. sd->active_ios--; for(int i = 0; i < sd->active_ios; i++) sd->ios[i] = sd->ios[i+1]; // try to issue the next IO. if EOF reached, indicate al_buf is the last. err = stream_issue(sd, sd->hf); if(err == ERR_EOF) return BUF_EOF; // al_buf valid and next IO issued successfully. return BUF_OK; } static int snd_data_free_buf(Handle hsd, ALuint al_buf) { H_DEREF(hsd, SndData, sd); // clip: no-op (caller will later release hsd reference; // it won't actually be freed until exit, because it's cached). if(!sd->stream) return 0; al_buf_free(al_buf); return 0; } /////////////////////////////////////////////////////////////////////////////// // // sound instance // /////////////////////////////////////////////////////////////////////////////// struct VSrc { ALuint al_src; // handle to this VSrc, so that it can close itself Handle hvs; // associated sound data Handle hsd; // - can't have 2 active instances of a streamed sound, so make sure // caller is aware of the limitation by requiring them to set this. bool stream; bool eof; bool closing; ALfloat pos[3]; ALfloat gain; ALboolean loop; ALboolean relative; float static_pri; float cur_pri; }; H_TYPE_DEFINE(VSrc); static int vsrc_update(VSrc* vs) { ALint _num_queued; alGetSourcei(vs->al_src, AL_BUFFERS_QUEUED, &_num_queued); // remove all finished buffers int num_processed; alGetSourcei(vs->al_src, AL_BUFFERS_PROCESSED, &num_processed); debug_out("%g: vs=%p hvs=%I64x q=%d p=%d\n", get_time(), vs, vs->hvs, _num_queued, num_processed); for(int i = 0; i < num_processed; i++) { ALuint al_buf; alSourceUnqueueBuffers(vs->al_src, 1, &al_buf); debug_out("removing processed buf=%p vs=%p src=%p hvs=%I64x\n", al_buf, vs, vs->al_src, vs->hvs); CHECK_ERR(snd_data_free_buf(vs->hsd, al_buf)); } if(vs->closing) return 0; // no more buffers left, and EOF reached ALint num_queued; alGetSourcei(vs->al_src, AL_BUFFERS_QUEUED, &num_queued); al_check("vsrc_update alGetSourcei"); if(num_queued == 0 && vs->eof) { Handle tmp = vs->hvs; debug_out("%g: reached end, closing vs=%p src=%p hvs=%I64x\n", get_time(), vs, vs->al_src, vs->hvs); snd_free(/*vs->hvs*/tmp); return 0; } if(!vs->eof) { int to_fill = 4; if(num_queued > 0) to_fill = num_processed; int ret; do { ALuint al_buf; ret = snd_data_get_buf(vs->hsd, al_buf); CHECK_ERR(ret); debug_out("%g: got buf: buf=%p vs=%p src=%p hvs=%I64x\n", get_time(), al_buf, vs, vs->al_src, vs->hvs); alSourceQueueBuffers(vs->al_src, 1, &al_buf); al_check("vsrc_update SourceQueueBuffers"); } while(to_fill-- && ret == BUF_OK); if(ret == BUF_EOF) { vs->eof = true; debug_out("EOF reported for vs=%p src=%p hvs=%I64x\n", vs, vs->al_src, vs->hvs); } } return 0; } static int vsrc_grant_src(VSrc* vs) { debug_out("grant vs=%p src=%p\n", vs, vs->al_src); // already playing - bail if(vs->al_src) return 0; // try to alloc source vs->al_src = al_src_alloc(); // called from 2 places: sound_play can't know if a source is available, // so this isn't an error if(!vs->al_src) { debug_out("grant couldn't alloc src!\n"); return -1; } // OpenAL docs don't specify default values, so initialize everything // ourselves to be sure. note: alSourcefv param is not const. float zero3[3] = { 0.0f, 0.0f, 0.0f }; alSourcefv(vs->al_src, AL_VELOCITY, zero3); alSourcefv(vs->al_src, AL_DIRECTION, zero3); alSourcef(vs->al_src, AL_ROLLOFF_FACTOR, 0.0f); alSourcei(vs->al_src, AL_SOURCE_RELATIVE, AL_TRUE); // we only now got a source, so latch previous settings // don't use snd_set*; this way is easiest alSourcef(vs->al_src, AL_GAIN, vs->gain); alSourcefv(vs->al_src, AL_POSITION, vs->pos); alSourcei(vs->al_src, AL_LOOPING, vs->loop); al_check("vsrc_grant_src Source*"); CHECK_ERR(vsrc_update(vs)); debug_out("play vs=%p src=%p hvs=%I64x\n", vs, vs->al_src, vs->hvs); alSourcePlay(vs->al_src); al_check("vsrc_grant_src SourcePlay"); return 0; } static int vsrc_reclaim_src(VSrc* vs) { debug_out("reclaim vs=%p src=%p\n", vs, vs->al_src); // not playing - bail if(!vs->al_src) return 0; debug_out("stop\n"); alSourceStop(vs->al_src); al_check("vsrc_reclaim_src SourceStop"); vs->closing = true; CHECK_ERR(vsrc_update(vs)); // (note: all buffers are now considered 'processed', since src is stopped) al_src_free(vs->al_src); return 0; } static void list_remove(VSrc*); static void VSrc_init(VSrc* vs, va_list args) { vs->stream = va_arg(args, bool); } static void VSrc_dtor(VSrc* vs) { debug_out("vsrc_dtor vs=%p\n hvs=%I64x\n", vs, vs->hvs); list_remove(vs); vsrc_reclaim_src(vs); snd_data_free(vs->hsd); } static int VSrc_reload(VSrc* vs, const char* def_fn, Handle hvs) { // cannot wait till play(), need to init here: // must load OpenAL so that snd_data_load can check for OGG extension. al_init(); /* void* def_file; size_t def_size; CHECK_ERR(vfs_load(def_fn, def_file, def_size)); std::istringstream def(std::string((char*)def_file, (int)def_size)); mem_free(def_file); std::string snd_data_fn; float gain_percent; def >> snd_data_fn; def >> gain_percent;*/ float gain_percent = 100.0; std::string snd_data_fn = def_fn; vs->gain = gain_percent / 100.0f; // store so we can shut ourselves down via snd_free when done playing vs->hvs = hvs; vs->hsd = snd_data_load(snd_data_fn.c_str(), vs->stream); return 0; } // open and return a handle to the sound . // stream: default false Handle snd_open(const char* const fn, const bool stream) { return h_alloc(H_VSrc, fn, RES_UNIQUE, stream); } // close the sound and set hs to 0. int snd_free(Handle& hvs) { return h_free(hvs, H_VSrc); } int snd_set_pos(Handle hvs, float x, float y, float z, bool relative /* = false */) { H_DEREF(hvs, VSrc, vs); vs->pos[0] = x; vs->pos[1] = y; vs->pos[2] = z; vs->relative = relative; if(vs->al_src) { alSourcefv(vs->al_src, AL_POSITION, vs->pos); alSourcei(vs->al_src, AL_SOURCE_RELATIVE, vs->relative); al_check("snd_set_pos"); } return 0; } int snd_set_gain(Handle hvs, float gain) { H_DEREF(hvs, VSrc, vs); vs->gain = gain; if(vs->al_src) { alSourcef(vs->al_src, AL_GAIN, vs->gain); al_check("snd_set_gain"); } return 0; } int snd_set_loop(Handle hvs, bool loop) { H_DEREF(hvs, VSrc, vs); vs->loop = loop; if(vs->al_src) { alSourcei(vs->al_src, AL_LOOPING, vs->loop); al_check("snd_set_loop"); } return 0; } static float norm(const float v[3]) { return v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; } const float MAX_DIST2 = 1000.0f; static void vsrc_calc_cur_pri(VSrc* vs) { float d2; // euclidean distance to listener (squared) if(vs->relative) d2 = norm(vs->pos); else d2 = al_listener_dist_2(vs->pos); // farther away than OpenAL cutoff - no sound contribution if(d2 > MAX_DIST2) { vs->cur_pri = -1.0f; return; // TODO: make sure these never play, even if no sounds active } // scale priority down exponentially float e = d2 / MAX_DIST2; // 0.0f (close) .. 1.0f (far) const float falloff = 10.0f; vs->cur_pri = vs->static_pri * pow(falloff, e); } static bool vsrc_greater(const VSrc* const s1, const VSrc* const s2) { return s1->cur_pri > s2->cur_pri; } /////////////////////////////////////////////////////////////////////////////// // // list of sounds // /////////////////////////////////////////////////////////////////////////////// // currently active sounds - needed to update them, i.e. remove old buffers // and enqueue just finished async buffers. this can't happen from // io_check_complete alone - see dox there. // sorted in ascending order of current priority // (we remove low pri items, and have to move down everything after them, // so they should come last) typedef std::vector VSources; typedef VSources::iterator It; static VSources vsources; // don't need to sort - that's done during full update static void list_add(VSrc* vs) { vsources.push_back(vs); } static void list_foreach(void(*cb)(VSrc*)) { // can't use std::for_each: some entries may have been deleted // (i.e. set to 0) since last update. for(It it = vsources.begin(); it != vsources.end(); ++it) { VSrc* vs = *it; if(vs) cb(vs); } } // O(N)! // // TODO: replace with last list entry, resize -1 // static void list_remove(VSrc* vs) { debug_out("list_remove vs=%p\n", vs); for(size_t i = 0; i < vsources.size(); i++) if(vsources[i] == vs) { vsources[i] = 0; return; } // debug_warn("list_remove: VSrc not found"); debug_out("NOT FOUND!\n"); } static bool vsrc_is_null(VSrc* vs) { return vs != 0; } static int list_update() { // prune NULL-entries, so code below doesn't have to check if non-NULL // (these were removed, but we didn't shuffle everything down to save time) It new_end = remove_if(vsources.begin(), vsources.end(), vsrc_is_null); vsources.erase(new_end, vsources.end()); // update current priorities (a function of static priority and distance) std::for_each(vsources.begin(), vsources.end(), vsrc_calc_cur_pri); // sort by descending current priority std::sort(vsources.begin(), vsources.end(), vsrc_greater); It it; It first_unimportant = vsources.begin() + min((int)vsources.size(), al_src_cap); // reclaim source from the less important vsources for(it = first_unimportant; it != vsources.end(); ++it) { VSrc* vs = *it; if(vs->al_src) { debug_out("reclaiming from low-pri\n"); vsrc_reclaim_src(vs); } if(!vs->loop) { debug_out("kicking out low-pri\n"); Handle tmp = vs->hvs; snd_free(/*vs->hvs*/tmp); } } // grant each of the most important vsources a source for(it = vsources.begin(); it != first_unimportant; ++it) { VSrc* vs = *it; if(!vs->al_src) { debug_out("now granting high-pri a new src\n"); vsrc_grant_src(vs); } } std::for_each(vsources.begin(), vsources.end(), vsrc_update); return 0; } int snd_play(Handle hs) { debug_out("snd_play\n"); H_DEREF(hs, VSrc, vs); list_add(vs); // optimization (don't want to do full update here - too slow) // either we get a source and playing begins immediately, or it'll be // taken care of on next update vsrc_grant_src(vs); return 0; } /////////////////////////////////////////////////////////////////////////////// // // sound engine // /////////////////////////////////////////////////////////////////////////////// static void vsrc_free(VSrc* vs) { Handle tmp = vs->hvs; snd_free(/*vs->hvs*/tmp); } int snd_update(const float pos[3], const float dir[3], const float up[3]) { al_listener_set_pos(pos, dir, up); list_update(); // CHECK_ERR(io_check_complete()); return 0; } void snd_shutdown() { debug_out("snd_shutdown\n"); list_foreach(vsrc_free); io_buf_shutdown(); al_shutdown(); }