mirror of https://github.com/bsnes-emu/bsnes.git
214 lines
6.7 KiB
C++
Executable File
214 lines
6.7 KiB
C++
Executable File
#include "xaudio2.hpp"
|
|
#undef interface
|
|
|
|
struct AudioXAudio2 : AudioDriver, public IXAudio2VoiceCallback {
|
|
enum : uint { Buffers = 32 };
|
|
|
|
AudioXAudio2& self = *this;
|
|
AudioXAudio2(Audio& super) : AudioDriver(super) { construct(); }
|
|
~AudioXAudio2() { destruct(); }
|
|
|
|
auto create() -> bool override {
|
|
super.setDevice(hasDevices().first());
|
|
super.setChannels(2);
|
|
super.setFrequency(48000);
|
|
super.setLatency(40);
|
|
return initialize();
|
|
}
|
|
|
|
auto driver() -> string override { return "XAudio 2.1"; }
|
|
auto ready() -> bool override { return self.isReady; }
|
|
|
|
auto hasBlocking() -> bool override { return true; }
|
|
auto hasDynamic() -> bool override { return true; }
|
|
|
|
auto hasDevices() -> vector<string> override {
|
|
vector<string> devices;
|
|
for(auto& device : self.devices) devices.append(device.name);
|
|
return devices;
|
|
}
|
|
|
|
auto hasFrequencies() -> vector<uint> override {
|
|
return {44100, 48000, 96000};
|
|
}
|
|
|
|
auto hasLatencies() -> vector<uint> override {
|
|
return {20, 40, 60, 80, 100};
|
|
}
|
|
|
|
auto setDevice(string device) -> bool override { return initialize(); }
|
|
auto setBlocking(bool blocking) -> bool override { return true; }
|
|
auto setFrequency(uint frequency) -> bool override { return initialize(); }
|
|
auto setLatency(uint latency) -> bool override { return initialize(); }
|
|
|
|
auto clear() -> void override {
|
|
self.sourceVoice->Stop(0);
|
|
self.sourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers
|
|
|
|
self.index = 0;
|
|
self.queue = 0;
|
|
for(uint n : range(Buffers)) self.buffers[n].fill();
|
|
|
|
self.sourceVoice->Start(0);
|
|
}
|
|
|
|
auto level() -> double override {
|
|
XAUDIO2_VOICE_STATE state{};
|
|
self.sourceVoice->GetState(&state);
|
|
uint level = state.BuffersQueued * self.period + buffers[self.index].size() - state.SamplesPlayed % self.period;
|
|
uint limit = Buffers * self.period;
|
|
return (double)level / limit;
|
|
}
|
|
|
|
auto output(const double samples[]) -> void override {
|
|
uint32_t frame = 0;
|
|
frame |= (uint16_t)sclamp<16>(samples[0] * 32767.0) << 0;
|
|
frame |= (uint16_t)sclamp<16>(samples[1] * 32767.0) << 16;
|
|
|
|
auto& buffer = self.buffers[self.index];
|
|
buffer.write(frame);
|
|
if(!buffer.full()) return;
|
|
|
|
buffer.flush();
|
|
if(self.queue == Buffers - 1) {
|
|
if(self.blocking) {
|
|
//wait until there is at least one other free buffer for the next sample
|
|
while(self.queue == Buffers - 1);
|
|
} else {
|
|
//there is no free buffer for the next block, so ignore the current contents
|
|
return;
|
|
}
|
|
}
|
|
|
|
write(buffer.data(), buffer.capacity<uint8_t>());
|
|
self.index = (self.index + 1) % Buffers;
|
|
}
|
|
|
|
private:
|
|
struct Device {
|
|
uint id = 0;
|
|
uint channels = 0;
|
|
uint frequency = 0;
|
|
Format format = Format::none;
|
|
string name;
|
|
};
|
|
vector<Device> devices;
|
|
|
|
auto construct() -> void {
|
|
XAudio2Create(&self.interface, 0 , XAUDIO2_DEFAULT_PROCESSOR);
|
|
|
|
uint deviceCount = 0;
|
|
self.interface->GetDeviceCount(&deviceCount);
|
|
for(uint deviceIndex : range(deviceCount)) {
|
|
XAUDIO2_DEVICE_DETAILS deviceDetails{};
|
|
self.interface->GetDeviceDetails(deviceIndex, &deviceDetails);
|
|
auto format = deviceDetails.OutputFormat.Format.wFormatTag;
|
|
auto bits = deviceDetails.OutputFormat.Format.wBitsPerSample;
|
|
|
|
Device device;
|
|
device.id = deviceIndex;
|
|
device.name = (const char*)utf8_t(deviceDetails.DisplayName);
|
|
device.channels = deviceDetails.OutputFormat.Format.nChannels;
|
|
device.frequency = deviceDetails.OutputFormat.Format.nSamplesPerSec;
|
|
if(format == WAVE_FORMAT_PCM) {
|
|
if(bits == 16) device.format = Format::int16;
|
|
if(bits == 32) device.format = Format::int32;
|
|
} else if(format == WAVE_FORMAT_IEEE_FLOAT) {
|
|
if(bits == 32) device.format = Format::float32;
|
|
}
|
|
|
|
//ensure devices.first() is the default device
|
|
if(deviceDetails.Role & DefaultGameDevice) {
|
|
devices.prepend(device);
|
|
} else {
|
|
devices.append(device);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto destruct() -> void {
|
|
terminate();
|
|
|
|
if(self.interface) {
|
|
self.interface->Release();
|
|
self.interface = nullptr;
|
|
}
|
|
}
|
|
|
|
auto initialize() -> bool {
|
|
terminate();
|
|
if(!self.interface) return false;
|
|
|
|
self.period = self.frequency * self.latency / Buffers / 1000.0 + 0.5;
|
|
for(uint n : range(Buffers)) buffers[n].resize(self.period);
|
|
self.index = 0;
|
|
self.queue = 0;
|
|
|
|
if(!hasDevices().find(self.device)) self.device = hasDevices().first();
|
|
uint deviceID = devices[hasDevices().find(self.device)()].id;
|
|
|
|
if(FAILED(self.interface->CreateMasteringVoice(&self.masterVoice, self.channels, self.frequency, 0, deviceID, nullptr))) return terminate(), false;
|
|
|
|
WAVEFORMATEX waveFormat{};
|
|
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
|
waveFormat.nChannels = self.channels;
|
|
waveFormat.nSamplesPerSec = self.frequency;
|
|
waveFormat.nBlockAlign = 4;
|
|
waveFormat.wBitsPerSample = 16;
|
|
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
|
|
waveFormat.cbSize = 0;
|
|
|
|
if(FAILED(self.interface->CreateSourceVoice(&self.sourceVoice, (WAVEFORMATEX*)&waveFormat, XAUDIO2_VOICE_NOSRC, XAUDIO2_DEFAULT_FREQ_RATIO, this, nullptr, nullptr))) return terminate(), false;
|
|
|
|
clear();
|
|
return self.isReady = true;
|
|
}
|
|
|
|
auto terminate() -> void {
|
|
self.isReady = false;
|
|
|
|
if(self.sourceVoice) {
|
|
self.sourceVoice->Stop(0);
|
|
self.sourceVoice->DestroyVoice();
|
|
self.sourceVoice = nullptr;
|
|
}
|
|
|
|
if(self.masterVoice) {
|
|
self.masterVoice->DestroyVoice();
|
|
self.masterVoice = nullptr;
|
|
}
|
|
}
|
|
|
|
auto write(const uint32_t* audioData, uint bytes) -> void {
|
|
XAUDIO2_BUFFER buffer{};
|
|
buffer.AudioBytes = bytes;
|
|
buffer.pAudioData = (const BYTE*)audioData;
|
|
buffer.pContext = nullptr;
|
|
InterlockedIncrement(&self.queue);
|
|
self.sourceVoice->SubmitSourceBuffer(&buffer);
|
|
}
|
|
|
|
bool isReady = false;
|
|
|
|
queue<uint32_t> buffers[Buffers];
|
|
uint period = 0; //amount (in 32-bit frames) of samples per buffer
|
|
uint index = 0; //current buffer for writing samples to
|
|
volatile long queue = 0; //how many buffers are queued and ready for playback
|
|
|
|
IXAudio2* interface = nullptr;
|
|
IXAudio2MasteringVoice* masterVoice = nullptr;
|
|
IXAudio2SourceVoice* sourceVoice = nullptr;
|
|
|
|
//inherited from IXAudio2VoiceCallback
|
|
STDMETHODIMP_(void) OnBufferStart(void* pBufferContext){}
|
|
STDMETHODIMP_(void) OnLoopEnd(void* pBufferContext){}
|
|
STDMETHODIMP_(void) OnStreamEnd() {}
|
|
STDMETHODIMP_(void) OnVoiceError(void* pBufferContext, HRESULT Error) {}
|
|
STDMETHODIMP_(void) OnVoiceProcessingPassEnd() {}
|
|
STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
|
|
|
|
STDMETHODIMP_(void) OnBufferEnd(void* pBufferContext) {
|
|
InterlockedDecrement(&self.queue);
|
|
}
|
|
};
|