mirror of
https://github.com/AxioDL/boo.git
synced 2025-06-12 09:33:31 +00:00
Alphabetizes includes and resolves quite a few instances of indirect inclusions, making the requirements of several interfaces explicit. This also trims out includes that aren't actually necessary (likely due to changes in the API over time).
896 lines
29 KiB
C++
896 lines
29 KiB
C++
#include "lib/win/Win32Common.hpp"
|
|
|
|
#include <iterator>
|
|
|
|
#include "boo/IApplication.hpp"
|
|
#include "lib/audiodev/AudioVoiceEngine.hpp"
|
|
|
|
#include <Mmdeviceapi.h>
|
|
#include <Audioclient.h>
|
|
#include <mmsystem.h>
|
|
#include <Functiondiscoverykeys_devpkey.h>
|
|
|
|
#include <logvisor/logvisor.hpp>
|
|
|
|
#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);
|
|
|
|
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(MBSTWCS(m_sinkName.c_str()).c_str(), &m_device))) {
|
|
Log.report(logvisor::Error, fmt("unable to obtain audio device %s"), m_sinkName);
|
|
m_device.Reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (FAILED(m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, &m_audClient))) {
|
|
Log.report(logvisor::Error, fmt(L"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(L"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("unknown channel layout %u; 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(L"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(L"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(L"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(L"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(L"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(L"unable to create MMDeviceEnumerator instance"));
|
|
return;
|
|
}
|
|
|
|
if (FAILED(m_enumerator->RegisterEndpointNotificationCallback(&m_notificationClient))) {
|
|
Log.report(logvisor::Error, fmt(L"unable to register multimedia event callback"));
|
|
m_device.Reset();
|
|
return;
|
|
}
|
|
|
|
if (FAILED(m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_device))) {
|
|
Log.report(logvisor::Error, fmt(L"unable to obtain default audio device"));
|
|
m_device.Reset();
|
|
return;
|
|
}
|
|
LPWSTR sinkName = nullptr;
|
|
m_device->GetId(&sinkName);
|
|
m_sinkName = WCSTMBS(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(L"audio device sample format changed, boo doesn't support this!!"));
|
|
|
|
_resetSampleRate();
|
|
}
|
|
|
|
void pumpAndMixVoices() override {
|
|
#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(L"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(MBSTWCS(name).c_str(), &newDevice))) {
|
|
Log.report(logvisor::Error, fmt("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(L"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 = WCSTMBS(val.pwszVal);
|
|
ret.emplace_back(WCSTMBS(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("in{}"), i);
|
|
|
|
MIDIINCAPS caps;
|
|
if (FAILED(midiInGetDevCaps(i, &caps, sizeof(caps))))
|
|
continue;
|
|
|
|
#ifdef UNICODE
|
|
ret.push_back(std::make_pair(std::move(name), WCSTMBS(caps.szPname)));
|
|
#else
|
|
ret.push_back(std::make_pair(std::move(name), std::string(caps.szPname)));
|
|
#endif
|
|
}
|
|
|
|
#if 0
|
|
for (UINT i=0 ; i<numOutDevices ; ++i)
|
|
{
|
|
std::string name = fmt::format(fmt("out{}"), i);
|
|
|
|
MIDIOUTCAPS caps;
|
|
if (FAILED(midiOutGetDevCaps(i, &caps, sizeof(caps))))
|
|
continue;
|
|
|
|
#ifdef UNICODE
|
|
ret.push_back(std::make_pair(std::move(name), WCSTMBS(caps.szPname)));
|
|
#else
|
|
ret.push_back(std::make_pair(std::move(name), std::string(caps.szPname)));
|
|
#endif
|
|
}
|
|
#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 WCSTMBS(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 WCSTMBS(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 WCSTMBS(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 {};
|
|
|
|
SystemString name = SystemString(APP->getFriendlyName()) + _SYS_STR(" 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 {};
|
|
|
|
SystemString name = SystemString(APP->getFriendlyName()) + _SYS_STR(" 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 {};
|
|
|
|
SystemString name = SystemString(APP->getFriendlyName()) + _SYS_STR(" 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() { return std::make_unique<WASAPIAudioVoiceEngine>(); }
|
|
|
|
} // namespace boo
|