-
-
Save t-mat/206e3e7dfc3f89421bc1 to your computer and use it in GitHub Desktop.
| // 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 | |
| // | |
| // 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 | |
| #define UNICODE | |
| #include <windows.h> | |
| #include <process.h> | |
| #include <mmdeviceapi.h> | |
| #include <audioclient.h> | |
| #include <algorithm> | |
| #include <atomic> | |
| #include <iostream> | |
| #include <map> | |
| #include <memory> | |
| #include <mutex> | |
| #include <string> | |
| #include <thread> | |
| #include <vector> | |
| #include <functional> | |
| #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, HWND hWndParent) { | |
| init(vstModulePath, hWndParent); | |
| } | |
| ~VstPlugin() { | |
| cleanup(); | |
| } | |
| 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; | |
| } | |
| bool getFlags(int32_t m) const { return (aEffect->flags & m) == m; } | |
| bool flagsHasEditor() const { return getFlags(effFlagsHasEditor); } | |
| bool flagsIsSynth() const { return getFlags(effFlagsIsSynth); } | |
| 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(editorHwnd) { | |
| 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 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() { | |
| vstMidiEvents.clear(); | |
| if(auto l = vstMidi.lock()) { | |
| std::swap(vstMidiEvents, vstMidi.events); | |
| } | |
| 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); | |
| } | |
| } | |
| // This function is called from refillCallback() which is running in audio thread. | |
| float** processAudio(size_t frameCount, size_t& outputFrameCount) { | |
| frameCount = std::min<size_t>(frameCount, outputBuffer.size() / getChannelCount()); | |
| aEffect->processReplacing(aEffect, inputBufferHeads.data(), outputBufferHeads.data(), frameCount); | |
| samplePos += frameCount; | |
| outputFrameCount = frameCount; | |
| return outputBufferHeads.data(); | |
| } | |
| private: | |
| 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; | |
| char mbBuf[_countof(buf) * 4] {}; | |
| if(auto s = WideCharToMultiByte(CP_OEMCP, 0, buf, -1, mbBuf, sizeof(mbBuf), 0, 0)) { | |
| directoryMultiByte = mbBuf; | |
| } | |
| } | |
| } | |
| hModule = LoadLibrary(vstModulePath); | |
| ASSERT_THROW(hModule, "Can't open VST DLL"); | |
| typedef AEffect* (VstEntryProc)(audioMasterCallback); | |
| auto* vstEntryProc = reinterpret_cast<VstEntryProc*>(GetProcAddress(hModule, "VSTPluginMain")); | |
| if(!vstEntryProc) { | |
| vstEntryProc = reinterpret_cast<VstEntryProc*>(GetProcAddress(hModule, "main")); | |
| } | |
| ASSERT_THROW(vstEntryProc, "VST's entry point not found"); | |
| aEffect = vstEntryProc(hostCallback_static); | |
| ASSERT_THROW(aEffect && aEffect->magic == kEffectMagic, "Not a VST plugin"); | |
| ASSERT_THROW(flagsIsSynth(), "Not a VST Synth"); | |
| aEffect->user = this; | |
| inputBuffer.resize(aEffect->numInputs * getBlockSize()); | |
| for(int i = 0; i < aEffect->numInputs; ++i) { | |
| inputBufferHeads.push_back(&inputBuffer[i * getBlockSize()]); | |
| } | |
| outputBuffer.resize(aEffect->numOutputs * getBlockSize()); | |
| for(int i = 0; i < aEffect->numOutputs; ++i) { | |
| outputBufferHeads.push_back(&outputBuffer[i * getBlockSize()]); | |
| } | |
| 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); | |
| } | |
| return true; | |
| } | |
| 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 | |
| ) { | |
| if(effect && effect->user) { | |
| auto* that = static_cast<VstPlugin*>(effect->user); | |
| return that->hostCallback(opcode, index, value, ptr, 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 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 getVendorVersion(); | |
| 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: | |
| timeinfo.flags = 0; | |
| timeinfo.samplePos = getSamplePos(); | |
| timeinfo.sampleRate = getSampleRate(); | |
| return reinterpret_cast<VstIntPtr>(&timeinfo); | |
| case audioMasterGetDirectory: | |
| return reinterpret_cast<VstIntPtr>(directoryMultiByte.c_str()); | |
| case audioMasterIdle: | |
| if(editorHwnd) { | |
| dispatcher(effEditIdle); | |
| } | |
| break; | |
| 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: | |
| for(const char** pp = getCapabilities(); *pp; ++pp) { | |
| if(strcmp(*pp, static_cast<const char*>(ptr)) == 0) { | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| protected: | |
| 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::vector<VstMidiEvent> vstMidiEvents; | |
| 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; | |
| }; | |
| struct Wasapi { | |
| using RefillFunc = std::function<bool(float*, uint32_t, const WAVEFORMATEX*)>; | |
| Wasapi(RefillFunc refillFunc, int hnsBufferDuration = 30 * 10000) { | |
| HRESULT hr = S_OK; | |
| hClose = CreateEventEx(0, 0, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); | |
| hRefillEvent = CreateEventEx(0, 0, 0, EVENT_MODIFY_STATE | SYNCHRONIZE); | |
| this->refillFunc = refillFunc; | |
| hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&mmDeviceEnumerator)); | |
| ASSERT_THROW(SUCCEEDED(hr), "CoCreateInstance(MMDeviceEnumerator) failed"); | |
| hr = mmDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &mmDevice); | |
| ASSERT_THROW(SUCCEEDED(hr), "mmDeviceEnumerator->GetDefaultAudioEndpoint() failed"); | |
| hr = mmDevice->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, 0, reinterpret_cast<void**>(&audioClient)); | |
| ASSERT_THROW(SUCCEEDED(hr), "mmDevice->Activate() failed"); | |
| audioClient->GetMixFormat(&mixFormat); | |
| hr = audioClient->Initialize( | |
| AUDCLNT_SHAREMODE_SHARED | |
| , AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST | |
| , hnsBufferDuration | |
| , 0 | |
| , mixFormat | |
| , nullptr | |
| ); | |
| ASSERT_THROW(SUCCEEDED(hr), "audioClient->Initialize() failed"); | |
| hr = audioClient->GetService(__uuidof(IAudioRenderClient), reinterpret_cast<void**>(&audioRenderClient)); | |
| ASSERT_THROW(SUCCEEDED(hr), "audioClient->GetService(IAudioRenderClient) failed"); | |
| hr = audioClient->GetBufferSize(&bufferFrameCount); | |
| ASSERT_THROW(SUCCEEDED(hr), "audioClient->GetBufferSize() failed"); | |
| hr = audioClient->SetEventHandle(hRefillEvent); | |
| ASSERT_THROW(SUCCEEDED(hr), "audioClient->SetEventHandle() failed"); | |
| BYTE* data = nullptr; | |
| hr = audioRenderClient->GetBuffer(bufferFrameCount, &data); | |
| ASSERT_THROW(SUCCEEDED(hr), "audioRenderClient->GetBuffer() failed"); | |
| hr = audioRenderClient->ReleaseBuffer(bufferFrameCount, AUDCLNT_BUFFERFLAGS_SILENT); | |
| ASSERT_THROW(SUCCEEDED(hr), "audioRenderClient->ReleaseBuffer() failed"); | |
| unsigned threadId = 0; | |
| hThread = reinterpret_cast<HANDLE>(_beginthreadex(0, 0, threadFunc_static, reinterpret_cast<void*>(this), 0, &threadId)); | |
| hr = audioClient->Start(); | |
| ASSERT_THROW(SUCCEEDED(hr), "audioClient->Start() failed"); | |
| } | |
| ~Wasapi() { | |
| if(hClose) { | |
| SetEvent(hClose); | |
| if(hThread) { | |
| WaitForSingleObject(hThread, INFINITE); | |
| } | |
| } | |
| CLOSE_HANDLE(hThread); | |
| CLOSE_HANDLE(hClose); | |
| CLOSE_HANDLE(hRefillEvent); | |
| if(mixFormat) { | |
| CoTaskMemFree(mixFormat); | |
| mixFormat = nullptr; | |
| } | |
| RELEASE(audioRenderClient); | |
| RELEASE(audioClient); | |
| RELEASE(mmDevice); | |
| RELEASE(mmDeviceEnumerator); | |
| } | |
| private: | |
| static unsigned __stdcall threadFunc_static(void* arg) { | |
| return reinterpret_cast<Wasapi*>(arg)->threadFunc(); | |
| } | |
| unsigned threadFunc() { | |
| ComInit comInit {}; | |
| const HANDLE events[2] = { hClose, hRefillEvent }; | |
| for(bool run = true; run; ) { | |
| const auto r = WaitForMultipleObjects(_countof(events), events, FALSE, INFINITE); | |
| if(WAIT_OBJECT_0 == r) { // hClose | |
| run = false; | |
| } else if(WAIT_OBJECT_0+1 == r) { // hRefillEvent | |
| UINT32 c = 0; | |
| audioClient->GetCurrentPadding(&c); | |
| const auto a = bufferFrameCount - c; | |
| float* data = nullptr; | |
| audioRenderClient->GetBuffer(a, reinterpret_cast<BYTE**>(&data)); | |
| const auto r = refillFunc(data, a, mixFormat); | |
| audioRenderClient->ReleaseBuffer(a, r ? 0 : AUDCLNT_BUFFERFLAGS_SILENT); | |
| } | |
| } | |
| return 0; | |
| } | |
| HANDLE hThread { nullptr }; | |
| IMMDeviceEnumerator* mmDeviceEnumerator { nullptr }; | |
| IMMDevice* mmDevice { nullptr }; | |
| IAudioClient* audioClient { nullptr }; | |
| IAudioRenderClient* audioRenderClient { nullptr }; | |
| WAVEFORMATEX* mixFormat { nullptr }; | |
| HANDLE hRefillEvent { nullptr }; | |
| HANDLE hClose { nullptr }; | |
| UINT32 bufferFrameCount { 0 }; | |
| RefillFunc refillFunc {}; | |
| }; | |
| // 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]; | |
| } | |
| } | |
| availableFrameCount -= nFrame; | |
| ofs += nFrame * nDstChannels; | |
| } | |
| return true; | |
| } | |
| 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 midiNote) : midiNote { midiNote } {} | |
| int midiNote {}; | |
| bool status { false }; | |
| }; | |
| 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; WaitMessage()) { | |
| MSG msg {}; | |
| while(BOOL b = PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { | |
| if(b == -1) { | |
| run = false; | |
| break; | |
| } | |
| TranslateMessage(&msg); | |
| DispatchMessage(&msg); | |
| } | |
| 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() { | |
| ComInit comInit {}; | |
| const auto dllFilename = []() -> std::wstring { | |
| wchar_t fn[MAX_PATH+1] {}; | |
| OPENFILENAME ofn { sizeof(ofn) }; | |
| 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; | |
| } (); | |
| try { | |
| mainLoop(dllFilename); | |
| } catch(std::exception &e) { | |
| std::cout << "Exception : " << e.what() << std::endl; | |
| } | |
| } |
Hello
I can't compile it, it brings me an error:
minimal_vst2x_host.cpp|380|error: invalid user-defined conversion from 'const Wasapi::Wasapi(Wasapi::RefillFunc, int)::<lambda(void*)>' to 'unsigned int (__attribute__((stdcall)) *)(void*)' [-fpermissive]|
Any solution?
Hi, thanks for the report.
I suppose you're using g++ (mingw).
If so, I've updated this gist to fix the issue. Now you can compile this code with clang++ or g++.
$ g++ minimal_vst2x_host.cpp -lcomdlg32 -lole32
$ clang++ minimal_vst2x_host.cpp -lcomdlg32 -lole32
I think stateless lambda to __stdcall conversion is a part of the C++ standard. And actually, if compile target is 64-bit Windows, both clang++ and g++ can compile this code without error. Of course it won't load any 32-bit VST2 instruments though.
Interestingly, when compile target is 32-bit Windows (-m32), both of clang++ and g++ say there's no conversion from stateless lambda to __stdcall as you've reported.
Since I don't want to know compiler's feeling, I just avoid to use stateless lambda. So, rewrinting the stateless lamda as a static function is the solution for this issue.
Thanks for your quick reply T-Mat,
I've tried it and it works. You are right, I use codeblocks with g++(mingw).
I just had to go to:
Project -> Build options -> Linker settings ->...and browse for the file libcomdlg32.a to add it.
And it works! Thank you very much!
BTW: Woul it be complicated to modify for using it with VST plugins? (not just VSTi)
Basically, it won't need much effort. You should implement the following functionalities:
- (A) MIDI event routing, if you want to use MIDI event generators such as arpeggiator.
- In
VstPlugin::processEvents(), send event to otherVstPlugin. You can useVstPlugin::sendMidiNote()to send the MIDI note event.
- In
- (B) Audio signal routing, if you want to use effectors such as reverb.
- In
VstPlugin::processAudio(), send audio to otherVstPlugin. You need to implement an analogue ofsendMidiNote()for audio. The new method writes audio toinputBuffer. Note thatprocessAudio()already sendsinputBufferto VST. But it's always filled with0so far.
- In
To keep the code simple, I highly recommend to think about and implement serial and fixed (not flexible) network. For example,
MIDI:
Input (Keyboard) --(sendMidiNote())--> Arpeggiator --(A)--> VSTi
Audio:
VSTi --(B)--> Reverb --(refillCallback())--> Output (WASAPI)
If your network is serial, refillCallback() just needs to know the last VST plugin which process the audio.
I will keep your recommendations in mind.
I am just beginning to try to host VST plugins in my audio C++ program (I use portaudio and ASIO drivers)...
Thanks T-Mat, you are very helpful!
Very nice VST host!
I'm trying to implement this in my C++ program, but I can't open two or more VSTs at the same time. The for loop with "PeekMessage" is necessary for the plugin window to work, so I only get one at a time.
Any solution?
@Israel-7, since I don't know anything about your program, I recommend you to play with this code before (re-) implement your own.
I can say that this example is able to extend to support multiple VSTs with the following modification:
/**/ indicates modified or added line.
(1) main() : Add second VST
int main() {
ComInit comInit {};
/**/const auto dllFilename0 = []() -> std::wstring {
wchar_t fn[MAX_PATH+1] {};
OPENFILENAME ofn { sizeof(ofn) };
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 (#0)";
ofn.Flags = OFN_FILEMUSTEXIST | OFN_ENABLESIZING;
GetOpenFileName(&ofn);
return fn;
} ();
/**/const auto dllFilename1 = []() -> std::wstring {
/**/ wchar_t fn[MAX_PATH+1] {};
/**/ OPENFILENAME ofn { sizeof(ofn) };
/**/ 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 (#1)";
/**/ ofn.Flags = OFN_FILEMUSTEXIST | OFN_ENABLESIZING;
/**/ GetOpenFileName(&ofn);
/**/ return fn;
/**/} ();
try {
/**/ mainLoop(dllFilename0, dllFilename1);
} catch(std::exception &e) {
std::cout << "Exception : " << e.what() << std::endl;
}
}(2) mainLoop() : Add second VST
/**/
void mainLoop(const std::wstring& dllFilename0, const std::wstring& dllFilename1) {
/**/ VstPlugin vstPlugin0 { dllFilename0.c_str(), GetConsoleWindow() };
/**/ VstPlugin vstPlugin1 { dllFilename1.c_str(), GetConsoleWindow() };
/**/ Wasapi wasapi { [&vstPlugin0, &vstPlugin1](float* const data, uint32_t availableFrameCount, const WAVEFORMATEX* const mixFormat) {
/**/ refillCallback(vstPlugin1, data, availableFrameCount, mixFormat, false);
/**/ return refillCallback(vstPlugin0, data, availableFrameCount, mixFormat, true);
}};
...
for(bool run = true; run; WaitMessage()) {
MSG msg {};
...
for(auto& e : keyMap) {
auto& key = e.second;
const auto on = (GetKeyState(e.first) & 0x8000) != 0;
if(key.status != on) {
key.status = on;
/**/ vstPlugin0.sendMidiNote(0, key.midiNote, on, 100);
/**/ vstPlugin1.sendMidiNote(0, key.midiNote, on, 100);
}
}
}
}(3) refillCallback() : Add additive mode
/**/
bool refillCallback(VstPlugin& vstPlugin, float* const data, uint32_t availableFrameCount, const WAVEFORMATEX* const mixFormat, bool additive) {
...
while(availableFrameCount > 0) {
...
for(size_t iFrame = 0; iFrame < nFrame; ++iFrame) {
...
for(size_t iChannel = 0; iChannel < nDstChannels; ++iChannel) {
...
/**/ if(additive) {
/**/ *(data + ofs + wasapiWriteIndex) += vstOutput[vstOutputPage][vstOutputIndex];
/**/ } else {
/**/ *(data + ofs + wasapiWriteIndex) = vstOutput[vstOutputPage][vstOutputIndex];
/**/ }
...
}
}
...
}
...
}
Thank you for this project, can i have your contacts to ask some questions about WASAPI please!