rlm@1: #include "stdafx.h" rlm@1: #include rlm@1: #include rlm@1: rlm@1: #include "resource.h" rlm@1: #include "AVIWrite.h" rlm@1: #include "Sound.h" rlm@1: #include "WavWriter.h" rlm@1: #include "VBA.h" rlm@1: rlm@1: #include "../gba/GBAGlobals.h" rlm@1: #include "../gba/GBASound.h" rlm@1: #include "../common/nesvideos-piece.h" rlm@1: rlm@1: extern void directXMessage(const char *); rlm@1: rlm@1: class DirectSound : public ISound rlm@1: { rlm@1: private: rlm@1: HINSTANCE dsoundDLL; rlm@1: LPDIRECTSOUND pDirectSound; rlm@1: LPDIRECTSOUNDBUFFER dsbPrimary; rlm@1: LPDIRECTSOUNDBUFFER dsbSecondary; rlm@1: LPDIRECTSOUNDNOTIFY dsbNotify; rlm@1: HANDLE dsbEvent; rlm@1: WAVEFORMATEX wfx; rlm@1: float curRate; rlm@1: public: rlm@1: DirectSound(); rlm@1: virtual ~DirectSound(); rlm@1: rlm@1: bool init(); rlm@1: void pause(); rlm@1: void reset(); rlm@1: void resume(); rlm@1: void write(); rlm@1: void setSpeed(float rate); rlm@1: bool isPlaying(); rlm@1: void clearAudioBuffer(); rlm@1: }; rlm@1: rlm@1: DirectSound::DirectSound() rlm@1: { rlm@1: dsoundDLL = NULL; rlm@1: pDirectSound = NULL; rlm@1: dsbPrimary = NULL; rlm@1: dsbSecondary = NULL; rlm@1: dsbNotify = NULL; rlm@1: dsbEvent = NULL; rlm@1: } rlm@1: rlm@1: DirectSound::~DirectSound() rlm@1: { rlm@1: if (theApp.aviRecorder != NULL) rlm@1: { rlm@1: delete theApp.aviRecorder; rlm@1: theApp.aviRecorder = NULL; rlm@1: theApp.aviRecording = false; rlm@1: } rlm@1: rlm@1: if (theApp.soundRecording) rlm@1: { rlm@1: if (theApp.soundRecorder != NULL) rlm@1: { rlm@1: delete theApp.soundRecorder; rlm@1: theApp.soundRecorder = NULL; rlm@1: } rlm@1: theApp.soundRecording = false; rlm@1: } rlm@1: rlm@1: if (dsbNotify != NULL) rlm@1: { rlm@1: dsbNotify->Release(); rlm@1: dsbNotify = NULL; rlm@1: } rlm@1: rlm@1: if (dsbEvent != NULL) rlm@1: { rlm@1: CloseHandle(dsbEvent); rlm@1: dsbEvent = NULL; rlm@1: } rlm@1: rlm@1: if (pDirectSound != NULL) rlm@1: { rlm@1: if (dsbPrimary != NULL) rlm@1: { rlm@1: dsbPrimary->Release(); rlm@1: dsbPrimary = NULL; rlm@1: } rlm@1: rlm@1: if (dsbSecondary != NULL) rlm@1: { rlm@1: dsbSecondary->Release(); rlm@1: dsbSecondary = NULL; rlm@1: } rlm@1: rlm@1: pDirectSound->Release(); rlm@1: pDirectSound = NULL; rlm@1: } rlm@1: rlm@1: if (dsoundDLL != NULL) rlm@1: { rlm@1: FreeLibrary(dsoundDLL); rlm@1: dsoundDLL = NULL; rlm@1: } rlm@1: } rlm@1: rlm@1: bool DirectSound::init() rlm@1: { rlm@1: HRESULT hr; rlm@1: rlm@1: dsoundDLL = LoadLibrary("DSOUND.DLL"); rlm@1: HRESULT (WINAPI *DSoundCreate)(LPCGUID, LPDIRECTSOUND *, IUnknown *); rlm@1: if (dsoundDLL != NULL) rlm@1: { rlm@1: DSoundCreate = (HRESULT (WINAPI *)(LPCGUID, LPDIRECTSOUND *, IUnknown *)) rlm@1: GetProcAddress(dsoundDLL, "DirectSoundCreate"); rlm@1: rlm@1: if (DSoundCreate == NULL) rlm@1: { rlm@1: directXMessage("DirectSoundCreate"); rlm@1: return false; rlm@1: } rlm@1: } rlm@1: else rlm@1: { rlm@1: directXMessage("DSOUND.DLL"); rlm@1: return false; rlm@1: } rlm@1: rlm@1: if (FAILED(hr = DSoundCreate(NULL, &pDirectSound, NULL))) rlm@1: { rlm@1: // errorMessage(myLoadString(IDS_ERROR_SOUND_CREATE), hr); rlm@1: systemMessage(IDS_CANNOT_CREATE_DIRECTSOUND, rlm@1: "Cannot create DirectSound %08x", hr); rlm@1: pDirectSound = NULL; rlm@1: dsbSecondary = NULL; rlm@1: return false; rlm@1: } rlm@1: rlm@1: if (FAILED(hr = pDirectSound->SetCooperativeLevel((HWND)*theApp.m_pMainWnd, DSSCL_EXCLUSIVE))) rlm@1: { rlm@1: // errorMessage(myLoadString(IDS_ERROR_SOUND_LEVEL), hr); rlm@1: systemMessage(IDS_CANNOT_SETCOOPERATIVELEVEL, rlm@1: "Cannot SetCooperativeLevel %08x", hr); rlm@1: return false; rlm@1: } rlm@1: rlm@1: DSBUFFERDESC dsbdesc; rlm@1: ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC)); rlm@1: dsbdesc.dwSize = sizeof(DSBUFFERDESC); rlm@1: dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER; rlm@1: rlm@1: if (FAILED(hr = pDirectSound->CreateSoundBuffer(&dsbdesc, &dsbPrimary, NULL))) rlm@1: { rlm@1: // errorMessage(myLoadString(IDS_ERROR_SOUND_BUFFER),hr); rlm@1: systemMessage(IDS_CANNOT_CREATESOUNDBUFFER, rlm@1: "Cannot CreateSoundBuffer %08x", hr); rlm@1: return false; rlm@1: } rlm@1: rlm@1: // Set primary buffer format rlm@1: rlm@1: memset(&wfx, 0, sizeof(WAVEFORMATEX)); rlm@1: wfx.wFormatTag = WAVE_FORMAT_PCM; rlm@1: wfx.nChannels = 2; rlm@1: switch (soundQuality) rlm@1: { rlm@1: case 2: rlm@1: wfx.nSamplesPerSec = 22050; rlm@1: soundBufferLen = 736 * 2; rlm@1: soundBufferTotalLen = 7360 * 2; rlm@1: break; rlm@1: case 4: rlm@1: wfx.nSamplesPerSec = 11025; rlm@1: soundBufferLen = 368 * 2; rlm@1: soundBufferTotalLen = 3680 * 2; rlm@1: break; rlm@1: default: rlm@1: soundQuality = 1; rlm@1: wfx.nSamplesPerSec = 44100; rlm@1: soundBufferLen = 1470 * 2; rlm@1: soundBufferTotalLen = 14700 * 2; rlm@1: } rlm@1: wfx.wBitsPerSample = 16; rlm@1: wfx.nBlockAlign = (wfx.wBitsPerSample / 8) * wfx.nChannels; rlm@1: wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; rlm@1: rlm@1: if (FAILED(hr = dsbPrimary->SetFormat(&wfx))) rlm@1: { rlm@1: // errorMessage(myLoadString(IDS_ERROR_SOUND_PRIMARY),hr); rlm@1: systemMessage(IDS_CANNOT_SETFORMAT_PRIMARY, rlm@1: "Cannot SetFormat for primary %08x", hr); rlm@1: return false; rlm@1: } rlm@1: rlm@1: ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC)); rlm@1: dsbdesc.dwSize = sizeof(DSBUFFERDESC); rlm@1: dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS; rlm@1: dsbdesc.dwBufferBytes = soundBufferTotalLen; rlm@1: dsbdesc.lpwfxFormat = &wfx; rlm@1: rlm@1: if (FAILED(hr = pDirectSound->CreateSoundBuffer(&dsbdesc, &dsbSecondary, NULL))) rlm@1: { rlm@1: bool ok = false; rlm@1: while (dsbdesc.dwFlags != DSBCAPS_GETCURRENTPOSITION2) rlm@1: { rlm@1: if (dsbdesc.dwFlags & DSBCAPS_CTRLFREQUENCY) rlm@1: dsbdesc.dwFlags ^= DSBCAPS_CTRLFREQUENCY; rlm@1: else if (dsbdesc.dwFlags & DSBCAPS_GLOBALFOCUS) rlm@1: dsbdesc.dwFlags ^= DSBCAPS_GLOBALFOCUS; rlm@1: else if (dsbdesc.dwFlags & DSBCAPS_CTRLPOSITIONNOTIFY) rlm@1: dsbdesc.dwFlags ^= DSBCAPS_CTRLPOSITIONNOTIFY; rlm@1: if (SUCCEEDED(hr = pDirectSound->CreateSoundBuffer(&dsbdesc, &dsbSecondary, NULL))) rlm@1: { rlm@1: ok = true; rlm@1: break; rlm@1: } rlm@1: } rlm@1: if (!ok) rlm@1: { rlm@1: systemMessage(IDS_CANNOT_CREATESOUNDBUFFER_SEC, "Cannot CreateSoundBuffer secondary %08x", hr); rlm@1: return false; rlm@1: } rlm@1: rlm@1: dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2; rlm@1: } rlm@1: rlm@1: dsbSecondary->SetCurrentPosition(0); rlm@1: rlm@1: if (!theApp.useOldSync) rlm@1: { rlm@1: hr = dsbSecondary->QueryInterface(IID_IDirectSoundNotify, rlm@1: (void * *)&dsbNotify); rlm@1: if (!FAILED(hr)) rlm@1: { rlm@1: dsbEvent = CreateEvent(NULL, FALSE, FALSE, NULL); rlm@1: rlm@1: DSBPOSITIONNOTIFY notify[10]; rlm@1: rlm@1: for (int i = 0; i < 10; i++) rlm@1: { rlm@1: notify[i].dwOffset = i * soundBufferLen; rlm@1: notify[i].hEventNotify = dsbEvent; rlm@1: } rlm@1: if (FAILED(dsbNotify->SetNotificationPositions(10, notify))) rlm@1: { rlm@1: dsbNotify->Release(); rlm@1: dsbNotify = NULL; rlm@1: CloseHandle(dsbEvent); rlm@1: dsbEvent = NULL; rlm@1: } rlm@1: } rlm@1: } rlm@1: rlm@1: hr = dsbPrimary->Play(0, 0, DSBPLAY_LOOPING); rlm@1: rlm@1: if (FAILED(hr)) rlm@1: { rlm@1: // errorMessage(myLoadString(IDS_ERROR_SOUND_PLAYPRIM), hr); rlm@1: systemMessage(IDS_CANNOT_PLAY_PRIMARY, "Cannot Play primary %08x", hr); rlm@1: return false; rlm@1: } rlm@1: rlm@1: systemSoundOn = true; rlm@1: rlm@1: return true; rlm@1: } rlm@1: rlm@1: void DirectSound::setSpeed(float rate) rlm@1: { rlm@1: if (dsbSecondary == NULL || wfx.nSamplesPerSec <= 0) rlm@1: return; rlm@1: rlm@1: if (rate != curRate) rlm@1: { rlm@1: curRate = rate; rlm@1: rlm@1: if (rate > 4.0f) rlm@1: rate = 4.0f; rlm@1: if (rate < 0.06f) rlm@1: rate = 0.06f; rlm@1: rlm@1: dsbSecondary->SetFrequency((DWORD)((float)wfx.nSamplesPerSec * rate)); rlm@1: } rlm@1: } rlm@1: rlm@1: void DirectSound::pause() rlm@1: { rlm@1: if (dsbSecondary != NULL) rlm@1: { rlm@1: DWORD status = 0; rlm@1: dsbSecondary->GetStatus(&status); rlm@1: rlm@1: if (status & DSBSTATUS_PLAYING) rlm@1: { rlm@1: //systemScreenMessage("sound stopped (pause)!", 3); rlm@1: dsbSecondary->Stop(); rlm@1: } rlm@1: } rlm@1: } rlm@1: rlm@1: bool DirectSound::isPlaying() rlm@1: { rlm@1: if (dsbSecondary != NULL) rlm@1: { rlm@1: DWORD status = 0; rlm@1: dsbSecondary->GetStatus(&status); rlm@1: rlm@1: if (status & DSBSTATUS_PLAYING) rlm@1: { rlm@1: return true; rlm@1: } rlm@1: } rlm@1: return false; rlm@1: } rlm@1: rlm@1: void DirectSound::reset() rlm@1: { rlm@1: if (dsbSecondary) rlm@1: { rlm@1: //systemScreenMessage("sound stopped (reset)!", 3); rlm@1: dsbSecondary->Stop(); rlm@1: dsbSecondary->SetCurrentPosition(0); rlm@1: } rlm@1: } rlm@1: rlm@1: void DirectSound::resume() rlm@1: { rlm@1: if (dsbSecondary != NULL) rlm@1: { rlm@1: dsbSecondary->Play(0, 0, DSBPLAY_LOOPING); rlm@1: } rlm@1: } rlm@1: rlm@1: long linearFrameCount = 0; rlm@1: long linearSoundByteCount = 0; rlm@1: long linearSoundFrameCount = 0; rlm@1: rlm@1: void DirectSound::write() rlm@1: { rlm@1: int len = soundBufferLen; rlm@1: LPVOID lpvPtr1; rlm@1: DWORD dwBytes1; rlm@1: LPVOID lpvPtr2; rlm@1: DWORD dwBytes2; rlm@1: rlm@1: do rlm@1: { rlm@1: linearSoundByteCount += len; rlm@1: if (wfx.nAvgBytesPerSec) rlm@1: linearSoundFrameCount = 60 * linearSoundByteCount / wfx.nAvgBytesPerSec; rlm@1: rlm@1: if (pDirectSound != NULL) rlm@1: { rlm@1: if (theApp.soundRecording) rlm@1: { rlm@1: if (dsbSecondary) rlm@1: { rlm@1: if (theApp.soundRecorder == NULL) rlm@1: { rlm@1: theApp.soundRecorder = new WavWriter; rlm@1: WAVEFORMATEX format; rlm@1: dsbSecondary->GetFormat(&format, sizeof(format), NULL); rlm@1: if (theApp.soundRecorder->Open(theApp.soundRecordName)) rlm@1: theApp.soundRecorder->SetFormat(&format); rlm@1: } rlm@1: } rlm@1: rlm@1: if (theApp.soundRecorder) rlm@1: { rlm@1: theApp.soundRecorder->AddSound((u8 *)soundFinalWave, len); rlm@1: } rlm@1: } rlm@1: rlm@1: if (theApp.nvAudioLog) rlm@1: { rlm@1: NESVideoLoggingAudio((u8 *)soundFinalWave, wfx.nSamplesPerSec, wfx.wBitsPerSample, wfx.nChannels, len / rlm@1: (wfx.nChannels * (wfx.wBitsPerSample / 8))); rlm@1: } rlm@1: rlm@1: // alternate avi record routine has been added in VBA.cpp rlm@1: if (!theApp.altAviRecordMethod && theApp.aviRecording) rlm@1: { rlm@1: if (theApp.aviRecorder && !theApp.aviRecorder->IsPaused()) rlm@1: { rlm@1: if (dsbSecondary) rlm@1: { rlm@1: if (!theApp.aviRecorder->IsSoundAdded()) rlm@1: { rlm@1: WAVEFORMATEX format; rlm@1: dsbSecondary->GetFormat(&format, sizeof(format), NULL); rlm@1: theApp.aviRecorder->SetSoundFormat(&format); rlm@1: } rlm@1: } rlm@1: rlm@1: theApp.aviRecorder->AddSound((u8 *)soundFinalWave, len); rlm@1: } rlm@1: } rlm@1: } rlm@1: } rlm@1: while (linearSoundFrameCount <= linearFrameCount); rlm@1: rlm@1: // arbitrarily wrap counters at 10000 frames to avoid mismatching wrap-around freeze rlm@1: if (linearSoundFrameCount > 10000 && linearFrameCount > 10000) rlm@1: { rlm@1: linearFrameCount -= 10000; rlm@1: linearSoundByteCount -= wfx.nAvgBytesPerSec * 10000 / 60; rlm@1: linearSoundFrameCount = 60 * linearSoundByteCount / wfx.nAvgBytesPerSec; rlm@1: } rlm@1: rlm@1: if (!pDirectSound) rlm@1: return; rlm@1: rlm@1: HRESULT hr; rlm@1: rlm@1: bool fastForward = speedup; rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: fastForward |= theApp.frameSearchSkipping; rlm@1: #endif rlm@1: rlm@1: // slows down emulator to match up with the sound speed rlm@1: if (!fastForward && synchronize && !(theApp.throttle > 100 && theApp.accuratePitchThrottle) rlm@1: && theApp.throttle >= 6 && theApp.throttle <= 400) rlm@1: { rlm@1: DWORD status = 0; rlm@1: hr = dsbSecondary->GetStatus(&status); rlm@1: if (status & DSBSTATUS_PLAYING) rlm@1: { rlm@1: if (!soundPaused) rlm@1: { rlm@1: DWORD play; rlm@1: while (true) rlm@1: { rlm@1: dsbSecondary->GetCurrentPosition(&play, NULL); rlm@1: rlm@1: if (soundNextPosition + soundBufferLen < soundBufferTotalLen) rlm@1: { rlm@1: if (play < soundNextPosition rlm@1: || play > soundNextPosition + soundBufferLen) rlm@1: break; rlm@1: } rlm@1: else rlm@1: { rlm@1: if (play < soundNextPosition rlm@1: && play > (soundNextPosition + soundBufferLen) % soundBufferTotalLen) rlm@1: break; rlm@1: } rlm@1: rlm@1: if (dsbEvent) rlm@1: { rlm@1: WaitForSingleObject(dsbEvent, 50); rlm@1: } rlm@1: } rlm@1: } rlm@1: } rlm@1: else rlm@1: { rlm@1: soundPaused = 1; rlm@1: } rlm@1: } rlm@1: rlm@1: // Obtain memory address of write block. This will be in two parts rlm@1: // if the block wraps around. rlm@1: hr = dsbSecondary->Lock(soundNextPosition, soundBufferLen, rlm@1: &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, rlm@1: 0); rlm@1: rlm@1: if (FAILED(hr)) rlm@1: { rlm@1: char str [256]; rlm@1: sprintf(str, "Locking secondary failed with %d", hr); rlm@1: systemScreenMessage(str); rlm@1: } rlm@1: rlm@1: // If DSERR_BUFFERLOST is returned, restore and retry lock. rlm@1: if (DSERR_BUFFERLOST == hr) rlm@1: { rlm@1: dsbSecondary->Restore(); rlm@1: hr = dsbSecondary->Lock(soundNextPosition, soundBufferLen, rlm@1: &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, rlm@1: 0); rlm@1: } rlm@1: rlm@1: if (SUCCEEDED(hr)) rlm@1: { rlm@1: if (theApp.muteFrameAdvance && theApp.winPauseNextFrame || theApp.winMuteForNow) rlm@1: { rlm@1: // Write 0 to pointers. rlm@1: if (NULL != lpvPtr1) rlm@1: ZeroMemory(lpvPtr1, dwBytes1); rlm@1: if (NULL != lpvPtr2) rlm@1: ZeroMemory(lpvPtr2, dwBytes2); rlm@1: } rlm@1: else rlm@1: { rlm@1: // Write to pointers. rlm@1: if (NULL != lpvPtr1) rlm@1: CopyMemory(lpvPtr1, soundFinalWave, dwBytes1); rlm@1: if (NULL != lpvPtr2) rlm@1: CopyMemory(lpvPtr2, soundFinalWave + dwBytes1, dwBytes2); rlm@1: } rlm@1: rlm@1: // Release the data back to DirectSound. rlm@1: hr = dsbSecondary->Unlock(lpvPtr1, dwBytes1, lpvPtr2, rlm@1: dwBytes2); rlm@1: } rlm@1: rlm@1: soundNextPosition += soundBufferLen; rlm@1: soundNextPosition %= soundBufferTotalLen; rlm@1: } rlm@1: rlm@1: void DirectSound::clearAudioBuffer() rlm@1: { rlm@1: LPVOID lpvPtr1; rlm@1: DWORD dwBytes1; rlm@1: LPVOID lpvPtr2; rlm@1: DWORD dwBytes2; rlm@1: HRESULT hr = dsbSecondary->Lock(0, soundBufferTotalLen, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); rlm@1: if (!FAILED(hr)) rlm@1: { rlm@1: if (lpvPtr1) rlm@1: memset(lpvPtr1, 0, dwBytes1); rlm@1: if (lpvPtr2) rlm@1: memset(lpvPtr2, 0, dwBytes2); rlm@1: hr = dsbSecondary->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); rlm@1: } rlm@1: } rlm@1: rlm@1: ISound *newDirectSound() rlm@1: { rlm@1: return new DirectSound(); rlm@1: } rlm@1: