#ifndef NO_FAUDIO // Application #include "wxvbam.h" #include // Interface #include "../common/SoundDriver.h" // FAudio #include // MMDevice API #include #include #include // Internals #include "../System.h" // for systemMessage() #include "../gba/Globals.h" int GetFADevices(FAudio* fa, wxArrayString* names, wxArrayString* ids, const wxString* match) { HRESULT hr; UINT32 dev_count = 0; hr = FAudio_GetDeviceCount(fa, &dev_count); if (hr != S_OK) { wxLogError(_("FAudio: Enumerating devices failed!")); return true; } else { FAudioDeviceDetails dd; for (UINT32 i = 0; i < dev_count; i++) { hr = FAudio_GetDeviceDetails(fa, i, &dd); if (hr != S_OK) { continue; } else { if (ids) { ids->push_back((wchar_t*) dd.DeviceID); //FAudio is an interesting beast, but not that hard to adapt... once you get used to it, XAudio2 wouldn't need this, but FAudio declares FAudioDeviceDetails as int32_t names->push_back((wchar_t*) dd.DisplayName); } else if (*match == dd.DeviceID) return i; } } } return -1; } bool GetFADevices(wxArrayString& names, wxArrayString& ids) { HRESULT hr; FAudio* fa = NULL; UINT32 flags = 0; #ifdef _DEBUG flags = FAUDIO_DEBUG_ENGINE; #endif hr = FAudioCreate(&fa, flags, FAUDIO_DEFAULT_PROCESSOR); //Apparently this needs 3 parameters, the processor. if (hr != S_OK) { wxLogError(_("The FAudio interface failed to initialize!")); return false; } GetFADevices(fa, &names, &ids, NULL); //fa->Release(); FAudio_Release(fa); return true; } static int FAGetDev(FAudio* fa) { if (gopts.audio_dev.empty()) return 0; else { int ret = GetFADevices(fa, NULL, NULL, &gopts.audio_dev); return ret < 0 ? 0 : ret; } } class FAudio_Output; static void faudio_device_changed(FAudio_Output*); class FAudio_Device_Notifier : public IMMNotificationClient { volatile LONG registered; IMMDeviceEnumerator* pEnumerator; std::wstring last_device; CRITICAL_SECTION lock; std::vector instances; public: FAudio_Device_Notifier() : registered(0) { InitializeCriticalSection(&lock); } ~FAudio_Device_Notifier() { DeleteCriticalSection(&lock); } ULONG STDMETHODCALLTYPE AddRef() { return 1; } ULONG STDMETHODCALLTYPE Release() { return 1; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface) { if (IID_IUnknown == riid) { *ppvInterface = (IUnknown*)this; } else if (__uuidof(IMMNotificationClient) == riid) { *ppvInterface = (IMMNotificationClient*)this; } else { *ppvInterface = NULL; return E_NOINTERFACE; } return S_OK; } HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { if (flow == eRender && last_device.compare(pwstrDeviceId) != 0) { last_device = pwstrDeviceId; EnterCriticalSection(&lock); for (auto it = instances.begin(); it < instances.end(); ++it) { faudio_device_changed(*it); } LeaveCriticalSection(&lock); } return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) { return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { return S_OK; } HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { return S_OK; } void do_register(FAudio_Output* p_instance) { if (InterlockedIncrement(®istered) == 1) { pEnumerator = NULL; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator); if (SUCCEEDED(hr)) { pEnumerator->RegisterEndpointNotificationCallback(this); } } EnterCriticalSection(&lock); instances.push_back(p_instance); LeaveCriticalSection(&lock); } void do_unregister(FAudio_Output* p_instance) { if (InterlockedDecrement(®istered) == 0) { if (pEnumerator) { pEnumerator->UnregisterEndpointNotificationCallback(this); pEnumerator->Release(); pEnumerator = NULL; } } EnterCriticalSection(&lock); for (auto it = instances.begin(); it < instances.end(); ++it) { if (*it == p_instance) { instances.erase(it); break; } } LeaveCriticalSection(&lock); } } g_notifier; // Synchronization Event class FAudio_BufferNotify : public FAudioVoiceCallback { public: HANDLE hBufferEndEvent; FAudio_BufferNotify() { hBufferEndEvent = NULL; hBufferEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL); assert(hBufferEndEvent != NULL); } ~FAudio_BufferNotify() { CloseHandle(hBufferEndEvent); hBufferEndEvent = NULL; } STDMETHOD_(void, OnBufferEnd) (void* pBufferContext) { assert(hBufferEndEvent != NULL); SetEvent(hBufferEndEvent); } // dummies: STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32 BytesRequired) {} STDMETHOD_(void, OnVoiceProcessingPassEnd) () {} STDMETHOD_(void, OnStreamEnd) () {} STDMETHOD_(void, OnBufferStart) (void* pBufferContext) {} STDMETHOD_(void, OnLoopEnd) (void* pBufferContext) {} STDMETHOD_(void, OnVoiceError) (void* pBufferContext, HRESULT Error){}; }; // Class Declaration class FAudio_Output : public SoundDriver { public: FAudio_Output(); ~FAudio_Output(); // Initialization bool init(long sampleRate); // Sound Data Feed void write(uint16_t* finalWave, int length); // Play Control void pause(); void resume(); void reset(); void close(); void device_change(); // Configuration Changes void setThrottle(unsigned short throttle); private: bool failed; bool initialized; bool playing; UINT32 freq; UINT32 bufferCount; BYTE* buffers; int currentBuffer; int soundBufferLen; volatile bool device_changed; FAudio* faud; FAudioMasteringVoice* mVoice; // listener FAudioSourceVoice* sVoice; // sound source FAudioBuffer buf; FAudioVoiceState vState; FAudio_BufferNotify notify; // buffer end notification }; // Class Implementation FAudio_Output::FAudio_Output() { failed = false; initialized = false; playing = false; freq = 0; bufferCount = gopts.audio_buffers; buffers = NULL; currentBuffer = 0; device_changed = false; faud = NULL; mVoice = NULL; sVoice = NULL; ZeroMemory(&buf, sizeof(buf)); ZeroMemory(&vState, sizeof(vState)); g_notifier.do_register(this); } FAudio_Output::~FAudio_Output() { g_notifier.do_unregister(this); close(); } void FAudio_Output::close() { initialized = false; if (sVoice) { if (playing) { HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW); assert(hr == S_OK); } FAudioVoice_DestroyVoice(sVoice); sVoice = NULL; } if (buffers) { free(buffers); buffers = NULL; } if (mVoice) { FAudioVoice_DestroyVoice(mVoice); mVoice = NULL; } if (faud) { FAudio_Release(faud); faud = NULL; } } void FAudio_Output::device_change() { device_changed = true; } bool FAudio_Output::init(long sampleRate) { if (failed || initialized) return false; HRESULT hr; // Initialize FAudio UINT32 flags = 0; //#ifdef _DEBUG // flags = FAUDIO_DEBUG_ENGINE; //#endif hr = FAudioCreate(&faud, flags, FAUDIO_DEFAULT_PROCESSOR); if (hr != S_OK) { wxLogError(_("The FAudio interface failed to initialize!")); failed = true; return false; } freq = sampleRate; // calculate the number of samples per frame first // then multiply it with the size of a sample frame (16 bit * stereo) soundBufferLen = (freq / 60) * 4; // create own buffers to store sound data because it must not be // manipulated while the voice plays from it buffers = (BYTE*)malloc((bufferCount + 1) * soundBufferLen); // + 1 because we need one temporary buffer when all others are in use WAVEFORMATEX wfx; ZeroMemory(&wfx, sizeof(wfx)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 2; wfx.nSamplesPerSec = freq; wfx.wBitsPerSample = 16; wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8); wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; // create sound receiver hr = FAudio_CreateMasteringVoice( faud, &mVoice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, FAGetDev(faud), NULL); if (hr != S_OK) { wxLogError(_("FAudio: Creating mastering voice failed!")); failed = true; return false; } // create sound emitter //This should be FAudio_CreateSourceVoice() //hr = faud->CreateSourceVoice(&sVoice, &wfx, 0, 4.0f, ¬ify); hr = FAudio_CreateSourceVoice(faud, &sVoice, (const FAudioWaveFormatEx*)&wfx, 0, 4.0f, ¬ify, NULL, NULL); if (hr != S_OK) { wxLogError(_("FAudio: Creating source voice failed!")); failed = true; return false; } if (gopts.upmix) { // set up stereo upmixing FAudioDeviceDetails dd; ZeroMemory(&dd, sizeof(dd)); hr = FAudio_GetDeviceDetails(faud, 0, &dd); assert(hr == S_OK); float* matrix = NULL; matrix = (float*)malloc(sizeof(float) * 2 * dd.OutputFormat.Format.nChannels); if (matrix == NULL) return false; bool matrixAvailable = true; switch (dd.OutputFormat.Format.nChannels) { case 4: // 4.0 //Speaker \ Left Source Right Source /*Front L*/ matrix[0] = 1.0000f; matrix[1] = 0.0000f; /*Front R*/ matrix[2] = 0.0000f; matrix[3] = 1.0000f; /*Back L*/ matrix[4] = 1.0000f; matrix[5] = 0.0000f; /*Back R*/ matrix[6] = 0.0000f; matrix[7] = 1.0000f; break; case 5: // 5.0 //Speaker \ Left Source Right Source /*Front L*/ matrix[0] = 1.0000f; matrix[1] = 0.0000f; /*Front R*/ matrix[2] = 0.0000f; matrix[3] = 1.0000f; /*Front C*/ matrix[4] = 0.7071f; matrix[5] = 0.7071f; /*Side L*/ matrix[6] = 1.0000f; matrix[7] = 0.0000f; /*Side R*/ matrix[8] = 0.0000f; matrix[9] = 1.0000f; break; case 6: // 5.1 //Speaker \ Left Source Right Source /*Front L*/ matrix[0] = 1.0000f; matrix[1] = 0.0000f; /*Front R*/ matrix[2] = 0.0000f; matrix[3] = 1.0000f; /*Front C*/ matrix[4] = 0.7071f; matrix[5] = 0.7071f; /*LFE */ matrix[6] = 0.0000f; matrix[7] = 0.0000f; /*Side L*/ matrix[8] = 1.0000f; matrix[9] = 0.0000f; /*Side R*/ matrix[10] = 0.0000f; matrix[11] = 1.0000f; break; case 7: // 6.1 //Speaker \ Left Source Right Source /*Front L*/ matrix[0] = 1.0000f; matrix[1] = 0.0000f; /*Front R*/ matrix[2] = 0.0000f; matrix[3] = 1.0000f; /*Front C*/ matrix[4] = 0.7071f; matrix[5] = 0.7071f; /*LFE */ matrix[6] = 0.0000f; matrix[7] = 0.0000f; /*Side L*/ matrix[8] = 1.0000f; matrix[9] = 0.0000f; /*Side R*/ matrix[10] = 0.0000f; matrix[11] = 1.0000f; /*Back C*/ matrix[12] = 0.7071f; matrix[13] = 0.7071f; break; case 8: // 7.1 //Speaker \ Left Source Right Source /*Front L*/ matrix[0] = 1.0000f; matrix[1] = 0.0000f; /*Front R*/ matrix[2] = 0.0000f; matrix[3] = 1.0000f; /*Front C*/ matrix[4] = 0.7071f; matrix[5] = 0.7071f; /*LFE */ matrix[6] = 0.0000f; matrix[7] = 0.0000f; /*Back L*/ matrix[8] = 1.0000f; matrix[9] = 0.0000f; /*Back R*/ matrix[10] = 0.0000f; matrix[11] = 1.0000f; /*Side L*/ matrix[12] = 1.0000f; matrix[13] = 0.0000f; /*Side R*/ matrix[14] = 0.0000f; matrix[15] = 1.0000f; break; default: matrixAvailable = false; break; } if (matrixAvailable) { hr = FAudioVoice_SetOutputMatrix(sVoice, NULL, 2, dd.OutputFormat.Format.nChannels, matrix, FAUDIO_DEFAULT_CHANNELS); //What I have here for the OperationSet maybe wrong... assert(hr == S_OK); } free(matrix); matrix = NULL; } hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW); assert(hr == S_OK); playing = true; currentBuffer = 0; device_changed = false; initialized = true; return true; } void FAudio_Output::write(uint16_t* finalWave, int length) { UINT32 flags = 0; if (!initialized || failed) return; while (true) { if (device_changed) { close(); if (!init(freq)) return; } FAudioSourceVoice_GetState(sVoice, &vState, flags); assert(vState.BuffersQueued <= bufferCount); if (vState.BuffersQueued < bufferCount) { if (vState.BuffersQueued == 0) { // buffers ran dry if (systemVerbose & VERBOSE_SOUNDOUTPUT) { static unsigned int i = 0; log("FAudio: Buffers were not refilled fast enough (i=%i)\n", i++); } } // there is at least one free buffer break; } else { // the maximum number of buffers is currently queued if (!speedup && throttle && !gba_joybus_active) { // wait for one buffer to finish playing if (WaitForSingleObject(notify.hBufferEndEvent, 10000) == WAIT_TIMEOUT) { device_changed = true; } } else { // drop current audio frame return; } } } // copy & protect the audio data in own memory area while playing it CopyMemory(&buffers[currentBuffer * soundBufferLen], finalWave, soundBufferLen); buf.AudioBytes = soundBufferLen; buf.pAudioData = &buffers[currentBuffer * soundBufferLen]; currentBuffer++; currentBuffer %= (bufferCount + 1); // + 1 because we need one temporary buffer HRESULT hr = FAudioSourceVoice_SubmitSourceBuffer(sVoice, &buf, NULL); // send buffer to queue. assert(hr == S_OK); } void FAudio_Output::pause() { if (!initialized || failed) return; if (playing) { HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW); assert(hr == S_OK); playing = false; } } void FAudio_Output::resume() { if (!initialized || failed) return; if (!playing) { HRESULT hr = FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW); assert(hr == S_OK); playing = true; } } void FAudio_Output::reset() { if (!initialized || failed) return; if (playing) { HRESULT hr = FAudioSourceVoice_Stop(sVoice, 0, FAUDIO_COMMIT_NOW); assert(hr == S_OK); } FAudioSourceVoice_FlushSourceBuffers(sVoice); FAudioSourceVoice_Start(sVoice, 0, FAUDIO_COMMIT_NOW); playing = true; } void FAudio_Output::setThrottle(unsigned short throttle_) { if (!initialized || failed) return; if (throttle_ == 0) throttle_ = 100; HRESULT hr = FAudioSourceVoice_SetFrequencyRatio(sVoice, (float)throttle_ / 100.0f, FAUDIO_MAX_FILTER_FREQUENCY); assert(hr == S_OK); } void faudio_device_changed(FAudio_Output* instance) { instance->device_change(); } SoundDriver* newFAudio_Output() { return new FAudio_Output(); } #endif // #ifndef NO_FAUDIO