// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator. // Copyright (C) 2007 VBA-M development team // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2, or(at your option) // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "stdafx.h" // includes VBA.h // Interface #include "Sound.h" // OpenAL #include #include #pragma comment( lib, "OpenAL32.lib" ) // Windows #include // for Sleep() and OutputDebugString() // Internals #include "../Sound.h" #include "../Globals.h" // for 'speedup' and 'synchronize' // Debug #include #define NBUFFERS 5 //#define LOGALL // LOGALL writes very detailed informations to vba-trace.log #ifndef LOGALL #define winlog // #define debugState() // #endif class OpenAL : public ISound { public: OpenAL(); virtual ~OpenAL(); bool init(); // initialize the primary and secondary sound buffer void pause(); // pause the secondary sound buffer void reset(); // stop and reset the secondary sound buffer void resume(); // resume the secondary sound buffer void write(); // write the emulated sound to the secondary sound buffer private: bool initialized; bool buffersLoaded; bool playing; ALCdevice *device; ALCcontext *context; ALuint buffer[NBUFFERS]; ALuint tempBuffer; ALuint source; int freq; #ifdef LOGALL void debugState(); #endif }; OpenAL::OpenAL() { initialized = false; buffersLoaded = false; playing = false; device = NULL; context = NULL; memset( buffer, 0, NBUFFERS * sizeof( ALuint ) ); tempBuffer = 0; source = 0; } OpenAL::~OpenAL() { alSourceStop( source ); assert( AL_NO_ERROR == alGetError() ); alSourcei( source, AL_BUFFER, 0 ); assert( AL_NO_ERROR == alGetError() ); alDeleteSources( 1, &source ); assert( AL_NO_ERROR == alGetError() ); alDeleteBuffers( NBUFFERS, buffer ); assert( AL_NO_ERROR == alGetError() ); alcMakeContextCurrent( NULL ); assert( AL_NO_ERROR == alGetError() ); alcDestroyContext( context ); assert( AL_NO_ERROR == alGetError() ); assert( ALC_TRUE == alcCloseDevice( device ) ); assert( AL_NO_ERROR == alGetError() ); } #ifdef LOGALL void OpenAL::debugState() { ALint value = 0; alGetSourcei( source, AL_SOURCE_STATE, &value ); assert( AL_NO_ERROR == alGetError() ); winlog( " playing = %i\n", playing ); 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; } alGetSourcei( source, AL_BUFFERS_QUEUED, &value ); assert( AL_NO_ERROR == alGetError() ); winlog( " Buffers in queue: %i\n", value ); alGetSourcei( source, AL_BUFFERS_PROCESSED, &value ); assert( AL_NO_ERROR == alGetError() ); winlog( " Buffers processed: %i\n", value ); } #endif bool OpenAL::init() { winlog( "OpenAL::init\n" ); assert( initialized == false ); device = alcOpenDevice( NULL ); assert( device != NULL ); context = alcCreateContext( device, NULL ); assert( context != NULL ); ALCboolean retVal = alcMakeContextCurrent( context ); assert( ALC_TRUE == retVal ); alGenBuffers( NBUFFERS, buffer ); assert( AL_NO_ERROR == alGetError() ); alGenSources( 1, &source ); assert( AL_NO_ERROR == alGetError() ); switch( soundQuality ) { case 4: freq = 11025; break; case 2: freq = 22050; break; default: soundQuality = 1; case 1: freq = 44100; break; } soundBufferLen = freq * 2 * 2 / 60; // 16bit stereo, buffer can store the sound for 1 frame in 60Hz setsystemSoundOn( true ); initialized = true; return true; } void OpenAL::resume() { winlog( "OpenAL::resume\n" ); assert( initialized ); playing = true; if( !buffersLoaded ) return; debugState(); ALint sourceState = 0; alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); assert( AL_NO_ERROR == alGetError() ); if( sourceState != AL_PLAYING ) { alSourcePlay( source ); assert( AL_NO_ERROR == alGetError() ); } debugState(); } void OpenAL::pause() { winlog( "OpenAL::pause\n" ); assert( initialized ); playing = false; if( !buffersLoaded ) return; debugState(); ALint sourceState = 0; alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); assert( AL_NO_ERROR == alGetError() ); if( sourceState == AL_PLAYING ) { alSourcePause( source ); assert( AL_NO_ERROR == alGetError() ); } debugState(); } void OpenAL::reset() { winlog( "OpenAL::reset\n" ); assert( initialized ); playing = false; if( !buffersLoaded ) return; debugState(); ALint sourceState = 0; alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); assert( AL_NO_ERROR == alGetError() ); if( sourceState != AL_STOPPED ) { alSourceStop( source ); assert( AL_NO_ERROR == alGetError() ); } debugState(); } void OpenAL::write() { winlog( "OpenAL::write\n" ); assert( initialized ); debugState(); ALint sourceState = 0; ALint nBuffersProcessed = 0; if( !buffersLoaded ) { // ==initial buffer filling== winlog( " initial buffer filling\n" ); for( int i = 0 ; i < NBUFFERS ; i++ ) { // filling the buffers explicitly with silence would be cleaner... alBufferData( buffer[i], AL_FORMAT_STEREO16, soundFinalWave, soundBufferLen, freq ); assert( AL_NO_ERROR == alGetError() ); } alSourceQueueBuffers( source, NBUFFERS, buffer ); assert( AL_NO_ERROR == alGetError() ); buffersLoaded = true; } else { // ==normal buffer refreshing== nBuffersProcessed = 0; alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed ); assert( AL_NO_ERROR == alGetError() ); if( !speedup && synchronize && !theApp.throttle ) { // wait until at least one buffer has finished while( nBuffersProcessed == 0 ) { winlog( " waiting...\n" ); // wait for 1/10 of the time one buffer needs to have finished Sleep( soundBufferLen * 1000 / ( freq * 2 * 2 * 10 ) ); alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed ); assert( AL_NO_ERROR == alGetError() ); } } else { if( nBuffersProcessed == 0 ) return; } assert( nBuffersProcessed > 0 ); // tempBuffer contains the Buffer ID for the unqueued Buffer tempBuffer = 0; alSourceUnqueueBuffers( source, 1, &tempBuffer ); assert( AL_NO_ERROR == alGetError() ); alBufferData( tempBuffer, AL_FORMAT_STEREO16, soundFinalWave, soundBufferLen, freq ); assert( AL_NO_ERROR == alGetError() ); // refill buffer alSourceQueueBuffers( source, 1, &tempBuffer ); assert( AL_NO_ERROR == alGetError() ); } // start playing the source if necessary alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); assert( AL_NO_ERROR == alGetError() ); if( playing && ( sourceState != AL_PLAYING ) ) { alSourcePlay( source ); assert( AL_NO_ERROR == alGetError() ); } } ISound *newOpenAL() { winlog( "newOpenAL\n" ); return new OpenAL(); }