mirror of https://github.com/bsnes-emu/bsnes.git
138 lines
4.4 KiB
C++
138 lines
4.4 KiB
C++
#include <mmsystem.h>
|
|
|
|
auto CALLBACK waveOutCallback(HWAVEOUT handle, UINT message, DWORD_PTR userData, DWORD_PTR, DWORD_PTR) -> void;
|
|
|
|
struct AudioWaveOut : AudioDriver {
|
|
AudioWaveOut& self = *this;
|
|
AudioWaveOut(Audio& super) : AudioDriver(super) {}
|
|
~AudioWaveOut() { terminate(); }
|
|
|
|
auto create() -> bool override {
|
|
super.setChannels(2);
|
|
super.setFrequency(44100);
|
|
super.setLatency(512);
|
|
return initialize();
|
|
}
|
|
|
|
auto driver() -> string override { return "waveOut"; }
|
|
auto ready() -> bool override { return true; }
|
|
|
|
auto hasDevices() -> vector<string> override {
|
|
vector<string> devices{"Default"};
|
|
for(uint index : range(waveOutGetNumDevs())) {
|
|
WAVEOUTCAPS caps{};
|
|
if(waveOutGetDevCaps(index, &caps, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR) {
|
|
devices.append((const char*)utf8_t(caps.szPname));
|
|
}
|
|
}
|
|
return devices;
|
|
}
|
|
|
|
auto hasBlocking() -> bool override { return true; }
|
|
auto hasDynamic() -> bool override { return true; }
|
|
auto hasFrequencies() -> vector<uint> override { return {44100}; }
|
|
auto hasLatencies() -> vector<uint> override { return {512, 384, 320, 256, 192, 160, 128, 96, 80, 64, 48, 40, 32}; }
|
|
|
|
auto setBlocking(bool blocking) -> bool override { return true; }
|
|
auto setDynamic(bool dynamic) -> bool override { return initialize(); }
|
|
auto setLatency(uint latency) -> bool override { return initialize(); }
|
|
|
|
auto clear() -> void override {
|
|
for(auto& header : headers) {
|
|
memory::fill(header.lpData, frameCount * 4);
|
|
}
|
|
}
|
|
|
|
auto level() -> double override {
|
|
return (double)((blockQueue * frameCount) + frameIndex) / (blockCount * frameCount);
|
|
}
|
|
|
|
auto output(const double samples[]) -> void override {
|
|
uint16_t lsample = sclamp<16>(samples[0] * 32767.0);
|
|
uint16_t rsample = sclamp<16>(samples[1] * 32767.0);
|
|
|
|
auto block = (uint32_t*)headers[blockIndex].lpData;
|
|
block[frameIndex] = lsample << 0 | rsample << 16;
|
|
|
|
if(++frameIndex >= frameCount) {
|
|
frameIndex = 0;
|
|
if(self.dynamic) {
|
|
while(waveOutWrite(handle, &headers[blockIndex], sizeof(WAVEHDR)) == WAVERR_STILLPLAYING);
|
|
InterlockedIncrement(&blockQueue);
|
|
} else while(true) {
|
|
auto result = waveOutWrite(handle, &headers[blockIndex], sizeof(WAVEHDR));
|
|
if(!self.blocking || result != WAVERR_STILLPLAYING) break;
|
|
InterlockedIncrement(&blockQueue);
|
|
}
|
|
if(++blockIndex >= blockCount) {
|
|
blockIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
auto initialize() -> bool {
|
|
terminate();
|
|
|
|
auto deviceIndex = hasDevices().find(self.device);
|
|
if(!deviceIndex) deviceIndex = 0;
|
|
|
|
WAVEFORMATEX format{};
|
|
format.wFormatTag = WAVE_FORMAT_PCM;
|
|
format.nChannels = 2;
|
|
format.nSamplesPerSec = 44100;
|
|
format.nBlockAlign = 4;
|
|
format.wBitsPerSample = 16;
|
|
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
|
|
format.cbSize = 0; //not sizeof(WAVEFORMAT); size of extra information after WAVEFORMATEX
|
|
//-1 = default; 0+ = specific device; subtract -1 as hasDevices() includes "Default" entry
|
|
waveOutOpen(&handle, (int)*deviceIndex - 1, &format, (DWORD_PTR)waveOutCallback, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
|
|
|
frameCount = self.latency;
|
|
blockCount = 32;
|
|
frameIndex = 0;
|
|
blockIndex = 0;
|
|
blockQueue = 0;
|
|
|
|
headers.resize(blockCount);
|
|
for(auto& header : headers) {
|
|
memory::fill(&header, sizeof(WAVEHDR));
|
|
header.lpData = (LPSTR)LocalAlloc(LMEM_FIXED, frameCount * 4);
|
|
header.dwBufferLength = frameCount * 4;
|
|
waveOutPrepareHeader(handle, &header, sizeof(WAVEHDR));
|
|
}
|
|
|
|
waveOutSetVolume(handle, 0xffff'ffff); //100% volume (65535 left, 65535 right)
|
|
waveOutRestart(handle);
|
|
return true;
|
|
}
|
|
|
|
auto terminate() -> void {
|
|
if(!handle) return;
|
|
waveOutPause(handle);
|
|
waveOutReset(handle);
|
|
for(auto& header : headers) {
|
|
waveOutUnprepareHeader(handle, &header, sizeof(WAVEHDR));
|
|
LocalFree(header.lpData);
|
|
}
|
|
waveOutClose(handle);
|
|
handle = nullptr;
|
|
headers.reset();
|
|
}
|
|
|
|
HWAVEOUT handle = nullptr;
|
|
vector<WAVEHDR> headers;
|
|
uint frameCount = 0;
|
|
uint blockCount = 0;
|
|
uint frameIndex = 0;
|
|
uint blockIndex = 0;
|
|
|
|
public:
|
|
LONG blockQueue = 0;
|
|
};
|
|
|
|
auto CALLBACK waveOutCallback(HWAVEOUT handle, UINT message, DWORD_PTR userData, DWORD_PTR, DWORD_PTR) -> void {
|
|
auto instance = (AudioWaveOut*)userData;
|
|
if(instance->blockQueue > 0) InterlockedDecrement(&instance->blockQueue);
|
|
}
|