/* * DISTRHO Plugin Toolkit (DPT) * Copyright (C) 2012-2013 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * For a full copy of the license see the LGPL.txt file */ #ifdef DISTRHO_PLUGIN_TARGET_VST #include "DistrhoPluginInternal.h" #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.h" #endif #define VST_FORCE_DEPRECATED 0 #include // ------------------------------------------------- START_NAMESPACE_DISTRHO #if DISTRHO_PLUGIN_HAS_UI class UIVst { public: UIVst(audioMasterCallback audioMaster, AEffect* effect, PluginInternal* plugin, intptr_t winId) : m_audioMaster(audioMaster), m_effect(effect), m_plugin(plugin), ui(this, winId, setParameterCallback, setStateCallback, uiEditParameterCallback, uiSendNoteCallback, uiResizeCallback) { uint32_t paramCount = plugin->parameterCount(); if (paramCount > 0) { parameterChecks = new bool [paramCount]; parameterValues = new float [paramCount]; for (uint32_t i=0; i < paramCount; i++) { parameterChecks[i] = false; parameterValues[i] = 0.0f; } } else { parameterChecks = nullptr; parameterValues = nullptr; } #if DISTRHO_PLUGIN_WANT_PROGRAMS nextProgram = -1; #endif #if DISTRHO_PLUGIN_IS_SYNTH midiEventCount = 0; #endif } ~UIVst() { if (parameterChecks) delete[] parameterChecks; if (parameterValues) delete[] parameterValues; } // --------------------------------------------- void idle() { #if DISTRHO_PLUGIN_WANT_PROGRAMS if (nextProgram != -1) { ui.programChanged(nextProgram); nextProgram = -1; } #endif for (uint32_t i=0, count = m_plugin->parameterCount(); i < count; i++) { if (parameterChecks[i]) { parameterChecks[i] = false; ui.parameterChanged(i, parameterValues[i]); } } #if DISTRHO_PLUGIN_IS_SYNTH // TODO - notes #endif ui.idle(); } int16_t getWidth() { return ui.getWidth(); } int16_t getHeight() { return ui.getHeight(); } void setParameterValueFromPlugin(uint32_t index, float perValue) { parameterChecks[index] = true; parameterValues[index] = perValue; } #if DISTRHO_PLUGIN_WANT_PROGRAMS void setProgramFromPlugin(uint32_t index) { nextProgram = index; // set previous parameters invalid for (uint32_t i=0, count = m_plugin->parameterCount(); i < count; i++) parameterChecks[i] = false; } #endif // --------------------------------------------- protected: intptr_t hostCallback(int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { return m_audioMaster(m_effect, opcode, index, value, ptr, opt); } void setParameterValue(uint32_t index, float realValue) { const ParameterRanges* ranges = m_plugin->parameterRanges(index); float perValue = (realValue - ranges->min) / (ranges->max - ranges->min); m_plugin->setParameterValue(index, realValue); hostCallback(audioMasterAutomate, index, 0, nullptr, perValue); } #if DISTRHO_PLUGIN_WANT_STATE void setState(const char* key, const char* value) { m_plugin->setState(key, value); } #endif void uiEditParameter(uint32_t index, bool started) { if (started) hostCallback(audioMasterBeginEdit, index, 0, nullptr, 0.0f); else hostCallback(audioMasterEndEdit, index, 0, nullptr, 0.0f); } #if DISTRHO_PLUGIN_IS_SYNTH void uiSendNote(bool onOff, uint8_t channel, uint8_t note, uint8_t velocity) { // TODO } #endif void uiResize(unsigned int width, unsigned int height) { hostCallback(audioMasterSizeWindow, width, height, nullptr, 0.0f); } private: // Vst stuff audioMasterCallback const m_audioMaster; AEffect* const m_effect; PluginInternal* const m_plugin; // Plugin UI UIInternal ui; // Temporary data bool* parameterChecks; float* parameterValues; #if DISTRHO_PLUGIN_WANT_PROGRAMS int32_t nextProgram; #endif #if DISTRHO_PLUGIN_IS_SYNTH uint32_t midiEventCount; MidiEvent midiEvents[MAX_MIDI_EVENTS]; #endif // --------------------------------------------- // Callbacks static void setParameterCallback(void* ptr, uint32_t rindex, float value) { UIVst* _this_ = (UIVst*)ptr; assert(_this_); _this_->setParameterValue(rindex, value); } static void setStateCallback(void* ptr, const char* key, const char* value) { #if DISTRHO_PLUGIN_WANT_STATE UIVst* _this_ = (UIVst*)ptr; assert(_this_); _this_->setState(key, value); #else // unused (void)ptr; (void)key; (void)value; #endif } static void uiEditParameterCallback(void* ptr, uint32_t index, bool started) { UIVst* _this_ = (UIVst*)ptr; assert(_this_); _this_->uiEditParameter(index, started); } static void uiSendNoteCallback(void* ptr, bool onOff, uint8_t channel, uint8_t note, uint8_t velocity) { #if DISTRHO_PLUGIN_IS_SYNTH UIVst* _this_ = (UIVst*)ptr; assert(_this_); _this_->uiSendNote(onOff, channel, note, velocity); #else // unused (void)ptr; (void)onOff; (void)channel; (void)note; (void)velocity; #endif } static void uiResizeCallback(void* ptr, unsigned int width, unsigned int height) { UIVst* _this_ = (UIVst*)ptr; assert(_this_); _this_->uiResize(width, height); } }; #endif class PluginVst { public: PluginVst(audioMasterCallback audioMaster, AEffect* effect) : m_audioMaster(audioMaster), m_effect(effect) { #if DISTRHO_PLUGIN_HAS_UI vstui = nullptr; rect.top = 0; rect.left = 0; rect.bottom = 0; rect.right = 0; #endif #if DISTRHO_PLUGIN_WANT_PROGRAMS curProgram = -1; #endif #if DISTRHO_PLUGIN_IS_SYNTH midiEventCount = 0; #endif } ~PluginVst() { } intptr_t vst_dispatcher(int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { int32_t ret = 0; switch (opcode) { #if DISTRHO_PLUGIN_WANT_PROGRAMS case effSetProgram: if (value >= 0 && value < plugin.programCount()) { curProgram = value; plugin.setProgram(curProgram); if (vstui) vstui->setProgramFromPlugin(curProgram); ret = 1; } break; case effGetProgram: ret = curProgram; break; case effSetProgramName: break; case effGetProgramName: if (ptr && curProgram >= 0 && curProgram < (int32_t)plugin.programCount()) { strncpy((char*)ptr, plugin.programName(curProgram), kVstMaxProgNameLen); ret = 1; } break; #endif case effGetParamDisplay: if (ptr && index < (int32_t)plugin.parameterCount()) { snprintf((char*)ptr, kVstMaxParamStrLen, "%f", plugin.parameterValue(index)); ret = 1; } break; case effSetSampleRate: // should not happen break; case effSetBlockSize: plugin.setBufferSize(value, true); break; case effMainsChanged: if (value) plugin.activate(); else plugin.deactivate(); break; #if DISTRHO_PLUGIN_HAS_UI case effEditGetRect: if (rect.bottom == 0 && ! vstui) { // This is stupid, but some hosts want to know the UI size before creating it, // so we have to create a temporary UI here setLastUiSampleRate(d_lastSampleRate); UIInternal tempUI(nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr); rect.bottom = tempUI.getHeight(); rect.right = tempUI.getWidth(); } else { rect.bottom = vstui->getHeight(); rect.right = vstui->getWidth(); } *(ERect**)ptr = ▭ ret = 1; break; case effEditOpen: if (! vstui) { setLastUiSampleRate(d_lastSampleRate); vstui = new UIVst(m_audioMaster, m_effect, &plugin, (intptr_t)ptr); } #if DISTRHO_PLUGIN_WANT_PROGRAMS if (curProgram >= 0) vstui->setProgramFromPlugin(curProgram); #endif for (uint32_t i=0, count = plugin.parameterCount(); i < count; i++) vstui->setParameterValueFromPlugin(i, plugin.parameterValue(i)); ret = 1; break; case effEditClose: if (vstui) { delete vstui; vstui = nullptr; ret = 1; } break; case effEditIdle: if (vstui) vstui->idle(); break; #endif case effGetChunk: case effSetChunk: // TODO break; #if DISTRHO_PLUGIN_IS_SYNTH case effProcessEvents: if (ptr) { //VstEvents* events = (VstEvents*)ptr; // TODO } break; #endif case effCanBeAutomated: if (index < (int32_t)plugin.parameterCount()) { uint32_t hints = plugin.parameterHints(index); // must be automable, and not output if ((hints & PARAMETER_IS_AUTOMABLE) > 0 && (hints & PARAMETER_IS_OUTPUT) == 0) ret = 1; } break; case effCanDo: // TODO break; case effStartProcess: case effStopProcess: break; } return ret; // unused (void)opt; } float vst_getParameter(int32_t index) { const ParameterRanges* ranges = plugin.parameterRanges(index); float realValue = plugin.parameterValue(index); float perValue = (realValue - ranges->min) / (ranges->max - ranges->min); return perValue; } void vst_setParameter(int32_t index, float value) { const ParameterRanges* ranges = plugin.parameterRanges(index); float realValue = ranges->min + (ranges->max - ranges->min) * value; plugin.setParameterValue(index, realValue); if (vstui) vstui->setParameterValueFromPlugin(index, realValue); } void vst_processReplacing(float** inputs, float** outputs, int32_t sampleFrames) { #if DISTRHO_PLUGIN_IS_SYNTH plugin.run((const float**)inputs, outputs, sampleFrames, midiEventCount, midiEvents); // TODO - send notes to UI #else plugin.run((const float**)inputs, outputs, sampleFrames, 0, nullptr); #endif } // --------------------------------------------- private: // VST stuff audioMasterCallback const m_audioMaster; AEffect* const m_effect; // Plugin PluginInternal plugin; #if DISTRHO_PLUGIN_HAS_UI // UI UIVst* vstui; ERect rect; #endif #if DISTRHO_PLUGIN_WANT_PROGRAMS int32_t curProgram; #endif #if DISTRHO_PLUGIN_IS_SYNTH uint32_t midiEventCount; MidiEvent midiEvents[MAX_MIDI_EVENTS]; #endif }; // ------------------------------------------------- static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { // first internal init bool doInternalInit = (opcode == -1 && index == 0xdead && value == 0xf00d); if (doInternalInit) { // set valid but dummy values d_lastBufferSize = 512; d_lastSampleRate = 44100.0; } // Create dummy plugin to get data from static PluginInternal plugin; if (doInternalInit) { // unset d_lastBufferSize = 0; d_lastSampleRate = 0.0; *(PluginInternal**)ptr = &plugin; return 0; } // handle opcodes switch (opcode) { case effOpen: if (! effect->object) { audioMasterCallback audioMaster = (audioMasterCallback)effect->user; d_lastBufferSize = audioMaster(effect, audioMasterGetBlockSize, 0, 0, nullptr, 0.0f); d_lastSampleRate = audioMaster(effect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f); effect->object = new PluginVst(audioMaster, effect); return 1; } return 0; case effClose: if (effect->object) { delete (PluginVst*)effect->object; effect->object = nullptr; delete effect; return 1; } return 0; case effGetParamLabel: if (ptr && index < (int32_t)plugin.parameterCount()) { strncpy((char*)ptr, plugin.parameterUnit(index), kVstMaxParamStrLen); return 1; } return 0; case effGetParamName: if (ptr && index < (int32_t)plugin.parameterCount()) { strncpy((char*)ptr, plugin.parameterName(index), kVstMaxParamStrLen); return 1; } return 0; #if DISTRHO_PLUGIN_WANT_PROGRAMS case effGetProgramNameIndexed: if (ptr && index < (int32_t)plugin.programCount()) { strncpy((char*)ptr, plugin.programName(index), kVstMaxProgNameLen); return 1; } return 0; #endif case effGetPlugCategory: #if DISTRHO_PLUGIN_IS_SYNTH return kPlugCategSynth; #else return kPlugCategUnknown; #endif case effGetEffectName: if (ptr) { strncpy((char*)ptr, plugin.name(), kVstMaxProductStrLen); return 1; } return 0; case effGetVendorString: if (ptr) { strncpy((char*)ptr, plugin.maker(), kVstMaxVendorStrLen); return 1; } return 0; case effGetProductString: if (ptr) { strncpy((char*)ptr, plugin.label(), kVstMaxEffectNameLen); return 1; } return 0; case effGetVendorVersion: return plugin.version(); case effGetVstVersion: return kVstVersion; }; if (effect->object) { PluginVst* _this_ = (PluginVst*)effect->object; return _this_->vst_dispatcher(opcode, index, value, ptr, opt); } return 0; } static float vst_getParameterCallback(AEffect* effect, int32_t index) { PluginVst* _this_ = (PluginVst*)effect->object; assert(_this_); if (_this_) return _this_->vst_getParameter(index); return 0.0f; } static void vst_setParameterCallback(AEffect* effect, int32_t index, float value) { PluginVst* _this_ = (PluginVst*)effect->object; assert(_this_); if (_this_) _this_->vst_setParameter(index, value); } static void vst_processCallback(AEffect* effect, float** inputs, float** outputs, int32_t sampleFrames) { PluginVst* _this_ = (PluginVst*)effect->object; assert(_this_); if (_this_) _this_->vst_processReplacing(inputs, outputs, sampleFrames); } static void vst_processReplacingCallback(AEffect* effect, float** inputs, float** outputs, int32_t sampleFrames) { PluginVst* _this_ = (PluginVst*)effect->object; assert(_this_); if (_this_) _this_->vst_processReplacing(inputs, outputs, sampleFrames); } END_NAMESPACE_DISTRHO // ------------------------------------------------- DISTRHO_PLUGIN_EXPORT const AEffect* VSTPluginMain(audioMasterCallback audioMaster) { USE_NAMESPACE_DISTRHO // old version if (! audioMaster(nullptr, audioMasterVersion, 0, 0, nullptr, 0.0f)) return nullptr; PluginInternal* plugin = nullptr; vst_dispatcherCallback(nullptr, -1, 0xdead, 0xf00d, &plugin, 0.0f); AEffect* effect = new AEffect; memset(effect, 0, sizeof(AEffect)); // vst fields effect->magic = kEffectMagic; effect->uniqueID = plugin->uniqueId(); effect->version = plugin->version(); // plugin fields effect->numParams = plugin->parameterCount(); #if DISTRHO_PLUGIN_WANT_PROGRAMS effect->numPrograms = plugin->programCount(); #endif effect->numInputs = DISTRHO_PLUGIN_NUM_INPUTS; effect->numOutputs = DISTRHO_PLUGIN_NUM_OUTPUTS; // static calls effect->dispatcher = vst_dispatcherCallback; effect->process = vst_processCallback; effect->getParameter = vst_getParameterCallback; effect->setParameter = vst_setParameterCallback; effect->processReplacing = vst_processReplacingCallback; effect->processDoubleReplacing = nullptr; // plugin flags effect->flags |= effFlagsCanReplacing; #if DISTRHO_PLUGIN_HAS_UI # ifdef DISTRHO_UI_QT4 if (QApplication::instance()) # endif effect->flags |= effFlagsHasEditor; #endif #if DISTRHO_PLUGIN_WANT_STATE effect->flags |= effFlagsProgramChunks; #endif // pointers effect->object = nullptr; effect->user = (void*)audioMaster; return effect; } // ------------------------------------------------- #endif // DISTRHO_PLUGIN_TARGET_VST