2010-08-16 04:10:50 +00:00
|
|
|
#include "xaudio2.hpp"
|
2013-05-02 11:25:45 +00:00
|
|
|
#include <windows.h>
|
2010-08-16 04:10:50 +00:00
|
|
|
|
2015-06-20 05:44:05 +00:00
|
|
|
struct AudioXAudio2 : Audio, public IXAudio2VoiceCallback {
|
Update to v097r02 release.
byuu says:
Note: balanced/performance profiles still broken, sorry.
Changelog:
- added nall/GNUmakefile unique() function; used on linking phase of
higan
- added nall/unique_pointer
- target-tomoko and {System}::Video updated to use
unique_pointer<ClassName> instead of ClassName* [1]
- locate() updated to search multiple paths [2]
- GB: pass gekkio's if_ie_registers and boot_hwio-G test ROMs
- FC, GB, GBA: merge video/ into the PPU cores
- ruby: fixed ~AudioXAudio2() typo
[1] I expected this to cause new crashes on exit due to changing the
order of destruction of objects (and deleting things that weren't
deleted before), but ... so far, so good. I guess we'll see what crops
up, especially on OS X (which is already crashing for unknown reasons on
exit.)
[2] right now, the search paths are: programpath(), {configpath(),
"higan/"}, {localpath(), "higan/"}; but we can add as many more as we
want, and we can also add platform-specific versions.
2016-01-25 11:27:18 +00:00
|
|
|
~AudioXAudio2() { term(); }
|
2010-08-16 04:10:50 +00:00
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
IXAudio2* pXAudio2 = nullptr;
|
|
|
|
IXAudio2MasteringVoice* pMasterVoice = nullptr;
|
|
|
|
IXAudio2SourceVoice* pSourceVoice = nullptr;
|
2013-05-02 11:25:45 +00:00
|
|
|
|
|
|
|
//inherited from IXAudio2VoiceCallback
|
|
|
|
STDMETHODIMP_(void) OnBufferStart(void* pBufferContext){}
|
|
|
|
STDMETHODIMP_(void) OnLoopEnd(void* pBufferContext){}
|
2010-08-16 04:10:50 +00:00
|
|
|
STDMETHODIMP_(void) OnStreamEnd() {}
|
2013-05-02 11:25:45 +00:00
|
|
|
STDMETHODIMP_(void) OnVoiceError(void* pBufferContext, HRESULT Error) {}
|
2010-08-16 04:10:50 +00:00
|
|
|
STDMETHODIMP_(void) OnVoiceProcessingPassEnd() {}
|
|
|
|
STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
|
|
|
|
|
|
|
|
struct {
|
2015-06-12 13:14:38 +00:00
|
|
|
unsigned buffers = 0;
|
|
|
|
unsigned latency = 0;
|
2010-08-16 04:10:50 +00:00
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
uint32_t* buffer = nullptr;
|
|
|
|
unsigned bufferoffset = 0;
|
2010-08-16 04:10:50 +00:00
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
volatile long submitbuffers = 0;
|
|
|
|
unsigned writebuffer = 0;
|
2010-08-16 04:10:50 +00:00
|
|
|
} device;
|
|
|
|
|
|
|
|
struct {
|
2015-06-12 13:14:38 +00:00
|
|
|
bool synchronize = false;
|
Update to v098r01 release.
byuu says:
Changelog:
- SFC: balanced profile removed
- SFC: performance profile removed
- SFC: code for handling non-threaded CPU, SMP, DSP, PPU removed
- SFC: Coprocessor, Controller (and expansion port) shared Thread code
merged to SFC::Cothread
- Cothread here just means "Thread with CPU affinity" (couldn't think
of a better name, sorry)
- SFC: CPU now has vector<Thread*> coprocessors, peripherals;
- this is the beginning of work to allow expansion port devices to be
dynamically changed at run-time
- ruby: all audio drivers default to 48000hz instead of 22050hz now if
no frequency is assigned
- note: the WASAPI driver can default to whatever the native frequency
is; doesn't have to be 48000hz
- tomoko: removed the ability to change the frequency from the UI (but
it will display the frequency used)
- tomoko: removed the timing settings panel
- the goal is to work toward smooth video via adaptive sync
- the model is broken by not being in control of the audio frequency
anyway
- it's further broken by PAL running at 50hz and WSC running at 75hz
- it was always broken anyway by SNES interlace timing varying from
progressive timing
- higan: audio/ stub created (for now, it's just nall/dsp/ moved here
and included as a header)
- higan: video/ stub created
- higan/GNUmakefile: now includes build rules for essential components
(libco, emulator, audio, video)
The audio changes are in preparation to merge wareya's awesome WASAPI
work without the need for the nall/dsp resampler.
2016-04-09 03:40:12 +00:00
|
|
|
unsigned frequency = 48000;
|
2015-06-12 13:14:38 +00:00
|
|
|
unsigned latency = 120;
|
2010-08-16 04:10:50 +00:00
|
|
|
} settings;
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto cap(const string& name) -> bool {
|
2010-08-16 04:10:50 +00:00
|
|
|
if(name == Audio::Synchronize) return true;
|
|
|
|
if(name == Audio::Frequency) return true;
|
|
|
|
if(name == Audio::Latency) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto get(const string& name) -> any {
|
2010-08-16 04:10:50 +00:00
|
|
|
if(name == Audio::Synchronize) return settings.synchronize;
|
|
|
|
if(name == Audio::Frequency) return settings.frequency;
|
|
|
|
if(name == Audio::Latency) return settings.latency;
|
2015-06-12 13:14:38 +00:00
|
|
|
return {};
|
2010-08-16 04:10:50 +00:00
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto set(const string& name, const any& value) -> bool {
|
|
|
|
if(name == Audio::Synchronize && value.is<bool>()) {
|
|
|
|
settings.synchronize = value.get<bool>();
|
2010-08-16 04:10:50 +00:00
|
|
|
if(pXAudio2) clear();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
if(name == Audio::Frequency && value.is<unsigned>()) {
|
|
|
|
settings.frequency = value.get<unsigned>();
|
2010-08-16 04:10:50 +00:00
|
|
|
if(pXAudio2) init();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
if(name == Audio::Latency && value.is<unsigned>()) {
|
|
|
|
settings.latency = value.get<unsigned>();
|
2010-08-16 04:10:50 +00:00
|
|
|
if(pXAudio2) init();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2013-05-02 11:25:45 +00:00
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto pushbuffer(unsigned bytes, uint32_t* pAudioData) -> void {
|
2013-05-02 11:25:45 +00:00
|
|
|
XAUDIO2_BUFFER xa2buffer = {0};
|
|
|
|
xa2buffer.AudioBytes = bytes;
|
|
|
|
xa2buffer.pAudioData = reinterpret_cast<BYTE*>(pAudioData);
|
|
|
|
xa2buffer.pContext = 0;
|
2010-08-16 04:10:50 +00:00
|
|
|
InterlockedIncrement(&device.submitbuffers);
|
|
|
|
pSourceVoice->SubmitSourceBuffer(&xa2buffer);
|
|
|
|
}
|
|
|
|
|
2016-04-18 10:49:45 +00:00
|
|
|
auto sample(int16_t left, int16_t right) -> void {
|
|
|
|
device.buffer[device.writebuffer * device.latency + device.bufferoffset++] = (uint16_t)left << 0 | (uint16_t)right << 16;
|
2010-08-16 04:10:50 +00:00
|
|
|
if(device.bufferoffset < device.latency) return;
|
|
|
|
device.bufferoffset = 0;
|
|
|
|
|
|
|
|
if(device.submitbuffers == device.buffers - 1) {
|
|
|
|
if(settings.synchronize == true) {
|
|
|
|
//wait until there is at least one other free buffer for the next sample
|
|
|
|
while(device.submitbuffers == device.buffers - 1) {
|
|
|
|
//Sleep(0);
|
|
|
|
}
|
2013-05-02 11:25:45 +00:00
|
|
|
} else { //we need one free buffer for the next sample, so ignore the current contents
|
2010-08-16 04:10:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2013-05-02 11:25:45 +00:00
|
|
|
|
2010-08-16 04:10:50 +00:00
|
|
|
pushbuffer(device.latency * 4,device.buffer + device.writebuffer * device.latency);
|
|
|
|
|
|
|
|
device.writebuffer = (device.writebuffer + 1) % device.buffers;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto clear() -> void {
|
2010-08-16 04:10:50 +00:00
|
|
|
if(!pSourceVoice) return;
|
|
|
|
pSourceVoice->Stop(0);
|
2013-05-02 11:25:45 +00:00
|
|
|
pSourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers
|
|
|
|
|
2010-08-16 04:10:50 +00:00
|
|
|
device.writebuffer = 0;
|
|
|
|
|
|
|
|
device.bufferoffset = 0;
|
|
|
|
if(device.buffer) memset(device.buffer, 0, device.latency * device.buffers * 4);
|
|
|
|
|
2013-05-02 11:25:45 +00:00
|
|
|
pSourceVoice->Start(0);
|
2010-08-16 04:10:50 +00:00
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto init() -> bool {
|
2013-05-02 11:25:45 +00:00
|
|
|
device.buffers = 8;
|
2010-08-16 04:10:50 +00:00
|
|
|
device.latency = settings.frequency * settings.latency / device.buffers / 1000.0 + 0.5;
|
2013-05-02 11:25:45 +00:00
|
|
|
device.buffer = new uint32_t[device.latency * device.buffers];
|
2010-08-16 04:10:50 +00:00
|
|
|
device.bufferoffset = 0;
|
|
|
|
device.submitbuffers = 0;
|
|
|
|
|
|
|
|
HRESULT hr;
|
|
|
|
if(FAILED(hr = XAudio2Create(&pXAudio2, 0 , XAUDIO2_DEFAULT_PROCESSOR))) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-05-02 11:25:45 +00:00
|
|
|
|
2014-01-28 10:04:58 +00:00
|
|
|
unsigned deviceCount = 0;
|
|
|
|
pXAudio2->GetDeviceCount(&deviceCount);
|
|
|
|
if(deviceCount == 0) { term(); return false; }
|
|
|
|
|
|
|
|
unsigned deviceID = 0;
|
|
|
|
for(unsigned deviceIndex = 0; deviceIndex < deviceCount; deviceIndex++) {
|
|
|
|
XAUDIO2_DEVICE_DETAILS deviceDetails;
|
|
|
|
memset(&deviceDetails, 0, sizeof(XAUDIO2_DEVICE_DETAILS));
|
|
|
|
pXAudio2->GetDeviceDetails(deviceIndex, &deviceDetails);
|
|
|
|
if(deviceDetails.Role & DefaultGameDevice) deviceID = deviceIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasterVoice, 2, settings.frequency, 0, deviceID, NULL))) {
|
2010-08-16 04:10:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
WAVEFORMATEX wfx;
|
|
|
|
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
|
|
|
wfx.nChannels = 2;
|
|
|
|
wfx.nSamplesPerSec = settings.frequency;
|
|
|
|
wfx.nBlockAlign = 4;
|
|
|
|
wfx.wBitsPerSample = 16;
|
|
|
|
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
|
|
|
wfx.cbSize = 0;
|
|
|
|
|
2014-01-28 10:04:58 +00:00
|
|
|
if(FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wfx, XAUDIO2_VOICE_NOSRC, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL))) {
|
2010-08-16 04:10:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
clear();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-12 13:14:38 +00:00
|
|
|
auto term() -> void {
|
2010-08-16 04:10:50 +00:00
|
|
|
if(pSourceVoice) {
|
|
|
|
pSourceVoice->Stop(0);
|
|
|
|
pSourceVoice->DestroyVoice();
|
2013-05-02 11:25:45 +00:00
|
|
|
pSourceVoice = nullptr;
|
2010-08-16 04:10:50 +00:00
|
|
|
}
|
|
|
|
if(pMasterVoice) {
|
|
|
|
pMasterVoice->DestroyVoice();
|
2013-05-02 11:25:45 +00:00
|
|
|
pMasterVoice = nullptr;
|
2010-08-16 04:10:50 +00:00
|
|
|
}
|
|
|
|
if(pXAudio2) {
|
|
|
|
pXAudio2->Release();
|
2013-05-02 11:25:45 +00:00
|
|
|
pXAudio2 = nullptr;
|
2010-08-16 04:10:50 +00:00
|
|
|
}
|
|
|
|
if(device.buffer) {
|
|
|
|
delete[] device.buffer;
|
2013-05-02 11:25:45 +00:00
|
|
|
device.buffer = nullptr;
|
2010-08-16 04:10:50 +00:00
|
|
|
}
|
|
|
|
}
|
2013-05-02 11:25:45 +00:00
|
|
|
|
|
|
|
STDMETHODIMP_(void) OnBufferEnd(void* pBufferContext) {
|
2010-08-16 04:10:50 +00:00
|
|
|
InterlockedDecrement(&device.submitbuffers);
|
|
|
|
}
|
|
|
|
};
|