diff --git a/src/win32/OpenAL.cpp b/src/win32/OpenAL.cpp new file mode 100644 index 00000000..c0044dac --- /dev/null +++ b/src/win32/OpenAL.cpp @@ -0,0 +1,331 @@ +// 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(); +}