#ifdef USE_VST2 /// vst2_main.cpp /// /// (c) 2018 bsp. very loosely based on pongasoft's "hello, world" example plugin. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// created: 25Jun2018 /// changed: 26Jun2018, 27Jun2018, 29Jun2018, 01Jul2018, 02Jul2018, 06Jul2018, 13Jul2018 /// 26Jul2018, 04Aug2018, 05Aug2018, 06Aug2018, 07Aug2018, 09Aug2018, 11Aug2018 /// 18Aug2018, 19Aug2018 /// /// // #define DEBUG_PRINT_EVENTS defined // #define DEBUG_PRINT_PARAMS defined #define NUM_INPUTS ( 8) // must match AudioInterface.cpp:AUDIO_INPUTS #define NUM_OUTPUTS ( 8) // must match AudioInterface.cpp:AUDIO_OUTPUTS // (note) causes reason to shut down when console is freed (when plugin is deleted) // #define USE_CONSOLE defined #undef RACK_HOST #define Dprintf if(0);else printf // #define Dprintf if(1);else printf // #define Dprintf_idle if(0);else printf #define Dprintf_idle if(1);else printf #include #include #include #include "../dep/yac/yac.h" #include "../dep/yac/yac_host.cpp" YAC_Host *yac_host; // not actually used, just to satisfy the linker #include "global_pre.hpp" #include "global.hpp" #include "global_ui.hpp" #define EDITWIN_X 0 #define EDITWIN_Y 0 #define EDITWIN_W 1200 #define EDITWIN_H 800 #define Dfltequal(a, b) ( (((a)-(b)) < 0.0f) ? (((a)-(b)) > -0.0001f) : (((a)-(b)) < 0.0001f) ) typedef union cmemptr_u { const sUI *u32; const sF32 *f32; const void *any; } cmemptr_t; typedef union mem_u { sUI u32; sF32 f32; } mem_t; extern int vst2_init (int argc, char* argv[]); extern void vst2_exit (void); namespace rack { extern void vst2_editor_redraw (void); } extern void vst2_set_samplerate (sF32 _rate); extern void vst2_engine_process (float *const*_in, float **_out, unsigned int _numFrames); extern void vst2_process_midi_input_event (sU8 _a, sU8 _b, sU8 _c); extern void vst2_queue_param (int uniqueParamId, float normValue); extern void vst2_handle_queued_params (void); extern float vst2_get_param (int uniqueParamId); extern void vst2_get_param_name (int uniqueParamId, char *s, int sMaxLen); extern void vst2_set_shared_plugin_tls_globals (void); extern "C" extern int vst2_handle_effeditkeydown (unsigned int _vkey); namespace rack { extern bool b_touchkeyboard_enable; extern void settingsLoad(std::string filename, bool bWindowSizeOnly); } #include "../include/window.hpp" #include "../dep/include/osdialog.h" #include "../include/app.hpp" #include // using namespace rack; // extern void rack::windowRun(void); #if defined(_WIN32) || defined(_WIN64) #define HAVE_WINDOWS defined #define WIN32_LEAN_AND_MEAN defined #include #include EXTERN_C IMAGE_DOS_HEADER __ImageBase; // Windows: #define VST_EXPORT extern "C" __declspec(dllexport) struct PluginMutex { CRITICAL_SECTION handle; PluginMutex(void) { ::InitializeCriticalSection( &handle ); } ~PluginMutex() { ::DeleteCriticalSection( &handle ); } void lock(void) { ::EnterCriticalSection(&handle); } void unlock(void) { ::LeaveCriticalSection(&handle); } }; #else // MacOSX, Linux: #define HAVE_UNIX defined #define VST_EXPORT extern #include #include #include #include #include //static pthread_mutex_t loc_pthread_mutex_t_init = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; static pthread_mutex_t loc_pthread_mutex_t_init = PTHREAD_MUTEX_INITIALIZER; struct PluginMutex { pthread_mutex_t handle; PluginMutex(void) { ::memcpy((void*)&handle, (const void*)&loc_pthread_mutex_t_init, sizeof(pthread_mutex_t)); } ~PluginMutex() { } void lock(void) { ::pthread_mutex_lock(&handle); } void unlock(void) { ::pthread_mutex_unlock(&handle); } }; #endif // _WIN32||_WIN64 // // extern "C" { // // extern void glfwSetInstance(void *_glfw); // // } class PluginString : public YAC_String { public: static const sUI QUOT2 =(sUI)(1<<26); // \'\' static const sUI STRFLQMASK = (QUOT | UTAG1 | QUOT2); void safeFreeChars (void); sSI _realloc (sSI _numChars); sSI lastIndexOf (sChar _c, sUI _start) const; void getDirName (PluginString *_r) const; void replace (sChar _c, sChar _o); }; void PluginString::safeFreeChars(void) { if(bflags & PluginString::DEL) { // if(!(bflags & PluginString::LA)) { Dyacfreechars(chars); } } } sSI PluginString::_realloc(sSI _numBytes) { // Force alloc if a very big string is about to shrink a lot or there is simply not enough space available if( ((buflen >= 1024) && ( (((sUI)_numBytes)<<3) < buflen )) || (NULL == chars) || (buflen < ((sUI)_numBytes)) ) // xxx (!chars) hack added 180702 { if(NULL != chars) { sUI l = length; if(((sUI)_numBytes) < l) { l = _numBytes; } sU8 *nc = Dyacallocchars(_numBytes + 1); sUI i = 0; for(; i idxBackSlash) { idx = idxSlash; } else { idx = idxBackSlash; } if(idxDrive > idx) { idx = idxDrive; } if(-1 != idx) { _r->_realloc(idx + 2); _r->length = idx + 2; sSI i; for(i=0; i<=idx; i++) { _r->chars[i] = chars[i]; } _r->chars[i++] = 0; _r->key = YAC_LOSTKEY; } else { _r->empty(); } } #define MAX_FLOATARRAYALLOCSIZE (1024*1024*64) class PluginFloatArray : public YAC_FloatArray { public: sSI alloc (sSI _maxelements); }; sSI PluginFloatArray::alloc(sSI _max_elements) { if(((sUI)_max_elements)>MAX_FLOATARRAYALLOCSIZE) { printf("[---] FloatArray::insane array size (maxelements=%08x)\n", _max_elements); return 0; } if(own_data) { if(elements) { delete [] elements; elements = NULL; } } if(_max_elements) { elements = new(std::nothrow) sF32[_max_elements]; if(elements) { max_elements = _max_elements; num_elements = 0; own_data = 1; return 1; } } num_elements = 0; max_elements = 0; return 0; } /* * I find the naming a bit confusing so I decided to use more meaningful names instead. */ /** * The VSTHostCallback is a function pointer so that the plugin can communicate with the host (not used in this small example) */ typedef audioMasterCallback VSTHostCallback; /** * The VSTPlugin structure (AEffect) contains information about the plugin (like version, number of inputs, ...) and * callbacks so that the host can call the plugin to do its work. The primary callback will be `processReplacing` for * single precision (float) sample processing (or `processDoubleReplacing` for double precision (double)). */ typedef AEffect VSTPlugin; // Since the host is expecting a very specific API we need to make sure it has C linkage (not C++) extern "C" { /* * This is the main entry point to the VST plugin. * * The host (DAW like Maschine, Ableton Live, Reason, ...) will look for this function with this exact API. * * It is the equivalent to `int main(int argc, char *argv[])` for a C executable. * * @param vstHostCallback is a callback so that the plugin can communicate with the host (not used in this small example) * @return a pointer to the AEffect structure */ VST_EXPORT VSTPlugin *VSTPluginMain (VSTHostCallback vstHostCallback); // note this looks like this without the type aliases (and is obviously 100% equivalent) // extern AEffect *VSTPluginMain(audioMasterCallback audioMaster); } /* * Constant for the version of the plugin. For example 1100 for version 1.1.0.0 */ const VstInt32 PLUGIN_VERSION = 1000; /** * Encapsulates the plugin as a C++ class. It will keep both the host callback and the structure required by the * host (VSTPlugin). This class will be stored in the `VSTPlugin.object` field (circular reference) so that it can * be accessed when the host calls the plugin back (for example in `processDoubleReplacing`). */ class VSTPluginWrapper { public: static const uint32_t MIN_SAMPLE_RATE = 8192u; static const uint32_t MAX_SAMPLE_RATE = 384000u; static const uint32_t MIN_BLOCK_SIZE = 64u; static const uint32_t MAX_BLOCK_SIZE = 16384u; static const uint32_t MAX_OVERSAMPLE_FACTOR = 16u; static const uint32_t IDLE_DETECT_NONE = 0u; // always active static const uint32_t IDLE_DETECT_MIDI = 1u; // become idle when output is silence, reactivate when there's MIDI input activity static const uint32_t IDLE_DETECT_AUDIO = 2u; // become idle when output is silence, reactivate when there's audio input activity public: rack::Global rack_global; rack::GlobalUI rack_global_ui; protected: PluginString dllname; PluginString cwd; public: struct { float factor; // 1=no SR conversion, 2=oversample x2, 4=oversample x4, 0.5=undersample /2, .. int quality; // SPEEX_RESAMPLER_QUALITY_xxx float realtime_factor; // used during realtime rendering int realtime_quality; float offline_factor; // used during offline rendering (bounce) int offline_quality; // sUI num_in; // hack that limits oversampling to "n" input channels. default = NUM_INPUTS sUI num_out; // hack that limits oversampling to "n" input channels. default = NUM_OUTPUTS SpeexResamplerState *srs_in; SpeexResamplerState *srs_out; sF32 in_buffers[NUM_INPUTS * MAX_BLOCK_SIZE * MAX_OVERSAMPLE_FACTOR]; sF32 out_buffers[NUM_OUTPUTS * MAX_BLOCK_SIZE]; } oversample; public: float sample_rate; // e.g. 44100.0 protected: uint32_t block_size; // e.g. 64 PluginMutex mtx_audio; public: PluginMutex mtx_mididev; public: bool b_open; bool b_processing; // true=generate output, false=suspended bool b_offline; // true=offline rendering (HQ) bool b_check_offline; // true=ask host if it's in offline rendering mode sUI idle_detect_mode; sF32 idle_input_level_threshold; sF32 idle_output_level_threshold; sF32 idle_output_sec_threshold; sUI idle_output_framecount; bool b_idle; ERect editor_rect; sBool b_editor_open; char *last_program_chunk_str; static sSI instance_count; sSI instance_id; sF32 tmp_input_buffers[NUM_INPUTS * MAX_BLOCK_SIZE]; sUI redraw_ival_ms; // 0=use DAW timer (effEditIdle) public: VSTPluginWrapper(VSTHostCallback vstHostCallback, VstInt32 vendorUniqueID, VstInt32 vendorVersion, VstInt32 numParams, VstInt32 numPrograms, VstInt32 numInputs, VstInt32 numOutputs ); ~VSTPluginWrapper(); VSTPlugin *getVSTPlugin(void) { return &_vstPlugin; } void setGlobals(void) { rack::global = &rack_global; rack::global_ui = &rack_global_ui; } sSI openEffect(void) { Dprintf("xxx vstrack_plugin::openEffect\n"); // (todo) use mutex instance_id = instance_count; Dprintf("xxx vstrack_plugin::openEffect: instance_id=%d\n", instance_id); rack_global.vst2.wrapper = this; #ifdef USE_CONSOLE AllocConsole(); freopen("CON", "w", stdout); freopen("CON", "w", stderr); freopen("CON", "r", stdin); // Note: "r", not "w". #endif // USE_CONSOLE setGlobals(); rack_global.init(); rack_global_ui.init(); rack::global->vst2.last_seen_instance_count = instance_count; char oldCWD[1024]; char dllnameraw[1024]; ::GetCurrentDirectory(1024, (LPSTR) oldCWD); // ::GetModuleFileNameA(NULL, dllnameraw, 1024); // returns executable name (not the dll pathname) GetModuleFileNameA((HINSTANCE)&__ImageBase, dllnameraw, 1024); dllname.visit(dllnameraw); dllname.getDirName(&cwd); rack::global->vst2.program_dir = (const char*)cwd.chars; Dprintf("xxx vstrack_plugin::openEffect: cd to \"%s\"\n", (const char*)cwd.chars); // // ::SetCurrentDirectory("f:/vst_64bit/vstrack_plugin"); ::SetCurrentDirectory((const char*)cwd.chars); Dprintf("xxx vstrack_plugin::openEffect: cwd change done\n"); // cwd.replace('\\', '/'); int argc = 1; char *argv[1]; //argv[0] = (char*)cwd.chars; argv[0] = (char*)dllnameraw; Dprintf("xxx vstrack_plugin::openEffect: dllname=\"%s\"\n", argv[0]); (void)vst2_init(argc, argv); Dprintf("xxx vstrack_plugin::openEffect: vst2_init() done\n"); vst2_set_shared_plugin_tls_globals(); Dprintf("xxx vstrack_plugin::openEffect: restore cwd=\"%s\"\n", oldCWD); ::SetCurrentDirectory(oldCWD); setSampleRate(sample_rate); b_open = true; b_editor_open = false; Dprintf("xxx vstrack_plugin::openEffect: LEAVE\n"); return 1; } void setWindowSize(int _width, int _height) { if(_width < 640) _width = 640; if(_height < 480) _height = 480; editor_rect.right = EDITWIN_X + _width; editor_rect.bottom = EDITWIN_Y + _height; } void setRefreshRate(float _hz) { if(_hz < 15.0f) redraw_ival_ms = 0u; else redraw_ival_ms = sUI(1000.0f / _hz); } void destroyResamplerStates(void) { if(NULL != oversample.srs_in) { speex_resampler_destroy(oversample.srs_in); oversample.srs_in = NULL; } if(NULL != oversample.srs_out) { speex_resampler_destroy(oversample.srs_out); oversample.srs_out = NULL; } } void openEditor(void *_hwnd) { Dprintf("xxx vstrack_plugin: openEditor() parentHWND=%p\n", _hwnd); setGlobals(); (void)lglw_window_open(rack_global_ui.window.lglw, _hwnd, 0/*x*/, 0/*y*/, (editor_rect.right - editor_rect.left), (editor_rect.bottom - editor_rect.top) ); if(0u != redraw_ival_ms) { lglw_timer_start(rack_global_ui.window.lglw, redraw_ival_ms); } b_editor_open = true; } void closeEditor(void) { Dprintf("xxx vstrack_plugin: closeEditor() b_editor_open=%d\n", b_editor_open); if(b_editor_open) { setGlobals(); lglw_timer_stop(rack_global_ui.window.lglw); lglw_window_close(rack_global_ui.window.lglw); b_editor_open = false; } } void closeEffect(void) { closeEditor(); // (todo) use mutex Dprintf("xxx vstrack_plugin::closeEffect: last_program_chunk_str=%p\n", last_program_chunk_str); if(NULL != last_program_chunk_str) { ::free(last_program_chunk_str); last_program_chunk_str = NULL; } Dprintf("xxx vstrack_plugin::closeEffect: b_open=%d\n", b_open); if(b_open) { b_open = false; setGlobals(); vst2_set_shared_plugin_tls_globals(); rack::global->vst2.last_seen_instance_count = instance_count; Dprintf("xxx vstrack_plugin: call vst2_exit()\n"); vst2_exit(); Dprintf("xxx vstrack_plugin: vst2_exit() done\n"); destroyResamplerStates(); Dprintf("xxx vstrack_plugin: destroyResamplerStates() done\n"); #ifdef USE_CONSOLE // FreeConsole(); #endif // USE_CONSOLE } } void lockAudio(void) { mtx_audio.lock(); } void unlockAudio(void) { mtx_audio.unlock(); } VstInt32 getNumInputs(void) const { return _vstPlugin.numInputs; } VstInt32 getNumOutputs(void) const { return _vstPlugin.numOutputs; } void setOversample(float _factor, int _quality, bool _bLock = true) { oversample.factor = _factor; oversample.quality = _quality; setSampleRate(sample_rate, _bLock); } void setOversampleRealtime(float _factor, int _quality) { if(_factor < 0.0f) _factor = oversample.realtime_factor; // keep if(_quality < 0) _quality = oversample.realtime_quality; // keep if(_factor < 0.001f) _factor = 1.0f; else if(_factor > float(MAX_OVERSAMPLE_FACTOR)) _factor = float(MAX_OVERSAMPLE_FACTOR); if(_quality < SPEEX_RESAMPLER_QUALITY_MIN/*0*/) _quality = SPEEX_RESAMPLER_QUALITY_MIN; else if(_quality > SPEEX_RESAMPLER_QUALITY_MAX/*10*/) _quality = SPEEX_RESAMPLER_QUALITY_MAX; oversample.realtime_factor = _factor; oversample.realtime_quality = _quality; if(!b_offline) { setOversample(oversample.realtime_factor, oversample.realtime_quality); } } void setOversampleOffline(float _factor, int _quality) { if(_factor < 0.0f) _factor = oversample.offline_factor; // keep if(_quality < 0) _quality = oversample.offline_quality; // keep if(_factor < 0.001f) _factor = 1.0f; else if(_factor > float(MAX_OVERSAMPLE_FACTOR)) _factor = float(MAX_OVERSAMPLE_FACTOR); if(_quality < SPEEX_RESAMPLER_QUALITY_MIN/*0*/) _quality = SPEEX_RESAMPLER_QUALITY_MIN; else if(_quality > SPEEX_RESAMPLER_QUALITY_MAX/*10*/) _quality = SPEEX_RESAMPLER_QUALITY_MAX; oversample.offline_factor = _factor; oversample.offline_quality = _quality; if(b_offline) { setOversample(oversample.offline_factor, oversample.offline_quality); } } void setOversampleChannels(int _numIn, int _numOut) { if(_numIn < 0) _numIn = int(oversample.num_in); // keep if(_numOut < 0) _numOut = int(oversample.num_out); // keep if(_numIn < 0) _numIn = 0; else if(_numIn > NUM_INPUTS) _numIn = NUM_INPUTS; if(_numOut < 1) _numOut = 1; else if(_numOut > NUM_OUTPUTS) _numOut = NUM_OUTPUTS; lockAudio(); oversample.num_in = sUI(_numIn); oversample.num_out = sUI(_numOut); unlockAudio(); } bool setSampleRate(float _rate, bool _bLock = true) { bool r = false; if((_rate >= float(MIN_SAMPLE_RATE)) && (_rate <= float(MAX_SAMPLE_RATE))) { if(_bLock) { setGlobals(); lockAudio(); } sample_rate = _rate; vst2_set_samplerate(sample_rate * oversample.factor); // see engine.cpp destroyResamplerStates(); // Lazy-alloc resampler state if(!Dfltequal(oversample.factor, 1.0f)) { int err; oversample.srs_in = speex_resampler_init(NUM_INPUTS, sUI(sample_rate), // in rate sUI(sample_rate * oversample.factor), // out rate oversample.quality, &err ); oversample.srs_out = speex_resampler_init(NUM_OUTPUTS, sUI(sample_rate * oversample.factor), // in rate sUI(sample_rate), // out rate oversample.quality, &err ); Dprintf("xxx vstrack_plugin: initialize speex resampler (rate=%f factor=%f quality=%d)\n", sample_rate, oversample.factor, oversample.quality); } if(_bLock) { unlockAudio(); } r = true; } return r; } bool setBlockSize(uint32_t _blockSize) { bool r = false; if((_blockSize >= MIN_BLOCK_SIZE) && (_blockSize <= MAX_BLOCK_SIZE)) { lockAudio(); block_size = _blockSize; unlockAudio(); r = true; } return r; } void setEnableProcessingActive(bool _bEnable) { lockAudio(); b_processing = _bEnable; unlockAudio(); } void checkOffline(void) { // Called by VSTPluginProcessReplacingFloat32() if(b_check_offline) { if(NULL != _vstHostCallback) { int level = (int)_vstHostCallback(&_vstPlugin, audioMasterGetCurrentProcessLevel, 0, 0/*value*/, NULL/*ptr*/, 0.0f/*opt*/); // (note) Reason sets process level to kVstProcessLevelUser during "bounce in place" bool bOffline = (kVstProcessLevelOffline == level) || (kVstProcessLevelUser == level); #if 0 { static int i = 0; if(0 == (++i & 127)) { Dprintf("xxx vstrack_plugin: audioMasterGetCurrentProcessLevel: level=%d\n", level); } } #endif if(b_offline ^ bOffline) { // Offline mode changed, update resampler b_offline = bOffline; if(bOffline) { Dprintf("xxx vstrack_plugin: enter OFFLINE mode. factor=%f quality=%d\n", oversample.offline_factor, oversample.offline_quality); setOversample(oversample.offline_factor, oversample.offline_quality, false/*bLock*/); } else { Dprintf("xxx vstrack_plugin: enter REALTIME mode. factor=%f quality=%d\n", oversample.realtime_factor, oversample.realtime_quality); setOversample(oversample.realtime_factor, oversample.realtime_quality, false/*bLock*/); } Dprintf("xxx vstrack_plugin: mode changed to %d\n", int(bOffline)); } } } } sUI getBankChunk(uint8_t **_addr) { return 0; } void setIdleDetectMode(uint32_t _mode) { switch(_mode) { default: case IDLE_DETECT_NONE: idle_detect_mode = IDLE_DETECT_NONE; break; case IDLE_DETECT_MIDI: idle_detect_mode = IDLE_DETECT_MIDI; break; case IDLE_DETECT_AUDIO: idle_detect_mode = IDLE_DETECT_AUDIO; break; } b_idle = false; idle_output_framecount = 0u; } sUI getProgramChunk(uint8_t**_addr) { setGlobals(); if(NULL != last_program_chunk_str) { ::free(last_program_chunk_str); } last_program_chunk_str = rack::global_ui->app.gRackWidget->savePatchToString(); if(NULL != last_program_chunk_str) { *_addr = (uint8_t*)last_program_chunk_str; return (sUI)strlen(last_program_chunk_str) + 1/*ASCIIZ*/; } return 0; } bool setBankChunk(size_t _size, uint8_t *_addr) { bool r = false; return r; } bool setProgramChunk(size_t _size, uint8_t *_addr) { setGlobals(); lockAudio(); #if 0 Dprintf("xxx vstrack_plugin:setProgramChunk: size=%u\n", _size); #endif lglw_glcontext_push(rack::global_ui->window.lglw); bool r = rack::global_ui->app.gRackWidget->loadPatchFromString((const char*)_addr); rack::global_ui->ui.gScene->step(); // w/o this the patch is bypassed lglw_glcontext_pop(rack::global_ui->window.lglw); Dprintf("xxx vstrack_plugin:setProgramChunk: r=%d\n", r); unlockAudio(); return r; } #ifdef HAVE_WINDOWS void sleepMillisecs(uint32_t _num) { ::Sleep((DWORD)_num); } #elif defined(HAVE_UNIX) void sleepMillisecs(uint32_t _num) { ::usleep(1000u * _num); } #endif const volatile float *getNextInputChannelChunk(void) { volatile float *r = NULL; return r; } volatile float *lockNextOutputChannelChunk(void) { volatile float *r = NULL; return r; } sBool getParameterProperties(sUI _index, struct VstParameterProperties *_ret) { sBool r = 0; ::memset((void*)_ret, 0, sizeof(struct VstParameterProperties)); if(_index < VST2_MAX_UNIQUE_PARAM_IDS) { _ret->stepFloat = 0.001f; _ret->smallStepFloat = 0.001f; _ret->largeStepFloat = 0.01f; _ret->flags = 0; _ret->displayIndex = VstInt16(_index); _ret->category = 0; // 0=no category _ret->numParametersInCategory = 0; vst2_get_param_name(_index, _ret->label, kVstMaxLabelLen); vst2_get_param_name(_index, _ret->shortLabel, kVstMaxShortLabelLen); r = 1; } return r; } void handleUIParam(int uniqueParamId, float normValue) { if(NULL != _vstHostCallback) (void)_vstHostCallback(&_vstPlugin, audioMasterAutomate, uniqueParamId, 0/*value*/, NULL/*ptr*/, normValue/*opt*/); } void getTimingInfo(int *_retPlaying, float *_retBPM, float *_retSongPosPPQ) { *_retPlaying = 0; if(NULL != _vstHostCallback) { VstIntPtr result = _vstHostCallback(&_vstPlugin, audioMasterGetTime, 0, 0/*value*/, NULL/*ptr*/, 0.0f/*opt*/); if(NULL != result) { const struct VstTimeInfo *timeInfo = (const struct VstTimeInfo *)result; *_retPlaying = (0 != (timeInfo->flags & kVstTransportPlaying)); if(0 != (timeInfo->flags & kVstTempoValid)) { *_retBPM = float(timeInfo->tempo); } if(0 != (timeInfo->flags & kVstPpqPosValid)) { *_retSongPosPPQ = (float)timeInfo->ppqPos; } } } } void queueRedraw(void) { setGlobals(); if(lglw_window_is_visible(rack::global_ui->window.lglw)) { lglw_redraw(rack::global_ui->window.lglw); } } void redraw(void) { setGlobals(); if(lglw_window_is_visible(rack::global_ui->window.lglw)) { vst2_set_shared_plugin_tls_globals(); // Save DAW GL context and bind our own lglw_glcontext_push(rack::global_ui->window.lglw); rack::vst2_editor_redraw(); // Restore the DAW's GL context lglw_glcontext_pop(rack::global_ui->window.lglw); } } private: // the host callback (a function pointer) VSTHostCallback _vstHostCallback; // the actual structure required by the host VSTPlugin _vstPlugin; }; sSI VSTPluginWrapper::instance_count = 0; /******************************************* * Callbacks: Host -> Plugin * * Defined here because they are used in the rest of the code later */ /** * This is the callback that will be called to process the samples in the case of single precision. This is where the * meat of the logic happens! * * @param vstPlugin the object returned by VSTPluginMain * @param inputs an array of array of input samples. You read from it. First dimension is for inputs, second dimension is for samples: inputs[numInputs][sampleFrames] * @param outputs an array of array of output samples. You write to it. First dimension is for outputs, second dimension is for samples: outputs[numOuputs][sampleFrames] * @param sampleFrames the number of samples (second dimension in both arrays) */ void VSTPluginProcessReplacingFloat32(VSTPlugin *vstPlugin, float **_inputs, float **_outputs, VstInt32 sampleFrames ) { if(sUI(sampleFrames) > VSTPluginWrapper::MAX_BLOCK_SIZE) return; // should not be reachable // we can get a hold to our C++ class since we stored it in the `object` field (see constructor) VSTPluginWrapper *wrapper = static_cast(vstPlugin->object); // Dprintf("xxx vstrack_plugin: VSTPluginProcessReplacingFloat32: ENTER\n"); wrapper->lockAudio(); wrapper->setGlobals(); vst2_set_shared_plugin_tls_globals(); if(wrapper->b_check_offline) { // Check if offline rendering state changed and update resampler when necessary wrapper->checkOffline(); } // // rack::global->engine.vipMutex.lock(); rack::global->engine.mutex.lock(); rack::global->vst2.last_seen_num_frames = sUI(sampleFrames); vst2_handle_queued_params(); //Dprintf("xxx vstrack_plugin: VSTPluginProcessReplacingFloat32: lockAudio done\n"); //Dprintf("xxx vstrack_plugin: VSTPluginProcessReplacingFloat32: wrapper=%p\n", wrapper); #ifdef HAVE_WINDOWS _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); #endif // HAVE_WINDOWS sUI chIdx; if(wrapper->b_idle) { switch(wrapper->idle_detect_mode) { default: case VSTPluginWrapper::IDLE_DETECT_NONE: // should not be reachable wrapper->b_idle = false; wrapper->idle_output_framecount = 0u; break; case VSTPluginWrapper::IDLE_DETECT_MIDI: break; case VSTPluginWrapper::IDLE_DETECT_AUDIO: { wrapper->b_idle = true; for(chIdx = 0u; chIdx < NUM_INPUTS; chIdx++) { if(chIdx < wrapper->oversample.num_in) { cmemptr_t s; s.f32 = _inputs[chIdx]; sF32 sum = 0.0f; for(sUI i = 0u; i < sUI(sampleFrames); i++) { mem_t m; m.u32 = s.u32[i] & ~0x80000000u; // abs sum += m.f32; // sum += (s.f32[i] * s.f32[i]); // RMS } sum = (sum / float(sampleFrames)); // sum = sqrtf(sum / float(sampleFrames)); // RMS if(sum >= wrapper->idle_input_level_threshold) { wrapper->b_idle = false; Dprintf_idle("xxx vstrack_plugin: become active after input (sum=%f, threshold=%f)\n", sum, wrapper->idle_input_level_threshold); wrapper->idle_output_framecount = 0u; break; } } } } break; } // switch idle_detect_mode } // if idle if(!wrapper->b_idle) { if( !Dfltequal(wrapper->oversample.factor, 1.0f) && (NULL != wrapper->oversample.srs_in) && (NULL != wrapper->oversample.srs_out) ) { sF32 *inputs[NUM_INPUTS]; sF32 *outputs[NUM_INPUTS]; sUI hostNumFrames = sampleFrames; sUI overNumFrames = sUI((sampleFrames * wrapper->oversample.factor) + 0.5f); // Up-sample inputs { sUI inNumFrames = hostNumFrames; sUI outNumFrames = overNumFrames; sF32 *d = wrapper->oversample.in_buffers; for(chIdx = 0u; chIdx < NUM_INPUTS; chIdx++) { if(chIdx < wrapper->oversample.num_in) { sF32 *s = _inputs[chIdx]; int err = speex_resampler_process_float(wrapper->oversample.srs_in, chIdx, s, &inNumFrames, d, &outNumFrames ); } else { // Skip channel ::memset(d, 0, sizeof(sF32) * outNumFrames); } inputs[chIdx] = d; // Next input channel d += outNumFrames; } } // Clear output buffers // (note) AudioInterface instances accumulate samples in the output buffer { sF32 *d = wrapper->oversample.out_buffers; ::memset((void*)d, 0, (sizeof(sF32) * wrapper->oversample.num_out * overNumFrames)); for(chIdx = 0u; chIdx < NUM_OUTPUTS; chIdx++) { outputs[chIdx] = d; d += overNumFrames; } } // Process rack modules if(wrapper->b_processing) { vst2_engine_process(inputs, outputs, overNumFrames); } // Down-sample outputs { sF32 *s = wrapper->oversample.out_buffers; sUI inNumFrames = overNumFrames; sUI outNumFrames = hostNumFrames; for(chIdx = 0u; chIdx < NUM_OUTPUTS; chIdx++) { sF32 *d = _outputs[chIdx]; if(chIdx < wrapper->oversample.num_out) { int err = speex_resampler_process_float(wrapper->oversample.srs_out, chIdx, s, &inNumFrames, d, &outNumFrames ); // Next output channel s += inNumFrames; } else { // Skip output ::memset((void*)d, 0, sizeof(sF32) * outNumFrames); } } } } else { // No oversampling // (note) Cubase (tested with 9.5.30) uses the same buffer(s) for both input&output // => back up the inputs before clearing the outputs sF32 *inputs[NUM_INPUTS]; sUI k = 0u; for(chIdx = 0u; chIdx < NUM_INPUTS; chIdx++) { inputs[chIdx] = &wrapper->tmp_input_buffers[k]; ::memcpy((void*)inputs[chIdx], _inputs[chIdx], sizeof(sF32)*sampleFrames); k += sampleFrames; } // Clear output buffers // (note) AudioInterface instances accumulate samples in the output buffer for(chIdx = 0u; chIdx < NUM_OUTPUTS; chIdx++) { ::memset((void*)_outputs[chIdx], 0, sizeof(sF32)*sampleFrames); } if(wrapper->b_processing) { vst2_engine_process(inputs, _outputs, sampleFrames); } } if(VSTPluginWrapper::IDLE_DETECT_NONE != wrapper->idle_detect_mode) { bool bSilence = true; for(chIdx = 0u; chIdx < NUM_OUTPUTS; chIdx++) { if(chIdx < wrapper->oversample.num_out) { cmemptr_t d; d.f32 = _outputs[chIdx]; sF32 sum = 0.0f; for(sUI i = 0u; i < sUI(sampleFrames); i++) { mem_t m; m.u32 = d.u32[i] & ~0x80000000u; // abs sum += m.f32; // sum += d.f32[i] * d.f32[i]; // RMS } sum = (sum / float(sampleFrames)); // sum = sqrtf(sum / float(sampleFrames)); // RMS { static int x = 0; if(0 == (++x & 127)) Dprintf_idle("xxx vstrack_plugin: output avg is %f\n", sum); } if(sum >= wrapper->idle_output_level_threshold) { bSilence = false; break; } } } if(bSilence) { wrapper->idle_output_framecount += sampleFrames; if(wrapper->idle_output_framecount >= sUI(wrapper->idle_output_sec_threshold * wrapper->sample_rate)) { // Frame threshold exceeded, become idle wrapper->b_idle = true; Dprintf_idle("xxx vstrack_plugin: now idle\n"); } } else { wrapper->idle_output_framecount = 0u; } } } // if !wrapper->b_idle else { // Idle, clear output buffers for(chIdx = 0u; chIdx < NUM_OUTPUTS; chIdx++) { ::memset((void*)_outputs[chIdx], 0, sizeof(sF32)*sampleFrames); } } // // rack::global->engine.vipMutex.unlock(); rack::global->engine.mutex.unlock(); wrapper->unlockAudio(); //Dprintf("xxx vstrack_plugin: VSTPluginProcessReplacingFloat32: LEAVE\n"); } /** * This is the plugin called by the host to communicate with the plugin, mainly to request information (like the * vendor string, the plugin category...) or communicate state/changes (like open/close, frame rate...) * * @param vstPlugin the object returned by VSTPluginMain * @param opCode defined in aeffect.h/AEffectOpcodes and which continues in aeffectx.h/AEffectXOpcodes for a grand * total of 79 of them! Only a few of them are implemented in this small plugin. * @param index depend on the opcode * @param value depend on the opcode * @param ptr depend on the opcode * @param opt depend on the opcode * @return depend on the opcode (0 is ok when you don't implement an opcode...) */ VstIntPtr VSTPluginDispatcher(VSTPlugin *vstPlugin, VstInt32 opCode, VstInt32 index, VstIntPtr value, void *ptr, float opt ) { // Dprintf("vstrack_plugin: called VSTPluginDispatcher(%d)\n", opCode); VstIntPtr r = 0; // we can get a hold to our C++ class since we stored it in the `object` field (see constructor) VSTPluginWrapper *wrapper = static_cast(vstPlugin->object); // see aeffect.h/AEffectOpcodes and aeffectx.h/AEffectXOpcodes for details on all of them switch(opCode) { case effGetPlugCategory: // request for the category of the plugin: in this case it is an effect since it is modifying the input (as opposed // to generating sound) #ifdef VST2_EFFECT return kPlugCategEffect; #else return kPlugCategSynth; #endif // VST2_EFFECT case effOpen: // called by the host after it has obtained the effect instance (but _not_ during plugin scans) // (note) any heavy-lifting init code should go here Dprintf("vstrack_plugin: effOpen\n"); r = wrapper->openEffect(); break; case effClose: // called by the host when the plugin was called... time to reclaim memory! wrapper->closeEffect(); // (note) hosts usually call effStopProcess before effClose delete wrapper; break; case effSetProgram: r = 1; break; case effGetProgram: r = 0; break; case effGetVendorString: // request for the vendor string (usually used in the UI for plugin grouping) ::strncpy(static_cast(ptr), "bsp", kVstMaxVendorStrLen); r = 1; break; case effGetVendorVersion: // request for the version return PLUGIN_VERSION; case effGetEffectName: #ifdef VST2_EFFECT ::strncpy((char*)ptr, "VeeSeeVST Rack 0.6.1", kVstMaxEffectNameLen); #else ::strncpy((char*)ptr, "VeeSeeVST Rack 0.6.1 I", kVstMaxEffectNameLen); #endif // VST2_EFFECT r = 1; break; case effGetProductString: #ifdef VST2_EFFECT ::strncpy((char*)ptr, "VeeSeeVST Rack 0.6.1 VST2 Plugin v0.4", kVstMaxProductStrLen); #else ::strncpy((char*)ptr, "VeeSeeVST Rack 0.6.1 I VST2 Plugin v0.4", kVstMaxProductStrLen); #endif // VST2_EFFECT r = 1; break; case effGetNumMidiInputChannels: r = 16; break; case effGetNumMidiOutputChannels: r = 0; break; case effGetInputProperties: { VstPinProperties *pin = (VstPinProperties*)ptr; ::snprintf(pin->label, kVstMaxLabelLen, "Input #%d", index); pin->flags = kVstPinIsActive | ((0 == (index & 1)) ? kVstPinIsStereo : 0); pin->arrangementType = ((0 == (index & 1)) ? kSpeakerArrStereo : kSpeakerArrMono); ::snprintf(pin->shortLabel, kVstMaxShortLabelLen, "in%d", index); memset((void*)pin->future, 0, 48); r = 1; } break; case effGetOutputProperties: { VstPinProperties *pin = (VstPinProperties*)ptr; ::snprintf(pin->label, kVstMaxLabelLen, "Output #%d", index); pin->flags = kVstPinIsActive | ((0 == (index & 1)) ? kVstPinIsStereo : 0); pin->arrangementType = ((0 == (index & 1)) ? kSpeakerArrStereo : kSpeakerArrMono); ::snprintf(pin->shortLabel, kVstMaxShortLabelLen, "out%d", index); memset((void*)pin->future, 0, 48); r = 1; } break; case effSetSampleRate: r = wrapper->setSampleRate(opt) ? 1 : 0; break; case effSetBlockSize: r = wrapper->setBlockSize(uint32_t(value)) ? 1 : 0; break; case effCanDo: // ptr: // "sendVstEvents" // "sendVstMidiEvent" // "sendVstTimeInfo" // "receiveVstEvents" // "receiveVstMidiEvent" // "receiveVstTimeInfo" // "offline" // "plugAsChannelInsert" // "plugAsSend" // "mixDryWet" // "noRealTime" // "multipass" // "metapass" // "1in1out" // "1in2out" // "2in1out" // "2in2out" // "2in4out" // "4in2out" // "4in4out" // "4in8out" // "8in4out" // "8in8out" // "midiProgramNames" // "conformsToWindowRules" if(!strcmp((char*)ptr, "receiveVstEvents")) r = 1; else if(!strcmp((char*)ptr, "receiveVstMidiEvent")) // (note) required by Jeskola Buzz r = 1; else if(!strcmp((char*)ptr, "noRealTime")) r = 1; else r = 0; break; case effGetProgramName: ::snprintf((char*)ptr, kVstMaxProgNameLen, "default"); r = 1; break; case effSetProgramName: r = 1; break; case effGetProgramNameIndexed: ::sprintf((char*)ptr, "default"); r = 1; break; case effGetParamName: // kVstMaxParamStrLen(8), much longer in other plugins // printf("xxx vstrack_plugin: effGetParamName: ptr=%p\n", ptr); wrapper->setGlobals(); vst2_get_param_name(index, (char*)ptr, kVstMaxParamStrLen); r = 1; break; case effCanBeAutomated: // fix Propellerhead Reason VST parameter support r = 1; break; case effGetParamLabel: // e.g. "dB" break; case effGetParamDisplay: // e.g. "-20" break; #if 0 case effGetParameterProperties: // [index]: parameter index [ptr]: #VstParameterProperties* [return value]: 1 if supported wrapper->setGlobals(); r = wrapper->getParameterProperties(sUI(index), (struct VstParameterProperties*)ptr); break; #endif case effGetChunk: // Query bank (index=0) or program (index=1) state // value: 0 // ptr: buffer address // r: buffer size Dprintf("xxx vstrack_plugin: effGetChunk index=%d ptr=%p\n", index, ptr); // // if(0 == index) // // { // // r = wrapper->getBankChunk((uint8_t**)ptr); // // } // // else // // { r = wrapper->getProgramChunk((uint8_t**)ptr); // // } break; case effSetChunk: // Restore bank (index=0) or program (index=1) state // value: buffer size // ptr: buffer address // r: 1 Dprintf("xxx vstrack_plugin: effSetChunk index=%d size=%lld ptr=%p\n", index, value, ptr); // // if(0 == index) // // { // // r = wrapper->setBankChunk(size_t(value), (uint8_t*)ptr) ? 1 : 0; // // } // // else // // { r = wrapper->setProgramChunk(size_t(value), (uint8_t*)ptr) ? 1 : 0; // // } break; case effShellGetNextPlugin: // For shell plugins (e.g. Waves), returns next sub-plugin UID (or 0) // (note) plugin uses audioMasterCurrentId while it's being instantiated to query the currently selected sub-plugin // if the host returns 0, it will then call effShellGetNextPlugin to enumerate the sub-plugins // ptr: effect name string ptr (filled out by the plugin) r = 0; break; case effMainsChanged: // value = 0=suspend, 1=resume wrapper->setEnableProcessingActive((value > 0) ? true : false); r = 1; break; case effStartProcess: wrapper->setEnableProcessingActive(true); r = 1; break; case effStopProcess: wrapper->setEnableProcessingActive(false); r = 1; break; case effProcessEvents: // ptr: VstEvents* { VstEvents *events = (VstEvents*)ptr; // Dprintf("vstrack_plugin:effProcessEvents: recvd %d events", events->numEvents); VstEvent**evAddr = &events->events[0]; if(events->numEvents > 0) { wrapper->setGlobals(); wrapper->mtx_mididev.lock(); for(uint32_t evIdx = 0u; evIdx < uint32_t(events->numEvents); evIdx++, evAddr++) { VstEvent *ev = *evAddr; if(NULL != ev) // paranoia { #ifdef DEBUG_PRINT_EVENTS Dprintf("vstrack_plugin:effProcessEvents: ev[%u].byteSize = %u\n", evIdx, uint32_t(ev->byteSize)); // sizeof(VstMidiEvent) = 32 Dprintf("vstrack_plugin:effProcessEvents: ev[%u].deltaFrames = %u\n", evIdx, uint32_t(ev->deltaFrames)); #endif // DEBUG_PRINT_EVENTS switch(ev->type) { default: //case kVstAudioType: // deprecated //case kVstVideoType: // deprecated //case kVstParameterType: // deprecated //case kVstTriggerType: // deprecated break; case kVstMidiType: // (note) ev->data stores the actual payload (up to 16 bytes) // (note) e.g. 0x90 0x30 0x7F for a C-4 note-on on channel 1 with velocity 127 // (note) don't forget to use a mutex (lockAudio(), unlockAudio()) when modifying the audio processor state! { VstMidiEvent *mev = (VstMidiEvent *)ev; #ifdef DEBUG_PRINT_EVENTS Dprintf("vstrack_plugin:effProcessEvents: ev[%u].noteLength = %u\n", evIdx, uint32_t(mev->noteLength)); // #frames Dprintf("vstrack_plugin:effProcessEvents: ev[%u].noteOffset = %u\n", evIdx, uint32_t(mev->noteOffset)); // #frames Dprintf("vstrack_plugin:effProcessEvents: ev[%u].midiData = %02x %02x %02x %02x\n", evIdx, uint8_t(mev->midiData[0]), uint8_t(mev->midiData[1]), uint8_t(mev->midiData[2]), uint8_t(mev->midiData[3])); Dprintf("vstrack_plugin:effProcessEvents: ev[%u].detune = %d\n", evIdx, mev->detune); // -64..63 Dprintf("vstrack_plugin:effProcessEvents: ev[%u].noteOffVelocity = %d\n", evIdx, mev->noteOffVelocity); // 0..127 #endif // DEBUG_PRINT_EVENTS if((VSTPluginWrapper::IDLE_DETECT_MIDI == wrapper->idle_detect_mode) && wrapper->b_idle) { if(0x90u == (mev->midiData[0] & 0xF0u)) // Note on ? { wrapper->lockAudio(); wrapper->b_idle = false; wrapper->idle_output_framecount = 0u; wrapper->unlockAudio(); Dprintf_idle("xxx vstrack_plugin: become active after MIDI note on\n"); } } vst2_process_midi_input_event(mev->midiData[0], mev->midiData[1], mev->midiData[2] ); } break; case kVstSysExType: { VstMidiSysexEvent *xev = (VstMidiSysexEvent*)ev; #ifdef DEBUG_PRINT_EVENTS Dprintf("vstrack_plugin:effProcessEvents: ev[%u].dumpBytes = %u\n", evIdx, uint32_t(xev->dumpBytes)); // size Dprintf("vstrack_plugin:effProcessEvents: ev[%u].sysexDump = %p\n", evIdx, xev->sysexDump); // buffer addr #endif // DEBUG_PRINT_EVENTS // (note) don't forget to use a mutex (lockAudio(), unlockAudio()) when modifying the audio processor state! } break; } } // if ev } // loop events wrapper->mtx_mididev.unlock(); } // if events } break; case effGetTailSize: // 52 break; #if 1 //case effIdle: case 53: // Periodic idle call (from UI thread), e.g. at 20ms intervals (depending on host) // (note) deprecated in vst2.4 (but some plugins still rely on this) r = 1; break; #endif case effEditIdle: if(0 == wrapper->redraw_ival_ms) { wrapper->queueRedraw(); } break; case effEditGetRect: // Query editor window geometry // ptr: ERect* (on Windows) if(NULL != ptr) { // ... *(void**)ptr = (void*) &wrapper->editor_rect; r = 1; } else { r = 0; } break; #if 0 case effEditTop: // deprecated in vst2.4 r = 0; break; #endif case effEditOpen: // Show editor window // ptr: native window handle (hWnd on Windows) wrapper->openEditor(ptr); r = 1; break; case effEditClose: // Close editor window wrapper->closeEditor(); r = 1; break; case effEditKeyDown: // [index]: ASCII character [value]: virtual key [opt]: modifiers [return value]: 1 if key used @see AEffEditor::onKeyDown // (note) only used for touch input // Dprintf("xxx effEditKeyDown: ascii=%d (\'%c\') vkey=0x%08x mod=0x%08x\n", index, index, value, opt); if(rack::b_touchkeyboard_enable) { wrapper->setGlobals(); { uint32_t vkey = 0u; switch(uint32_t(value)) { // see aeffectx.h case VKEY_BACK: vkey = LGLW_VKEY_BACKSPACE; break; case VKEY_TAB: vkey = LGLW_VKEY_TAB; break; case VKEY_RETURN: vkey = LGLW_VKEY_RETURN; break; case VKEY_ESCAPE: vkey = LGLW_VKEY_ESCAPE; break; case VKEY_END: vkey = LGLW_VKEY_END; break; case VKEY_HOME: vkey = LGLW_VKEY_HOME; break; case VKEY_LEFT: vkey = LGLW_VKEY_LEFT; break; case VKEY_UP: vkey = LGLW_VKEY_UP; break; case VKEY_RIGHT: vkey = LGLW_VKEY_RIGHT; break; case VKEY_DOWN: vkey = LGLW_VKEY_DOWN; break; case VKEY_PAGEUP: vkey = LGLW_VKEY_PAGEUP; break; case VKEY_PAGEDOWN: vkey = LGLW_VKEY_PAGEDOWN; break; case VKEY_ENTER: vkey = LGLW_VKEY_RETURN; break; case VKEY_INSERT: vkey = LGLW_VKEY_INSERT; break; case VKEY_DELETE: vkey = LGLW_VKEY_DELETE; break; break; default: vkey = (char)index; // (note) some(most?) DAWs don't send the correct VKEY_xxx values for all special keys switch(vkey) { case 8: vkey = LGLW_VKEY_BACKSPACE; break; // case 13: vkey = LGLW_VKEY_RETURN; break; // case 9: vkey = LGLW_VKEY_TAB; break; // case 27: vkey = LGLW_VKEY_ESCAPE; break; default: if(vkey >= 128) vkey = 0u; break; } break; } if(vkey > 0u) { r = vst2_handle_effeditkeydown(vkey); } } } break; default: // ignoring all other opcodes Dprintf("vstrack_plugin:dispatcher: unhandled opCode %d [ignored] \n", opCode); break; } return r; } /** * Set parameter setting */ void VSTPluginSetParameter(VSTPlugin *vstPlugin, VstInt32 index, float parameter ) { #ifdef DEBUG_PRINT_PARAMS Dprintf("vstrack_plugin: called VSTPluginSetParameter(%d, %f)\n", index, parameter); #endif // DEBUG_PRINT_PARAMS // we can get a hold to our C++ class since we stored it in the `object` field (see constructor) VSTPluginWrapper *wrapper = static_cast(vstPlugin->object); wrapper->lockAudio(); wrapper->setGlobals(); vst2_queue_param(index, parameter); wrapper->unlockAudio(); } /** * Query parameter */ float VSTPluginGetParameter(VSTPlugin *vstPlugin, VstInt32 index ) { #ifdef DEBUG_PRINT_PARAMS Dprintf("vstrack_plugin: called VSTPluginGetParameter(%d)\n", index); #endif // DEBUG_PRINT_PARAMS // we can get a hold to our C++ class since we stored it in the `object` field (see constructor) VSTPluginWrapper *wrapper = static_cast(vstPlugin->object); wrapper->lockAudio(); // don't query a param while the module is deleted wrapper->setGlobals(); float r = vst2_get_param(index); wrapper->unlockAudio(); return r; } /** * Main constructor for our C++ class */ VSTPluginWrapper::VSTPluginWrapper(audioMasterCallback vstHostCallback, VstInt32 vendorUniqueID, VstInt32 vendorVersion, VstInt32 numParams, VstInt32 numPrograms, VstInt32 numInputs, VstInt32 numOutputs ) : _vstHostCallback(vstHostCallback) { instance_count++; // Make sure that the memory is properly initialized memset(&_vstPlugin, 0, sizeof(_vstPlugin)); // this field must be set with this constant... _vstPlugin.magic = kEffectMagic; // storing this object into the VSTPlugin so that it can be retrieved when called back (see callbacks for use) _vstPlugin.object = this; // specifying that we handle both single and NOT double precision (there are other flags see aeffect.h/VstAEffectFlags) _vstPlugin.flags = #ifndef VST2_EFFECT effFlagsIsSynth | #endif effFlagsCanReplacing | // (effFlagsCanDoubleReplacing & 0) | effFlagsProgramChunks | effFlagsHasEditor ; // initializing the plugin with the various values _vstPlugin.uniqueID = vendorUniqueID; _vstPlugin.version = vendorVersion; _vstPlugin.numParams = numParams; _vstPlugin.numPrograms = numPrograms; _vstPlugin.numInputs = numInputs; _vstPlugin.numOutputs = numOutputs; // setting the callbacks to the previously defined functions _vstPlugin.dispatcher = &VSTPluginDispatcher; _vstPlugin.getParameter = &VSTPluginGetParameter; _vstPlugin.setParameter = &VSTPluginSetParameter; _vstPlugin.processReplacing = &VSTPluginProcessReplacingFloat32; _vstPlugin.processDoubleReplacing = NULL;//&VSTPluginProcessReplacingFloat64; // report latency _vstPlugin.initialDelay = 0; oversample.factor = 1.0f; oversample.quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; oversample.realtime_factor = 1.0f; oversample.realtime_quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; oversample.offline_factor = 1.0f; oversample.offline_quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; oversample.srs_in = NULL; oversample.srs_out = NULL; oversample.num_in = NUM_INPUTS; oversample.num_out = NUM_OUTPUTS; sample_rate = 44100.0f; block_size = 64u; b_processing = true; b_offline = false; b_check_offline = false; idle_detect_mode = IDLE_DETECT_NONE; b_idle = false; idle_input_level_threshold = 0.00018f;//0.00007f; idle_output_level_threshold = 0.00018f;//0.00003f; idle_output_sec_threshold = 120.0f / 1000.0f; // idle after 120ms of silence idle_output_framecount = 0u; last_program_chunk_str = NULL; b_open = false; b_editor_open = false; editor_rect.left = EDITWIN_X; editor_rect.top = EDITWIN_Y; editor_rect.right = EDITWIN_X + EDITWIN_W; editor_rect.bottom = EDITWIN_Y + EDITWIN_H; redraw_ival_ms = 0; } /** * Destructor called when the plugin is closed (see VSTPluginDispatcher with effClose opCode). In this very simply plugin * there is nothing to do but in general the memory that gets allocated MUST be freed here otherwise there might be a * memory leak which may end up slowing down and/or crashing the host */ VSTPluginWrapper::~VSTPluginWrapper() { closeEffect(); instance_count--; } void vst2_lock_midi_device() { rack::global->vst2.wrapper->mtx_mididev.lock(); } void vst2_unlock_midi_device() { rack::global->vst2.wrapper->mtx_mididev.unlock(); } void vst2_handle_ui_param(int uniqueParamId, float normValue) { // Called by engineSetParam() rack::global->vst2.wrapper->handleUIParam(uniqueParamId, normValue); } void vst2_get_timing_info(int *_retPlaying, float *_retBPM, float *_retSongPosPPQ) { // updates the requested fields when query was successful rack::global->vst2.wrapper->getTimingInfo(_retPlaying, _retBPM, _retSongPosPPQ); } void vst2_set_globals(void *_wrapper) { VSTPluginWrapper *wrapper = (VSTPluginWrapper *)_wrapper; wrapper->setGlobals(); } void vst2_window_size_set(int _width, int _height) { rack::global->vst2.wrapper->setWindowSize(_width, _height); } void vst2_refresh_rate_set(float _hz) { rack::global->vst2.wrapper->setRefreshRate(_hz); } extern "C" void lglw_timer_cbk(lglw_t _lglw) { VSTPluginWrapper *wrapper = (VSTPluginWrapper*)lglw_userdata_get(_lglw); wrapper->queueRedraw(); } extern "C" void lglw_redraw_cbk(lglw_t _lglw) { VSTPluginWrapper *wrapper = (VSTPluginWrapper*)lglw_userdata_get(_lglw); wrapper->redraw(); } void vst2_oversample_realtime_set(float _factor, int _quality) { rack::global->vst2.wrapper->setOversampleRealtime(_factor, _quality); } void vst2_oversample_realtime_get(float *_factor, int *_quality) { *_factor = rack::global->vst2.wrapper->oversample.realtime_factor; *_quality = int(rack::global->vst2.wrapper->oversample.realtime_quality); } void vst2_oversample_offline_set(float _factor, int _quality) { rack::global->vst2.wrapper->setOversampleOffline(_factor, _quality); } void vst2_oversample_offline_get(float *_factor, int *_quality) { *_factor = rack::global->vst2.wrapper->oversample.offline_factor; *_quality = int(rack::global->vst2.wrapper->oversample.offline_quality); } void vst2_oversample_offline_check_set(int _bEnable) { rack::global->vst2.wrapper->b_check_offline = (0 != _bEnable); } void vst2_oversample_channels_set(int _numIn, int _numOut) { rack::global->vst2.wrapper->setOversampleChannels(_numIn, _numOut); } void vst2_oversample_channels_get(int *_numIn, int *_numOut) { *_numIn = int(rack::global->vst2.wrapper->oversample.num_in); *_numOut = int(rack::global->vst2.wrapper->oversample.num_out); } void vst2_idle_detect_mode_fx_set(int _mode) { #ifdef VST2_EFFECT rack::global->vst2.wrapper->setIdleDetectMode(uint32_t(_mode)); #endif // VST2_EFFECT } void vst2_idle_detect_mode_instr_set(int _mode) { #ifndef VST2_EFFECT rack::global->vst2.wrapper->setIdleDetectMode(uint32_t(_mode)); #endif // VST2_EFFECT } void vst2_idle_detect_mode_set(int _mode) { rack::global->vst2.wrapper->setIdleDetectMode(uint32_t(_mode)); } void vst2_idle_detect_mode_get(int *_mode) { *_mode = int(rack::global->vst2.wrapper->idle_detect_mode); } /** * Implementation of the main entry point of the plugin */ VST_EXPORT VSTPlugin *VSTPluginMain(VSTHostCallback vstHostCallback) { Dprintf("vstrack_plugin: called VSTPluginMain... \n"); #if 0 if(!vstHostCallback(0, audioMasterVersion, 0, 0, 0, 0)) { return 0; // old version } #endif // simply create our plugin C++ class VSTPluginWrapper *plugin = new VSTPluginWrapper(vstHostCallback, // registered with Steinberg (http://service.steinberg.de/databases/plugin.nsf/plugIn?openForm) #ifdef VST2_EFFECT CCONST('g', 'v', 'g', 'y'), #else CCONST('v', '5', 'k', 'v'), #endif PLUGIN_VERSION, // version VST2_MAX_UNIQUE_PARAM_IDS, // num params 1, // one program NUM_INPUTS, NUM_OUTPUTS ); // return the plugin per the contract of the API return plugin->getVSTPlugin(); } #endif // USE_VST2