// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator. // Copyright (C) 1999-2003 Forgotten // Copyright (C) 2004 Forgotten and the VBA development team // Copyright (C) 2004-2006 VBA development team // Copyright (C) 2007-2008 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. // MFC #include "stdafx.h" // Tools #include "AVIWrite.h" #include "WavWriter.h" // Internals #include "../System.h" #include "../GBA.h" #include "../Globals.h" #include "../Sound.h" // DirectSound8 #include #pragma comment( lib, "Dsound" ) #pragma comment( lib, "Dxguid" ) extern bool soundBufferLow; extern void setsystemSoundOn(bool value); class DirectSound : public ISound { private: LPDIRECTSOUND8 pDirectSound; // DirectSound interface LPDIRECTSOUNDBUFFER dsbPrimary; // Primary DirectSound buffer LPDIRECTSOUNDBUFFER dsbSecondary; // Secondary DirectSound buffer LPDIRECTSOUNDNOTIFY8 dsbNotify; HANDLE dsbEvent; WAVEFORMATEX wfx; // Primary buffer wave format int soundBufferTotalLen; public: DirectSound(); virtual ~DirectSound(); 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 }; DirectSound::DirectSound() { CoInitialize( NULL ); pDirectSound = NULL; dsbPrimary = NULL; dsbSecondary = NULL; dsbNotify = NULL; dsbEvent = NULL; soundBufferTotalLen = 14700; } DirectSound::~DirectSound() { if(dsbNotify) { dsbNotify->Release(); dsbNotify = NULL; } if(dsbEvent) { CloseHandle(dsbEvent); dsbEvent = NULL; } if(pDirectSound) { if(dsbPrimary) { dsbPrimary->Release(); dsbPrimary = NULL; } if(dsbSecondary) { dsbSecondary->Release(); dsbSecondary = NULL; } pDirectSound->Release(); pDirectSound = NULL; } CoUninitialize(); } bool DirectSound::init() { HRESULT hr; DWORD freq; DSBUFFERDESC dsbdesc; int i; // Initialize DirectSound if( FAILED( hr = DirectSoundCreate8( &DSDEVID_DefaultPlayback, &pDirectSound, NULL ) ) ) { systemMessage( IDS_CANNOT_CREATE_DIRECTSOUND, _T("Cannot create DirectSound %08x"), hr ); pDirectSound = NULL; return false; } if( FAILED( hr = pDirectSound->SetCooperativeLevel( theApp.m_pMainWnd->GetSafeHwnd(), DSSCL_EXCLUSIVE ) ) ) { systemMessage( IDS_CANNOT_SETCOOPERATIVELEVEL, _T("Cannot SetCooperativeLevel %08x"), hr ); return false; } // Create primary sound buffer ZeroMemory( &dsbdesc, sizeof(DSBUFFERDESC) ); dsbdesc.dwSize = sizeof(DSBUFFERDESC); dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER; if( theApp.dsoundDisableHardwareAcceleration ) { dsbdesc.dwFlags |= DSBCAPS_LOCSOFTWARE; } if( FAILED( hr = pDirectSound->CreateSoundBuffer( &dsbdesc, &dsbPrimary, NULL ) ) ) { systemMessage(IDS_CANNOT_CREATESOUNDBUFFER, _T("Cannot CreateSoundBuffer %08x"), hr); return false; } freq = 44100 / soundQuality; // 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; soundBufferTotalLen = soundBufferLen * 10; ZeroMemory( &wfx, sizeof(WAVEFORMATEX) ); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 2; wfx.nSamplesPerSec = freq; wfx.wBitsPerSample = 16; wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; if( FAILED( hr = dsbPrimary->SetFormat( &wfx ) ) ) { systemMessage( IDS_CANNOT_SETFORMAT_PRIMARY, _T("CreateSoundBuffer(primary) failed %08x"), hr ); return false; } // Create secondary sound buffer ZeroMemory( &dsbdesc, sizeof(DSBUFFERDESC) ); dsbdesc.dwSize = sizeof(DSBUFFERDESC); dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS; if( theApp.dsoundDisableHardwareAcceleration ) { dsbdesc.dwFlags |= DSBCAPS_LOCSOFTWARE; } dsbdesc.dwBufferBytes = soundBufferTotalLen; dsbdesc.lpwfxFormat = &wfx; if( FAILED( hr = pDirectSound->CreateSoundBuffer( &dsbdesc, &dsbSecondary, NULL ) ) ) { systemMessage( IDS_CANNOT_CREATESOUNDBUFFER, _T("CreateSoundBuffer(secondary) failed %08x"), hr ); return false; } if( FAILED( hr = dsbSecondary->SetCurrentPosition( 0 ) ) ) { systemMessage( 0, _T("dsbSecondary->SetCurrentPosition failed %08x"), hr ); return false; } if( SUCCEEDED( hr = dsbSecondary->QueryInterface( IID_IDirectSoundNotify8, (LPVOID*)&dsbNotify ) ) ) { dsbEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); DSBPOSITIONNOTIFY notify[10]; for( i = 0; i < 10; i++ ) { notify[i].dwOffset = i * soundBufferLen; notify[i].hEventNotify = dsbEvent; } if( FAILED( dsbNotify->SetNotificationPositions( 10, notify ) ) ) { dsbNotify->Release(); dsbNotify = NULL; CloseHandle(dsbEvent); dsbEvent = NULL; } } // Play primary buffer if( FAILED( hr = dsbPrimary->Play( 0, 0, DSBPLAY_LOOPING ) ) ) { systemMessage( IDS_CANNOT_PLAY_PRIMARY, _T("Cannot Play primary %08x"), hr ); return false; } setsystemSoundOn(true); return true; } void DirectSound::pause() { if( dsbSecondary == NULL ) return; DWORD status; dsbSecondary->GetStatus( &status ); if( status & DSBSTATUS_PLAYING ) dsbSecondary->Stop(); } void DirectSound::reset() { if( dsbSecondary == NULL ) return; dsbSecondary->Stop(); dsbSecondary->SetCurrentPosition( 0 ); } void DirectSound::resume() { if( dsbSecondary == NULL ) return; dsbSecondary->Play( 0, 0, DSBPLAY_LOOPING ); } void DirectSound::write() { if(!pDirectSound) return; HRESULT hr; DWORD status = 0; DWORD play = 0; LPVOID lpvPtr1; DWORD dwBytes1 = 0; LPVOID lpvPtr2; DWORD dwBytes2 = 0; if( !speedup && synchronize && !theApp.throttle ) { hr = dsbSecondary->GetStatus(&status); if( status & DSBSTATUS_PLAYING ) { if( !soundPaused ) { while( true ) { dsbSecondary->GetCurrentPosition(&play, NULL); int BufferLeft = ((soundNextPosition <= play) ? play - soundNextPosition : soundBufferTotalLen - soundNextPosition + play); if(BufferLeft > soundBufferLen) { if (BufferLeft > soundBufferTotalLen - (soundBufferLen * 3)) soundBufferLow = true; break; } soundBufferLow = false; if(dsbEvent) { WaitForSingleObject(dsbEvent, 50); } } } } else { setsoundPaused(true); } } // Obtain memory address of write block. // This will be in two parts if the block wraps around. if( DSERR_BUFFERLOST == ( hr = dsbSecondary->Lock( soundNextPosition, soundBufferLen, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0 ) ) ) { // If DSERR_BUFFERLOST is returned, restore and retry lock. dsbSecondary->Restore(); hr = dsbSecondary->Lock( soundNextPosition, soundBufferLen, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0 ); } soundNextPosition += soundBufferLen; soundNextPosition = soundNextPosition % soundBufferTotalLen; if( SUCCEEDED( hr ) ) { // Write to pointers. CopyMemory( lpvPtr1, soundFinalWave, dwBytes1 ); if ( lpvPtr2 ) { CopyMemory( lpvPtr2, soundFinalWave + dwBytes1, dwBytes2 ); } // Release the data back to DirectSound. hr = dsbSecondary->Unlock( lpvPtr1, dwBytes1, lpvPtr2, dwBytes2 ); } else { systemMessage( 0, _T("dsbSecondary->Lock() failed: %08x"), hr ); return; } } ISound *newDirectSound() { return new DirectSound(); }