Skip to content

Instantly share code, notes, and snippets.

@TronicLabs
Forked from t-mat/minimal_vst2x_host.cpp
Created November 15, 2018 16:06
Show Gist options
  • Save TronicLabs/06a5ce4be3de1a09f92154bf430d3090 to your computer and use it in GitHub Desktop.
Save TronicLabs/06a5ce4be3de1a09f92154bf430d3090 to your computer and use it in GitHub Desktop.

Revisions

  1. @t-mat t-mat revised this gist Dec 27, 2014. 1 changed file with 326 additions and 472 deletions.
    798 changes: 326 additions & 472 deletions minimal_vst2x_host.cpp
    Original file line number Diff line number Diff line change
    @@ -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();
    }
  2. @t-mat t-mat renamed this gist Dec 25, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion minimal_vst_host.cpp → minimal_vst2x_host.cpp
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // WIN32 : Minimal VST host.
    // WIN32 : Minimal VST 2.x host.
    //
    // This is a simplified version of the following project by hotwatermorning :
    // A sample VST Host Application for C++ Advent Calendar 2013 5th day.
  3. @t-mat t-mat created this gist Dec 25, 2014.
    695 changes: 695 additions & 0 deletions minimal_vst_host.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,695 @@
    // WIN32 : Minimal VST host.
    //
    // 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
    //
    #include <windows.h>
    #include <process.h>
    #include <mmdeviceapi.h>
    #include <audioclient.h>

    #include <stdio.h>

    #include <algorithm>
    #include <array>
    #include <atomic>
    #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)


    class VstPlugin {
    public:
    VstPlugin(
    const wchar_t* vstModulePath, size_t sampleRate, size_t blockSize
    ) {
    init(vstModulePath, sampleRate, blockSize);
    }

    ~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);
    }

    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 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;
    }

    void resizeEditor(const RECT& clientRc) const {
    if(isEditorOpened()) {
    auto rc = clientRc;
    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
    );
    }
    }

    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 processEvents() {
    procVstMidiEvents.clear();

    {
    auto lock = lockVstMidiEvents();
    std::swap(procVstMidiEvents, vstMidiEvents);
    }

    if(procVstMidiEvents.empty()) {
    return;
    }

    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]);
    }
    vstProcessEvents(ve);
    }

    float** processAudio(size_t frameCount, size_t& outputFrameCount) {
    const auto maxFrameCount = outputBuffer.size() / nChannel;
    if(frameCount > maxFrameCount) {
    frameCount = maxFrameCount;
    }
    outputFrameCount = frameCount;

    processReplacing(inputBufferHeads.data(), outputBufferHeads.data(), frameCount);
    return outputBufferHeads.data();
    }

    private:
    void init(const wchar_t* vstModulePath, size_t sampleRate, size_t blockSize) {
    {
    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) {
    directoryMultiByte = mbBuf;
    }
    }
    }
    this->sampleRate = sampleRate;
    this->blockSize = blockSize;

    hModule = LoadLibrary(vstModulePath);

    typedef AEffect* (VstPluginEntryProc)(audioMasterCallback);
    auto* vstEntryProc = reinterpret_cast<VstPluginEntryProc*>(GetProcAddress(hModule, "VSTPluginMain"));
    if(!vstEntryProc) {
    vstEntryProc = reinterpret_cast<VstPluginEntryProc*>(GetProcAddress(hModule, "main"));
    }
    if(! vstEntryProc) {
    throw std::runtime_error("entry point not found");
    }

    aEffect = vstEntryProc(hostCallback_static);
    if(!aEffect || aEffect->magic != kEffectMagic) {
    throw std::runtime_error("not a vst plugin");
    }
    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);
    }
    }

    {
    const int no = aEffect->numOutputs;
    outputBuffer.resize(no * blockSize);
    for(int i = 0; i < no; ++i) {
    outputBufferHeads.push_back(outputBuffer.data() + i * blockSize);
    }
    }

    vstOpen();
    vstSetSampleRate(static_cast<float>(sampleRate));
    vstSetBlockSize(blockSize);
    vstSetProcessPrecision(false);
    vstMainsChanged(true);
    vstStartProcess();
    }

    void cleanup() {
    closeEditor();
    vstStopProcess();
    vstMainsChanged(false);
    vstClose();
    }

    std::unique_lock<std::mutex> lockVstMidiEvents() const {
    return std::unique_lock<std::mutex>(vstMidiEventsMutex);
    }

    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 {
    auto* that = static_cast<VstPlugin*>(effect->user);
    result = that->hostCallback(opcode, index, value, ptr, opt);
    }
    return result;
    }

    VstIntPtr hostCallback(
    VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt
    ) {
    opt;

    switch(opcode) {
    default: break;
    case audioMasterVersion: return kVstVersion;
    case audioMasterCurrentId: return getEffect()->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 audioMasterIdle:
    if(isEditorOpened()) {
    vstEditIdle();
    }
    return 0;

    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;
    }
    return reinterpret_cast<VstIntPtr>(&timeinfo);

    case audioMasterSizeWindow:
    setWindowSize(static_cast<size_t>(index), static_cast<size_t>(value));

    case audioMasterGetVendorString:
    strcpy_s(static_cast<char*>(ptr), kVstMaxVendorStrLen, "TEST VENDOR");
    return 1;

    case audioMasterGetProductString:
    strcpy_s(static_cast<char*>(ptr), kVstMaxVendorStrLen, "TEST PRODUCT");
    return 1;

    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;
    }
    }
    }
    return 0;
    }
    return 0;
    }

    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 };

    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;
    };


    class Wasapi {
    public:
    using RefillCallbackFunc = std::function<bool(float*, uint32_t, const WAVEFORMATEX*)>;

    Wasapi() {}
    ~Wasapi() { close(); }

    void open(RefillCallbackFunc refillCallbackFunc, int hnsBufferDuration = 30 * 10000) {
    HRESULT hr;
    close();

    this->refillCallbackFunc = refillCallbackFunc;

    hClose = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
    hRefillEvent = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);

    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));

    audioClient->GetMixFormat(&mixFormat);
    if(mixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
    mixFormatEx = reinterpret_cast<WAVEFORMATEXTENSIBLE*>(mixFormat);
    }

    hr = audioClient->Initialize(
    AUDCLNT_SHAREMODE_SHARED
    , AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST
    , hnsBufferDuration
    , 0
    , mixFormat
    , nullptr
    );
    assert(SUCCEEDED(hr));

    hr = audioClient->GetService(
    __uuidof(IAudioRenderClient)
    , reinterpret_cast<void**>(&audioRenderClient)
    );
    assert(SUCCEEDED(hr));

    hr = audioClient->GetBufferSize(&bufferFrameCount);
    assert(SUCCEEDED(hr));

    hr = audioClient->SetEventHandle(hRefillEvent);
    assert(SUCCEEDED(hr));

    // initial zero fill
    {
    BYTE* data = nullptr;
    hr = audioRenderClient->GetBuffer(bufferFrameCount, &data);
    assert(SUCCEEDED(hr));

    hr = audioRenderClient->ReleaseBuffer(bufferFrameCount, AUDCLNT_BUFFERFLAGS_SILENT);
    assert(SUCCEEDED(hr));
    }

    unsigned threadId = 0;
    hThread = reinterpret_cast<HANDLE>(_beginthreadex(
    nullptr, 0, threadFunc_static, reinterpret_cast<void*>(this), 0, &threadId
    ));

    hr = audioClient->Start();
    assert(SUCCEEDED(hr));
    }

    void close() {
    if(hClose) {
    SetEvent(hClose);
    if(hThread) {
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    hThread = nullptr;
    }
    CloseHandle(hClose);
    hClose = nullptr;
    }

    if(hRefillEvent) {
    CloseHandle(hRefillEvent);
    hRefillEvent = nullptr;
    }

    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; }
    }

    static unsigned WINAPI threadFunc_static(void* arg) {
    auto* that = reinterpret_cast<Wasapi*>(arg);
    CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    that->threadFunc();
    CoUninitialize();
    return 0;
    }

    void threadFunc() {
    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
    run = false;
    break;
    case WAIT_OBJECT_0 + 1: // hRefillEvent
    onRefill();
    break;
    }
    }
    }

    void onRefill() {
    UINT32 paddingFrameCount = 0;
    audioClient->GetCurrentPadding(&paddingFrameCount);

    const auto availableFrameCount = bufferFrameCount - paddingFrameCount;

    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;
    }
    audioRenderClient->ReleaseBuffer(availableFrameCount, dwReleaseFlags);
    }

    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 {};
    };


    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
    });

    if(!vstPlugin->flagsIsSynth()) {
    MessageBox(nullptr, L"Specified DLL is not VST Synth", dllFilename.c_str(), MB_ICONSTOP);
    return;
    }

    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);

    struct Key {
    Key(int vk, int midiNote) : vk{vk}, midiNote{midiNote} {}
    int vk { 0 };
    int midiNote { 0 };
    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);
    }

    for(bool run = true; run; Sleep(10)) {
    MSG msg {};
    while(BOOL b = PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
    if(b == -1) {
    run = false;
    break;
    }
    TranslateMessage(&msg);
    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);
    }
    }
    }
    }


    int main() {
    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    std::wstring dllFilename;
    {
    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;
    GetOpenFileName(&ofn);

    dllFilename = fn;
    }

    if(! dllFilename.empty()) {
    mainLoop(dllFilename);
    }

    CoUninitialize();
    }