2016-03-23 02:00:45 +00:00
|
|
|
#include "../win/Win32Common.hpp"
|
2016-03-24 01:50:34 +00:00
|
|
|
#include "AudioVoiceEngine.hpp"
|
2016-03-23 02:00:45 +00:00
|
|
|
#include "logvisor/logvisor.hpp"
|
|
|
|
|
|
|
|
#include <Mmdeviceapi.h>
|
|
|
|
#include <Audioclient.h>
|
2016-05-23 05:34:20 +00:00
|
|
|
#include <mmsystem.h>
|
|
|
|
|
|
|
|
#include <iterator>
|
2016-03-23 02:00:45 +00:00
|
|
|
|
|
|
|
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
|
|
|
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
|
|
|
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
|
|
|
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
|
|
|
|
|
|
|
|
namespace boo
|
|
|
|
{
|
|
|
|
static logvisor::Module Log("boo::WASAPI");
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
ComPtr<IMMDevice> m_device;
|
2016-03-23 02:00:45 +00:00
|
|
|
ComPtr<IAudioClient> m_audClient;
|
|
|
|
ComPtr<IAudioRenderClient> m_renderClient;
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
WASAPIAudioVoiceEngine()
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
/* Enumerate default audio device */
|
|
|
|
ComPtr<IMMDeviceEnumerator> pEnumerator;
|
|
|
|
if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr,
|
|
|
|
CLSCTX_ALL, IID_IMMDeviceEnumerator,
|
|
|
|
&pEnumerator)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to create MMDeviceEnumerator instance");
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
if (FAILED(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_device)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to obtain default audio device");
|
|
|
|
m_device.Reset();
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
if (FAILED(m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, &m_audClient)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to create audio client from device");
|
|
|
|
m_device.Reset();
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
WAVEFORMATEXTENSIBLE* pwfx;
|
|
|
|
if (FAILED(m_audClient->GetMixFormat((WAVEFORMATEX**)&pwfx)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to obtain audio mix format from device");
|
|
|
|
m_device.Reset();
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
/* 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)
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
|
|
|
case 2:
|
2016-03-24 01:50:34 +00:00
|
|
|
chMapOut.m_channelCount = 2;
|
|
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
2016-03-23 02:00:45 +00:00
|
|
|
break;
|
|
|
|
case 4:
|
2016-03-24 01:50:34 +00:00
|
|
|
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;
|
2016-03-23 02:00:45 +00:00
|
|
|
break;
|
|
|
|
case 5:
|
2016-03-24 01:50:34 +00:00
|
|
|
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;
|
2016-03-23 02:00:45 +00:00
|
|
|
break;
|
|
|
|
case 6:
|
2016-03-24 01:50:34 +00:00
|
|
|
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;
|
2016-03-23 02:00:45 +00:00
|
|
|
break;
|
|
|
|
case 8:
|
2016-03-24 01:50:34 +00:00
|
|
|
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;
|
2016-03-23 02:00:45 +00:00
|
|
|
break;
|
|
|
|
default:
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Warning, "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;
|
2016-03-23 02:00:45 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
/* Initialize audio client */
|
|
|
|
if (FAILED(m_audClient->Initialize(
|
|
|
|
AUDCLNT_SHAREMODE_SHARED,
|
|
|
|
0,
|
|
|
|
1000000,
|
|
|
|
0,
|
|
|
|
(WAVEFORMATEX*)pwfx,
|
|
|
|
nullptr)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to initialize audio client");
|
|
|
|
m_device.Reset();
|
|
|
|
CoTaskMemFree(pwfx);
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-03-24 01:50:34 +00:00
|
|
|
m_mixInfo.m_sampleRate = pwfx->Format.nSamplesPerSec;
|
2016-05-23 05:34:20 +00:00
|
|
|
m_5msFrames = (m_mixInfo.m_sampleRate * 5 / 500 + 1) / 2;
|
2016-03-23 02:00:45 +00:00
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
if (pwfx->Format.wFormatTag == WAVE_FORMAT_PCM ||
|
|
|
|
(pwfx->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_PCM))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
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, L"unsupported bits-per-sample %d", pwfx->Format.wBitsPerSample);
|
|
|
|
m_device.Reset();
|
|
|
|
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::Fatal, L"unsupported floating-point bits-per-sample %d", pwfx->Format.wBitsPerSample);
|
|
|
|
m_device.Reset();
|
|
|
|
return;
|
|
|
|
}
|
2016-03-23 02:00:45 +00:00
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
CoTaskMemFree(pwfx);
|
2016-03-23 02:00:45 +00:00
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
UINT32 bufferFrameCount;
|
|
|
|
if (FAILED(m_audClient->GetBufferSize(&bufferFrameCount)))
|
|
|
|
{
|
|
|
|
Log.report(logvisor::Fatal, L"unable to get audio buffer frame count");
|
|
|
|
m_device.Reset();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_mixInfo.m_periodFrames = bufferFrameCount;
|
2016-03-23 02:00:45 +00:00
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
if (FAILED(m_audClient->GetService(IID_IAudioRenderClient, &m_renderClient)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to create audio render client");
|
|
|
|
m_device.Reset();
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
bool m_started = false;
|
2016-03-23 02:00:45 +00:00
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
void pumpAndMixVoices()
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
UINT32 numFramesPadding;
|
|
|
|
if (FAILED(m_audClient->GetCurrentPadding(&numFramesPadding)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to get available buffer frames");
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
size_t frames = m_mixInfo.m_periodFrames - numFramesPadding;
|
|
|
|
if (frames <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
BYTE* bufOut;
|
|
|
|
if (FAILED(m_renderClient->GetBuffer(frames, &bufOut)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to map audio buffer");
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
DWORD flags = 0;
|
|
|
|
switch (m_mixInfo.m_sampleFormat)
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
case SOXR_INT16_I:
|
|
|
|
_pumpAndMixVoices(frames, reinterpret_cast<int16_t*>(bufOut));
|
|
|
|
break;
|
|
|
|
case SOXR_INT32_I:
|
|
|
|
_pumpAndMixVoices(frames, reinterpret_cast<int32_t*>(bufOut));
|
|
|
|
break;
|
|
|
|
case SOXR_FLOAT32_I:
|
|
|
|
_pumpAndMixVoices(frames, reinterpret_cast<float*>(bufOut));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
flags = AUDCLNT_BUFFERFLAGS_SILENT;
|
|
|
|
break;
|
2016-03-23 02:00:45 +00:00
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
if (FAILED(m_renderClient->ReleaseBuffer(frames, flags)))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to unmap audio buffer");
|
2016-03-23 02:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
if (!m_started)
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
if (FAILED(m_audClient->Start()))
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
Log.report(logvisor::Fatal, L"unable to start audio client");
|
|
|
|
m_device.Reset();
|
|
|
|
return;
|
2016-03-23 02:00:45 +00:00
|
|
|
}
|
2016-03-24 01:50:34 +00:00
|
|
|
m_started = true;
|
2016-03-23 02:00:45 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-21 03:22:00 +00:00
|
|
|
|
2016-05-23 05:34:20 +00:00
|
|
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
|
|
|
|
{
|
|
|
|
std::vector<std::pair<std::string, std::string>> ret;
|
|
|
|
|
|
|
|
UINT numInDevices = midiInGetNumDevs();
|
|
|
|
UINT numOutDevices = midiOutGetNumDevs();
|
|
|
|
ret.reserve(numInDevices + numOutDevices);
|
|
|
|
|
|
|
|
for (UINT i=0 ; i<numInDevices ; ++i)
|
|
|
|
{
|
|
|
|
char name[256];
|
|
|
|
snprintf(name, 256, "in%u", i);
|
|
|
|
|
|
|
|
MIDIINCAPS caps;
|
|
|
|
if (FAILED(midiInGetDevCaps(i, &caps, sizeof(caps))))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
#ifdef UNICODE
|
|
|
|
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nullptr, 0, nullptr, nullptr);
|
|
|
|
std::string strTo(sizeNeeded, 0);
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, &strTo[0], sizeNeeded, nullptr, nullptr);
|
|
|
|
ret.push_back(std::make_pair(std::string(name), std::move(strTo)));
|
|
|
|
#else
|
|
|
|
ret.push_back(std::make_pair(std::string(name), std::string(caps.szPname)));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
for (UINT i=0 ; i<numOutDevices ; ++i)
|
|
|
|
{
|
|
|
|
char name[256];
|
|
|
|
snprintf(name, 256, "out%u", i);
|
|
|
|
|
|
|
|
MIDIOUTCAPS caps;
|
|
|
|
if (FAILED(midiOutGetDevCaps(i, &caps, sizeof(caps))))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
#ifdef UNICODE
|
|
|
|
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nullptr, 0, nullptr, nullptr);
|
|
|
|
std::string strTo(sizeNeeded, 0);
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, &strTo[0], sizeNeeded, nullptr, nullptr);
|
|
|
|
ret.push_back(std::make_pair(std::string(name), std::move(strTo)));
|
|
|
|
#else
|
|
|
|
ret.push_back(std::make_pair(std::string(name), std::string(caps.szPname)));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void 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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct MIDIIn : public IMIDIIn
|
|
|
|
{
|
|
|
|
HMIDIIN m_midi = 0;
|
|
|
|
|
|
|
|
MIDIIn(bool virt, ReceiveFunctor&& receiver)
|
|
|
|
: IMIDIIn(virt, std::move(receiver)) {}
|
|
|
|
|
|
|
|
~MIDIIn()
|
|
|
|
{
|
|
|
|
midiInClose(m_midi);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string description() const
|
|
|
|
{
|
|
|
|
UINT id = 0;
|
|
|
|
midiInGetID(m_midi, &id);
|
|
|
|
MIDIINCAPS caps;
|
|
|
|
if (FAILED(midiInGetDevCaps(id, &caps, sizeof(caps))))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
#ifdef UNICODE
|
|
|
|
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nullptr, 0, nullptr, nullptr);
|
|
|
|
std::string strTo(sizeNeeded, 0);
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, &strTo[0], sizeNeeded, nullptr, nullptr);
|
|
|
|
return strTo;
|
|
|
|
#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(bool virt) : IMIDIOut(virt) {}
|
|
|
|
|
|
|
|
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, NULL, NULL, CALLBACK_NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
~MIDIOut()
|
|
|
|
{
|
|
|
|
midiStreamClose(m_strm);
|
|
|
|
midiOutUnprepareHeader(m_midi, &m_hdr, sizeof(m_hdr));
|
|
|
|
midiOutClose(m_midi);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string description() const
|
|
|
|
{
|
|
|
|
UINT id = 0;
|
|
|
|
midiOutGetID(m_midi, &id);
|
|
|
|
MIDIOUTCAPS caps;
|
|
|
|
if (FAILED(midiOutGetDevCaps(id, &caps, sizeof(caps))))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
#ifdef UNICODE
|
|
|
|
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nullptr, 0, nullptr, nullptr);
|
|
|
|
std::string strTo(sizeNeeded, 0);
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, &strTo[0], sizeNeeded, nullptr, nullptr);
|
|
|
|
return strTo;
|
|
|
|
#else
|
|
|
|
return caps.szPname;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t send(const void* buf, size_t len) const
|
|
|
|
{
|
|
|
|
memcpy(((MIDIOut*)this)->m_buf, buf, std::min(len, size_t(512)));
|
|
|
|
((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(bool virt, ReceiveFunctor&& receiver)
|
|
|
|
: IMIDIInOut(virt, 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, NULL, NULL, CALLBACK_NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
~MIDIInOut()
|
|
|
|
{
|
|
|
|
midiInClose(m_midiIn);
|
|
|
|
midiStreamClose(m_strm);
|
|
|
|
midiOutUnprepareHeader(m_midiOut, &m_hdr, sizeof(m_hdr));
|
|
|
|
midiOutClose(m_midiOut);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string description() const
|
|
|
|
{
|
|
|
|
UINT id = 0;
|
|
|
|
midiOutGetID(m_midiOut, &id);
|
|
|
|
MIDIOUTCAPS caps;
|
|
|
|
if (FAILED(midiOutGetDevCaps(id, &caps, sizeof(caps))))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
#ifdef UNICODE
|
|
|
|
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, nullptr, 0, nullptr, nullptr);
|
|
|
|
std::string strTo(sizeNeeded, 0);
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, &strTo[0], sizeNeeded, nullptr, nullptr);
|
|
|
|
return strTo;
|
|
|
|
#else
|
|
|
|
return caps.szPname;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t send(const void* buf, size_t len) const
|
|
|
|
{
|
|
|
|
memcpy(((MIDIOut*)this)->m_buf, buf, std::min(len, size_t(512)));
|
|
|
|
((MIDIOut*)this)->m_hdr.dwBytesRecorded = len;
|
|
|
|
midiStreamOut(m_strm, LPMIDIHDR(&m_hdr), sizeof(m_hdr));
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<IMIDIOut> newVirtualMIDIOut()
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
if (strcmp(name, "in"))
|
|
|
|
return {};
|
|
|
|
long id = strtol(name + 2, nullptr, 10);
|
2016-05-21 03:22:00 +00:00
|
|
|
|
2016-05-23 05:34:20 +00:00
|
|
|
std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(false, std::move(receiver));
|
|
|
|
if (!ret)
|
|
|
|
return {};
|
2016-05-21 03:22:00 +00:00
|
|
|
|
2016-05-23 05:34:20 +00:00
|
|
|
if (FAILED(midiInOpen(&static_cast<MIDIIn&>(*ret).m_midi, id, DWORD_PTR(MIDIReceiveProc),
|
|
|
|
DWORD_PTR(static_cast<IMIDIReceiver*>(ret.get())), CALLBACK_FUNCTION)))
|
|
|
|
return {};
|
2016-05-21 03:22:00 +00:00
|
|
|
|
2016-05-23 05:34:20 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2016-05-21 03:22:00 +00:00
|
|
|
|
2016-05-23 05:34:20 +00:00
|
|
|
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name)
|
|
|
|
{
|
|
|
|
if (strcmp(name, "out"))
|
|
|
|
return {};
|
|
|
|
long id = strtol(name + 3, nullptr, 10);
|
2016-05-21 03:22:00 +00:00
|
|
|
|
2016-05-23 05:34:20 +00:00
|
|
|
std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(false);
|
|
|
|
if (!ret)
|
|
|
|
return {};
|
2016-05-21 03:22:00 +00:00
|
|
|
|
2016-05-23 05:34:20 +00:00
|
|
|
if (FAILED(midiOutOpen(&static_cast<MIDIOut&>(*ret).m_midi, id, NULL,
|
|
|
|
NULL, CALLBACK_NULL)))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
static_cast<MIDIOut&>(*ret).prepare();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
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>(false, 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 {};
|
|
|
|
|
|
|
|
if (FAILED(midiOutOpen(&static_cast<MIDIInOut&>(*ret).m_midiOut, outId, NULL,
|
|
|
|
NULL, CALLBACK_NULL)))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
static_cast<MIDIInOut&>(*ret).prepare();
|
|
|
|
return ret;
|
|
|
|
}
|
2016-03-23 02:00:45 +00:00
|
|
|
};
|
|
|
|
|
2016-03-24 01:50:34 +00:00
|
|
|
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine()
|
2016-03-23 02:00:45 +00:00
|
|
|
{
|
2016-03-24 01:50:34 +00:00
|
|
|
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WASAPIAudioVoiceEngine>();
|
|
|
|
if (!static_cast<WASAPIAudioVoiceEngine&>(*ret).m_device)
|
|
|
|
return {};
|
|
|
|
return ret;
|
2016-03-23 02:00:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|