// === LOGALL writes very detailed informations to vba-trace.log === //#define LOGALL #ifndef NO_OAL // for gopts // also, wx-related #include "wxvbam.h" // Interface #include "../common/SoundDriver.h" // OpenAL #include "openal.h" // Internals #include "../gba/Sound.h" #include "../gba/Globals.h" // for 'speedup' and 'synchronize' // Debug #include #define ASSERT_SUCCESS assert( AL_NO_ERROR == ALFunction.alGetError() ) #ifndef LOGALL // replace logging functions with comments #ifdef winlog #undef winlog #endif #define winlog // #define debugState() // #endif struct OPENALFNTABLE; class OpenAL : public SoundDriver { public: OpenAL(); virtual ~OpenAL(); static wxDynamicLibrary Lib; static bool LoadOAL(); static bool GetDevices(wxArrayString &names, wxArrayString &ids); bool init(long sampleRate); // initialize the sound buffer queue void pause(); // pause the secondary sound buffer void reset(); // stop and reset the secondary sound buffer void resume(); // play/resume the secondary sound buffer void write(u16 * finalWave, int length); // write the emulated sound to a sound buffer private: static OPENALFNTABLE ALFunction; bool initialized; bool buffersLoaded; ALCdevice *device; ALCcontext *context; ALuint *buffer; ALuint tempBuffer; ALuint source; int freq; int soundBufferLen; #ifdef LOGALL void debugState(); #endif }; OpenAL::OpenAL() { initialized = false; buffersLoaded = false; device = NULL; context = NULL; buffer = (ALuint*)malloc( gopts.audio_buffers * sizeof( ALuint ) ); memset( buffer, 0, gopts.audio_buffers * sizeof( ALuint ) ); tempBuffer = 0; source = 0; } OpenAL::~OpenAL() { if( !initialized ) return; ALFunction.alSourceStop( source ); ASSERT_SUCCESS; ALFunction.alSourcei( source, AL_BUFFER, 0 ); ASSERT_SUCCESS; ALFunction.alDeleteSources( 1, &source ); ASSERT_SUCCESS; ALFunction.alDeleteBuffers( gopts.audio_buffers, buffer ); ASSERT_SUCCESS; free( buffer ); ALFunction.alcMakeContextCurrent( NULL ); // Wine incorrectly returns ALC_INVALID_VALUE // and then fails the rest of these functions as well // so there will be a leak under Wine, but that's a bug in Wine, not // this code //ASSERT_SUCCESS; ALFunction.alcDestroyContext( context ); //ASSERT_SUCCESS; ALFunction.alcCloseDevice( device ); //ASSERT_SUCCESS; ALFunction.alGetError(); // reset error state } #ifdef LOGALL void OpenAL::debugState() { ALint value = 0; ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &value ); ASSERT_SUCCESS; winlog( " soundPaused = %i\n", soundPaused ); winlog( " Source:\n" ); winlog( " State: " ); switch( value ) { case AL_INITIAL: winlog( "AL_INITIAL\n" ); break; case AL_PLAYING: winlog( "AL_PLAYING\n" ); break; case AL_PAUSED: winlog( "AL_PAUSED\n" ); break; case AL_STOPPED: winlog( "AL_STOPPED\n" ); break; default: winlog( "!unknown!\n" ); break; } ALFunction.alGetSourcei( source, AL_BUFFERS_QUEUED, &value ); ASSERT_SUCCESS; winlog( " Buffers in queue: %i\n", value ); ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &value ); ASSERT_SUCCESS; winlog( " Buffers processed: %i\n", value ); } #endif bool OpenAL::init(long sampleRate) { winlog( "OpenAL::init\n" ); assert( initialized == false ); if( !LoadOAL() ) { wxLogError( _("OpenAL library could not be found on your system. Please install the runtime from http://openal.org") ); return false; } if( !gopts.audio_dev.empty() ) { device = ALFunction.alcOpenDevice( gopts.audio_dev.mb_str() ); } else { device = ALFunction.alcOpenDevice( NULL ); } assert( device != NULL ); context = ALFunction.alcCreateContext( device, NULL ); assert( context != NULL ); ALCboolean retVal = ALFunction.alcMakeContextCurrent( context ); assert( ALC_TRUE == retVal ); ALFunction.alGenBuffers( gopts.audio_buffers, buffer ); ASSERT_SUCCESS; ALFunction.alGenSources( 1, &source ); ASSERT_SUCCESS; freq = sampleRate; // calculate the number of samples per frame first // then multiply it with the size of a sample frame (16 bit * stereo) soundBufferLen = ( freq / 60 ) * 4; initialized = true; return true; } void OpenAL::resume() { if( !initialized ) return; winlog( "OpenAL::resume\n" ); if( !buffersLoaded ) return; debugState(); ALint sourceState = 0; ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); ASSERT_SUCCESS; if( sourceState != AL_PLAYING ) { ALFunction.alSourcePlay( source ); ASSERT_SUCCESS; } debugState(); } void OpenAL::pause() { if( !initialized ) return; winlog( "OpenAL::pause\n" ); if( !buffersLoaded ) return; debugState(); ALint sourceState = 0; ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); ASSERT_SUCCESS; if( sourceState == AL_PLAYING ) { ALFunction.alSourcePause( source ); ASSERT_SUCCESS; } debugState(); } void OpenAL::reset() { if( !initialized ) return; winlog( "OpenAL::reset\n" ); if( !buffersLoaded ) return; debugState(); ALint sourceState = 0; ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); ASSERT_SUCCESS; if( sourceState != AL_STOPPED ) { ALFunction.alSourceStop( source ); ASSERT_SUCCESS; } debugState(); } void OpenAL::write(u16 * finalWave, int length) { if( !initialized ) return; winlog( "OpenAL::write\n" ); debugState(); ALint sourceState = 0; ALint nBuffersProcessed = 0; if( !buffersLoaded ) { // ==initial buffer filling== winlog( " initial buffer filling\n" ); for( int i = 0 ; i < gopts.audio_buffers ; i++ ) { // Filling the buffers explicitly with silence would be cleaner, // but the very first sample is usually silence anyway. ALFunction.alBufferData( buffer[i], AL_FORMAT_STEREO16, finalWave, soundBufferLen, freq ); ASSERT_SUCCESS; } ALFunction.alSourceQueueBuffers( source, gopts.audio_buffers, buffer ); ASSERT_SUCCESS; buffersLoaded = true; } else { // ==normal buffer refreshing== nBuffersProcessed = 0; ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed ); ASSERT_SUCCESS; if( nBuffersProcessed == gopts.audio_buffers ) { // we only want to know about it when we are emulating at full speed or faster: if( ( gopts.throttle >= 100 ) || ( gopts.throttle == 0 ) ) { if( systemVerbose & VERBOSE_SOUNDOUTPUT ) { static unsigned int i = 0; log( "OpenAL: Buffers were not refilled fast enough (i=%i)\n", i++ ); } } } if( !speedup && synchronize && !gopts.throttle ) { // wait until at least one buffer has finished while( nBuffersProcessed == 0 ) { winlog( " waiting...\n" ); // wait for about half the time one buffer needs to finish // unoptimized: ( sourceBufferLen * 1000 ) / ( freq * 2 * 2 ) * 1/2 wxMilliSleep( soundBufferLen / ( freq >> 7 ) ); ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed ); ASSERT_SUCCESS; } } else { if( nBuffersProcessed == 0 ) return; } assert( nBuffersProcessed > 0 ); // unqueue buffer tempBuffer = 0; ALFunction.alSourceUnqueueBuffers( source, 1, &tempBuffer ); ASSERT_SUCCESS; // refill buffer ALFunction.alBufferData( tempBuffer, AL_FORMAT_STEREO16, finalWave, soundBufferLen, freq ); ASSERT_SUCCESS; // requeue buffer ALFunction.alSourceQueueBuffers( source, 1, &tempBuffer ); ASSERT_SUCCESS; } // start playing the source if necessary ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); ASSERT_SUCCESS; if( !soundPaused && ( sourceState != AL_PLAYING ) ) { ALFunction.alSourcePlay( source ); ASSERT_SUCCESS; } } SoundDriver *newOpenAL() { winlog( "newOpenAL\n" ); return new OpenAL(); } // no more use of copyrighted OpenAL code just to load the stupid library // this is for compatibility with MFC version // positive: make an OpenAL-capable binary which does not require OpenAL // negative: openal lib may not be in library path // openal lib name is OS-dependent, and may even change based // on where it was installed from // On UNIX, it would probably be better to just hard link with libopenal OPENALFNTABLE OpenAL::ALFunction = {NULL}; wxDynamicLibrary OpenAL::Lib; bool OpenAL::LoadOAL() { if(!Lib.IsLoaded() && #ifdef __WXMSW__ // on win32, it's openal32.dll !Lib.Load(wxT("openal32")) && #else #ifdef __WXMAC__ // on macosx, it's just plain OpenAL !Lib.Load(wxT("OpenAL"), wxDL_NOW|wxDL_VERBATIM) && #endif #endif // on linux, it's libopenal.so // try standard name on all platforms !Lib.Load(wxDynamicLibrary::CanonicalizeName(wxT("openal")))) return false; #define loadfn(t, n) do { \ if(!(ALFunction.n = (t)Lib.GetSymbol(wxT(#n)))) \ return false; \ } while(0) //loadfn(LPALENABLE, alEnable); //loadfn(LPALDISABLE, alDisable); //loadfn(LPALISENABLED, alIsEnabled); //loadfn(LPALGETSTRING, alGetString); //loadfn(LPALGETBOOLEANV, alGetBooleanv); //loadfn(LPALGETINTEGERV, alGetIntegerv); //loadfn(LPALGETFLOATV, alGetFloatv); //loadfn(LPALGETDOUBLEV, alGetDoublev); //loadfn(LPALGETBOOLEAN, alGetBoolean); //loadfn(LPALGETINTEGER, alGetInteger); //loadfn(LPALGETFLOAT, alGetFloat); //loadfn(LPALGETDOUBLE, alGetDouble); loadfn(LPALGETERROR, alGetError); //loadfn(LPALISEXTENSIONPRESENT, alIsExtensionPresent); //loadfn(LPALGETPROCADDRESS, alGetProcAddress); //loadfn(LPALGETENUMVALUE, alGetEnumValue); //loadfn(LPALLISTENERF, alListenerf); //loadfn(LPALLISTENER3F, alListener3f); //loadfn(LPALLISTENERFV, alListenerfv); //loadfn(LPALLISTENERI, alListeneri); //loadfn(LPALLISTENER3I, alListener3i); //loadfn(LPALLISTENERIV, alListeneriv); //loadfn(LPALGETLISTENERF, alGetListenerf); //loadfn(LPALGETLISTENER3F, alGetListener3f); //loadfn(LPALGETLISTENERFV, alGetListenerfv); //loadfn(LPALGETLISTENERI, alGetListeneri); //loadfn(LPALGETLISTENER3I, alGetListener3i); //loadfn(LPALGETLISTENERIV, alGetListeneriv); loadfn(LPALGENSOURCES, alGenSources); loadfn(LPALDELETESOURCES, alDeleteSources); //loadfn(LPALISSOURCE, alIsSource); //loadfn(LPALSOURCEF, alSourcef); //loadfn(LPALSOURCE3F, alSource3f); //loadfn(LPALSOURCEFV, alSourcefv); loadfn(LPALSOURCEI, alSourcei); //loadfn(LPALSOURCE3I, alSource3i); //loadfn(LPALSOURCEIV, alSourceiv); //loadfn(LPALGETSOURCEF, alGetSourcef); //loadfn(LPALGETSOURCE3F, alGetSource3f); //loadfn(LPALGETSOURCEFV, alGetSourcefv); loadfn(LPALGETSOURCEI, alGetSourcei); //loadfn(LPALGETSOURCE3I, alGetSource3i); //loadfn(LPALGETSOURCEIV, alGetSourceiv); //loadfn(LPALSOURCEPLAYV, alSourcePlayv); //loadfn(LPALSOURCESTOPV, alSourceStopv); //loadfn(LPALSOURCEREWINDV, alSourceRewindv); //loadfn(LPALSOURCEPAUSEV, alSourcePausev); loadfn(LPALSOURCEPLAY, alSourcePlay); loadfn(LPALSOURCESTOP, alSourceStop); //loadfn(LPALSOURCEREWIND, alSourceRewind); loadfn(LPALSOURCEPAUSE, alSourcePause); loadfn(LPALSOURCEQUEUEBUFFERS, alSourceQueueBuffers); loadfn(LPALSOURCEUNQUEUEBUFFERS, alSourceUnqueueBuffers); loadfn(LPALGENBUFFERS, alGenBuffers); loadfn(LPALDELETEBUFFERS, alDeleteBuffers); //loadfn(LPALISBUFFER, alIsBuffer); loadfn(LPALBUFFERDATA, alBufferData); //loadfn(LPALBUFFERF, alBufferf); //loadfn(LPALBUFFER3F, alBuffer3f); //loadfn(LPALBUFFERFV, alBufferfv); //loadfn(LPALBUFFERI, alBufferi); //loadfn(LPALBUFFER3I, alBuffer3i); //loadfn(LPALBUFFERIV, alBufferiv); //loadfn(LPALGETBUFFERF, alGetBufferf); //loadfn(LPALGETBUFFER3F, alGetBuffer3f); //loadfn(LPALGETBUFFERFV, alGetBufferfv); //loadfn(LPALGETBUFFERI, alGetBufferi); //loadfn(LPALGETBUFFER3I, alGetBuffer3i); //loadfn(LPALGETBUFFERIV, alGetBufferiv); //loadfn(LPALDOPPLERFACTOR, alDopplerFactor); //loadfn(LPALDOPPLERVELOCITY, alDopplerVelocity); //loadfn(LPALSPEEDOFSOUND, alSpeedOfSound); //loadfn(LPALDISTANCEMODEL, alDistanceModel); loadfn(LPALCCREATECONTEXT, alcCreateContext); loadfn(LPALCMAKECONTEXTCURRENT, alcMakeContextCurrent); //loadfn(LPALCPROCESSCONTEXT, alcProcessContext); //loadfn(LPALCSUSPENDCONTEXT, alcSuspendContext); loadfn(LPALCDESTROYCONTEXT, alcDestroyContext); //loadfn(LPALCGETCURRENTCONTEXT, alcGetCurrentContext); //loadfn(LPALCGETCONTEXTSDEVICE, alcGetContextsDevice); loadfn(LPALCOPENDEVICE, alcOpenDevice); loadfn(LPALCCLOSEDEVICE, alcCloseDevice); //loadfn(LPALCGETERROR, alcGetError); loadfn(LPALCISEXTENSIONPRESENT, alcIsExtensionPresent); //loadfn(LPALCGETPROCADDRESS, alcGetProcAddress); //loadfn(LPALCGETENUMVALUE, alcGetEnumValue); loadfn(LPALCGETSTRING, alcGetString); //loadfn(LPALCGETINTEGERV, alcGetIntegerv); //loadfn(LPALCCAPTUREOPENDEVICE, alcCaptureOpenDevice); //loadfn(LPALCCAPTURECLOSEDEVICE, alcCaptureCloseDevice); //loadfn(LPALCCAPTURESTART, alcCaptureStart); //loadfn(LPALCCAPTURESTOP, alcCaptureStop); //loadfn(LPALCCAPTURESAMPLES, alcCaptureSamples); return true; } bool GetOALDevices(wxArrayString &names, wxArrayString &ids) { return OpenAL::GetDevices(names, ids); } bool OpenAL::GetDevices(wxArrayString &names, wxArrayString &ids) { if(!OpenAL::LoadOAL()) return false; #ifdef ALC_DEVICE_SPECIFIER if(ALFunction.alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_FALSE) // this extension isn't critical to OpenAL operating return true; const char *devs = ALFunction.alcGetString(NULL, ALC_DEVICE_SPECIFIER); while(*devs) { names.push_back(wxString(devs, wxConvLibc)); ids.push_back(names[names.size() - 1]); devs += strlen(devs) + 1; } #else // should work anyway, but must always use default driver return true; #endif } #endif