#include "lib/audiodev/AudioVoiceEngine.hpp" #include #include #include #include #include #include #include #include #ifdef TE_VIRTUAL_MIDI #include 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 template using ComPtr = Microsoft::WRL::ComPtr; template static inline ComPtr* ReferenceComPtr(ComPtr& ptr) { return reinterpret_cast*>(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 m_enumerator; ComPtr m_device; #else bool m_ready = false; #endif ComPtr m_audClient; ComPtr m_renderClient; std::string m_sinkName; size_t m_curBufFrame = 0; std::vector 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 punkAudioInterface; HRESULT hrActivateResult; operation->GetActivateResult(&hrActivateResult, &punkAudioInterface); punkAudioInterface.As(&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 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(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 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> enumerateAudioOutputs() const override { std::vector> ret; ComPtr 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 device; collection->Item(i, &device); LPWSTR devName; device->GetId(&devName); ComPtr 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> enumerateMIDIInputs() const override { std::vector> 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 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(dwParam1); std::vector 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(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(this)->m_buf, buf, std::min(len, size_t(512))); const_cast(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(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(m_buf), buf, std::min(len, size_t(512))); const_cast(m_hdr).dwBytesRecorded = len; midiStreamOut(m_strm, LPMIDIHDR(&m_hdr), sizeof(m_hdr)); return len; } }; std::unique_ptr newVirtualMIDIIn(ReceiveFunctor&& receiver) override { #ifdef TE_VIRTUAL_MIDI if (!virtualMIDICreatePortEx2PROC) return {}; std::unique_ptr ret = std::make_unique(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(ret.get())), 512, TE_VM_FLAGS_PARSE_RX | TE_VM_FLAGS_INSTANTIATE_RX_ONLY); if (!port) return {}; static_cast(*ret).m_midi = port; return ret; #else return {}; #endif } std::unique_ptr newVirtualMIDIOut() override { #ifdef TE_VIRTUAL_MIDI if (!virtualMIDICreatePortEx2PROC) return {}; std::unique_ptr ret = std::make_unique(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(*ret).m_midi = port; return ret; #else return {}; #endif } std::unique_ptr newVirtualMIDIInOut(ReceiveFunctor&& receiver) override { #ifdef TE_VIRTUAL_MIDI if (!virtualMIDICreatePortEx2PROC) return {}; std::unique_ptr ret = std::make_unique(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(ret.get())), 512, TE_VM_FLAGS_SUPPORTED); if (!port) return {}; static_cast(*ret).m_midi = port; return ret; #else return {}; #endif } std::unique_ptr newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override { if (strncmp(name, "in", 2)) return {}; long id = strtol(name + 2, nullptr, 10); std::unique_ptr ret = std::make_unique(this, std::move(receiver)); if (!ret) return {}; if (FAILED(midiInOpen(&static_cast(*ret).m_midi, id, DWORD_PTR(MIDIReceiveProc), DWORD_PTR(static_cast(ret.get())), CALLBACK_FUNCTION))) return {}; midiInStart(static_cast(*ret).m_midi); return ret; } std::unique_ptr newRealMIDIOut(const char* name) override { if (strncmp(name, "out", 3)) return {}; long id = strtol(name + 3, nullptr, 10); std::unique_ptr ret = std::make_unique(this); if (!ret) return {}; if (FAILED(midiOutOpen(&static_cast(*ret).m_midi, id, 0, 0, CALLBACK_NULL))) return {}; static_cast(*ret).prepare(); return ret; } std::unique_ptr 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 ret = std::make_unique(this, std::move(receiver)); if (!ret) return {}; if (FAILED(midiInOpen(&static_cast(*ret).m_midiIn, inId, DWORD_PTR(MIDIReceiveProc), DWORD_PTR(static_cast(ret.get())), CALLBACK_FUNCTION))) return {}; midiInStart(static_cast(*ret).m_midiIn); if (FAILED(midiOutOpen(&static_cast(*ret).m_midiOut, outId, 0, 0, CALLBACK_NULL))) return {}; static_cast(*ret).prepare(); return ret; } bool useMIDILock() const override { return true; } #else std::vector> enumerateMIDIDevices() const override { return {}; } std::unique_ptr newVirtualMIDIIn(ReceiveFunctor&& receiver) override { return {}; } std::unique_ptr newVirtualMIDIOut() override { return {}; } std::unique_ptr newVirtualMIDIInOut(ReceiveFunctor&& receiver) override { return {}; } std::unique_ptr newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override { return {}; } std::unique_ptr newRealMIDIOut(const char* name) override { return {}; } std::unique_ptr newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver) override { return {}; } bool useMIDILock() const { return false; } #endif }; std::unique_ptr NewAudioVoiceEngine(const char* uniqueName, const char* friendlyName) { return std::make_unique(); } } // namespace boo