-
-
Save TronicLabs/06a5ce4be3de1a09f92154bf430d3090 to your computer and use it in GitHub Desktop.
WIN32 : Minimal VST 2.x host in C++11.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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. | |
| // 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(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment