-
-
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; | |
} | |
} |
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 sendsinputBuffer
to VST. But it's always filled with0
so 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];
/**/ }
...
}
}
...
}
...
}
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)