project64/Source/Project64-audio/Driver/DirectSound.cpp

282 lines
8.8 KiB
C++

// Project64 - A Nintendo 64 emulator
// https://www.pj64-emu.com/
// Copyright(C) 2001-2021 Project64.
// Copyright(C) 2000-2015 Azimer
// GNU/GPLv2 licensed: https://gnu.org/licenses/gpl-2.0.html
#include <windows.h>
#include <mmreg.h>
#include <dsound.h>
#include "DirectSound.h"
#include "AudioMain.h"
#include "trace.h"
#include "AudioSettings.h"
DirectSoundDriver::DirectSoundDriver() :
m_AudioIsDone(true),
m_LOCK_SIZE(0),
m_lpds(nullptr),
m_lpdsb(nullptr),
m_lpdsbuf(nullptr),
m_handleAudioThread(nullptr),
m_dwAudioThreadId(0)
{
}
bool DirectSoundDriver::Initialize()
{
WriteTrace(TraceAudioDriver, TraceDebug, "Start");
if (!SoundDriverBase::Initialize())
{
WriteTrace(TraceAudioDriver, TraceDebug, "Done (res: false)");
return false;
}
CGuard guard(m_CS);
LPDIRECTSOUND8 & lpds = (LPDIRECTSOUND8 &)m_lpds;
HRESULT hr = DirectSoundCreate8(nullptr, &lpds, nullptr);
if (FAILED(hr))
{
WriteTrace(TraceAudioDriver, TraceWarning, "Unable to DirectSoundCreate (hr: 0x%08X)", hr);
WriteTrace(TraceAudioDriver, TraceDebug, "Done (res: false)");
return false;
}
hr = lpds->SetCooperativeLevel((HWND)g_AudioInfo.hwnd, DSSCL_PRIORITY);
if (FAILED(hr))
{
WriteTrace(TraceAudioDriver, TraceWarning, "Failed to SetCooperativeLevel (hr: 0x%08X)", hr);
WriteTrace(TraceAudioDriver, TraceDebug, "Done (res: false)");
return false;
}
LPDIRECTSOUNDBUFFER & lpdsbuf = (LPDIRECTSOUNDBUFFER &)m_lpdsbuf;
if (lpdsbuf)
{
IDirectSoundBuffer_Release(lpdsbuf);
lpdsbuf = nullptr;
}
DSBUFFERDESC dsPrimaryBuff = { 0 };
dsPrimaryBuff.dwSize = sizeof(DSBUFFERDESC);
dsPrimaryBuff.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
dsPrimaryBuff.dwBufferBytes = 0;
dsPrimaryBuff.lpwfxFormat = nullptr;
WAVEFORMATEX wfm = { 0 };
wfm.wFormatTag = WAVE_FORMAT_PCM;
wfm.nChannels = 2;
wfm.nSamplesPerSec = 48000;
wfm.wBitsPerSample = 16;
wfm.nBlockAlign = wfm.wBitsPerSample / 8 * wfm.nChannels;
wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;
LPDIRECTSOUNDBUFFER & lpdsb = (LPDIRECTSOUNDBUFFER &)m_lpdsb;
hr = lpds->CreateSoundBuffer(&dsPrimaryBuff, &lpdsb, nullptr);
if (SUCCEEDED(hr))
{
lpdsb->SetFormat(&wfm);
lpdsb->Play(0, 0, DSBPLAY_LOOPING);
}
WriteTrace(TraceAudioDriver, TraceDebug, "Done (res: true)");
return true;
}
void DirectSoundDriver::StopAudio()
{
if (m_handleAudioThread != nullptr)
{
m_AudioIsDone = true;
if (WaitForSingleObject((HANDLE)m_handleAudioThread, 5000) == WAIT_TIMEOUT)
{
WriteTrace(TraceAudioDriver, TraceError, "Timeout on close");
TerminateThread((HANDLE)m_handleAudioThread, 1);
}
CloseHandle((HANDLE)m_handleAudioThread);
m_handleAudioThread = nullptr;
}
}
void DirectSoundDriver::StartAudio()
{
WriteTrace(TraceAudioDriver, TraceDebug, "Start");
if (m_handleAudioThread == nullptr)
{
m_AudioIsDone = false;
m_handleAudioThread = CreateThread(nullptr, NULL, (LPTHREAD_START_ROUTINE)stAudioThreadProc, this, 0, (LPDWORD)&m_dwAudioThreadId);
LPDIRECTSOUNDBUFFER & lpdsbuf = (LPDIRECTSOUNDBUFFER &)m_lpdsbuf;
if (lpdsbuf != nullptr)
{
lpdsbuf->Play(0, 0, DSBPLAY_LOOPING);
}
}
WriteTrace(TraceAudioDriver, TraceDebug, "Done");
}
void DirectSoundDriver::SetFrequency(uint32_t Frequency, uint32_t BufferSize)
{
WriteTrace(TraceAudioDriver, TraceDebug, "Start (Frequency: 0x%08X)", Frequency);
StopAudio();
m_LOCK_SIZE = (BufferSize * 2);
SetSegmentSize(m_LOCK_SIZE, Frequency);
StartAudio();
WriteTrace(TraceAudioDriver, TraceDebug, "Done");
}
void DirectSoundDriver::SetVolume(uint32_t Volume)
{
LPDIRECTSOUNDBUFFER & lpdsb = (LPDIRECTSOUNDBUFFER &)m_lpdsb;
int32_t dsVolume = -((100 - (int32_t)Volume) * 25);
if (Volume == 0)
{
dsVolume = DSBVOLUME_MIN;
}
if (lpdsb != nullptr)
{
lpdsb->SetVolume(dsVolume);
}
}
void DirectSoundDriver::SetSegmentSize(uint32_t length, uint32_t SampleRate)
{
if (SampleRate == 0) { return; }
CGuard guard(m_CS);
WAVEFORMATEX wfm = { 0 };
wfm.wFormatTag = WAVE_FORMAT_PCM;
wfm.nChannels = 2;
wfm.nSamplesPerSec = SampleRate;
wfm.wBitsPerSample = 16;
wfm.nBlockAlign = wfm.wBitsPerSample / 8 * wfm.nChannels;
wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;
DSBUFFERDESC dsbdesc = { 0 };
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE;
dsbdesc.dwBufferBytes = length * DS_SEGMENTS;
dsbdesc.lpwfxFormat = &wfm;
LPDIRECTSOUND8 & lpds = (LPDIRECTSOUND8 &)m_lpds;
LPDIRECTSOUNDBUFFER & lpdsbuf = (LPDIRECTSOUNDBUFFER &)m_lpdsbuf;
if (lpds != nullptr)
{
HRESULT hr = lpds->CreateSoundBuffer(&dsbdesc, &lpdsbuf, nullptr);
if (FAILED(hr))
{
WriteTrace(TraceAudioDriver, TraceWarning, "CreateSoundBuffer failed (hr: 0x%08X)", hr);
}
}
if (lpdsbuf != nullptr)
{
lpdsbuf->Play(0, 0, DSBPLAY_LOOPING);
}
}
void DirectSoundDriver::AudioThreadProc()
{
LPDIRECTSOUNDBUFFER & lpdsbuff = (LPDIRECTSOUNDBUFFER &)m_lpdsbuf;
while (lpdsbuff == nullptr && !m_AudioIsDone)
{
Sleep(10);
}
if (!m_AudioIsDone)
{
WriteTrace(TraceAudioDriver, TraceDebug, "Audio thread started...");
DWORD dwStatus;
lpdsbuff->GetStatus(&dwStatus);
if ((dwStatus & DSBSTATUS_PLAYING) == 0)
{
lpdsbuff->Play(0, 0, 0);
}
SetThreadPriority(m_handleAudioThread, THREAD_PRIORITY_HIGHEST);
}
uint32_t next_pos = 0, write_pos = 0, last_pos = 0;
while (lpdsbuff != nullptr && !m_AudioIsDone)
{
WriteTrace(TraceAudioDriver, TraceVerbose, "last_pos: 0x%08X write_pos: 0x%08X next_pos: 0x%08X", last_pos, write_pos, next_pos);
while (last_pos == write_pos)
{ // Cycle around until a new buffer position is available
if (lpdsbuff == nullptr)
{
break;
}
uint32_t play_pos = 0;
if (lpdsbuff == nullptr || FAILED(lpdsbuff->GetCurrentPosition((unsigned long*)&play_pos, nullptr)))
{
WriteTrace(TraceAudioDriver, TraceDebug, "Error getting audio position...");
m_AudioIsDone = true;
break;
}
write_pos = play_pos < m_LOCK_SIZE ? (m_LOCK_SIZE * DS_SEGMENTS) - m_LOCK_SIZE : ((play_pos / m_LOCK_SIZE) * m_LOCK_SIZE) - m_LOCK_SIZE;
WriteTrace(TraceAudioDriver, TraceVerbose, "play_pos: 0x%08X m_write_pos: 0x%08X next_pos: 0x%08X m_LOCK_SIZE: 0x%08X", play_pos, write_pos, next_pos, m_LOCK_SIZE);
if (last_pos == write_pos)
{
WriteTrace(TraceAudioDriver, TraceVerbose, "Sleep");
Sleep(1);
}
}
// This means we had a buffer segment skipped
if (next_pos != write_pos)
{
WriteTrace(TraceAudioDriver, TraceDebug, "Buffer segment skipped");
}
// Store our last position
last_pos = write_pos;
// Set out next anticipated segment
next_pos = write_pos + m_LOCK_SIZE;
if (next_pos >= (m_LOCK_SIZE*DS_SEGMENTS))
{
next_pos -= (m_LOCK_SIZE*DS_SEGMENTS);
}
if (m_AudioIsDone)
{
break;
}
// Time to write out to the buffer
LPVOID lpvPtr1, lpvPtr2;
DWORD dwBytes1, dwBytes2;
WriteTrace(TraceAudioDriver, TraceVerbose, "Lock buffer");
if (lpdsbuff->Lock(write_pos, m_LOCK_SIZE, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0) != DS_OK)
{
WriteTrace(TraceAudioDriver, TraceError, "Error locking sound buffer");
break;
}
WriteTrace(TraceAudioDriver, TraceVerbose, "dwBytes1: 0x%08X dwBytes2: 0x%08X", dwBytes1, dwBytes2);
{
CGuard guard(m_CS);
LoadAiBuffer((uint8_t *)lpvPtr1, dwBytes1);
if (dwBytes2)
{
WriteTrace(TraceAudioDriver, TraceDebug, "Loading second buffer");
LoadAiBuffer((BYTE *)lpvPtr2, dwBytes2);
}
}
// Fills dwBytes to the sound buffer
WriteTrace(TraceAudioDriver, TraceVerbose, "Unlock buffer");
if (FAILED(lpdsbuff->Unlock(lpvPtr1, dwBytes1, lpvPtr2, dwBytes2)))
{
WriteTrace(TraceAudioDriver, TraceError, "Error unlocking sound buffer");
break;
}
}
LPDIRECTSOUNDBUFFER & lpdsbuf = (LPDIRECTSOUNDBUFFER &)m_lpdsbuf;
if (lpdsbuf)
{
lpdsbuf->Stop();
}
WriteTrace(TraceAudioDriver, TraceDebug, "Audio thread terminated...");
}