|
|
@@ -1,349 +1,297 @@ |
|
|
// WIN32 : Minimal VST 2.x host. |
|
|
// Win32 : Minimal VST 2.x Synth host in C++11. |
|
|
// |
|
|
// This is a simplified version of the following project by hotwatermorning : |
|
|
// A sample VST Host Application for C++ Advent Calendar 2013 5th day. |
|
|
// https://github.com/hotwatermorning/VstHostDemo |
|
|
// A sample VST Host Application for C++ Advent Calendar 2013 5th day. |
|
|
// https://github.com/hotwatermorning/VstHostDemo |
|
|
// |
|
|
// Usage : |
|
|
// 1. Compile & Run this program. |
|
|
// 2. Select your VST Synth DLL. |
|
|
// 3. Press QWERTY, ZXCV, etc. |
|
|
// 4. Press Ctrl-C to exit. |
|
|
// |
|
|
// See also : |
|
|
// Steinberg Media Technologies - The Yvan Grabit Developer Resource |
|
|
// http://ygrabit.steinberg.de/~ygrabit/public_html/index.html |
|
|
// |
|
|
// MrsWatson - a command-line audio plugin host. |
|
|
// https://github.com/teragonaudio/MrsWatson |
|
|
// |
|
|
// Notes : |
|
|
// "vst2.x/aeffectx.h" is part of VST3 SDK. You can download VST3 SDK from |
|
|
// Steinberg SDK Download Portal : http://www.steinberg.net/nc/en/company/developers/sdk_download_portal.html |
|
|
|
|
|
#include <windows.h> |
|
|
#include <process.h> |
|
|
#include <mmdeviceapi.h> |
|
|
#include <audioclient.h> |
|
|
|
|
|
#include <stdio.h> |
|
|
|
|
|
#include <algorithm> |
|
|
#include <array> |
|
|
#include <atomic> |
|
|
#include <iostream> |
|
|
#include <map> |
|
|
#include <memory> |
|
|
#include <mutex> |
|
|
#include <string> |
|
|
#include <thread> |
|
|
#include <utility> |
|
|
#include <vector> |
|
|
|
|
|
// "aeffectx.h" is part of VST SDK. You can download VST SDK from |
|
|
// Steinberg SDK Download Portal : |
|
|
// http://www.steinberg.net/nc/en/company/developers/sdk_download_portal.html |
|
|
#pragma warning(push) |
|
|
#pragma warning(disable : 4996) |
|
|
#include "VST3 SDK/pluginterfaces/vst2.x/aeffectx.h" |
|
|
#pragma warning(pop) |
|
|
|
|
|
#define ASSERT_THROW(c,e) if(!(c)) { throw std::runtime_error(e); } |
|
|
#define CLOSE_HANDLE(x) if((x)) { CloseHandle(x); x = nullptr; } |
|
|
#define RELEASE(x) if((x)) { (x)->Release(); x = nullptr; } |
|
|
|
|
|
|
|
|
struct ComInit { |
|
|
ComInit() { CoInitializeEx(nullptr, COINIT_MULTITHREADED); } |
|
|
~ComInit() { CoUninitialize(); } |
|
|
}; |
|
|
|
|
|
|
|
|
class VstPlugin { |
|
|
public: |
|
|
VstPlugin( |
|
|
const wchar_t* vstModulePath, size_t sampleRate, size_t blockSize |
|
|
) { |
|
|
init(vstModulePath, sampleRate, blockSize); |
|
|
VstPlugin(const wchar_t* vstModulePath, HWND hWndParent) { |
|
|
init(vstModulePath, hWndParent); |
|
|
} |
|
|
|
|
|
~VstPlugin() { |
|
|
cleanup(); |
|
|
} |
|
|
|
|
|
AEffect* getEffect() { return aEffect; } |
|
|
AEffect* getEffect() const { return aEffect; } |
|
|
|
|
|
intptr_t dispatcher( |
|
|
int32_t opcode, int32_t index, intptr_t value, void *ptr, float opt |
|
|
) const { |
|
|
auto* e = getEffect(); |
|
|
return e->dispatcher(e, opcode, index, value, ptr, opt); |
|
|
size_t getSamplePos() const { return samplePos; } |
|
|
size_t getSampleRate() const { return 44100; } |
|
|
size_t getBlockSize() const { return 1024; } |
|
|
size_t getChannelCount() const { return 2; } |
|
|
static const char* getVendorString() { return "TEST_VENDOR"; } |
|
|
static const char* getProductString() { return "TEST_PRODUCT"; } |
|
|
static int getVendorVersion() { return 1; } |
|
|
static const char** getCapabilities() { |
|
|
static const char* hostCapabilities[] = { |
|
|
"sendVstEvents", |
|
|
"sendVstMidiEvents", |
|
|
"sizeWindow", |
|
|
"startStopProcess", |
|
|
"sendVstMidiEventFlagIsRealtime", |
|
|
nullptr |
|
|
}; |
|
|
return hostCapabilities; |
|
|
} |
|
|
|
|
|
void processReplacing( |
|
|
float** inputs, float** outputs, int32_t sampleFrames |
|
|
) const { |
|
|
auto* e = getEffect(); |
|
|
e->processReplacing(e, inputs, outputs, sampleFrames); |
|
|
} |
|
|
|
|
|
int32_t getFlags() const { return getEffect()->flags; } |
|
|
bool getFlags(int32_t m) const { return (getFlags() & m) == m; } |
|
|
bool getFlags(int32_t m) const { return (aEffect->flags & m) == m; } |
|
|
bool flagsHasEditor() const { return getFlags(effFlagsHasEditor); } |
|
|
bool flagsIsSynth() const { return getFlags(effFlagsIsSynth); } |
|
|
size_t getSampleRate() const { return sampleRate; } |
|
|
size_t getBlockSize() const { return blockSize; } |
|
|
|
|
|
void vstOpen() const { dispatcher(effOpen, 0, 0, 0, 0); } |
|
|
void vstClose() const { dispatcher(effClose, 0, 0, 0, 0); } |
|
|
void vstSetProgram(int pn) const { dispatcher(effSetProgram, 0, pn, 0, 0); } |
|
|
int vstGetProgram() const { return static_cast<int>(dispatcher(effGetProgram, 0, 0, 0, 0)); } |
|
|
void vstSetSampleRate(float f) const { dispatcher(effSetSampleRate, 0, 0, 0, f); } |
|
|
void vstSetBlockSize(int32_t bs) const { dispatcher(effSetBlockSize, 0, bs, 0, 0); } |
|
|
void vstMainsChanged(bool sw) const { dispatcher(effMainsChanged, 0, sw ? 1 : 0, 0, 0); } |
|
|
void vstEditOpen(void* ptr) const { dispatcher(effEditOpen, 0, 0, ptr, 0); } |
|
|
void vstEditClose() const { dispatcher(effEditClose, 0, 0, 0, 0); } |
|
|
void vstEditIdle() const { dispatcher(effEditIdle, 0, 0, 0, 0); } |
|
|
void vstProcessEvents(VstEvents* e) const { dispatcher(effProcessEvents, 0, 0, e, 0); } |
|
|
void vstStartProcess() const { dispatcher(effStartProcess, 0, 0, 0, 0); } |
|
|
void vstStopProcess() const { dispatcher(effStopProcess, 0, 0, 0, 0); } |
|
|
bool vstCanBeAutomated(int i) const { return 0 != dispatcher(effCanBeAutomated, i, 0, 0, 0); } |
|
|
|
|
|
RECT vstEditGetRect() const { |
|
|
RECT rc {}; |
|
|
ERect* erc = nullptr; |
|
|
dispatcher(effEditGetRect, 0, 0, &erc, 0); |
|
|
rc.left = erc->left; |
|
|
rc.top = erc->top; |
|
|
rc.right = erc->right; |
|
|
rc.bottom = erc->bottom; |
|
|
return rc; |
|
|
} |
|
|
|
|
|
void vstSetProcessPrecision(bool bDouble) const { |
|
|
const auto c = bDouble ? kVstProcessPrecision64 : kVstProcessPrecision32; |
|
|
dispatcher(effSetProcessPrecision, 0, c, 0, 0); |
|
|
} |
|
|
|
|
|
void openEditor(HWND hWnd) { |
|
|
if(! isEditorOpened()) { |
|
|
editorHwnd = hWnd; |
|
|
vstEditOpen(hWnd); |
|
|
auto rc = vstEditGetRect(); |
|
|
resizeEditor(rc); |
|
|
ShowWindow(hWnd, SW_SHOW); |
|
|
} |
|
|
} |
|
|
|
|
|
void closeEditor() { |
|
|
if(isEditorOpened()) { |
|
|
vstEditClose(); |
|
|
editorHwnd = nullptr; |
|
|
} |
|
|
} |
|
|
|
|
|
bool isEditorOpened() const { |
|
|
return editorHwnd != nullptr; |
|
|
intptr_t dispatcher(int32_t opcode, int32_t index = 0, intptr_t value = 0, void *ptr = nullptr, float opt = 0.0f) const { |
|
|
return aEffect->dispatcher(aEffect, opcode, index, value, ptr, opt); |
|
|
} |
|
|
|
|
|
void resizeEditor(const RECT& clientRc) const { |
|
|
if(isEditorOpened()) { |
|
|
if(editorHwnd) { |
|
|
auto rc = clientRc; |
|
|
const auto style = GetWindowLongPtr(editorHwnd, GWL_STYLE); |
|
|
const auto exStyle = GetWindowLongPtr(editorHwnd, GWL_EXSTYLE); |
|
|
const BOOL fMenu = GetMenu(editorHwnd) != nullptr; |
|
|
const auto style = GetWindowLongPtr(editorHwnd, GWL_STYLE); |
|
|
const auto exStyle = GetWindowLongPtr(editorHwnd, GWL_EXSTYLE); |
|
|
const BOOL fMenu = GetMenu(editorHwnd) != nullptr; |
|
|
AdjustWindowRectEx(&rc, style, fMenu, exStyle); |
|
|
MoveWindow( |
|
|
editorHwnd, 0, 0, rc.right-rc.left, rc.bottom-rc.top, TRUE |
|
|
); |
|
|
MoveWindow(editorHwnd, 0, 0, rc.right-rc.left, rc.bottom-rc.top, TRUE); |
|
|
} |
|
|
} |
|
|
|
|
|
void setWindowSize(size_t width, size_t height) { |
|
|
RECT rc {}; |
|
|
GetWindowRect(editorHwnd, &rc); |
|
|
rc.right = rc.left + width; |
|
|
rc.bottom = rc.top + height; |
|
|
resizeEditor(rc); |
|
|
} |
|
|
|
|
|
const char* getDirectory() const { |
|
|
return directoryMultiByte.c_str(); |
|
|
} |
|
|
|
|
|
void note(int midiChannel, int noteNumber, bool onOff, int velocity) { |
|
|
const int base = onOff ? 0x90 : 0x80; |
|
|
VstMidiEvent event {}; |
|
|
event.type = kVstMidiType; |
|
|
event.byteSize = sizeof(VstMidiEvent); |
|
|
event.flags = kVstMidiEventIsRealtime; |
|
|
event.midiData[0] = static_cast<char>(base + midiChannel); |
|
|
event.midiData[1] = static_cast<char>(noteNumber); |
|
|
event.midiData[2] = static_cast<char>(velocity); |
|
|
|
|
|
{ |
|
|
auto lock = lockVstMidiEvents(); |
|
|
vstMidiEvents.push_back(event); |
|
|
void sendMidiNote(int midiChannel, int noteNumber, bool onOff, int velocity) { |
|
|
VstMidiEvent e {}; |
|
|
e.type = kVstMidiType; |
|
|
e.byteSize = sizeof(e); |
|
|
e.flags = kVstMidiEventIsRealtime; |
|
|
e.midiData[0] = static_cast<char>(midiChannel + (onOff ? 0x90 : 0x80)); |
|
|
e.midiData[1] = static_cast<char>(noteNumber); |
|
|
e.midiData[2] = static_cast<char>(velocity); |
|
|
if(auto l = vstMidi.lock()) { |
|
|
vstMidi.events.push_back(e); |
|
|
} |
|
|
} |
|
|
|
|
|
// This function is called from refillCallback() which is running in audio thread. |
|
|
void processEvents() { |
|
|
procVstMidiEvents.clear(); |
|
|
|
|
|
{ |
|
|
auto lock = lockVstMidiEvents(); |
|
|
std::swap(procVstMidiEvents, vstMidiEvents); |
|
|
} |
|
|
|
|
|
if(procVstMidiEvents.empty()) { |
|
|
return; |
|
|
vstMidiEvents.clear(); |
|
|
if(auto l = vstMidi.lock()) { |
|
|
std::swap(vstMidiEvents, vstMidi.events); |
|
|
} |
|
|
|
|
|
const auto n = procVstMidiEvents.size(); |
|
|
const auto bytes = sizeof(VstEvents) + sizeof(VstEvent*) * std::max<size_t>(n, 2) - 2; |
|
|
vstEventBuffer.resize(bytes); |
|
|
auto* ve = reinterpret_cast<VstEvents*>(vstEventBuffer.data()); |
|
|
ve->numEvents = n; |
|
|
ve->reserved = 0; |
|
|
for(size_t i = 0; i < n; ++i) { |
|
|
ve->events[i] = reinterpret_cast<VstEvent*>(&procVstMidiEvents[i]); |
|
|
if(! vstMidiEvents.empty()) { |
|
|
const auto n = vstMidiEvents.size(); |
|
|
const auto bytes = sizeof(VstEvents) + sizeof(VstEvent*) * n; |
|
|
vstEventBuffer.resize(bytes); |
|
|
auto* ve = reinterpret_cast<VstEvents*>(vstEventBuffer.data()); |
|
|
ve->numEvents = n; |
|
|
ve->reserved = 0; |
|
|
for(size_t i = 0; i < n; ++i) { |
|
|
ve->events[i] = reinterpret_cast<VstEvent*>(&vstMidiEvents[i]); |
|
|
} |
|
|
dispatcher(effProcessEvents, 0, 0, ve); |
|
|
} |
|
|
vstProcessEvents(ve); |
|
|
} |
|
|
|
|
|
// This function is called from refillCallback() which is running in audio thread. |
|
|
float** processAudio(size_t frameCount, size_t& outputFrameCount) { |
|
|
const auto maxFrameCount = outputBuffer.size() / nChannel; |
|
|
if(frameCount > maxFrameCount) { |
|
|
frameCount = maxFrameCount; |
|
|
} |
|
|
frameCount = std::min<size_t>(frameCount, outputBuffer.size() / getChannelCount()); |
|
|
aEffect->processReplacing(aEffect, inputBufferHeads.data(), outputBufferHeads.data(), frameCount); |
|
|
samplePos += frameCount; |
|
|
outputFrameCount = frameCount; |
|
|
|
|
|
processReplacing(inputBufferHeads.data(), outputBufferHeads.data(), frameCount); |
|
|
return outputBufferHeads.data(); |
|
|
} |
|
|
|
|
|
private: |
|
|
void init(const wchar_t* vstModulePath, size_t sampleRate, size_t blockSize) { |
|
|
bool init(const wchar_t* vstModulePath, HWND hWndParent) { |
|
|
{ |
|
|
wchar_t buf[MAX_PATH+1] {}; |
|
|
wchar_t* namePtr = nullptr; |
|
|
const auto r = GetFullPathName(vstModulePath, _countof(buf), buf, &namePtr); |
|
|
if(r && namePtr) { |
|
|
*namePtr = 0; |
|
|
directoryUtf16 = buf; |
|
|
|
|
|
char mbBuf[_countof(buf) * 4] {}; |
|
|
const auto s = WideCharToMultiByte( |
|
|
CP_OEMCP, 0, directoryUtf16.c_str(), -1, mbBuf, sizeof(mbBuf), 0, 0); |
|
|
if(s) { |
|
|
if(auto s = WideCharToMultiByte(CP_OEMCP, 0, buf, -1, mbBuf, sizeof(mbBuf), 0, 0)) { |
|
|
directoryMultiByte = mbBuf; |
|
|
} |
|
|
} |
|
|
} |
|
|
this->sampleRate = sampleRate; |
|
|
this->blockSize = blockSize; |
|
|
|
|
|
hModule = LoadLibrary(vstModulePath); |
|
|
ASSERT_THROW(hModule, "Can't open VST DLL"); |
|
|
|
|
|
typedef AEffect* (VstPluginEntryProc)(audioMasterCallback); |
|
|
auto* vstEntryProc = reinterpret_cast<VstPluginEntryProc*>(GetProcAddress(hModule, "VSTPluginMain")); |
|
|
typedef AEffect* (VstEntryProc)(audioMasterCallback); |
|
|
auto* vstEntryProc = reinterpret_cast<VstEntryProc*>(GetProcAddress(hModule, "VSTPluginMain")); |
|
|
if(!vstEntryProc) { |
|
|
vstEntryProc = reinterpret_cast<VstPluginEntryProc*>(GetProcAddress(hModule, "main")); |
|
|
} |
|
|
if(! vstEntryProc) { |
|
|
throw std::runtime_error("entry point not found"); |
|
|
vstEntryProc = reinterpret_cast<VstEntryProc*>(GetProcAddress(hModule, "main")); |
|
|
} |
|
|
ASSERT_THROW(vstEntryProc, "VST's entry point not found"); |
|
|
|
|
|
aEffect = vstEntryProc(hostCallback_static); |
|
|
if(!aEffect || aEffect->magic != kEffectMagic) { |
|
|
throw std::runtime_error("not a vst plugin"); |
|
|
} |
|
|
ASSERT_THROW(aEffect && aEffect->magic == kEffectMagic, "Not a VST plugin"); |
|
|
ASSERT_THROW(flagsIsSynth(), "Not a VST Synth"); |
|
|
aEffect->user = this; |
|
|
|
|
|
{ |
|
|
const int ni = aEffect->numInputs; |
|
|
inputBuffer.resize(ni * blockSize); |
|
|
for(int i = 0; i < ni; ++i) { |
|
|
inputBufferHeads.push_back(inputBuffer.data() + i * blockSize); |
|
|
} |
|
|
inputBuffer.resize(aEffect->numInputs * getBlockSize()); |
|
|
for(int i = 0; i < aEffect->numInputs; ++i) { |
|
|
inputBufferHeads.push_back(&inputBuffer[i * getBlockSize()]); |
|
|
} |
|
|
|
|
|
{ |
|
|
const int no = aEffect->numOutputs; |
|
|
outputBuffer.resize(no * blockSize); |
|
|
for(int i = 0; i < no; ++i) { |
|
|
outputBufferHeads.push_back(outputBuffer.data() + i * blockSize); |
|
|
} |
|
|
outputBuffer.resize(aEffect->numOutputs * getBlockSize()); |
|
|
for(int i = 0; i < aEffect->numOutputs; ++i) { |
|
|
outputBufferHeads.push_back(&outputBuffer[i * getBlockSize()]); |
|
|
} |
|
|
|
|
|
vstOpen(); |
|
|
vstSetSampleRate(static_cast<float>(sampleRate)); |
|
|
vstSetBlockSize(blockSize); |
|
|
vstSetProcessPrecision(false); |
|
|
vstMainsChanged(true); |
|
|
vstStartProcess(); |
|
|
} |
|
|
dispatcher(effOpen); |
|
|
dispatcher(effSetSampleRate, 0, 0, 0, static_cast<float>(getSampleRate())); |
|
|
dispatcher(effSetBlockSize, 0, getBlockSize()); |
|
|
dispatcher(effSetProcessPrecision, 0, kVstProcessPrecision32); |
|
|
dispatcher(effMainsChanged, 0, 1); |
|
|
dispatcher(effStartProcess); |
|
|
|
|
|
if(hWndParent && flagsHasEditor()) { |
|
|
WNDCLASSEX wcex { sizeof(wcex) }; |
|
|
wcex.lpfnWndProc = DefWindowProc; |
|
|
wcex.hInstance = GetModuleHandle(0); |
|
|
wcex.lpszClassName = L"Minimal VST host - Guest VST Window Frame"; |
|
|
RegisterClassEx(&wcex); |
|
|
|
|
|
const auto style = WS_CAPTION | WS_THICKFRAME | WS_OVERLAPPEDWINDOW; |
|
|
editorHwnd = CreateWindow( |
|
|
wcex.lpszClassName, vstModulePath, style |
|
|
, 0, 0, 0, 0, hWndParent, 0, 0, 0 |
|
|
); |
|
|
dispatcher(effEditOpen, 0, 0, editorHwnd); |
|
|
RECT rc {}; |
|
|
ERect* erc = nullptr; |
|
|
dispatcher(effEditGetRect, 0, 0, &erc); |
|
|
rc.left = erc->left; |
|
|
rc.top = erc->top; |
|
|
rc.right = erc->right; |
|
|
rc.bottom = erc->bottom; |
|
|
resizeEditor(rc); |
|
|
ShowWindow(editorHwnd, SW_SHOW); |
|
|
} |
|
|
|
|
|
void cleanup() { |
|
|
closeEditor(); |
|
|
vstStopProcess(); |
|
|
vstMainsChanged(false); |
|
|
vstClose(); |
|
|
return true; |
|
|
} |
|
|
|
|
|
std::unique_lock<std::mutex> lockVstMidiEvents() const { |
|
|
return std::unique_lock<std::mutex>(vstMidiEventsMutex); |
|
|
void cleanup() { |
|
|
if(editorHwnd) { |
|
|
dispatcher(effEditClose); |
|
|
editorHwnd = nullptr; |
|
|
} |
|
|
dispatcher(effStopProcess); |
|
|
dispatcher(effMainsChanged, 0, 0); |
|
|
dispatcher(effClose); |
|
|
if(hModule) { |
|
|
FreeLibrary(hModule); |
|
|
hModule = nullptr; |
|
|
} |
|
|
} |
|
|
|
|
|
static VstIntPtr hostCallback_static( |
|
|
AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt |
|
|
) { |
|
|
VstIntPtr result = 0; |
|
|
if(effect == nullptr || effect->user == nullptr) { |
|
|
if(audioMasterVersion == opcode) { |
|
|
result = kVstVersion; |
|
|
} |
|
|
} else { |
|
|
if(effect && effect->user) { |
|
|
auto* that = static_cast<VstPlugin*>(effect->user); |
|
|
result = that->hostCallback(opcode, index, value, ptr, opt); |
|
|
return that->hostCallback(opcode, index, value, ptr, opt); |
|
|
} |
|
|
return result; |
|
|
} |
|
|
|
|
|
VstIntPtr hostCallback( |
|
|
VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt |
|
|
) { |
|
|
opt; |
|
|
switch(opcode) { |
|
|
case audioMasterVersion: return kVstVersion; |
|
|
default: return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
VstIntPtr hostCallback(VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float) { |
|
|
switch(opcode) { |
|
|
default: break; |
|
|
case audioMasterVersion: return kVstVersion; |
|
|
case audioMasterCurrentId: return getEffect()->uniqueID; |
|
|
case audioMasterCurrentId: return aEffect->uniqueID; |
|
|
case audioMasterGetSampleRate: return getSampleRate(); |
|
|
case audioMasterGetBlockSize: return getBlockSize(); |
|
|
case audioMasterGetCurrentProcessLevel: return kVstProcessLevelUnknown; |
|
|
case audioMasterGetAutomationState: return kVstAutomationOff; |
|
|
case audioMasterGetLanguage: return kVstLangEnglish; |
|
|
case audioMasterGetVendorVersion: return 1; |
|
|
case audioMasterGetDirectory: return reinterpret_cast<VstIntPtr>(getDirectory()); |
|
|
case audioMasterGetVendorVersion: return getVendorVersion(); |
|
|
|
|
|
case audioMasterIdle: |
|
|
if(isEditorOpened()) { |
|
|
vstEditIdle(); |
|
|
} |
|
|
return 0; |
|
|
case audioMasterGetVendorString: |
|
|
strcpy_s(static_cast<char*>(ptr), kVstMaxVendorStrLen, getVendorString()); |
|
|
return 1; |
|
|
|
|
|
case audioMasterGetProductString: |
|
|
strcpy_s(static_cast<char*>(ptr), kVstMaxProductStrLen, getProductString()); |
|
|
return 1; |
|
|
|
|
|
case audioMasterGetTime: |
|
|
{ |
|
|
VstTimeInfo ti {}; |
|
|
ti.sampleRate = getSampleRate(); |
|
|
ti.nanoSeconds = GetTickCount() * 1000.0 * 1000.0; |
|
|
ti.tempo = 120.0; |
|
|
ti.timeSigNumerator = 4; |
|
|
ti.timeSigDenominator = 4; |
|
|
ti.smpteFrameRate = kVstSmpte24fps; |
|
|
ti.flags = (kVstNanosValid | kVstPpqPosValid |
|
|
| kVstTempoValid | kVstTimeSigValid); |
|
|
timeinfo = ti; |
|
|
} |
|
|
timeinfo.flags = 0; |
|
|
timeinfo.samplePos = getSamplePos(); |
|
|
timeinfo.sampleRate = getSampleRate(); |
|
|
return reinterpret_cast<VstIntPtr>(&timeinfo); |
|
|
|
|
|
case audioMasterSizeWindow: |
|
|
setWindowSize(static_cast<size_t>(index), static_cast<size_t>(value)); |
|
|
case audioMasterGetDirectory: |
|
|
return reinterpret_cast<VstIntPtr>(directoryMultiByte.c_str()); |
|
|
|
|
|
case audioMasterGetVendorString: |
|
|
strcpy_s(static_cast<char*>(ptr), kVstMaxVendorStrLen, "TEST VENDOR"); |
|
|
return 1; |
|
|
case audioMasterIdle: |
|
|
if(editorHwnd) { |
|
|
dispatcher(effEditIdle); |
|
|
} |
|
|
break; |
|
|
|
|
|
case audioMasterGetProductString: |
|
|
strcpy_s(static_cast<char*>(ptr), kVstMaxVendorStrLen, "TEST PRODUCT"); |
|
|
return 1; |
|
|
case audioMasterSizeWindow: |
|
|
if(editorHwnd) { |
|
|
RECT rc {}; |
|
|
GetWindowRect(editorHwnd, &rc); |
|
|
rc.right = rc.left + static_cast<int>(index); |
|
|
rc.bottom = rc.top + static_cast<int>(value); |
|
|
resizeEditor(rc); |
|
|
} |
|
|
break; |
|
|
|
|
|
case audioMasterCanDo: |
|
|
{ |
|
|
static const char* hostCapabilities[] = { |
|
|
"sendVstEvents", |
|
|
"sendVstMidiEvents", |
|
|
"sizeWindow", |
|
|
"startStopProcess", |
|
|
"sendVstMidiEventFlagIsRealtime" |
|
|
}; |
|
|
for(auto e : hostCapabilities) { |
|
|
if(strcmp(e, static_cast<const char*>(ptr)) == 0) { |
|
|
return 1; |
|
|
} |
|
|
for(const char** pp = getCapabilities(); *pp; ++pp) { |
|
|
if(strcmp(*pp, static_cast<const char*>(ptr)) == 0) { |
|
|
return 1; |
|
|
} |
|
|
} |
|
|
return 0; |
|
|
@@ -352,71 +300,52 @@ class VstPlugin { |
|
|
} |
|
|
|
|
|
protected: |
|
|
HMODULE hModule { nullptr }; |
|
|
AEffect* aEffect { nullptr }; |
|
|
size_t sampleRate { 44100 }; |
|
|
size_t blockSize { 1024 }; |
|
|
HWND editorHwnd { nullptr }; |
|
|
VstTimeInfo timeinfo {}; |
|
|
std::wstring directoryUtf16; |
|
|
std::string directoryMultiByte; |
|
|
size_t nChannel { 2 }; |
|
|
HWND editorHwnd { nullptr }; |
|
|
HMODULE hModule { nullptr }; |
|
|
AEffect* aEffect { nullptr }; |
|
|
std::atomic<size_t> samplePos { 0 }; |
|
|
VstTimeInfo timeinfo {}; |
|
|
std::string directoryMultiByte {}; |
|
|
|
|
|
std::vector<float> outputBuffer; |
|
|
std::vector<float*> outputBufferHeads; |
|
|
std::vector<float> inputBuffer; |
|
|
std::vector<float*> inputBufferHeads; |
|
|
|
|
|
std::mutex mutable vstMidiEventsMutex; |
|
|
std::vector<VstMidiEvent> vstMidiEvents; |
|
|
std::vector<VstMidiEvent> procVstMidiEvents; |
|
|
std::vector<char> vstEventBuffer; |
|
|
|
|
|
struct { |
|
|
std::vector<VstMidiEvent> events; |
|
|
std::unique_lock<std::mutex> lock() const { |
|
|
return std::unique_lock<std::mutex>(mutex); |
|
|
} |
|
|
private: |
|
|
std::mutex mutable mutex; |
|
|
} vstMidi; |
|
|
}; |
|
|
|
|
|
|
|
|
class Wasapi { |
|
|
public: |
|
|
using RefillCallbackFunc = std::function<bool(float*, uint32_t, const WAVEFORMATEX*)>; |
|
|
struct Wasapi { |
|
|
using RefillFunc = std::function<bool(float*, uint32_t, const WAVEFORMATEX*)>; |
|
|
|
|
|
Wasapi() {} |
|
|
~Wasapi() { close(); } |
|
|
Wasapi(RefillFunc refillFunc, int hnsBufferDuration = 30 * 10000) { |
|
|
HRESULT hr = S_OK; |
|
|
|
|
|
void open(RefillCallbackFunc refillCallbackFunc, int hnsBufferDuration = 30 * 10000) { |
|
|
HRESULT hr; |
|
|
close(); |
|
|
hClose = CreateEventEx(0, 0, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); |
|
|
hRefillEvent = CreateEventEx(0, 0, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); |
|
|
this->refillFunc = refillFunc; |
|
|
|
|
|
this->refillCallbackFunc = refillCallbackFunc; |
|
|
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&mmDeviceEnumerator)); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "CoCreateInstance(MMDeviceEnumerator) failed"); |
|
|
|
|
|
hClose = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); |
|
|
hRefillEvent = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); |
|
|
hr = mmDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &mmDevice); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "mmDeviceEnumerator->GetDefaultAudioEndpoint() failed"); |
|
|
|
|
|
hr = CoCreateInstance( |
|
|
__uuidof(MMDeviceEnumerator) |
|
|
, nullptr |
|
|
, CLSCTX_INPROC_SERVER |
|
|
, IID_PPV_ARGS(&mmDeviceEnumerator) |
|
|
); |
|
|
assert(SUCCEEDED(hr)); |
|
|
|
|
|
hr = mmDeviceEnumerator->GetDefaultAudioEndpoint( |
|
|
eRender |
|
|
, eMultimedia |
|
|
, &mmDevice |
|
|
); |
|
|
assert(SUCCEEDED(hr)); |
|
|
|
|
|
hr = mmDevice->Activate( |
|
|
__uuidof(IAudioClient) |
|
|
, CLSCTX_INPROC_SERVER |
|
|
, nullptr |
|
|
, reinterpret_cast<void**>(&audioClient) |
|
|
); |
|
|
assert(SUCCEEDED(hr)); |
|
|
hr = mmDevice->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, 0, reinterpret_cast<void**>(&audioClient)); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "mmDevice->Activate() failed"); |
|
|
|
|
|
audioClient->GetMixFormat(&mixFormat); |
|
|
if(mixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { |
|
|
mixFormatEx = reinterpret_cast<WAVEFORMATEXTENSIBLE*>(mixFormat); |
|
|
} |
|
|
|
|
|
hr = audioClient->Initialize( |
|
|
AUDCLNT_SHAREMODE_SHARED |
|
|
@@ -426,224 +355,155 @@ class Wasapi { |
|
|
, mixFormat |
|
|
, nullptr |
|
|
); |
|
|
assert(SUCCEEDED(hr)); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "audioClient->Initialize() failed"); |
|
|
|
|
|
hr = audioClient->GetService( |
|
|
__uuidof(IAudioRenderClient) |
|
|
, reinterpret_cast<void**>(&audioRenderClient) |
|
|
); |
|
|
assert(SUCCEEDED(hr)); |
|
|
hr = audioClient->GetService(__uuidof(IAudioRenderClient), reinterpret_cast<void**>(&audioRenderClient)); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "audioClient->GetService(IAudioRenderClient) failed"); |
|
|
|
|
|
hr = audioClient->GetBufferSize(&bufferFrameCount); |
|
|
assert(SUCCEEDED(hr)); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "audioClient->GetBufferSize() failed"); |
|
|
|
|
|
hr = audioClient->SetEventHandle(hRefillEvent); |
|
|
assert(SUCCEEDED(hr)); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "audioClient->SetEventHandle() failed"); |
|
|
|
|
|
// initial zero fill |
|
|
{ |
|
|
BYTE* data = nullptr; |
|
|
hr = audioRenderClient->GetBuffer(bufferFrameCount, &data); |
|
|
assert(SUCCEEDED(hr)); |
|
|
BYTE* data = nullptr; |
|
|
hr = audioRenderClient->GetBuffer(bufferFrameCount, &data); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "audioRenderClient->GetBuffer() failed"); |
|
|
|
|
|
hr = audioRenderClient->ReleaseBuffer(bufferFrameCount, AUDCLNT_BUFFERFLAGS_SILENT); |
|
|
assert(SUCCEEDED(hr)); |
|
|
} |
|
|
hr = audioRenderClient->ReleaseBuffer(bufferFrameCount, AUDCLNT_BUFFERFLAGS_SILENT); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "audioRenderClient->ReleaseBuffer() failed"); |
|
|
|
|
|
const auto tf = [](void* arg) -> unsigned { |
|
|
return reinterpret_cast<Wasapi*>(arg)->threadFunc(); |
|
|
}; |
|
|
unsigned threadId = 0; |
|
|
hThread = reinterpret_cast<HANDLE>(_beginthreadex( |
|
|
nullptr, 0, threadFunc_static, reinterpret_cast<void*>(this), 0, &threadId |
|
|
)); |
|
|
hThread = reinterpret_cast<HANDLE>(_beginthreadex(0, 0, tf, reinterpret_cast<void*>(this), 0, &threadId)); |
|
|
|
|
|
hr = audioClient->Start(); |
|
|
assert(SUCCEEDED(hr)); |
|
|
ASSERT_THROW(SUCCEEDED(hr), "audioClient->Start() failed"); |
|
|
} |
|
|
|
|
|
void close() { |
|
|
~Wasapi() { |
|
|
if(hClose) { |
|
|
SetEvent(hClose); |
|
|
if(hThread) { |
|
|
WaitForSingleObject(hThread, INFINITE); |
|
|
CloseHandle(hThread); |
|
|
hThread = nullptr; |
|
|
} |
|
|
CloseHandle(hClose); |
|
|
hClose = nullptr; |
|
|
} |
|
|
|
|
|
if(hRefillEvent) { |
|
|
CloseHandle(hRefillEvent); |
|
|
hRefillEvent = nullptr; |
|
|
} |
|
|
CLOSE_HANDLE(hThread); |
|
|
CLOSE_HANDLE(hClose); |
|
|
CLOSE_HANDLE(hRefillEvent); |
|
|
|
|
|
if(mixFormat) { CoTaskMemFree(mixFormat); mixFormat = nullptr; } |
|
|
mixFormatEx = nullptr; |
|
|
if(audioRenderClient) { audioRenderClient->Release(); audioRenderClient = nullptr; } |
|
|
if(audioClient) { audioClient->Release(); audioClient = nullptr; } |
|
|
if(mmDevice) { mmDevice->Release(); mmDevice = nullptr; } |
|
|
if(mmDeviceEnumerator) { mmDeviceEnumerator->Release(); mmDeviceEnumerator = nullptr; } |
|
|
} |
|
|
if(mixFormat) { |
|
|
CoTaskMemFree(mixFormat); |
|
|
mixFormat = nullptr; |
|
|
} |
|
|
|
|
|
static unsigned WINAPI threadFunc_static(void* arg) { |
|
|
auto* that = reinterpret_cast<Wasapi*>(arg); |
|
|
CoInitializeEx(nullptr, COINIT_MULTITHREADED); |
|
|
that->threadFunc(); |
|
|
CoUninitialize(); |
|
|
return 0; |
|
|
RELEASE(audioRenderClient); |
|
|
RELEASE(audioClient); |
|
|
RELEASE(mmDevice); |
|
|
RELEASE(mmDeviceEnumerator); |
|
|
} |
|
|
|
|
|
void threadFunc() { |
|
|
HANDLE events[2] = { hClose, hRefillEvent }; |
|
|
private: |
|
|
unsigned threadFunc() { |
|
|
ComInit comInit {}; |
|
|
const HANDLE events[2] = { hClose, hRefillEvent }; |
|
|
for(bool run = true; run; ) { |
|
|
const auto r = WaitForMultipleObjects(_countof(events), events, FALSE, INFINITE); |
|
|
switch(r) { |
|
|
default: |
|
|
break; |
|
|
case WAIT_OBJECT_0: // hClose |
|
|
if(WAIT_OBJECT_0 == r) { // hClose |
|
|
run = false; |
|
|
break; |
|
|
case WAIT_OBJECT_0 + 1: // hRefillEvent |
|
|
onRefill(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void onRefill() { |
|
|
UINT32 paddingFrameCount = 0; |
|
|
audioClient->GetCurrentPadding(&paddingFrameCount); |
|
|
} else if(WAIT_OBJECT_0+1 == r) { // hRefillEvent |
|
|
UINT32 c = 0; |
|
|
audioClient->GetCurrentPadding(&c); |
|
|
|
|
|
const auto availableFrameCount = bufferFrameCount - paddingFrameCount; |
|
|
const auto a = bufferFrameCount - c; |
|
|
float* data = nullptr; |
|
|
audioRenderClient->GetBuffer(a, reinterpret_cast<BYTE**>(&data)); |
|
|
|
|
|
BYTE* data = nullptr; |
|
|
audioRenderClient->GetBuffer(availableFrameCount, &data); |
|
|
const auto b = refillCallbackFunc( |
|
|
reinterpret_cast<float*>(data) |
|
|
, availableFrameCount |
|
|
, mixFormat |
|
|
); |
|
|
|
|
|
DWORD dwReleaseFlags = 0; |
|
|
if(! b) { |
|
|
dwReleaseFlags = AUDCLNT_BUFFERFLAGS_SILENT; |
|
|
const auto r = refillFunc(data, a, mixFormat); |
|
|
audioRenderClient->ReleaseBuffer(a, r ? 0 : AUDCLNT_BUFFERFLAGS_SILENT); |
|
|
} |
|
|
} |
|
|
audioRenderClient->ReleaseBuffer(availableFrameCount, dwReleaseFlags); |
|
|
return 0; |
|
|
} |
|
|
|
|
|
private: |
|
|
HANDLE hThread { nullptr }; |
|
|
IMMDeviceEnumerator* mmDeviceEnumerator { nullptr }; |
|
|
IMMDevice* mmDevice { nullptr }; |
|
|
IAudioClient* audioClient { nullptr }; |
|
|
IAudioRenderClient* audioRenderClient { nullptr }; |
|
|
WAVEFORMATEX* mixFormat { nullptr }; |
|
|
WAVEFORMATEXTENSIBLE* mixFormatEx { nullptr }; |
|
|
HANDLE hRefillEvent { nullptr }; |
|
|
HANDLE hClose { nullptr }; |
|
|
UINT32 bufferFrameCount { 0 }; |
|
|
RefillCallbackFunc refillCallbackFunc {}; |
|
|
RefillFunc refillFunc {}; |
|
|
}; |
|
|
|
|
|
|
|
|
void mainLoop(const std::wstring& dllFilename) { |
|
|
enum { |
|
|
SAMPLE_RATE = 44100, |
|
|
BLOCK_SIZE = 1024, |
|
|
}; |
|
|
|
|
|
HWND const hwnd = GetConsoleWindow(); |
|
|
|
|
|
auto vstPlugin = std::unique_ptr<VstPlugin>(new VstPlugin { |
|
|
dllFilename.c_str(), SAMPLE_RATE, BLOCK_SIZE |
|
|
}); |
|
|
// This function is called from Wasapi::threadFunc() which is running in audio thread. |
|
|
bool refillCallback(VstPlugin& vstPlugin, float* const data, uint32_t availableFrameCount, const WAVEFORMATEX* const mixFormat) { |
|
|
vstPlugin.processEvents(); |
|
|
|
|
|
const auto nDstChannels = mixFormat->nChannels; |
|
|
const auto nSrcChannels = vstPlugin.getChannelCount(); |
|
|
const auto vstSamplesPerBlock = vstPlugin.getBlockSize(); |
|
|
|
|
|
int ofs = 0; |
|
|
while(availableFrameCount > 0) { |
|
|
size_t outputFrameCount = 0; |
|
|
float** vstOutput = vstPlugin.processAudio(availableFrameCount, outputFrameCount); |
|
|
|
|
|
// VST vstOutput[][] format : |
|
|
// vstOutput[a][b] |
|
|
// channel = a % vstPlugin.getChannelCount() |
|
|
// frame = b + floor(a/2) * vstPlugin.getBlockSize() |
|
|
|
|
|
// wasapi data[] format : |
|
|
// data[x] |
|
|
// channel = x % mixFormat->nChannels |
|
|
// frame = floor(x / mixFormat->nChannels); |
|
|
|
|
|
const auto nFrame = outputFrameCount; |
|
|
for(size_t iFrame = 0; iFrame < nFrame; ++iFrame) { |
|
|
for(size_t iChannel = 0; iChannel < nDstChannels; ++iChannel) { |
|
|
const int sChannel = iChannel % nSrcChannels; |
|
|
const int vstOutputPage = (iFrame / vstSamplesPerBlock) * sChannel + sChannel; |
|
|
const int vstOutputIndex = (iFrame % vstSamplesPerBlock); |
|
|
const int wasapiWriteIndex = iFrame * nDstChannels + iChannel; |
|
|
*(data + ofs + wasapiWriteIndex) = vstOutput[vstOutputPage][vstOutputIndex]; |
|
|
} |
|
|
} |
|
|
|
|
|
if(!vstPlugin->flagsIsSynth()) { |
|
|
MessageBox(nullptr, L"Specified DLL is not VST Synth", dllFilename.c_str(), MB_ICONSTOP); |
|
|
return; |
|
|
availableFrameCount -= nFrame; |
|
|
ofs += nFrame * nDstChannels; |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
if(vstPlugin->flagsHasEditor()) { |
|
|
WNDCLASSEX wcex = { 0 }; |
|
|
wcex.cbSize = sizeof(wcex); |
|
|
wcex.lpfnWndProc = DefWindowProc; |
|
|
wcex.hInstance = GetModuleHandle(0); |
|
|
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); |
|
|
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); |
|
|
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); |
|
|
wcex.lpszClassName = L"TEST APP VST WINDOW"; |
|
|
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); |
|
|
RegisterClassEx(&wcex); |
|
|
|
|
|
const auto style = WS_BORDER | WS_CAPTION; |
|
|
HWND dummyHwnd = CreateWindow( |
|
|
wcex.lpszClassName, dllFilename.c_str(), style, 0, 0, 320, 240, hwnd, 0, 0, 0 |
|
|
); |
|
|
vstPlugin->openEditor(dummyHwnd); |
|
|
} |
|
|
|
|
|
const auto refillCallback = [&]( |
|
|
float* const data, uint32_t availableFrameCount, const WAVEFORMATEX* const mixFormat |
|
|
) { |
|
|
const auto nChannels = mixFormat->nChannels; |
|
|
vstPlugin->processEvents(); |
|
|
const auto vstSamplesPerBlock = vstPlugin->getBlockSize(); |
|
|
int ofs = 0; |
|
|
while(availableFrameCount > 0) { |
|
|
size_t outputFrameCount = 0; |
|
|
float** const vstOutput = vstPlugin->processAudio(availableFrameCount, outputFrameCount); |
|
|
|
|
|
// VST vstOutput[][] format : |
|
|
// vstOutput[0][0..1023] : Left [0..1023] |
|
|
// vstOutput[1][0..1023] : Right [0..1023] |
|
|
// vstOutput[2][0..1023] : Left [1024..2047] |
|
|
// vstOutput[3][0..1023] : Right [1024..2047] |
|
|
|
|
|
// wasapi data[] format : |
|
|
// data[0..1023] : Left[0], Right[0], Left[1], Right[1] .. Left[511], Right[511] |
|
|
// data[1024..2047] : Left[512], Right[512], Left[513], Right[513] .. Left[1023], Right[1023] |
|
|
|
|
|
const auto nFrame = outputFrameCount; |
|
|
for(size_t iFrame = 0; iFrame < nFrame; ++iFrame) { |
|
|
for(size_t iChannel = 0; iChannel < nChannels; ++iChannel) { |
|
|
const int vstOutputPage = (iFrame / vstSamplesPerBlock) * nChannels + iChannel; |
|
|
const int vstOutputIndex = (iFrame % vstSamplesPerBlock); |
|
|
const int wasapiWriteIndex = iFrame * nChannels + iChannel; |
|
|
*(data + ofs + wasapiWriteIndex) = vstOutput[vstOutputPage][vstOutputIndex]; |
|
|
} |
|
|
} |
|
|
|
|
|
availableFrameCount -= nFrame; |
|
|
ofs += nFrame * nChannels; |
|
|
} |
|
|
return true; |
|
|
}; |
|
|
|
|
|
const int latencyInHns = 100 * 10000; // 100 ms |
|
|
Wasapi wasapi; |
|
|
wasapi.open(refillCallback, latencyInHns); |
|
|
void mainLoop(const std::wstring& dllFilename) { |
|
|
VstPlugin vstPlugin { dllFilename.c_str(), GetConsoleWindow() }; |
|
|
Wasapi wasapi { [&vstPlugin](float* const data, uint32_t availableFrameCount, const WAVEFORMATEX* const mixFormat) { |
|
|
return refillCallback(vstPlugin, data, availableFrameCount, mixFormat); |
|
|
}}; |
|
|
|
|
|
struct Key { |
|
|
Key(int vk, int midiNote) : vk{vk}, midiNote{midiNote} {} |
|
|
int vk { 0 }; |
|
|
int midiNote { 0 }; |
|
|
Key(int midiNote) : midiNote { midiNote } {} |
|
|
int midiNote {}; |
|
|
bool status { false }; |
|
|
}; |
|
|
|
|
|
std::vector<Key> keys; |
|
|
keys.emplace_back('Z', 48); |
|
|
keys.emplace_back('X', 50); |
|
|
keys.emplace_back('C', 52); |
|
|
keys.emplace_back('V', 53); |
|
|
keys.emplace_back('B', 55); |
|
|
keys.emplace_back('N', 57); |
|
|
keys.emplace_back('M', 59); |
|
|
keys.emplace_back('K', 60); |
|
|
|
|
|
for(const auto& k : keys) { |
|
|
printf("[%c] = %d\n", k.vk, k.midiNote); |
|
|
} |
|
|
std::map<int, Key> keyMap { |
|
|
{'2', {61}}, {'3', {63}}, {'5', {66}}, {'6', {68}}, {'7', {70}}, |
|
|
{'Q', {60}}, {'W', {62}}, {'E', {64}}, {'R', {65}}, {'T', {67}}, {'Y', {69}}, {'U', {71}}, {'I', {72}}, |
|
|
|
|
|
{'S', {49}}, {'D', {51}}, {'G', {54}}, {'H', {56}}, {'J', {58}}, |
|
|
{'Z', {48}}, {'X', {50}}, {'C', {52}}, {'V', {53}}, {'B', {55}}, {'N', {57}}, {'M', {59}}, {VK_OEM_COMMA, {60}}, |
|
|
}; |
|
|
|
|
|
for(bool run = true; run; Sleep(10)) { |
|
|
for(bool run = true; run; WaitMessage()) { |
|
|
MSG msg {}; |
|
|
while(BOOL b = PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { |
|
|
if(b == -1) { |
|
|
@@ -654,42 +514,36 @@ void mainLoop(const std::wstring& dllFilename) { |
|
|
DispatchMessage(&msg); |
|
|
} |
|
|
|
|
|
for(auto& k : keys) { |
|
|
const auto on = (GetAsyncKeyState(k.vk) & 0x8000) != 0; |
|
|
if(k.status != on) { |
|
|
k.status = on; |
|
|
const int midiChannel = 0; |
|
|
const int noteNumber = k.midiNote; |
|
|
const int velocity = 100; |
|
|
vstPlugin->note(midiChannel, noteNumber, on, velocity); |
|
|
for(auto& e : keyMap) { |
|
|
auto& key = e.second; |
|
|
const auto on = (GetKeyState(e.first) & 0x8000) != 0; |
|
|
if(key.status != on) { |
|
|
key.status = on; |
|
|
vstPlugin.sendMidiNote(0, key.midiNote, on, 100); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
int main() { |
|
|
CoInitializeEx(NULL, COINIT_MULTITHREADED); |
|
|
ComInit comInit {}; |
|
|
|
|
|
std::wstring dllFilename; |
|
|
{ |
|
|
const auto dllFilename = []() -> std::wstring { |
|
|
wchar_t fn[MAX_PATH+1] {}; |
|
|
|
|
|
OPENFILENAME ofn { sizeof(ofn) }; |
|
|
ofn.lStructSize = sizeof(ofn); |
|
|
ofn.lpstrFile = fn; |
|
|
ofn.lpstrFilter = L"VSTi DLL(*.dll)\0*.dll\0All Files(*.*)\0*.*\0\0"; |
|
|
ofn.nMaxFile = _countof(fn); |
|
|
ofn.lpstrTitle = L"Select VST DLL"; |
|
|
ofn.Flags = OFN_FILEMUSTEXIST | OFN_ENABLESIZING; |
|
|
ofn.lpstrFilter = L"VSTi DLL(*.dll)\0*.dll\0All Files(*.*)\0*.*\0\0"; |
|
|
ofn.lpstrFile = fn; |
|
|
ofn.nMaxFile = _countof(fn); |
|
|
ofn.lpstrTitle = L"Select VST DLL"; |
|
|
ofn.Flags = OFN_FILEMUSTEXIST | OFN_ENABLESIZING; |
|
|
GetOpenFileName(&ofn); |
|
|
return fn; |
|
|
} (); |
|
|
|
|
|
dllFilename = fn; |
|
|
} |
|
|
|
|
|
if(! dllFilename.empty()) { |
|
|
try { |
|
|
mainLoop(dllFilename); |
|
|
} catch(std::exception &e) { |
|
|
std::cout << "Exception : " << e.what() << std::endl; |
|
|
} |
|
|
|
|
|
CoUninitialize(); |
|
|
} |