mirror of https://github.com/AxioDL/boo.git
889 lines
29 KiB
C++
889 lines
29 KiB
C++
#include "lib/audiodev/AudioVoiceEngine.hpp"
|
|
|
|
#include <iterator>
|
|
|
|
#include <Mmdeviceapi.h>
|
|
#include <Audioclient.h>
|
|
#include <mmsystem.h>
|
|
#include <Functiondiscoverykeys_devpkey.h>
|
|
|
|
#include <logvisor/logvisor.hpp>
|
|
#include <nowide/convert.hpp>
|
|
#include <optick.h>
|
|
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
#include <teVirtualMIDI.h>
|
|
using pfnvirtualMIDICreatePortEx2 = LPVM_MIDI_PORT(CALLBACK*)(LPCWSTR portName, LPVM_MIDI_DATA_CB callback,
|
|
DWORD_PTR dwCallbackInstance, DWORD maxSysexLength,
|
|
DWORD flags);
|
|
using pfnvirtualMIDIClosePort = void(CALLBACK*)(LPVM_MIDI_PORT midiPort);
|
|
using pfnvirtualMIDISendData = BOOL(CALLBACK*)(LPVM_MIDI_PORT midiPort, LPBYTE midiDataBytes, DWORD length);
|
|
using pfnvirtualMIDIGetDriverVersion = LPCWSTR(CALLBACK*)(PWORD major, PWORD minor, PWORD release, PWORD build);
|
|
static pfnvirtualMIDICreatePortEx2 virtualMIDICreatePortEx2PROC = nullptr;
|
|
static pfnvirtualMIDIClosePort virtualMIDIClosePortPROC = nullptr;
|
|
static pfnvirtualMIDISendData virtualMIDISendDataPROC = nullptr;
|
|
static pfnvirtualMIDIGetDriverVersion virtualMIDIGetDriverVersionPROC = nullptr;
|
|
static double PerfFrequency = 0.0;
|
|
#endif
|
|
|
|
#if !WINDOWS_STORE
|
|
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
|
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
|
#else
|
|
using namespace Windows::Media::Devices;
|
|
#endif
|
|
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
|
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
|
|
|
|
#include <wrl/client.h>
|
|
template <class T>
|
|
using ComPtr = Microsoft::WRL::ComPtr<T>;
|
|
template <class T>
|
|
static inline ComPtr<T>* ReferenceComPtr(ComPtr<T>& ptr) {
|
|
return reinterpret_cast<ComPtr<T>*>(ptr.GetAddressOf());
|
|
}
|
|
|
|
namespace boo {
|
|
static logvisor::Module Log("boo::WASAPI");
|
|
|
|
#define SAFE_RELEASE(punk) \
|
|
if ((punk) != nullptr) { \
|
|
(punk)->Release(); \
|
|
(punk) = nullptr; \
|
|
}
|
|
|
|
struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine {
|
|
#if !WINDOWS_STORE
|
|
ComPtr<IMMDeviceEnumerator> m_enumerator;
|
|
ComPtr<IMMDevice> m_device;
|
|
#else
|
|
bool m_ready = false;
|
|
#endif
|
|
ComPtr<IAudioClient> m_audClient;
|
|
ComPtr<IAudioRenderClient> m_renderClient;
|
|
std::string m_sinkName;
|
|
|
|
size_t m_curBufFrame = 0;
|
|
std::vector<float> m_5msBuffer;
|
|
|
|
#if !WINDOWS_STORE
|
|
struct NotificationClient final : public IMMNotificationClient {
|
|
WASAPIAudioVoiceEngine& m_parent;
|
|
|
|
LONG _cRef;
|
|
IMMDeviceEnumerator* _pEnumerator;
|
|
|
|
NotificationClient(WASAPIAudioVoiceEngine& parent) : m_parent(parent), _cRef(1), _pEnumerator(nullptr) {}
|
|
|
|
~NotificationClient(){SAFE_RELEASE(_pEnumerator)}
|
|
|
|
// IUnknown methods -- AddRef, Release, and QueryInterface
|
|
|
|
ULONG STDMETHODCALLTYPE AddRef() override {
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release() override {
|
|
ULONG ulRef = InterlockedDecrement(&_cRef);
|
|
if (0 == ulRef) {
|
|
delete this;
|
|
}
|
|
return ulRef;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface) override {
|
|
if (IID_IUnknown == riid) {
|
|
AddRef();
|
|
*ppvInterface = (IUnknown*)this;
|
|
} else if (__uuidof(IMMNotificationClient) == riid) {
|
|
AddRef();
|
|
*ppvInterface = (IMMNotificationClient*)this;
|
|
} else {
|
|
*ppvInterface = nullptr;
|
|
return E_NOINTERFACE;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
// Callback methods for device-event notifications.
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) override {
|
|
m_parent.m_rebuild = true;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) override { return S_OK; }
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) override { return S_OK; }
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override { return S_OK; }
|
|
|
|
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) override {
|
|
return S_OK;
|
|
}
|
|
} m_notificationClient;
|
|
#endif
|
|
|
|
void _buildAudioRenderClient() {
|
|
#if !WINDOWS_STORE
|
|
if (!m_device) {
|
|
if (FAILED(m_enumerator->GetDevice(nowide::widen(m_sinkName).c_str(), &m_device))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to obtain audio device {}"), m_sinkName);
|
|
m_device.Reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (FAILED(m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, &m_audClient))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to create audio client from device"));
|
|
m_device.Reset();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
WAVEFORMATEXTENSIBLE* pwfx;
|
|
if (FAILED(m_audClient->GetMixFormat((WAVEFORMATEX**)&pwfx))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to obtain audio mix format from device"));
|
|
#if !WINDOWS_STORE
|
|
m_device.Reset();
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/* Get channel information */
|
|
if ((pwfx->dwChannelMask & (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)) ==
|
|
(SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)) {
|
|
m_mixInfo.m_channels = AudioChannelSet::Stereo;
|
|
if ((pwfx->dwChannelMask & (SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)) ==
|
|
(SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)) {
|
|
m_mixInfo.m_channels = AudioChannelSet::Quad;
|
|
if ((pwfx->dwChannelMask & (SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY)) ==
|
|
(SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY)) {
|
|
m_mixInfo.m_channels = AudioChannelSet::Surround51;
|
|
if ((pwfx->dwChannelMask & (SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)) ==
|
|
(SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)) {
|
|
m_mixInfo.m_channels = AudioChannelSet::Surround71;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ChannelMap& chMapOut = m_mixInfo.m_channelMap;
|
|
switch (pwfx->Format.nChannels) {
|
|
case 2:
|
|
chMapOut.m_channelCount = 2;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
break;
|
|
case 4:
|
|
chMapOut.m_channelCount = 4;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
chMapOut.m_channels[2] = AudioChannel::RearLeft;
|
|
chMapOut.m_channels[3] = AudioChannel::RearRight;
|
|
break;
|
|
case 5:
|
|
chMapOut.m_channelCount = 5;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
chMapOut.m_channels[2] = AudioChannel::FrontCenter;
|
|
chMapOut.m_channels[3] = AudioChannel::RearLeft;
|
|
chMapOut.m_channels[4] = AudioChannel::RearRight;
|
|
break;
|
|
case 6:
|
|
chMapOut.m_channelCount = 6;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
chMapOut.m_channels[2] = AudioChannel::FrontCenter;
|
|
chMapOut.m_channels[3] = AudioChannel::LFE;
|
|
chMapOut.m_channels[4] = AudioChannel::RearLeft;
|
|
chMapOut.m_channels[5] = AudioChannel::RearRight;
|
|
break;
|
|
case 8:
|
|
chMapOut.m_channelCount = 8;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
chMapOut.m_channels[2] = AudioChannel::FrontCenter;
|
|
chMapOut.m_channels[3] = AudioChannel::LFE;
|
|
chMapOut.m_channels[4] = AudioChannel::RearLeft;
|
|
chMapOut.m_channels[5] = AudioChannel::RearRight;
|
|
chMapOut.m_channels[6] = AudioChannel::SideLeft;
|
|
chMapOut.m_channels[7] = AudioChannel::SideRight;
|
|
break;
|
|
default:
|
|
Log.report(logvisor::Warning, FMT_STRING("unknown channel layout {}; using stereo"), pwfx->Format.nChannels);
|
|
chMapOut.m_channelCount = 2;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
break;
|
|
}
|
|
|
|
/* Initialize audio client */
|
|
if (FAILED(m_audClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 450000, /* 45ms */
|
|
0, (WAVEFORMATEX*)pwfx, nullptr))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to initialize audio client"));
|
|
#if !WINDOWS_STORE
|
|
m_device.Reset();
|
|
#endif
|
|
CoTaskMemFree(pwfx);
|
|
return;
|
|
}
|
|
m_mixInfo.m_sampleRate = pwfx->Format.nSamplesPerSec;
|
|
m_5msFrames = (m_mixInfo.m_sampleRate * 5 / 500 + 1) / 2;
|
|
m_curBufFrame = m_5msFrames;
|
|
m_5msBuffer.resize(m_5msFrames * chMapOut.m_channelCount);
|
|
|
|
if (pwfx->Format.wFormatTag == WAVE_FORMAT_PCM ||
|
|
(pwfx->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)) {
|
|
if (pwfx->Format.wBitsPerSample == 16) {
|
|
m_mixInfo.m_sampleFormat = SOXR_INT16_I;
|
|
m_mixInfo.m_bitsPerSample = 16;
|
|
} else if (pwfx->Format.wBitsPerSample == 32) {
|
|
m_mixInfo.m_sampleFormat = SOXR_INT32_I;
|
|
m_mixInfo.m_bitsPerSample = 32;
|
|
} else {
|
|
Log.report(logvisor::Fatal, FMT_STRING("unsupported bits-per-sample {}"), pwfx->Format.wBitsPerSample);
|
|
#if !WINDOWS_STORE
|
|
m_device.Reset();
|
|
#endif
|
|
return;
|
|
}
|
|
} else if (pwfx->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
|
|
(pwfx->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
|
pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
|
|
if (pwfx->Format.wBitsPerSample == 32) {
|
|
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
|
|
m_mixInfo.m_bitsPerSample = 32;
|
|
} else {
|
|
Log.report(logvisor::Error, FMT_STRING("unsupported floating-point bits-per-sample {}"), pwfx->Format.wBitsPerSample);
|
|
#if !WINDOWS_STORE
|
|
m_device.Reset();
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
CoTaskMemFree(pwfx);
|
|
|
|
UINT32 bufferFrameCount;
|
|
if (FAILED(m_audClient->GetBufferSize(&bufferFrameCount))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to get audio buffer frame count"));
|
|
#if !WINDOWS_STORE
|
|
m_device.Reset();
|
|
#endif
|
|
return;
|
|
}
|
|
m_mixInfo.m_periodFrames = bufferFrameCount;
|
|
|
|
if (FAILED(m_audClient->GetService(IID_IAudioRenderClient, &m_renderClient))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to create audio render client"));
|
|
#if !WINDOWS_STORE
|
|
m_device.Reset();
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if WINDOWS_STORE
|
|
struct CompletionHandler : IActivateAudioInterfaceCompletionHandler {
|
|
WASAPIAudioVoiceEngine& e;
|
|
LONG _cRef = 1;
|
|
|
|
CompletionHandler(WASAPIAudioVoiceEngine& e) : e(e) {}
|
|
HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation* operation) {
|
|
return e.ActivateCompleted(operation);
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&_cRef); }
|
|
|
|
ULONG STDMETHODCALLTYPE Release() {
|
|
ULONG ulRef = InterlockedDecrement(&_cRef);
|
|
if (0 == ulRef) {
|
|
delete this;
|
|
}
|
|
return ulRef;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface) {
|
|
if (IID_IUnknown == riid) {
|
|
AddRef();
|
|
*ppvInterface = (IUnknown*)this;
|
|
} else if (__uuidof(IActivateAudioInterfaceCompletionHandler) == riid) {
|
|
AddRef();
|
|
*ppvInterface = (IActivateAudioInterfaceCompletionHandler*)this;
|
|
} else {
|
|
*ppvInterface = nullptr;
|
|
return E_NOINTERFACE;
|
|
}
|
|
return S_OK;
|
|
}
|
|
} m_completion = {*this};
|
|
HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation* operation) {
|
|
ComPtr<IUnknown> punkAudioInterface;
|
|
HRESULT hrActivateResult;
|
|
operation->GetActivateResult(&hrActivateResult, &punkAudioInterface);
|
|
punkAudioInterface.As<IAudioClient>(&m_audClient);
|
|
_buildAudioRenderClient();
|
|
m_ready = true;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
WASAPIAudioVoiceEngine()
|
|
#if !WINDOWS_STORE
|
|
: m_notificationClient(*this)
|
|
#endif
|
|
{
|
|
#if !WINDOWS_STORE
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
HMODULE virtualMidiModule;
|
|
if (!virtualMIDICreatePortEx2PROC && (virtualMidiModule = LoadLibraryW(L"teVirtualMIDI64.dll"))) {
|
|
virtualMIDICreatePortEx2PROC =
|
|
(pfnvirtualMIDICreatePortEx2)GetProcAddress(virtualMidiModule, "virtualMIDICreatePortEx2");
|
|
virtualMIDIClosePortPROC = (pfnvirtualMIDIClosePort)GetProcAddress(virtualMidiModule, "virtualMIDIClosePort");
|
|
virtualMIDISendDataPROC = (pfnvirtualMIDISendData)GetProcAddress(virtualMidiModule, "virtualMIDISendData");
|
|
virtualMIDIGetDriverVersionPROC =
|
|
(pfnvirtualMIDIGetDriverVersion)GetProcAddress(virtualMidiModule, "virtualMIDIGetDriverVersion");
|
|
LARGE_INTEGER pf;
|
|
QueryPerformanceFrequency(&pf);
|
|
PerfFrequency = double(pf.QuadPart);
|
|
}
|
|
#endif
|
|
|
|
/* Enumerate default audio device */
|
|
if (FAILED(
|
|
CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, &m_enumerator))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to create MMDeviceEnumerator instance"));
|
|
return;
|
|
}
|
|
|
|
if (FAILED(m_enumerator->RegisterEndpointNotificationCallback(&m_notificationClient))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to register multimedia event callback"));
|
|
m_device.Reset();
|
|
return;
|
|
}
|
|
|
|
if (FAILED(m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_device))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to obtain default audio device"));
|
|
m_device.Reset();
|
|
return;
|
|
}
|
|
LPWSTR sinkName = nullptr;
|
|
m_device->GetId(&sinkName);
|
|
m_sinkName = nowide::narrow(sinkName);
|
|
CoTaskMemFree(sinkName);
|
|
|
|
_buildAudioRenderClient();
|
|
#else
|
|
auto deviceIdStr = MediaDevice::GetDefaultAudioRenderId(Windows::Media::Devices::AudioDeviceRole::Default);
|
|
ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
|
|
ActivateAudioInterfaceAsync(deviceIdStr->Data(), __uuidof(IAudioClient3), nullptr, &m_completion, &asyncOp);
|
|
#endif
|
|
}
|
|
|
|
bool m_started = false;
|
|
bool m_rebuild = false;
|
|
|
|
void _rebuildAudioRenderClient() {
|
|
soxr_datatype_t oldFmt = m_mixInfo.m_sampleFormat;
|
|
|
|
_buildAudioRenderClient();
|
|
m_rebuild = false;
|
|
m_started = false;
|
|
|
|
if (m_mixInfo.m_sampleFormat != oldFmt)
|
|
Log.report(logvisor::Fatal, FMT_STRING("audio device sample format changed, boo doesn't support this!!"));
|
|
|
|
_resetSampleRate();
|
|
}
|
|
|
|
void pumpAndMixVoices() override {
|
|
OPTICK_EVENT();
|
|
#if WINDOWS_STORE
|
|
if (!m_ready)
|
|
return;
|
|
#else
|
|
if (!m_device)
|
|
return;
|
|
#endif
|
|
|
|
int attempt = 0;
|
|
while (true) {
|
|
if (attempt >= 10)
|
|
Log.report(logvisor::Fatal, FMT_STRING("unable to setup AudioRenderClient"));
|
|
|
|
if (m_rebuild) {
|
|
m_device.Reset();
|
|
_rebuildAudioRenderClient();
|
|
}
|
|
|
|
HRESULT res;
|
|
if (!m_started) {
|
|
res = m_audClient->Start();
|
|
if (FAILED(res)) {
|
|
m_rebuild = true;
|
|
++attempt;
|
|
continue;
|
|
}
|
|
m_started = true;
|
|
}
|
|
|
|
UINT32 numFramesPadding;
|
|
res = m_audClient->GetCurrentPadding(&numFramesPadding);
|
|
if (FAILED(res)) {
|
|
m_rebuild = true;
|
|
++attempt;
|
|
continue;
|
|
}
|
|
|
|
size_t frames = m_mixInfo.m_periodFrames - numFramesPadding;
|
|
if (frames <= 0)
|
|
return;
|
|
|
|
BYTE* bufOut;
|
|
res = m_renderClient->GetBuffer(frames, &bufOut);
|
|
if (FAILED(res)) {
|
|
m_rebuild = true;
|
|
++attempt;
|
|
continue;
|
|
}
|
|
|
|
for (size_t f = 0; f < frames;) {
|
|
if (m_curBufFrame == m_5msFrames) {
|
|
_pumpAndMixVoices(m_5msFrames, m_5msBuffer.data());
|
|
m_curBufFrame = 0;
|
|
}
|
|
|
|
size_t remRenderFrames = std::min(frames - f, m_5msFrames - m_curBufFrame);
|
|
if (remRenderFrames) {
|
|
memmove(reinterpret_cast<float*>(bufOut) + m_mixInfo.m_channelMap.m_channelCount * f,
|
|
&m_5msBuffer[m_curBufFrame * m_mixInfo.m_channelMap.m_channelCount],
|
|
remRenderFrames * m_mixInfo.m_channelMap.m_channelCount * sizeof(float));
|
|
m_curBufFrame += remRenderFrames;
|
|
f += remRenderFrames;
|
|
}
|
|
}
|
|
|
|
res = m_renderClient->ReleaseBuffer(frames, 0);
|
|
if (FAILED(res)) {
|
|
m_rebuild = true;
|
|
++attempt;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::string getCurrentAudioOutput() const override { return m_sinkName; }
|
|
|
|
bool setCurrentAudioOutput(const char* name) override {
|
|
ComPtr<IMMDevice> newDevice;
|
|
if (FAILED(m_enumerator->GetDevice(nowide::widen(name).c_str(), &newDevice))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to obtain audio device {}"), name);
|
|
return false;
|
|
}
|
|
m_device = newDevice;
|
|
m_sinkName = name;
|
|
_rebuildAudioRenderClient();
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::string>> enumerateAudioOutputs() const override {
|
|
std::vector<std::pair<std::string, std::string>> ret;
|
|
|
|
ComPtr<IMMDeviceCollection> collection;
|
|
if (FAILED(m_enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection))) {
|
|
Log.report(logvisor::Error, FMT_STRING("unable to enumerate audio outputs"));
|
|
return ret;
|
|
}
|
|
|
|
UINT count = 0;
|
|
collection->GetCount(&count);
|
|
for (UINT i = 0; i < count; ++i) {
|
|
ComPtr<IMMDevice> device;
|
|
collection->Item(i, &device);
|
|
LPWSTR devName;
|
|
device->GetId(&devName);
|
|
ComPtr<IPropertyStore> props;
|
|
device->OpenPropertyStore(STGM_READ, &props);
|
|
PROPVARIANT val = {};
|
|
props->GetValue(PKEY_Device_FriendlyName, &val);
|
|
std::string friendlyName;
|
|
if (val.vt == VT_LPWSTR)
|
|
friendlyName = nowide::narrow(val.pwszVal);
|
|
ret.emplace_back(nowide::narrow(devName), std::move(friendlyName));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if !WINDOWS_STORE
|
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const override {
|
|
std::vector<std::pair<std::string, std::string>> ret;
|
|
|
|
UINT numInDevices = midiInGetNumDevs();
|
|
ret.reserve(numInDevices);
|
|
|
|
for (UINT i = 0; i < numInDevices; ++i) {
|
|
std::string name = fmt::format(FMT_STRING("in{}"), i);
|
|
|
|
MIDIINCAPS caps;
|
|
if (FAILED(midiInGetDevCaps(i, &caps, sizeof(caps))))
|
|
continue;
|
|
|
|
#ifdef UNICODE
|
|
ret.emplace_back(std::move(name), nowide::narrow(caps.szPname));
|
|
#else
|
|
ret.emplace_back(std::move(name), std::string(caps.szPname));
|
|
#endif
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool supportsVirtualMIDIIn() const override {
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
WORD major, minor, release, build;
|
|
return virtualMIDIGetDriverVersionPROC &&
|
|
virtualMIDIGetDriverVersionPROC(&major, &minor, &release, &build) != nullptr;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
static void CALLBACK VirtualMIDIReceiveProc(LPVM_MIDI_PORT midiPort, LPBYTE midiDataBytes, DWORD length,
|
|
IMIDIReceiver* dwInstance) {
|
|
std::vector<uint8_t> bytes;
|
|
bytes.resize(length);
|
|
memcpy(&bytes[0], midiDataBytes, length);
|
|
|
|
double timestamp;
|
|
LARGE_INTEGER perf;
|
|
QueryPerformanceCounter(&perf);
|
|
timestamp = perf.QuadPart / PerfFrequency;
|
|
|
|
dwInstance->m_receiver(std::move(bytes), timestamp);
|
|
}
|
|
#endif
|
|
|
|
static void CALLBACK MIDIReceiveProc(HMIDIIN hMidiIn, UINT wMsg, IMIDIReceiver* dwInstance, DWORD_PTR dwParam1,
|
|
DWORD_PTR dwParam2) {
|
|
if (wMsg == MIM_DATA) {
|
|
uint8_t(&ptr)[3] = reinterpret_cast<uint8_t(&)[3]>(dwParam1);
|
|
std::vector<uint8_t> bytes(std::cbegin(ptr), std::cend(ptr));
|
|
dwInstance->m_receiver(std::move(bytes), dwParam2 / 1000.0);
|
|
}
|
|
}
|
|
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
struct VMIDIIn : public IMIDIIn {
|
|
LPVM_MIDI_PORT m_midi = 0;
|
|
|
|
VMIDIIn(WASAPIAudioVoiceEngine* parent, ReceiveFunctor&& receiver) : IMIDIIn(parent, true, std::move(receiver)) {}
|
|
|
|
~VMIDIIn() override { virtualMIDIClosePortPROC(m_midi); }
|
|
|
|
std::string description() const override { return "Virtual MIDI-In"; }
|
|
};
|
|
|
|
struct VMIDIOut : public IMIDIOut {
|
|
LPVM_MIDI_PORT m_midi = 0;
|
|
|
|
VMIDIOut(WASAPIAudioVoiceEngine* parent) : IMIDIOut(parent, true) {}
|
|
|
|
~VMIDIOut() override { virtualMIDIClosePortPROC(m_midi); }
|
|
|
|
std::string description() const override { return "Virtual MIDI-Out"; }
|
|
|
|
size_t send(const void* buf, size_t len) const override {
|
|
return virtualMIDISendDataPROC(m_midi, (LPBYTE)buf, len) ? len : 0;
|
|
}
|
|
};
|
|
|
|
struct VMIDIInOut : public IMIDIInOut {
|
|
LPVM_MIDI_PORT m_midi = 0;
|
|
|
|
VMIDIInOut(WASAPIAudioVoiceEngine* parent, ReceiveFunctor&& receiver)
|
|
: IMIDIInOut(parent, true, std::move(receiver)) {}
|
|
|
|
~VMIDIInOut() override { virtualMIDIClosePortPROC(m_midi); }
|
|
|
|
std::string description() const override { return "Virtual MIDI-In/Out"; }
|
|
|
|
size_t send(const void* buf, size_t len) const override {
|
|
return virtualMIDISendDataPROC(m_midi, (LPBYTE)buf, len) ? len : 0;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
struct MIDIIn : public IMIDIIn {
|
|
HMIDIIN m_midi = 0;
|
|
|
|
MIDIIn(WASAPIAudioVoiceEngine* parent, ReceiveFunctor&& receiver) : IMIDIIn(parent, false, std::move(receiver)) {}
|
|
|
|
~MIDIIn() override { midiInClose(m_midi); }
|
|
|
|
std::string description() const override {
|
|
UINT id = 0;
|
|
midiInGetID(m_midi, &id);
|
|
MIDIINCAPS caps;
|
|
if (FAILED(midiInGetDevCaps(id, &caps, sizeof(caps))))
|
|
return {};
|
|
|
|
#ifdef UNICODE
|
|
return nowide::narrow(caps.szPname);
|
|
#else
|
|
return caps.szPname;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
struct MIDIOut : public IMIDIOut {
|
|
HMIDIOUT m_midi = 0;
|
|
HMIDISTRM m_strm = 0;
|
|
uint8_t m_buf[512];
|
|
MIDIHDR m_hdr = {};
|
|
|
|
MIDIOut(WASAPIAudioVoiceEngine* parent) : IMIDIOut(parent, false) {}
|
|
|
|
void prepare() {
|
|
UINT id = 0;
|
|
midiOutGetID(m_midi, &id);
|
|
|
|
m_hdr.lpData = reinterpret_cast<LPSTR>(m_buf);
|
|
m_hdr.dwBufferLength = 512;
|
|
m_hdr.dwFlags = MHDR_ISSTRM;
|
|
midiOutPrepareHeader(m_midi, &m_hdr, sizeof(m_hdr));
|
|
midiStreamOpen(&m_strm, &id, 1, 0, 0, CALLBACK_NULL);
|
|
}
|
|
|
|
~MIDIOut() override {
|
|
midiStreamClose(m_strm);
|
|
midiOutUnprepareHeader(m_midi, &m_hdr, sizeof(m_hdr));
|
|
midiOutClose(m_midi);
|
|
}
|
|
|
|
std::string description() const override {
|
|
UINT id = 0;
|
|
midiOutGetID(m_midi, &id);
|
|
MIDIOUTCAPS caps;
|
|
if (FAILED(midiOutGetDevCaps(id, &caps, sizeof(caps))))
|
|
return {};
|
|
|
|
#ifdef UNICODE
|
|
return nowide::narrow(caps.szPname);
|
|
#else
|
|
return caps.szPname;
|
|
#endif
|
|
}
|
|
|
|
size_t send(const void* buf, size_t len) const override {
|
|
memcpy(const_cast<MIDIOut*>(this)->m_buf, buf, std::min(len, size_t(512)));
|
|
const_cast<MIDIOut*>(this)->m_hdr.dwBytesRecorded = len;
|
|
midiStreamOut(m_strm, LPMIDIHDR(&m_hdr), sizeof(m_hdr));
|
|
return len;
|
|
}
|
|
};
|
|
|
|
struct MIDIInOut : public IMIDIInOut {
|
|
HMIDIIN m_midiIn = 0;
|
|
HMIDIOUT m_midiOut = 0;
|
|
HMIDISTRM m_strm = 0;
|
|
uint8_t m_buf[512];
|
|
MIDIHDR m_hdr = {};
|
|
|
|
MIDIInOut(WASAPIAudioVoiceEngine* parent, ReceiveFunctor&& receiver)
|
|
: IMIDIInOut(parent, false, std::move(receiver)) {}
|
|
|
|
void prepare() {
|
|
UINT id = 0;
|
|
midiOutGetID(m_midiOut, &id);
|
|
|
|
m_hdr.lpData = reinterpret_cast<LPSTR>(m_buf);
|
|
m_hdr.dwBufferLength = 512;
|
|
m_hdr.dwFlags = MHDR_ISSTRM;
|
|
midiOutPrepareHeader(m_midiOut, &m_hdr, sizeof(m_hdr));
|
|
midiStreamOpen(&m_strm, &id, 1, 0, 0, CALLBACK_NULL);
|
|
}
|
|
|
|
~MIDIInOut() override {
|
|
midiInClose(m_midiIn);
|
|
midiStreamClose(m_strm);
|
|
midiOutUnprepareHeader(m_midiOut, &m_hdr, sizeof(m_hdr));
|
|
midiOutClose(m_midiOut);
|
|
}
|
|
|
|
std::string description() const override {
|
|
UINT id = 0;
|
|
midiOutGetID(m_midiOut, &id);
|
|
MIDIOUTCAPS caps;
|
|
if (FAILED(midiOutGetDevCaps(id, &caps, sizeof(caps))))
|
|
return {};
|
|
|
|
#ifdef UNICODE
|
|
return nowide::narrow(caps.szPname);
|
|
#else
|
|
return caps.szPname;
|
|
#endif
|
|
}
|
|
|
|
size_t send(const void* buf, size_t len) const override {
|
|
memcpy(const_cast<uint8_t*>(m_buf), buf, std::min(len, size_t(512)));
|
|
const_cast<MIDIHDR&>(m_hdr).dwBytesRecorded = len;
|
|
midiStreamOut(m_strm, LPMIDIHDR(&m_hdr), sizeof(m_hdr));
|
|
return len;
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver) override {
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
if (!virtualMIDICreatePortEx2PROC)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIIn> ret = std::make_unique<VMIDIIn>(this, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
std::string name = std::string(APP->getFriendlyName()) + " MIDI-In";
|
|
auto port = virtualMIDICreatePortEx2PROC(name.c_str(), LPVM_MIDI_DATA_CB(VirtualMIDIReceiveProc),
|
|
DWORD_PTR(static_cast<IMIDIReceiver*>(ret.get())), 512,
|
|
TE_VM_FLAGS_PARSE_RX | TE_VM_FLAGS_INSTANTIATE_RX_ONLY);
|
|
if (!port)
|
|
return {};
|
|
static_cast<VMIDIIn&>(*ret).m_midi = port;
|
|
return ret;
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
std::unique_ptr<IMIDIOut> newVirtualMIDIOut() override {
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
if (!virtualMIDICreatePortEx2PROC)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIOut> ret = std::make_unique<VMIDIOut>(this);
|
|
if (!ret)
|
|
return {};
|
|
|
|
std::string name = std::string(APP->getFriendlyName()) + " MIDI-Out";
|
|
auto port = virtualMIDICreatePortEx2PROC(name.c_str(), nullptr, 0, 512,
|
|
TE_VM_FLAGS_PARSE_TX | TE_VM_FLAGS_INSTANTIATE_TX_ONLY);
|
|
if (!port)
|
|
return {};
|
|
static_cast<VMIDIOut&>(*ret).m_midi = port;
|
|
return ret;
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver) override {
|
|
#ifdef TE_VIRTUAL_MIDI
|
|
if (!virtualMIDICreatePortEx2PROC)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIInOut> ret = std::make_unique<VMIDIInOut>(this, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
std::string name = std::string(APP->getFriendlyName()) + " MIDI-In/Out";
|
|
auto port =
|
|
virtualMIDICreatePortEx2PROC(name.c_str(), LPVM_MIDI_DATA_CB(VirtualMIDIReceiveProc),
|
|
DWORD_PTR(static_cast<IMIDIReceiver*>(ret.get())), 512, TE_VM_FLAGS_SUPPORTED);
|
|
if (!port)
|
|
return {};
|
|
static_cast<VMIDIInOut&>(*ret).m_midi = port;
|
|
return ret;
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override {
|
|
if (strncmp(name, "in", 2))
|
|
return {};
|
|
long id = strtol(name + 2, nullptr, 10);
|
|
|
|
std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(this, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
if (FAILED(midiInOpen(&static_cast<MIDIIn&>(*ret).m_midi, id, DWORD_PTR(MIDIReceiveProc),
|
|
DWORD_PTR(static_cast<IMIDIReceiver*>(ret.get())), CALLBACK_FUNCTION)))
|
|
return {};
|
|
midiInStart(static_cast<MIDIIn&>(*ret).m_midi);
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name) override {
|
|
if (strncmp(name, "out", 3))
|
|
return {};
|
|
long id = strtol(name + 3, nullptr, 10);
|
|
|
|
std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(this);
|
|
if (!ret)
|
|
return {};
|
|
|
|
if (FAILED(midiOutOpen(&static_cast<MIDIOut&>(*ret).m_midi, id, 0, 0, CALLBACK_NULL)))
|
|
return {};
|
|
|
|
static_cast<MIDIOut&>(*ret).prepare();
|
|
return ret;
|
|
}
|
|
|
|
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver) override {
|
|
const char* in = strstr(name, "in");
|
|
const char* out = strstr(name, "out");
|
|
|
|
if (!in || !out)
|
|
return {};
|
|
|
|
long inId = strtol(in + 2, nullptr, 10);
|
|
long outId = strtol(out + 3, nullptr, 10);
|
|
|
|
std::unique_ptr<IMIDIInOut> ret = std::make_unique<MIDIInOut>(this, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
if (FAILED(midiInOpen(&static_cast<MIDIInOut&>(*ret).m_midiIn, inId, DWORD_PTR(MIDIReceiveProc),
|
|
DWORD_PTR(static_cast<IMIDIReceiver*>(ret.get())), CALLBACK_FUNCTION)))
|
|
return {};
|
|
midiInStart(static_cast<MIDIInOut&>(*ret).m_midiIn);
|
|
|
|
if (FAILED(midiOutOpen(&static_cast<MIDIInOut&>(*ret).m_midiOut, outId, 0, 0, CALLBACK_NULL)))
|
|
return {};
|
|
|
|
static_cast<MIDIInOut&>(*ret).prepare();
|
|
return ret;
|
|
}
|
|
|
|
bool useMIDILock() const override { return true; }
|
|
#else
|
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const override { return {}; }
|
|
|
|
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver) override { return {}; }
|
|
|
|
std::unique_ptr<IMIDIOut> newVirtualMIDIOut() override { return {}; }
|
|
|
|
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver) override { return {}; }
|
|
|
|
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override { return {}; }
|
|
|
|
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name) override { return {}; }
|
|
|
|
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver) override { return {}; }
|
|
|
|
bool useMIDILock() const { return false; }
|
|
#endif
|
|
};
|
|
|
|
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine(const char* uniqueName, const char* friendlyName) {
|
|
return std::make_unique<WASAPIAudioVoiceEngine>();
|
|
}
|
|
|
|
} // namespace boo
|