/* * Tibia * * Copyright (C) 2023-2025 Orastron Srl unipersonale * * Tibia is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * Tibia 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tibia. If not, see . * * File author: Stefano D'Angelo, Paolo Marrone */ #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include "vst3_c_api.h" #pragma GCC diagnostic pop #define TEMPLATE_SUPPORTS_MESSAGING 1 #include "data.h" #include "plugin_api.h" #include "plugin.h" #ifdef DATA_UI # include "plugin_ui.h" #endif #include #include #include #include #include #include #if defined(_WIN32) || defined(__CYGWIN__) # include #else # include #endif #ifdef __APPLE__ # include #endif #if defined(__i386__) || defined(__x86_64__) #include #include #endif #if DATA_PRODUCT_PARAMETERS_IN_N > 0 # include # if defined(_WIN32) || defined(__CYGWIN__) # include # define yield SwitchToThread # else # include # define yield sched_yield # endif #endif #ifdef DATA_MESSAGING # include #endif // COM in C doc: // https://github.com/rubberduck-vba/Rubberduck/wiki/COM-in-plain-C // https://devblogs.microsoft.com/oldnewthing/20040205-00/?p=40733 //#define TIBIA_TRACE #ifdef TIBIA_TRACE # define TRACE(...) printf(__VA_ARGS__); fflush(stdout); #else # define TRACE(...) /* do nothing */ #endif #ifdef DATA_UI # ifdef __linux__ // Why generate the C interface when you can just not give a fuck? Thank you Steinberg! typedef struct Steinberg_ITimerHandlerVtbl { /* methods derived from "Steinberg_FUnknown": */ Steinberg_tresult (SMTG_STDMETHODCALLTYPE* queryInterface) (void* thisInterface, const Steinberg_TUID iid, void** obj); Steinberg_uint32 (SMTG_STDMETHODCALLTYPE* addRef) (void* thisInterface); Steinberg_uint32 (SMTG_STDMETHODCALLTYPE* release) (void* thisInterface); /* methods derived from "Steinberg_ITimerHandler": */ void (SMTG_STDMETHODCALLTYPE* onTimer) (void* thisInterface); } Steinberg_ITimerHandlerVtbl; typedef struct Steinberg_ITimerHandler { struct Steinberg_ITimerHandlerVtbl* lpVtbl; } Steinberg_ITimerHandler; static const Steinberg_TUID Steinberg_ITimerHandler_iid = SMTG_INLINE_UID (0x10BDD94F, 0x41424774, 0x821FAD8F, 0xECA72CA9); typedef struct Steinberg_IEventHandlerVtbl { /* methods derived from "Steinberg_FUnknown": */ Steinberg_tresult (SMTG_STDMETHODCALLTYPE* queryInterface) (void* thisInterface, const Steinberg_TUID iid, void** obj); Steinberg_uint32 (SMTG_STDMETHODCALLTYPE* addRef) (void* thisInterface); Steinberg_uint32 (SMTG_STDMETHODCALLTYPE* release) (void* thisInterface); /* methods derived from "Steinberg_IEventHandler": */ void (SMTG_STDMETHODCALLTYPE* onFDIsSet) (void* thisInterface, int fd); } Steinberg_IEventHandlerVtbl; typedef struct Steinberg_IEventHandler { struct Steinberg_IEventHandlerVtbl* lpVtbl; } Steinberg_IEventHandler; // not used //static const Steinberg_TUID Steinberg_IEventHandler_iid = SMTG_INLINE_UID (0x561E65C9, 0x13A0496F, 0x813A2C35, 0x654D7983); typedef struct Steinberg_IRunLoopVtbl { /* methods derived from "Steinberg_FUnknown": */ Steinberg_tresult (SMTG_STDMETHODCALLTYPE* queryInterface) (void* thisInterface, const Steinberg_TUID iid, void** obj); Steinberg_uint32 (SMTG_STDMETHODCALLTYPE* addRef) (void* thisInterface); Steinberg_uint32 (SMTG_STDMETHODCALLTYPE* release) (void* thisInterface); /* methods derived from "Steinberg_IRunLoop": */ Steinberg_tresult (SMTG_STDMETHODCALLTYPE* registerEventHandler) (void* thisInterface, struct Steinberg_IEventHandler* handler, int fd); Steinberg_tresult (SMTG_STDMETHODCALLTYPE* unregisterEventHandler) (void* thisInterface, struct Steinberg_IEventHandler* handler); Steinberg_tresult (SMTG_STDMETHODCALLTYPE* registerTimer) (void* thisInterface, struct Steinberg_ITimerHandler* handler, uint64_t milliseconds); Steinberg_tresult (SMTG_STDMETHODCALLTYPE* unregisterTimer) (void* thisInterface, struct Steinberg_ITimerHandler* handler); } Steinberg_IRunLoopVtbl; typedef struct Steinberg_IRunLoop { struct Steinberg_IRunLoopVtbl* lpVtbl; } Steinberg_IRunLoop; static const Steinberg_TUID Steinberg_IRunLoop_iid = SMTG_INLINE_UID (0x18C35366, 0x97764F1A, 0x9C5B8385, 0x7A871389); # endif #endif #if defined(__linux__) || defined(__APPLE__) static char *x_asprintf(const char * restrict format, ...) { va_list args, tmp; va_start(args, format); va_copy(tmp, args); int len = vsnprintf(NULL, 0, format, tmp); va_end(tmp); char *s = malloc(len + 1); if (s != NULL) vsprintf(s, format, args); va_end(args); return s; } #endif static char *bindir; static char *datadir; static const char * getBindirCb(void *handle) { (void)handle; return bindir; } static const char * getDatadirCb(void *handle) { (void)handle; return datadir; } static double clamp(double x, double m, double M) { return x < m ? m : (x > M ? M : x); } #if DATA_PRODUCT_PARAMETERS_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 static int parameterGetIndexById(Steinberg_Vst_ParamID id) { for (int i = 0; i < DATA_PRODUCT_PARAMETERS_N + 3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N; i++) if (parameterInfo[i].id == id) return i; return -1; } # if DATA_PRODUCT_PARAMETERS_N > 0 static double parameterMap(const ParameterData *data, double v) { return data->flags & DATA_PARAM_MAP_LOG ? data->min * exp(data->mapK * v) : data->min + (data->max - data->min) * v; } static double parameterUnmap(const ParameterData *data, double v) { return data->flags & DATA_PARAM_MAP_LOG ? log(v / data->min) / data->mapK : (v - data->min) / (data->max - data->min); } static double parameterAdjust(const ParameterData *data, double v) { v = data->flags & (DATA_PARAM_BYPASS | DATA_PARAM_TOGGLED) ? (v >= 0.5 ? 1.0 : 0.0) : (data->flags & DATA_PARAM_INTEGER ? (int32_t)(v + (v >= 0.0 ? 0.5 : -0.5)) : v); return clamp(v, data->min, data->max); } # endif static ParameterData *parameterGetDataByIndex(size_t index) { # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 # if DATA_PRODUCT_PARAMETERS_N > 0 if (index >= DATA_PRODUCT_PARAMETERS_N) # endif return NULL; # endif # if DATA_PRODUCT_PARAMETERS_N > 0 # if (DATA_PRODUCT_PARAMETERS_IN_N > 0) && (DATA_PRODUCT_PARAMETERS_OUT_N > 0) ParameterData *p = (parameterInfo[index].flags & Steinberg_Vst_ParameterInfo_ParameterFlags_kIsReadOnly ? parameterOutData : parameterInData) + parameterInfoToDataIndex[index]; # elif DATA_PRODUCT_PARAMETERS_IN_N > 0 ParameterData *p = parameterInData + parameterInfoToDataIndex[index]; # else ParameterData *p = parameterOutData + parameterInfoToDataIndex[index]; # endif return p; # endif } static double parameterMapByIndex(size_t index, double v) { # if DATA_PRODUCT_PARAMETERS_N > 0 ParameterData *p = parameterGetDataByIndex(index); # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (p == NULL) return v; # endif return parameterMap(p, v); # else return v; # endif } static double parameterUnmapByIndex(size_t index, double v) { # if DATA_PRODUCT_PARAMETERS_N > 0 ParameterData *p = parameterGetDataByIndex(index); # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (p == NULL) return v; # endif return parameterUnmap(p, v); # else return v; # endif } #endif static int stateRead(struct Steinberg_IBStream * state, char ** data, Steinberg_int64 * length) { if (state->lpVtbl->seek(state, 0, Steinberg_IBStream_IStreamSeekMode_kIBSeekEnd, NULL) != Steinberg_kResultOk) return -1; if (state->lpVtbl->tell(state, length) != Steinberg_kResultOk) return -1; if (state->lpVtbl->seek(state, 0, Steinberg_IBStream_IStreamSeekMode_kIBSeekSet, NULL) != Steinberg_kResultOk) return -1; *data = *length > 0 ? malloc(*length) : NULL; if (*length > 0 && *data == NULL) return -1; Steinberg_int64 read = 0; while (read < *length) { Steinberg_int32 r = (*length - read) <= 0x7fffffff ? *length - read : 0x7fffffff; Steinberg_int32 n; state->lpVtbl->read(state, *data + read, *length, &n); if (n != r) { free(*data); return -1; } read += n; } return 0; } typedef struct pluginInstance { Steinberg_Vst_IComponentVtbl * vtblIComponent; // must stay first Steinberg_Vst_IAudioProcessorVtbl * vtblIAudioProcessor; Steinberg_Vst_IProcessContextRequirementsVtbl * vtblIProcessContextRequirements; Steinberg_uint32 refs; Steinberg_FUnknown * context; plugin p; float lastSampleRate; float curSampleRate; float nextSampleRate; #if DATA_PRODUCT_PARAMETERS_IN_N > 0 float parametersIn[DATA_PRODUCT_PARAMETERS_IN_N]; float parametersInSync[DATA_PRODUCT_PARAMETERS_IN_N]; atomic_flag syncLockFlag; char synced; char loaded; #endif #if DATA_PRODUCT_PARAMETERS_OUT_N > 0 float parametersOut[DATA_PRODUCT_PARAMETERS_OUT_N]; #endif #if DATA_PRODUCT_CHANNELS_AUDIO_INPUT_N > 0 const float * inputs[DATA_PRODUCT_CHANNELS_AUDIO_INPUT_N]; #endif #if DATA_PRODUCT_CHANNELS_AUDIO_OUTPUT_N > 0 float * outputs[DATA_PRODUCT_CHANNELS_AUDIO_OUTPUT_N]; #endif #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N + DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N + DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 // see https://github.com/steinbergmedia/vst3sdk/issues/128 char neededBusesActive; #endif #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N > 0 char inputsActive[DATA_PRODUCT_BUSES_AUDIO_INPUT_N]; #endif #if DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N > 0 char outputsActive[DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N]; #endif #if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 char midiInputsActive[DATA_PRODUCT_BUSES_MIDI_INPUT_N]; #endif #if DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 char midiOutputsActive[DATA_PRODUCT_BUSES_MIDI_OUTPUT_N]; #endif void * mem; struct Steinberg_IBStream * state; #ifdef DATA_MESSAGING Steinberg_Vst_IConnectionPointVtbl *vtblIConnectionPoint; Steinberg_Vst_IConnectionPoint *connectedPoint; uint8_t message_data_in[DATA_MESSAGING_MAX]; size_t message_data_in_size; uint8_t message_data_out[DATA_MESSAGING_MAX]; size_t message_data_out_size; char message_data_out_tosend; // This is to tell message_thread to send data thrd_t message_thread; int message_cmd; mtx_t message_mutex; cnd_t message_cond; #endif } pluginInstance; static Steinberg_Vst_IComponentVtbl pluginVtblIComponent; static Steinberg_Vst_IAudioProcessorVtbl pluginVtblIAudioProcessor; static void pluginStateLockCb(void *handle) { pluginInstance *p = (pluginInstance *)handle; while (atomic_flag_test_and_set(&p->syncLockFlag)) yield(); p->synced = 0; } static void pluginStateUnlockCb(void *handle) { pluginInstance *p = (pluginInstance *)handle; atomic_flag_clear(&p->syncLockFlag); } static int pluginStateWriteCb(void *handle, const char *data, size_t length) { pluginInstance *p = (pluginInstance *)handle; size_t written = 0; while (written < length) { Steinberg_int32 w = (length - written) <= 0x7fffffff ? length - written : 0x7fffffff; Steinberg_int32 n; p->state->lpVtbl->write(p->state, (void *)data, length, &n); if (n != w) return -1; written += n; } return 0; } # if DATA_PRODUCT_PARAMETERS_IN_N > 0 static void pluginStateSetParameterCb(void *handle, size_t index, float value) { size_t i = index; # ifdef DATA_PARAM_LATENCY_INDEX if (i >= DATA_PARAM_LATENCY_INDEX) i--; # endif pluginInstance *p = (pluginInstance *)handle; size_t ii = parameterInfoToDataIndex[i]; p->parametersInSync[ii] = parameterAdjust(parameterInData + ii, value); p->loaded = 1; } # endif static Steinberg_tresult pluginQueryInterface(pluginInstance *p, const Steinberg_TUID iid, void ** obj) { // This seems to violate the way multiple inheritance should work in COM, but hosts like it, so what do I know... size_t offset; if (memcmp(iid, Steinberg_FUnknown_iid, sizeof(Steinberg_TUID)) == 0 || memcmp(iid, Steinberg_IPluginBase_iid, sizeof(Steinberg_TUID)) == 0 || memcmp(iid, Steinberg_Vst_IComponent_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(pluginInstance, vtblIComponent); else if (memcmp(iid, Steinberg_Vst_IAudioProcessor_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(pluginInstance, vtblIAudioProcessor); else if (memcmp(iid, Steinberg_Vst_IProcessContextRequirements_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(pluginInstance, vtblIProcessContextRequirements); #ifdef DATA_MESSAGING else if (memcmp(iid, Steinberg_Vst_IConnectionPoint_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(pluginInstance, vtblIConnectionPoint); #endif else { TRACE(" not supported\n"); for (int i = 0; i < 16; i++) { TRACE(" %x", iid[i]); } TRACE("\n"); *obj = NULL; return Steinberg_kNoInterface; } *obj = (void *)((char *)p + offset); p->refs++; return Steinberg_kResultOk; } static Steinberg_uint32 pluginAddRef(pluginInstance *p) { p->refs++; return p->refs; } static Steinberg_uint32 pluginRelease(pluginInstance *p) { p->refs--; if (p->refs == 0) { TRACE(" free %p\n", (void *)p); free(p); return 0; } return p->refs; } #ifdef DATA_MESSAGING static Steinberg_tresult pluginIConnectionPointQueryInterface(void* thisInterface, const Steinberg_TUID iid, void** obj) { TRACE("plugin IConnectionPoint queryInterface %p\n", thisInterface); return pluginQueryInterface((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint)), iid, obj); } static Steinberg_uint32 pluginIConnectionPointAddRef(void* thisInterface) { TRACE("plugin IConnectionPoint addRef %p\n", thisInterface); return pluginAddRef((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint))); } static Steinberg_uint32 pluginIConnectionPointRelease(void* thisInterface) { TRACE("plugin IConnectionPoint release %p\n", thisInterface); return pluginRelease((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint))); } static Steinberg_tresult pluginIConnectionPointConnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) { pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint)); if (!other) return Steinberg_kInvalidArgument; if (p->connectedPoint) return Steinberg_kResultFalse; mtx_lock(&p->message_mutex); p->connectedPoint = other; mtx_unlock(&p->message_mutex); return Steinberg_kResultOk; } static Steinberg_tresult pluginIConnectionPointDisconnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) { pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint)); if (p->connectedPoint && other == p->connectedPoint) { mtx_lock(&p->message_mutex); p->connectedPoint = NULL; mtx_unlock(&p->message_mutex); return Steinberg_kResultOk; } return Steinberg_kResultFalse; } static Steinberg_tresult pluginIConnectionPointNotify(void* thisInterface, struct Steinberg_Vst_IMessage* message) { pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIConnectionPoint)); if (!message) return Steinberg_kResultFalse; Steinberg_Vst_IAttributeList *alist = message->lpVtbl->getAttributes(message); if (!alist) return Steinberg_kResultFalse; const void *data = NULL; unsigned int size = 0; alist->lpVtbl->getBinary(alist, "message_data", &data, &size); if (!data || size == 0) return Steinberg_kResultFalse; mtx_lock(&p->message_mutex); memcpy(&p->message_data_in, data, size); p->message_data_in_size = size; mtx_unlock(&p->message_mutex); return Steinberg_kResultOk; } static Steinberg_Vst_IConnectionPointVtbl pluginVtblIConnectionPoint = { /* FUnknown */ /* .queryInterface = */ pluginIConnectionPointQueryInterface, /* .addRef = */ pluginIConnectionPointAddRef, /* .release = */ pluginIConnectionPointRelease, /* IConnectionPoint */ /* .connect = */ pluginIConnectionPointConnect, /* .disconnect = */ pluginIConnectionPointDisconnect, /* .notify = */ pluginIConnectionPointNotify }; // Assuming this gets called by audio thread only static char send_to_ui (void *handle, const void *data, size_t bytes) { pluginInstance *p = (pluginInstance *)handle; if (!data || bytes == 0 || bytes > DATA_MESSAGING_MAX) { return 1; } memcpy(p->message_data_out, data, bytes); p->message_data_out_size = bytes; p->message_data_out_tosend = 1; return 0; } # endif static Steinberg_tresult pluginIComponentQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) { TRACE("plugin IComponent queryInterface %p\n", thisInterface); return pluginQueryInterface((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)), iid, obj); } static Steinberg_uint32 pluginIComponentAddRef(void *thisInterface) { TRACE("plugin IComponent addRef %p\n", thisInterface); return pluginAddRef((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent))); } static Steinberg_uint32 pluginIComponentRelease(void *thisInterface) { TRACE("plugin IComponent release %p\n", thisInterface); return pluginRelease((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent))); } #ifdef DATA_MESSAGING int message_thread_f(void *arg) { pluginInstance *p = (pluginInstance *) arg; while (1) { mtx_lock(&p->message_mutex); if (p->message_cmd == 0) { // Wait cnd_wait(&p->message_cond, &p->message_mutex); } if (p->message_cmd == 1) { // do work if (!p->connectedPoint || ! p->context) { mtx_unlock(&p->message_mutex); thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec continue; } Steinberg_Vst_IConnectionPointVtbl *ov = (Steinberg_Vst_IConnectionPointVtbl*) p->connectedPoint->lpVtbl; Steinberg_Vst_IHostApplication *app = (Steinberg_Vst_IHostApplication*) p->context; Steinberg_Vst_IMessage *msg = NULL; app->lpVtbl->createInstance(app, (char*) Steinberg_Vst_IMessage_iid, (char*) Steinberg_Vst_IMessage_iid, (void**)&msg); if (!msg) { mtx_unlock(&p->message_mutex); thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec continue; } Steinberg_Vst_IAttributeList *alist = msg->lpVtbl->getAttributes(msg); if (!alist) { msg->lpVtbl->release(msg); mtx_unlock(&p->message_mutex); thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec continue; } alist->lpVtbl->setBinary(alist, "message_data", p->message_data_out, p->message_data_out_size); Steinberg_tresult r = ov->notify(p->connectedPoint, msg); if (r == Steinberg_kResultFalse) { msg->lpVtbl->release(msg); mtx_unlock(&p->message_mutex); thrd_sleep(&(struct timespec){.tv_nsec=1e+8}, NULL); // sleep 100 msec continue; } msg->lpVtbl->release(msg); p->message_cmd = 0; } else if (p->message_cmd == 2) { // exit mtx_unlock(&p->message_mutex); break; } mtx_unlock(&p->message_mutex); } return 0; } #endif static Steinberg_tresult pluginInitialize(void *thisInterface, struct Steinberg_FUnknown *context) { TRACE("plugin initialize\n"); pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)); if (p->context != NULL) return Steinberg_kResultFalse; p->context = context; p->lastSampleRate = 0.f; p->curSampleRate = 0.f; p->nextSampleRate = 0.f; plugin_callbacks cbs = { /* .handle = */ (void *)p, /* .format = */ "vst3", /* .get_bindir = */ getBindirCb, /* .get_datadir = */ getDatadirCb, #ifdef DATA_MESSAGING /* .send_to_ui = */ send_to_ui #endif }; plugin_init(&p->p, &cbs); #if DATA_PRODUCT_PARAMETERS_IN_N > 0 for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) { p->parametersIn[i] = parameterInData[i].def; plugin_set_parameter(&p->p, parameterInData[i].index, parameterInData[i].def); } for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) p->parametersInSync[i] = p->parametersIn[i]; atomic_flag_clear(&p->syncLockFlag); p->synced = 1; p->loaded = 0; #endif #if DATA_PRODUCT_PARAMETERS_OUT_N > 0 for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_OUT_N; i++) p->parametersOut[i] = parameterOutData[i].def; #endif #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N + DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N + DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 p->neededBusesActive = 0; # if DATA_PRODUCT_BUSES_AUDIO_INPUT_N > 0 for (size_t i = 0; i < DATA_PRODUCT_BUSES_AUDIO_INPUT_N; i++) p->inputsActive[i] = 0; # endif # if DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N > 0 for (size_t i = 0; i < DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N; i++) p->outputsActive[i] = 0; # endif # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 for (size_t i = 0; i < DATA_PRODUCT_BUSES_MIDI_INPUT_N; i++) p->midiInputsActive[i] = 0; # endif # if DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 for (size_t i = 0; i < DATA_PRODUCT_BUSES_MIDI_OUTPUT_N; i++) p->midiOutputsActive[i] = 0; # endif #endif p->mem = NULL; #ifdef DATA_MESSAGING mtx_init(&p->message_mutex, mtx_plain); cnd_init(&p->message_cond); if (thrd_create(&p->message_thread, message_thread_f, p) != thrd_success) { return Steinberg_kResultFalse; } #endif return Steinberg_kResultOk; } static Steinberg_tresult pluginTerminate(void *thisInterface) { TRACE("plugin terminate\n"); pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)); p->context = NULL; plugin_fini(&p->p); if (p->mem) free(p->mem); #ifdef DATA_MESSAGING mtx_lock(&p->message_mutex); p->message_cmd = 2; cnd_signal(&p->message_cond); mtx_unlock(&p->message_mutex); thrd_join(p->message_thread, NULL); mtx_destroy(&p->message_mutex); cnd_destroy(&p->message_cond); #endif return Steinberg_kResultOk; } static Steinberg_tresult pluginGetControllerClassId(void *thisInterface, Steinberg_TUID classId) { (void)thisInterface; TRACE("plugin get controller class id %p %p\n", thisInterface, classId); if (classId != NULL) { memcpy(classId, dataControllerCID, sizeof(Steinberg_TUID)); return Steinberg_kResultTrue; } return Steinberg_kResultFalse; } static Steinberg_tresult pluginSetIoMode(void *thisInterface, Steinberg_Vst_IoMode mode) { (void)thisInterface; (void)mode; TRACE("plugin set io mode\n"); return Steinberg_kNotImplemented; } static Steinberg_int32 pluginGetBusCount(void *thisInterface, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir) { (void)thisInterface; TRACE("plugin get bus count\n"); if (type == Steinberg_Vst_MediaTypes_kAudio) { if (dir == Steinberg_Vst_BusDirections_kInput) return DATA_PRODUCT_BUSES_AUDIO_INPUT_N; else if (dir == Steinberg_Vst_BusDirections_kOutput) return DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N; } else if (type == Steinberg_Vst_MediaTypes_kEvent) { if (dir == Steinberg_Vst_BusDirections_kInput) return DATA_PRODUCT_BUSES_MIDI_INPUT_N; else if (dir == Steinberg_Vst_BusDirections_kOutput) return DATA_PRODUCT_BUSES_MIDI_OUTPUT_N; } return 0; } static Steinberg_tresult pluginGetBusInfo(void* thisInterface, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, struct Steinberg_Vst_BusInfo* bus) { (void)thisInterface; TRACE("plugin get bus info\n"); if (index < 0) return Steinberg_kInvalidArgument; if (type == Steinberg_Vst_MediaTypes_kAudio) { if (dir == Steinberg_Vst_BusDirections_kInput) { #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_AUDIO_INPUT_N) return Steinberg_kInvalidArgument; *bus = busInfoAudioInput[index]; return Steinberg_kResultTrue; #endif } else if (dir == Steinberg_Vst_BusDirections_kOutput) { #if DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N) return Steinberg_kInvalidArgument; *bus = busInfoAudioOutput[index]; return Steinberg_kResultTrue; #endif } } else if (type == Steinberg_Vst_MediaTypes_kEvent) { if (dir == Steinberg_Vst_BusDirections_kInput) { #if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_MIDI_INPUT_N) return Steinberg_kInvalidArgument; *bus = busInfoMidiInput[index]; return Steinberg_kResultTrue; #endif } else if (dir == Steinberg_Vst_BusDirections_kOutput) { #if DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_MIDI_OUTPUT_N) return Steinberg_kInvalidArgument; *bus = busInfoMidiOutput[index]; return Steinberg_kResultTrue; #endif } } return Steinberg_kInvalidArgument; } static Steinberg_tresult pluginGetRoutingInfo(void* thisInterface, struct Steinberg_Vst_RoutingInfo* inInfo, struct Steinberg_Vst_RoutingInfo* outInfo) { (void)thisInterface; (void)inInfo; (void)outInfo; TRACE("plugin get routing info\n"); return Steinberg_kNotImplemented; } static Steinberg_tresult pluginActivateBus(void* thisInterface, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, Steinberg_TBool state) { TRACE("plugin activate bus\n"); if (index < 0) return Steinberg_kInvalidArgument; pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)); if (type == Steinberg_Vst_MediaTypes_kAudio) { if (dir == Steinberg_Vst_BusDirections_kInput) { #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_AUDIO_INPUT_N) return Steinberg_kInvalidArgument; p->inputsActive[index] = state; return Steinberg_kResultTrue; #endif } else if (dir == Steinberg_Vst_BusDirections_kOutput) { #if DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N) return Steinberg_kInvalidArgument; p->outputsActive[index] = state; return Steinberg_kResultTrue; #endif } } else if (type == Steinberg_Vst_MediaTypes_kEvent) { if (dir == Steinberg_Vst_BusDirections_kInput) { #if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_MIDI_INPUT_N) return Steinberg_kInvalidArgument; p->midiInputsActive[index] = state; return Steinberg_kResultTrue; #endif } else if (dir == Steinberg_Vst_BusDirections_kOutput) { #if DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 if (index >= DATA_PRODUCT_BUSES_MIDI_OUTPUT_N) return Steinberg_kInvalidArgument; p->midiOutputsActive[index] = state; return Steinberg_kResultTrue; #endif } } return Steinberg_kInvalidArgument; } static Steinberg_tresult pluginSetActive(void* thisInterface, Steinberg_TBool state) { TRACE("plugin set active\n"); pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)); if (p->mem != NULL) { free(p->mem); p->mem = NULL; } if (state) { #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N + DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N + DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 p->neededBusesActive = 1; # if DATA_PRODUCT_BUSES_AUDIO_INPUT_N > 0 for (size_t i = 0; p->neededBusesActive && i < DATA_PRODUCT_BUSES_AUDIO_INPUT_N; i++) if (!p->inputsActive[i] && (busInfoAudioInput[i].flags & Steinberg_Vst_BusInfo_BusFlags_kDefaultActive)) p->neededBusesActive = 0; # endif # if DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N > 0 for (size_t i = 0; p->neededBusesActive && i < DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N; i++) if (!p->outputsActive[i] && (busInfoAudioOutput[i].flags & Steinberg_Vst_BusInfo_BusFlags_kDefaultActive)) p->neededBusesActive = 0; # endif # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 for (size_t i = 0; p->neededBusesActive && i < DATA_PRODUCT_BUSES_MIDI_INPUT_N; i++) if (!p->midiInputsActive[i] && (busInfoMidiInput[i].flags & Steinberg_Vst_BusInfo_BusFlags_kDefaultActive)) p->neededBusesActive = 0; # endif # if DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 for (size_t i = 0; p->neededBusesActive && i < DATA_PRODUCT_BUSES_MIDI_OUTPUT_N; i++) if (!p->midiOutputsActive[i] && (busInfoMidiOutput[i].flags & Steinberg_Vst_BusInfo_BusFlags_kDefaultActive)) p->neededBusesActive = 0; # endif #endif plugin_set_sample_rate(&p->p, p->nextSampleRate); size_t req = plugin_mem_req(&p->p); if (req != 0) { p->mem = malloc(req); if (p->mem == NULL) return Steinberg_kOutOfMemory; plugin_mem_set(&p->p, p->mem); } p->curSampleRate = p->nextSampleRate; p->lastSampleRate = p->nextSampleRate; plugin_reset(&p->p); } else p->curSampleRate = 0.f; return Steinberg_kResultOk; } // https://stackoverflow.com/questions/2100331/macro-definition-to-determine-big-endian-or-little-endian-machine #define IS_BIG_ENDIAN (!(union { uint16_t u16; unsigned char c; }){ .u16 = 1 }.c) // https://stackoverflow.com/questions/2182002/how-to-convert-big-endian-to-little-endian-in-c-without-using-library-functions #define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24)) static Steinberg_tresult pluginSetState(void* thisInterface, struct Steinberg_IBStream* state) { TRACE("plugin set state\n"); if (state == NULL) return Steinberg_kResultFalse; char *data; Steinberg_int64 length; if (stateRead(state, &data, &length) != 0) return Steinberg_kResultFalse; pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)); #ifdef DATA_STATE_DSP_CUSTOM plugin_state_callbacks cbs = { /* .handle = */ (void *)p, /* .lock = */ pluginStateLockCb, /* .unlock = */ pluginStateUnlockCb, /* .write = */ NULL, # if DATA_PRODUCT_PARAMETERS_IN_N > 0 /* .set_parameter = */ pluginStateSetParameterCb # endif }; int err = plugin_state_load(&cbs, p->curSampleRate, data, length); #else // we need to provide a default implementation because of certain hosts (e.g. Ardour) int err = 0; if (length != DATA_PRODUCT_PARAMETERS_IN_N * 4) { if (data) free(data); return Steinberg_kResultFalse; } # if DATA_PRODUCT_PARAMETERS_IN_N > 0 for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) { union { float f; uint32_t u; } v; v.u = ((uint32_t *)data)[i]; if (IS_BIG_ENDIAN) v.u = SWAP_UINT32(v.u); if (isnan(v.f)) { free(data); return Steinberg_kResultFalse; } } pluginStateLockCb(p); for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) { union { float f; uint32_t u; } v; v.u = ((uint32_t *)data)[i]; if (IS_BIG_ENDIAN) v.u = SWAP_UINT32(v.u); pluginStateSetParameterCb(p, parameterInData[i].index, v.f); } pluginStateUnlockCb(p); # endif #endif if (data) free(data); TRACE(err == 0 ? " ok\n" : " err\n"); return err == 0 ? Steinberg_kResultOk : Steinberg_kResultFalse; } static Steinberg_tresult pluginGetState(void* thisInterface, struct Steinberg_IBStream* state) { TRACE("plugin get state\n"); if (state == NULL) return Steinberg_kResultFalse; pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIComponent)); p->state = state; #ifdef DATA_STATE_DSP_CUSTOM plugin_state_callbacks cbs = { /* .handle = */ (void *)p, /* .lock = */ pluginStateLockCb, /* .unlock = */ pluginStateUnlockCb, /* .write = */ pluginStateWriteCb, # if DATA_PRODUCT_PARAMETERS_IN_N > 0 /* .set_parameter = */ NULL # endif }; int err = plugin_state_save(&p->p, &cbs, p->lastSampleRate); #else // we need to provide a default implementation because of certain hosts (e.g. Ardour) int err = 0; # if DATA_PRODUCT_PARAMETERS_IN_N > 0 size_t length = DATA_PRODUCT_PARAMETERS_IN_N * 4; char *data = malloc(length); if (data == NULL) return Steinberg_kResultFalse; pluginStateLockCb(p); for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) { union { float f; uint32_t u; } v; v.f = p->parametersInSync[i]; if (IS_BIG_ENDIAN) v.u = SWAP_UINT32(v.u); ((uint32_t *)data)[i] = v.u; } pluginStateUnlockCb(p); err = pluginStateWriteCb(p, data, length); # endif #endif TRACE(err == 0 ? " ok" : " err"); return err == 0 ? Steinberg_kResultOk : Steinberg_kResultFalse; } static Steinberg_Vst_IComponentVtbl pluginVtblIComponent = { /* FUnknown */ /* .queryInterface = */ pluginIComponentQueryInterface, /* .addRef = */ pluginIComponentAddRef, /* .release = */ pluginIComponentRelease, /* IPluginBase */ /* .initialize = */ pluginInitialize, /* .terminate = */ pluginTerminate, /* IComponent */ /* .getControllerClassId = */ pluginGetControllerClassId, /* .setIoMode = */ pluginSetIoMode, /* .getBusCount = */ pluginGetBusCount, /* .getBusInfo = */ pluginGetBusInfo, /* .getRoutingInfo = */ pluginGetRoutingInfo, /* .activateBus = */ pluginActivateBus, /* .setActive = */ pluginSetActive, /* .setState = */ pluginSetState, /* .getState = */ pluginGetState }; static Steinberg_tresult pluginIAudioProcessorQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) { TRACE("plugin IAudioProcessor queryInterface %p\n", thisInterface); return pluginQueryInterface((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIAudioProcessor)), iid, obj); } static Steinberg_uint32 pluginIAudioProcessorAddRef(void *thisInterface) { TRACE("plugin IAudioProcessor addRef %p\n", thisInterface); return pluginAddRef((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIAudioProcessor))); } static Steinberg_uint32 pluginIAudioProcessorRelease(void *thisInterface) { TRACE("plugin IAudioProcessor release %p\n", thisInterface); return pluginRelease((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIAudioProcessor))); } static Steinberg_tresult pluginSetBusArrangements(void* thisInterface, Steinberg_Vst_SpeakerArrangement* inputs, Steinberg_int32 numIns, Steinberg_Vst_SpeakerArrangement* outputs, Steinberg_int32 numOuts) { (void)thisInterface; TRACE("plugin IAudioProcessor set bus arrangements\n"); if (numIns < 0 || numOuts < 0) return Steinberg_kInvalidArgument; if (numIns != DATA_PRODUCT_BUSES_AUDIO_INPUT_N || numOuts != DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N) return Steinberg_kResultFalse; #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N > 0 for (Steinberg_int32 i = 0; i < numIns; i++) if ((busInfoAudioInput[i].channelCount == 1 && inputs[i] != Steinberg_Vst_SpeakerArr_kMono) || (busInfoAudioInput[i].channelCount == 2 && inputs[i] != Steinberg_Vst_SpeakerArr_kStereo)) return Steinberg_kResultFalse; #else (void)inputs; #endif #if DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N > 0 for (Steinberg_int32 i = 0; i < numOuts; i++) if ((busInfoAudioOutput[i].channelCount == 1 && outputs[i] != Steinberg_Vst_SpeakerArr_kMono) || (busInfoAudioOutput[i].channelCount == 2 && outputs[i] != Steinberg_Vst_SpeakerArr_kStereo)) return Steinberg_kResultFalse; #else (void)outputs; #endif return Steinberg_kResultTrue; } static Steinberg_tresult pluginGetBusArrangement(void* thisInterface, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, Steinberg_Vst_SpeakerArrangement* arr) { (void)thisInterface; TRACE("plugin IAudioProcessor get bus arrangement\n"); #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N > 0 if (dir == Steinberg_Vst_BusDirections_kInput && index >= 0 && index < DATA_PRODUCT_BUSES_AUDIO_INPUT_N) { *arr = busInfoAudioInput[index].channelCount == 1 ? Steinberg_Vst_SpeakerArr_kMono : Steinberg_Vst_SpeakerArr_kStereo; return Steinberg_kResultTrue; } #endif #if DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N > 0 if (dir == Steinberg_Vst_BusDirections_kOutput && index >= 0 && index < DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N) { *arr = busInfoAudioOutput[index].channelCount == 1 ? Steinberg_Vst_SpeakerArr_kMono : Steinberg_Vst_SpeakerArr_kStereo; return Steinberg_kResultTrue; } #endif return Steinberg_kInvalidArgument; } static Steinberg_tresult pluginCanProcessSampleSize(void* thisInterface, Steinberg_int32 symbolicSampleSize) { (void)thisInterface; TRACE("plugin IAudioProcessor can process sample size\n"); return symbolicSampleSize == Steinberg_Vst_SymbolicSampleSizes_kSample32 ? Steinberg_kResultOk : Steinberg_kNotImplemented; } static Steinberg_uint32 pluginGetLatencySamples(void* thisInterface) { (void)thisInterface; TRACE("plugin IAudioProcessor get latency samples\n"); //TBD return 0; } static Steinberg_tresult pluginSetupProcessing(void* thisInterface, struct Steinberg_Vst_ProcessSetup* setup) { TRACE("plugin IAudioProcessor setup processing\n"); pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIAudioProcessor)); p->nextSampleRate = (float)setup->sampleRate; return Steinberg_kResultOk; } static Steinberg_tresult pluginSetProcessing(void* thisInterface, Steinberg_TBool state) { (void)thisInterface; (void)state; TRACE("plugin IAudioProcessor set processing\n"); return Steinberg_kNotImplemented; } static void processParams(pluginInstance *p, struct Steinberg_Vst_ProcessData *data, char before) { #if DATA_PRODUCT_PARAMETERS_IN_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 _Bool locked = !atomic_flag_test_and_set(&p->syncLockFlag); if (locked) { if (!p->synced) { if (p->loaded) { for (uint32_t j = 0; j < DATA_PRODUCT_PARAMETERS_IN_N; j++) { p->parametersIn[j] = p->parametersInSync[j]; plugin_set_parameter(&p->p, parameterInData[j].index, p->parametersIn[j]); } } else { for (uint32_t j = 0; j < DATA_PRODUCT_PARAMETERS_IN_N; j++) if (p->parametersIn[j] != p->parametersInSync[j]) { p->parametersInSync[j] = p->parametersIn[j]; plugin_set_parameter(&p->p, parameterInData[j].index, p->parametersIn[j]); } } } p->synced = 1; p->loaded = 0; } if (data->inputParameterChanges == NULL) { if (locked) atomic_flag_clear(&p->syncLockFlag); return; } Steinberg_int32 n = data->inputParameterChanges->lpVtbl->getParameterCount(data->inputParameterChanges); for (Steinberg_int32 i = 0; i < n; i++) { struct Steinberg_Vst_IParamValueQueue *q = data->inputParameterChanges->lpVtbl->getParameterData(data->inputParameterChanges, i); if (q == NULL) continue; Steinberg_int32 c = q->lpVtbl->getPointCount(q); if (c <= 0) continue; Steinberg_int32 o; Steinberg_Vst_ParamValue v; if (q->lpVtbl->getPoint(q, before ? 0 : c - 1, &o, &v) != Steinberg_kResultTrue) continue; if (before ? o != 0 : o <= 0) continue; Steinberg_Vst_ParamID id = q->lpVtbl->getParameterId(q); int pi = parameterGetIndexById(id); # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (pi >= DATA_PRODUCT_PARAMETERS_N) { size_t j = pi - DATA_PRODUCT_PARAMETERS_N; size_t index = j / 3; uint8_t data[3]; switch (j & 0x3) { case 0: // channel pressure data[0] = 0xd0 /* | channel */; data[1] = (uint8_t)(127.0 * v); data[2] = 0; break; case 1: { // pitch bend change // MIDI spec: 0 = max down bend, 8192 = no bend, 16383 = max up bend // to make it symmetrical we have max down bend = 1 instead uint16_t x = (uint16_t)(16382.0 * v) + 1; data[0] = 0xe0 /* | channel */; data[1] = (x >> 7) & 0x7f; data[2] = x & 0x7f; } break; case 2: // mod wheel data[0] = 0xb0 /* | channel */; data[1] = 1; data[2] = (uint8_t)(127.0 * v); break; default: continue; } plugin_midi_msg_in(&p->p, midiInIndex[index], data); continue; } # endif # if DATA_PRODUCT_PARAMETERS_IN_N > 0 size_t ii = parameterInfoToDataIndex[pi]; v = parameterAdjust(parameterInData + ii, parameterMap(parameterInData + ii, v)); if (v != p->parametersIn[ii]) { p->parametersIn[ii] = v; if (locked) { p->parametersInSync[ii] = p->parametersIn[ii]; plugin_set_parameter(&p->p, parameterInData[ii].index, v); } } # endif } if (locked) atomic_flag_clear(&p->syncLockFlag); #endif } static Steinberg_tresult pluginProcess(void* thisInterface, struct Steinberg_Vst_ProcessData* data) { TRACE("plugin IAudioProcessor process\n"); #if defined(__aarch64__) uint64_t fpcr; __asm__ __volatile__ ("mrs %0, fpcr" : "=r"(fpcr)); __asm__ __volatile__ ("msr fpcr, %0" :: "r"(fpcr | 0x1000000)); // enable FZ #elif defined(__i386__) || defined(__x86_64__) const unsigned int flush_zero_mode = _MM_GET_FLUSH_ZERO_MODE(); const unsigned int denormals_zero_mode = _MM_GET_DENORMALS_ZERO_MODE(); _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); #endif pluginInstance *p = (pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIAudioProcessor)); processParams(p, data, 1); #if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (data->inputEvents != NULL) { Steinberg_int32 n = data->inputEvents->lpVtbl->getEventCount(data->inputEvents); for (Steinberg_int32 i = 0; i < n; i++) { struct Steinberg_Vst_Event ev; if (data->inputEvents->lpVtbl->getEvent(data->inputEvents, i, &ev) != Steinberg_kResultOk) continue; switch (ev.type) { case Steinberg_Vst_Event_EventTypes_kNoteOnEvent: { const uint8_t data[3] = { 0x90 | ev.Steinberg_Vst_Event_noteOn.channel, ev.Steinberg_Vst_Event_noteOn.pitch, (uint8_t)(127.f * ev.Steinberg_Vst_Event_noteOn.velocity) }; plugin_midi_msg_in(&p->p, midiInIndex[ev.busIndex], data); } break; case Steinberg_Vst_Event_EventTypes_kNoteOffEvent: { const uint8_t data[3] = { 0x80 | ev.Steinberg_Vst_Event_noteOff.channel, ev.Steinberg_Vst_Event_noteOff.pitch, (uint8_t)(127.f * ev.Steinberg_Vst_Event_noteOff.velocity) }; plugin_midi_msg_in(&p->p, midiInIndex[ev.busIndex], data); } break; case Steinberg_Vst_Event_EventTypes_kPolyPressureEvent: { const uint8_t data[3] = { 0xa0 | ev.Steinberg_Vst_Event_polyPressure.channel, ev.Steinberg_Vst_Event_polyPressure.pitch, (uint8_t)(127.f * ev.Steinberg_Vst_Event_polyPressure.pressure) }; plugin_midi_msg_in(&p->p, midiInIndex[ev.busIndex], data); } break; } } } #endif #if DATA_PRODUCT_BUSES_AUDIO_INPUT_N + DATA_PRODUCT_BUSES_AUDIO_OUTPUT_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N + DATA_PRODUCT_BUSES_MIDI_OUTPUT_N > 0 if (p->neededBusesActive) { # if DATA_PRODUCT_CHANNELS_AUDIO_INPUT_N > 0 const float **inputs = p->inputs; Steinberg_int32 ki = 0; for (Steinberg_int32 i = 0; i < data->numInputs; i++) for (Steinberg_int32 j = 0; j < data->inputs[i].numChannels; j++, ki++) inputs[ki] = data->inputs[i].Steinberg_Vst_AudioBusBuffers_channelBuffers32[j]; # else const float **inputs = NULL; # endif # if DATA_PRODUCT_CHANNELS_AUDIO_OUTPUT_N > 0 float **outputs = p->outputs; Steinberg_int32 ko = 0; for (Steinberg_int32 i = 0; i < data->numOutputs; i++) for (Steinberg_int32 j = 0; j < data->outputs[i].numChannels; j++, ko++) outputs[ko] = data->outputs[i].Steinberg_Vst_AudioBusBuffers_channelBuffers32[j]; # else float **outputs = NULL; # endif plugin_process(&p->p, inputs, outputs, data->numSamples); } else { # if DATA_PRODUCT_CHANNELS_AUDIO_OUTPUT_N > 0 for (Steinberg_int32 i = 0; i < data->numOutputs; i++) for (Steinberg_int32 j = 0; j < data->outputs[i].numChannels; j++) memset(data->outputs[i].Steinberg_Vst_AudioBusBuffers_channelBuffers32[j], 0, data->numSamples * sizeof(float)); # endif } #else plugin_process(&p->p, NULL, NULL, data->numSamples); #endif processParams(p, data, 0); #if DATA_PRODUCT_PARAMETERS_OUT_N > 0 for (Steinberg_int32 i = 0; i < DATA_PRODUCT_PARAMETERS_OUT_N; i++) { float v = plugin_get_parameter(&p->p, parameterOutData[i].index); if (v == p->parametersOut[i]) continue; p->parametersOut[i] = v; if (data->outputParameterChanges == NULL) continue; Steinberg_Vst_ParamID id = parameterInfo[parameterOutDataToInfoIndex[i]].id; Steinberg_int32 index; struct Steinberg_Vst_IParamValueQueue *q = data->outputParameterChanges->lpVtbl->addParameterData(data->outputParameterChanges, &id, &index); if (q == NULL) continue; q->lpVtbl->addPoint(q, data->numSamples - 1, parameterUnmap(parameterOutData + i, v), &index); } #else (void)plugin_get_parameter; #endif // TBD: latency + IComponentHandler::restartComponent (kLatencyChanged), see https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Workflow+Diagrams/Get+Latency+Call+Sequence.html #if defined(__aarch64__) __asm__ __volatile__ ("msr fpcr, %0" : : "r"(fpcr)); #elif defined(__i386__) || defined(__x86_64__) _MM_SET_FLUSH_ZERO_MODE(flush_zero_mode); _MM_SET_DENORMALS_ZERO_MODE(denormals_zero_mode); #endif #ifdef DATA_MESSAGING if (p->message_data_in_size > 0 || p->message_data_out_tosend) { if (mtx_trylock(&p->message_mutex) == thrd_success) { if (p->message_data_in_size > 0) { plugin_receive_from_ui(&(p->p), p->message_data_in, p->message_data_in_size); p->message_data_in_size = 0; } if (p->message_data_out_tosend) { // message_data_out_tosend is manipulated by audio thread only p->message_cmd = 1; cnd_signal(&p->message_cond); p->message_data_out_tosend = 0; } mtx_unlock(&p->message_mutex); } } #endif return Steinberg_kResultOk; } static Steinberg_uint32 pluginGetTailSamples(void* thisInterface) { (void)thisInterface; TRACE("plugin IAudioProcessor get tail samples\n"); //TBD return 0; } static Steinberg_Vst_IAudioProcessorVtbl pluginVtblIAudioProcessor = { /* FUnknown */ /* .queryInterface = */ pluginIAudioProcessorQueryInterface, /* .addRef = */ pluginIAudioProcessorAddRef, /* .release = */ pluginIAudioProcessorRelease, /* IAudioProcessor */ /* .setBusArrangements = */ pluginSetBusArrangements, /*. getBusArrangement = */ pluginGetBusArrangement, /* .canProcessSampleSize = */ pluginCanProcessSampleSize, /* .getLatencySamples = */ pluginGetLatencySamples, /* .setupProcessing = */ pluginSetupProcessing, /* .setProcessing = */ pluginSetProcessing, /* .process = */ pluginProcess, /* .getTailSamples = */ pluginGetTailSamples }; static Steinberg_tresult pluginIProcessContextRequirementsQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) { TRACE("plugin IProcessContextRequirements queryInterface %p\n", thisInterface); return pluginQueryInterface((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIProcessContextRequirements)), iid, obj); } static Steinberg_uint32 pluginIProcessContextRequirementsAddRef(void *thisInterface) { TRACE("plugin IComponent addRef %p\n", thisInterface); return pluginAddRef((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIProcessContextRequirements))); } static Steinberg_uint32 pluginIProcessContextRequirementsRelease(void *thisInterface) { TRACE("plugin IComponent release %p\n", thisInterface); return pluginRelease((pluginInstance *)((char *)thisInterface - offsetof(pluginInstance, vtblIProcessContextRequirements))); } static Steinberg_uint32 pluginGetProcessContextRequirements(void* thisInterface) { (void)thisInterface; // TBD return 0; } static Steinberg_Vst_IProcessContextRequirementsVtbl pluginVtblIProcessContextRequirements = { /* FUnknown */ /* .queryInterface = */ pluginIProcessContextRequirementsQueryInterface, /* .addRef = */ pluginIProcessContextRequirementsAddRef, /* .release = */ pluginIProcessContextRequirementsRelease, /* IProcessContextRequirements */ /* .getProcessContextRequirements = */ pluginGetProcessContextRequirements }; typedef struct plugView plugView; typedef struct controller { Steinberg_Vst_IEditControllerVtbl * vtblIEditController; // must stay first Steinberg_Vst_IMidiMappingVtbl * vtblIMidiMapping; #ifdef DATA_MESSAGING Steinberg_Vst_IConnectionPointVtbl *vtblIConnectionPoint; Steinberg_Vst_IConnectionPoint *connectedPoint; #endif Steinberg_uint32 refs; Steinberg_FUnknown * context; #if DATA_PRODUCT_PARAMETERS_IN_N > 0 double parametersIn[DATA_PRODUCT_PARAMETERS_IN_N]; #endif #if DATA_PRODUCT_PARAMETERS_OUT_N > 0 double parametersOut[DATA_PRODUCT_PARAMETERS_OUT_N]; #endif #if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 double parametersMidiIn[3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N]; #endif struct Steinberg_Vst_IComponentHandler * componentHandler; #ifdef DATA_UI plugView ** views; size_t viewsCount; #endif } controller; static Steinberg_Vst_IEditControllerVtbl controllerVtblIEditController; #if DATA_PRODUCT_PARAMETERS_N > 0 static void controllerGetParamDataValuePtrs(controller *ctrl, size_t index, ParameterData **p, double **pv) { # if (DATA_PRODUCT_PARAMETERS_IN_N > 0) && (DATA_PRODUCT_PARAMETERS_OUT_N > 0) if (parameterInfo[index].flags & Steinberg_Vst_ParameterInfo_ParameterFlags_kIsReadOnly) { *p = parameterOutData + parameterInfoToDataIndex[index]; *pv = ctrl->parametersOut + parameterInfoToDataIndex[index]; } else { *p = parameterInData + parameterInfoToDataIndex[index]; *pv = ctrl->parametersIn + parameterInfoToDataIndex[index]; } # elif DATA_PRODUCT_PARAMETERS_IN_N > 0 *p = parameterInData + parameterInfoToDataIndex[index]; *pv = ctrl->parametersIn + parameterInfoToDataIndex[index]; # else *p = parameterOutData + parameterInfoToDataIndex[index]; *pv = ctrl->parametersOut + parameterInfoToDataIndex[index]; # endif } #endif #ifdef DATA_STATE_DSP_CUSTOM static void controllerStateLockCb(void *handle) { (void)handle; } static void controllerStateUnlockCb(void *handle) { (void)handle; } # if DATA_PRODUCT_PARAMETERS_IN_N > 0 static void controllerStateSetParameterCb(void *handle, size_t index, float value) { size_t i = index; # ifdef DATA_PARAM_LATENCY_INDEX if (i >= DATA_PARAM_LATENCY_INDEX) i--; # endif controller *c = (controller *)handle; size_t ii = parameterInfoToDataIndex[i]; c->parametersIn[ii] = parameterAdjust(parameterInData + ii, value); } # endif #endif #ifdef DATA_UI # ifdef __linux__ # include typedef struct { Steinberg_ITimerHandlerVtbl * vtblITimerHandler; Steinberg_uint32 refs; void * data; void (*cb)(void *data); } timerHandler; static Steinberg_tresult timerHandlerQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) { TRACE("timerHandler queryInterface %p\n", thisInterface); // Same as above (pluginQueryInterface) size_t offset; if (memcmp(iid, Steinberg_FUnknown_iid, sizeof(Steinberg_TUID)) == 0 || memcmp(iid, Steinberg_ITimerHandler_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(timerHandler, vtblITimerHandler); else { TRACE(" not supported\n"); for (int i = 0; i < 16; i++) TRACE(" %x", iid[i]); TRACE("\n"); *obj = NULL; return Steinberg_kNoInterface; } timerHandler *t = (timerHandler *)((char *)thisInterface - offsetof(timerHandler, vtblITimerHandler)); *obj = (void *)((char *)t + offset); t->refs++; return Steinberg_kResultOk; } static Steinberg_uint32 timerHandlerAddRef(void *thisInterface) { TRACE("timerHandler addRef %p\n", thisInterface); timerHandler *t = (timerHandler *)((char *)thisInterface - offsetof(timerHandler, vtblITimerHandler)); t->refs++; return t->refs; } static Steinberg_uint32 timerHandlerRelease(void *thisInterface) { TRACE("timerHandler release %p\n", thisInterface); timerHandler *t = (timerHandler *)((char *)thisInterface - offsetof(timerHandler, vtblITimerHandler)); t->refs--; if (t->refs == 0) { TRACE(" free %p\n", (void *)t); free(t); return 0; } return t->refs; } static void timerHandlerOnTimer(void* thisInterface) { TRACE("timerHandler onTimer %p\n", thisInterface); timerHandler *t = (timerHandler *)((char *)thisInterface - offsetof(timerHandler, vtblITimerHandler)); t->cb(t->data); } static Steinberg_ITimerHandlerVtbl timerHandlerVtblITimerHandler = { /* FUnknown */ /* .queryInterface = */ timerHandlerQueryInterface, /* .addRef = */ timerHandlerAddRef, /* .release = */ timerHandlerRelease, /* ITimerHandler */ /* .onTimer = */ timerHandlerOnTimer }; # elif defined(__APPLE__) # include # include # include # endif typedef struct plugView { Steinberg_IPlugViewVtbl * vtblIPlugView; Steinberg_uint32 refs; Steinberg_IPlugFrame * frame; plugin_ui * ui; controller * ctrl; # ifdef __linux__ Steinberg_IRunLoop * runLoop; timerHandler timer; Display * display; # elif defined(__APPLE__) CFRunLoopTimerRef timer; # elif defined(_WIN32) || defined(__CYGWIN__) UINT_PTR timer; # endif } plugView; static Steinberg_tresult plugViewRemoved(void* thisInterface); static Steinberg_tresult plugViewQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) { TRACE("plugView queryInterface %p\n", thisInterface); // Same as above (pluginQueryInterface) size_t offset; if (memcmp(iid, Steinberg_FUnknown_iid, sizeof(Steinberg_TUID)) == 0 || memcmp(iid, Steinberg_IPlugView_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(plugView, vtblIPlugView); else { TRACE(" not supported\n"); for (int i = 0; i < 16; i++) { TRACE(" %x", iid[i]); } TRACE("\n"); *obj = NULL; return Steinberg_kNoInterface; } plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); *obj = (void *)((char *)v + offset); v->refs++; return Steinberg_kResultOk; } static Steinberg_uint32 plugViewAddRef(void *thisInterface) { TRACE("plugView addRef %p\n", thisInterface); plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); v->refs++; return v->refs; } static Steinberg_uint32 plugViewRelease(void *thisInterface) { TRACE("plugView IEditController release %p\n", thisInterface); plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); v->refs--; if (v->refs == 0) { TRACE(" free %p\n", (void *)v); if (v->ui) plugViewRemoved(thisInterface); for (size_t i = 0; i < v->ctrl->viewsCount; i++) if (v->ctrl->views[i] == v) { v->ctrl->views[i] = NULL; break; } free(v); return 0; } return v->refs; } static Steinberg_tresult plugViewIsPlatformTypeSupported(void* thisInterface, Steinberg_FIDString type) { (void)thisInterface; TRACE("plugView isPlatformTypeSupported %p %s\n", thisInterface, type); # if defined(_WIN32) || defined(__CYGWIN__) return strcmp(type, "HWND") ? Steinberg_kResultFalse : Steinberg_kResultTrue; # elif defined(__APPLE__) && defined(__MACH__) return strcmp(type, "NSView") ? Steinberg_kResultFalse : Steinberg_kResultTrue; # elif defined(__linux__) return strcmp(type, "X11EmbedWindowID") ? Steinberg_kResultFalse : Steinberg_kResultTrue; # else (void)type; return Steinberg_kResultFalse; # endif } # if DATA_PRODUCT_PARAMETERS_N > 0 # if DATA_PRODUCT_PARAMETERS_IN_N > 0 static void plugViewUpdateParameterIn(plugView *view, size_t index) { if (view && view->ui) plugin_ui_set_parameter(view->ui, parameterInData[index].index, view->ctrl->parametersIn[index]); } # endif # if DATA_PRODUCT_PARAMETERS_OUT_N > 0 static void plugViewUpdateParameterOut(plugView *view, size_t index) { if (view && view->ui) plugin_ui_set_parameter(view->ui, parameterOutData[index].index, view->ctrl->parametersOut[index]); } # endif static void plugViewUpdateAllParameters(plugView *view) { if (view == NULL || view->ui == NULL) return; # if DATA_PRODUCT_PARAMETERS_IN_N > 0 for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) plugin_ui_set_parameter(view->ui, parameterInData[i].index, view->ctrl->parametersIn[i]); # endif # if DATA_PRODUCT_PARAMETERS_OUT_N > 0 for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_OUT_N; i++) plugin_ui_set_parameter(view->ui, parameterOutData[i].index, view->ctrl->parametersOut[i]); # endif } static void plugViewSetParameterBeginCb(void *handle, size_t index, float value) { TRACE("set parameter begin cb\n"); # ifdef DATA_PARAM_LATENCY_INDEX if (index == DATA_PARAM_LATENCY_INDEX) return; index = index >= DATA_PARAM_LATENCY_INDEX ? index - 1 : index; # endif plugView *v = (plugView *)handle; ParameterData *p; double *pv; controllerGetParamDataValuePtrs(v->ctrl, index, &p, &pv); *pv = parameterAdjust(p, value); // let Reaper find the updated value v->ctrl->componentHandler->lpVtbl->beginEdit(v->ctrl->componentHandler, parameterInfo[index].id); v->ctrl->componentHandler->lpVtbl->performEdit(v->ctrl->componentHandler, parameterInfo[index].id, parameterUnmap(p, *pv)); } static void plugViewSetParameterCb(void *handle, size_t index, float value) { TRACE("set parameter cb\n"); # ifdef DATA_PARAM_LATENCY_INDEX if (index == DATA_PARAM_LATENCY_INDEX) return; index = index >= DATA_PARAM_LATENCY_INDEX ? index - 1 : index; # endif plugView *v = (plugView *)handle; ParameterData *p; double *pv; controllerGetParamDataValuePtrs(v->ctrl, index, &p, &pv); *pv = parameterAdjust(p, value); // let Reaper find the updated value v->ctrl->componentHandler->lpVtbl->performEdit(v->ctrl->componentHandler, parameterInfo[index].id, parameterUnmap(p, *pv)); } static void plugViewSetParameterEndCb(void *handle, size_t index, float value) { TRACE("set parameter end cb\n"); # ifdef DATA_PARAM_LATENCY_INDEX if (index == DATA_PARAM_LATENCY_INDEX) return; index = index >= DATA_PARAM_LATENCY_INDEX ? index - 1 : index; # endif plugView *v = (plugView *)handle; ParameterData *p; double *pv; controllerGetParamDataValuePtrs(v->ctrl, index, &p, &pv); *pv = parameterAdjust(p, value); // let Reaper find the updated value v->ctrl->componentHandler->lpVtbl->performEdit(v->ctrl->componentHandler, parameterInfo[index].id, parameterUnmap(p, *pv)); v->ctrl->componentHandler->lpVtbl->endEdit(v->ctrl->componentHandler, parameterInfo[index].id); } # endif # ifdef DATA_MESSAGING static char send_to_dsp (void *handle, const void *data, size_t bytes) { plugView *v = (plugView *)handle; if (!data || bytes == 0 || bytes > DATA_MESSAGING_MAX) { return 1; } if (!v->ctrl->connectedPoint || !v->ctrl->context) { return 1; } Steinberg_Vst_IConnectionPointVtbl *ov = (Steinberg_Vst_IConnectionPointVtbl*) v->ctrl->connectedPoint->lpVtbl; Steinberg_Vst_IHostApplication *app = (Steinberg_Vst_IHostApplication*) v->ctrl->context; Steinberg_Vst_IMessage *msg = NULL; app->lpVtbl->createInstance(app, (char*) Steinberg_Vst_IMessage_iid, (char*) Steinberg_Vst_IMessage_iid, (void**)&(msg)); if (!msg) return 1; Steinberg_Vst_IAttributeList *alist = msg->lpVtbl->getAttributes(msg); if (!alist) { msg->lpVtbl->release(msg); return 1; } alist->lpVtbl->setBinary(alist, "message_data", data, bytes); Steinberg_tresult r = ov->notify(v->ctrl->connectedPoint, msg); if (r == Steinberg_kResultFalse) { msg->lpVtbl->release(msg); return 1; } msg->lpVtbl->release(msg); return 0; } # endif # ifdef __APPLE__ static void plugViewTimerCb(CFRunLoopTimerRef timer, void *info) { (void)timer; plugin_ui_idle(((plugView *)info)->ui); } # elif defined(_WIN32) || defined(__CYGWIN__) static void plugViewTimerCb(HWND p1, UINT p2, UINT_PTR p3, DWORD p4) { (void)p1; (void)p2; (void)p4; plugin_ui_idle(((plugView *)p3)->ui); } # endif static Steinberg_tresult plugViewAttached(void* thisInterface, void* parent, Steinberg_FIDString type) { // GUI needs to be created here, see https://forums.steinberg.net/t/vst-and-hidpi/201916/3 TRACE("plugView attached %p\n", thisInterface); if (plugViewIsPlatformTypeSupported(thisInterface, type) != Steinberg_kResultOk) return Steinberg_kInvalidArgument; plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); if (v->ui) return Steinberg_kInvalidArgument; plugin_ui_callbacks cbs = { /* .handle = */ (void *)v, /* .format = */ "vst3", /* .get_bindir = */ getBindirCb, /* .get_datadir = */ getDatadirCb, # if DATA_PRODUCT_PARAMETERS_N > 0 /* .set_parameter_begin = */ plugViewSetParameterBeginCb, /* .set_parameter = */ plugViewSetParameterCb, /* .set_parameter_end = */ plugViewSetParameterEndCb, # endif # ifdef DATA_MESSAGING /* .send_to_dsp = */ send_to_dsp # endif }; v->ui = plugin_ui_create(1, parent, &cbs); if (!v->ui) return Steinberg_kResultFalse; # ifdef __linux__ v->display = XOpenDisplay(NULL); if (v->display == NULL) { plugin_ui_free(v->ui); v->ui = NULL; return Steinberg_kResultFalse; } if (v->runLoop->lpVtbl->registerTimer(v->runLoop, (struct Steinberg_ITimerHandler *)&v->timer, 20) != Steinberg_kResultOk) { XCloseDisplay(v->display); plugin_ui_free(v->ui); v->ui = NULL; return Steinberg_kResultFalse; } # elif defined(__APPLE__) CFRunLoopTimerContext ctx = { /* .version = */ 0, /* .info = */ v, /* .retain = */ NULL, /* .release = */ NULL, /* .copyDescription = */ NULL }; v->timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 20.0 / 1000.0, 0, 0, plugViewTimerCb, &ctx); CFRunLoopAddTimer(CFRunLoopGetCurrent(), v->timer, kCFRunLoopCommonModes); # elif defined(_WIN32) || defined(__CYGWIN__) v->timer = SetTimer((HWND)*((char **)v->ui), (UINT_PTR)v, 20, (TIMERPROC)plugViewTimerCb); if (v->timer == 0) { plugin_ui_free(v->ui); v->ui = NULL; return Steinberg_kResultFalse; } # endif # if DATA_PRODUCT_PARAMETERS_N > 0 plugViewUpdateAllParameters(v); # endif return Steinberg_kResultTrue; } static Steinberg_tresult plugViewRemoved(void* thisInterface) { TRACE("plugView removed %p\n", thisInterface); plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); # ifdef __linux__ v->runLoop->lpVtbl->unregisterTimer(v->runLoop, (struct Steinberg_ITimerHandler *)&v->timer); XCloseDisplay(v->display); # elif defined(__APPLE__) CFRunLoopTimerInvalidate(v->timer); CFRelease(v->timer); # elif defined(_WIN32) || defined(__CYGWIN__) KillTimer((HWND)(*((char **)v->ui)), (UINT_PTR)v); # endif plugin_ui_free(v->ui); v->ui = NULL; return Steinberg_kResultTrue; } static Steinberg_tresult plugViewOnWheel(void* thisInterface, float distance) { (void)thisInterface; (void)distance; TRACE("plugView onWheel %p\n", thisInterface); //TODO return Steinberg_kResultFalse; } static Steinberg_tresult plugViewOnKeyDown(void* thisInterface, Steinberg_char16 key, Steinberg_int16 keyCode, Steinberg_int16 modifiers) { (void)thisInterface; (void)key; (void)keyCode; (void)modifiers; TRACE("plugView onKeyDown %p\n", thisInterface); //TODO return Steinberg_kResultFalse; } static Steinberg_tresult plugViewOnKeyUp(void* thisInterface, Steinberg_char16 key, Steinberg_int16 keyCode, Steinberg_int16 modifiers) { (void)thisInterface; (void)key; (void)keyCode; (void)modifiers; TRACE("plugView onKeyUp %p\n", thisInterface); //TODO return Steinberg_kResultFalse; } static Steinberg_tresult plugViewGetSize(void* thisInterface, struct Steinberg_ViewRect* size) { TRACE("plugView getSize %p %p\n", thisInterface, (void*) size); if (!size) return Steinberg_kInvalidArgument; plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); size->left = 0; size->top = 0; size->right = 0; size->bottom = 0; if (v->ui) { # ifdef __linux__ XWindowAttributes attr; TRACE(" window %u\n", (Window)(*((char **)v->ui))); XGetWindowAttributes(v->display, (Window)(*((char **)v->ui)), &attr); size->right = attr.width; size->bottom = attr.height; # elif defined(__APPLE__) SEL boundsSelector = sel_registerName("bounds"); CGRect (*boundsMsgSend)(id, SEL) = (CGRect (*)(id, SEL))objc_msgSend; CGRect bounds = boundsMsgSend((id)(*((char **)v->ui)), boundsSelector); CGFloat width = bounds.size.width; CGFloat height = bounds.size.height; size->right = width; size->bottom = height; # elif defined(_WIN32) || defined(__CYGWIN__) RECT rect; if (GetWindowRect((HWND)*((char **)v->ui), &rect)) { size->right = rect.right - rect.left; size->bottom = rect.bottom - rect.top; } # endif } if (!v->ui || size->right < 1 || size->bottom < 1) { uint32_t width, height; plugin_ui_get_default_size(&width, &height); size->right = width; size->bottom = height; } TRACE(" %u x %u\n", size->right, size->bottom); return Steinberg_kResultTrue; } static Steinberg_tresult plugViewOnSize(void* thisInterface, struct Steinberg_ViewRect* newSize) { TRACE("plugView onSize %p %d %d\n", thisInterface, newSize->right - newSize->left, newSize->bottom - newSize->top); plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); // TODO: if not resizable by both user and plugin just return false if (!v->ui) return Steinberg_kResultFalse; # ifdef __linux__ TRACE(" window %u\n", (Window)(*((char **)v->ui))); XResizeWindow(v->display, (Window)(*((char **)v->ui)), newSize->right - newSize->left, newSize->bottom - newSize->top); # elif defined(__APPLE__) CGSize size = { newSize->right - newSize->left, newSize->bottom - newSize->top }; void (*f)(id, SEL, CGSize) = (void (*)(id, SEL, CGSize))objc_msgSend; f((id)(*((char **)v->ui)), sel_getUid("setFrameSize:"), size); # elif defined(_WIN32) || defined(__CYGWIN__) SetWindowPos((HWND)*((char **)v->ui), HWND_TOP, 0, 0, newSize->right - newSize->left, newSize->bottom - newSize->top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); # endif return Steinberg_kResultTrue; } static Steinberg_tresult plugViewOnFocus(void* thisInterface, Steinberg_TBool state) { (void)thisInterface; (void)state; TRACE("plugView onFocus %p\n", thisInterface); //TODO return Steinberg_kResultFalse; } static Steinberg_tresult plugViewSetFrame(void* thisInterface, struct Steinberg_IPlugFrame* frame) { TRACE("plugView setFrame %p\n", thisInterface); plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); v->frame = frame; # ifdef __linux__ if (v->frame) { if (v->frame->lpVtbl->queryInterface(v->frame, Steinberg_IRunLoop_iid, (void **)&v->runLoop) != Steinberg_kResultOk) return Steinberg_kResultFalse; v->frame->lpVtbl->release(v->frame); } # endif return Steinberg_kResultTrue; } static Steinberg_tresult plugViewCanResize(void* thisInterface) { (void) thisInterface; TRACE("plugView canResize %p\n", thisInterface); # if DATA_UI_USER_RESIZABLE return Steinberg_kResultTrue; # else return Steinberg_kResultFalse; # endif } static Steinberg_tresult plugViewCheckSizeConstraint(void* thisInterface, struct Steinberg_ViewRect* rect) { (void)thisInterface; (void)rect; TRACE("plugView chekSizeContraint %p\n", thisInterface); # if DATA_UI_USER_RESIZABLE return Steinberg_kResultTrue; # else # ifdef __linux__ plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); XWindowAttributes attr; XGetWindowAttributes(v->display, (Window)(*((char **)v->ui)), &attr); rect->right = rect->left + attr.width; rect->bottom = rect->top + attr.height; # endif return Steinberg_kResultFalse; # endif } # ifdef __linux__ static void plugViewOnTimer(void *thisInterface) { TRACE("plugView onTimer %p\n", thisInterface); plugView *v = (plugView *)((char *)thisInterface - offsetof(plugView, vtblIPlugView)); // Bitwig doesn't call onSize() as it should, so we compare the editor and parent window and resize if needed Window w = (Window)(*((char **)v->ui)); Window root, parent, *children; unsigned nchildren; if (XQueryTree(v->display, w, &root, &parent, &children, &nchildren)) { if (children) XFree(children); XWindowAttributes parent_attr, editor_attr; XGetWindowAttributes(v->display, parent, &parent_attr); XGetWindowAttributes(v->display, w, &editor_attr); if (parent_attr.width != editor_attr.width || parent_attr.height != editor_attr.height) XResizeWindow(v->display, w, parent_attr.width, parent_attr.height); } plugin_ui_idle(v->ui); } # endif static Steinberg_IPlugViewVtbl plugViewVtblIPlugView = { /* FUnknown */ /* .queryInterface = */ plugViewQueryInterface, /* .addRef = */ plugViewAddRef, /* .release = */ plugViewRelease, /* IPlugView */ /* .isPlatformTypeSupported = */ plugViewIsPlatformTypeSupported, /* .attached = */ plugViewAttached, /* .removed = */ plugViewRemoved, /* .onWheel = */ plugViewOnWheel, /* .onKeyDown = */ plugViewOnKeyDown, /* .onKeyUp = */ plugViewOnKeyUp, /* .getSize = */ plugViewGetSize, /* .onSize = */ plugViewOnSize, /* .onFocus = */ plugViewOnFocus, /* .setFrame = */ plugViewSetFrame, /* .canResize = */ plugViewCanResize, /* .checkSizeConstraint = */ plugViewCheckSizeConstraint }; #endif static Steinberg_Vst_IMidiMappingVtbl controllerVtblIMidiMapping; static Steinberg_tresult controllerQueryInterface(controller *c, const Steinberg_TUID iid, void ** obj) { // Same as above (pluginQueryInterface) size_t offset; if (memcmp(iid, Steinberg_FUnknown_iid, sizeof(Steinberg_TUID)) == 0 || memcmp(iid, Steinberg_IPluginBase_iid, sizeof(Steinberg_TUID)) == 0 || memcmp(iid, Steinberg_Vst_IEditController_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(controller, vtblIEditController); else if (memcmp(iid, Steinberg_Vst_IMidiMapping_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(controller, vtblIMidiMapping); #ifdef DATA_MESSAGING else if (memcmp(iid, Steinberg_Vst_IConnectionPoint_iid, sizeof(Steinberg_TUID)) == 0) offset = offsetof(controller, vtblIConnectionPoint); #endif else { TRACE(" not supported\n"); for (int i = 0; i < 16; i++) { TRACE(" %x", iid[i]); } TRACE("\n"); *obj = NULL; return Steinberg_kNoInterface; } *obj = (void *)((char *)c + offset); c->refs++; return Steinberg_kResultOk; } static Steinberg_uint32 controllerAddRef(controller *c) { c->refs++; return c->refs; } static Steinberg_uint32 controllerRelease(controller *c) { c->refs--; if (c->refs == 0) { TRACE(" free %p\n", (void *)c); #ifdef DATA_UI if (c->views) { for (size_t i = 0; i < c->viewsCount; i++) if (c->views[i]) // this should not happen but you never know plugViewRelease(c->views[i]); free(c->views); } #endif free(c); return 0; } return c->refs; } static Steinberg_tresult controllerIEditControllerQueryInterface(void* thisInterface, const Steinberg_TUID iid, void** obj) { TRACE("controller IEditController queryInterface %p\n", thisInterface); return controllerQueryInterface((controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)), iid, obj); } static Steinberg_uint32 controllerIEditControllerAddRef(void* thisInterface) { TRACE("controller IEditController addRef %p\n", thisInterface); return controllerAddRef((controller *)((char *)thisInterface - offsetof(controller, vtblIEditController))); } static Steinberg_uint32 controllerIEditControllerRelease(void* thisInterface) { TRACE("controller IEditController release %p\n", thisInterface); return controllerRelease((controller *)((char *)thisInterface - offsetof(controller, vtblIEditController))); } static Steinberg_tresult controllerInitialize(void* thisInterface, struct Steinberg_FUnknown* context) { TRACE("controller initialize\n"); controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)); if (c->context != NULL) return Steinberg_kResultFalse; c->context = context; #if DATA_PRODUCT_PARAMETERS_IN_N > 0 for (int i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) c->parametersIn[i] = parameterInData[i].def; #endif #if DATA_PRODUCT_PARAMETERS_OUT_N > 0 for (int i = 0; i < DATA_PRODUCT_PARAMETERS_OUT_N; i++) c->parametersOut[i] = parameterOutData[i].def; #endif #if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 for (int i = 0; i < 3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N; i += 3) { c->parametersMidiIn[i] = 0.0; c->parametersMidiIn[i + 1] = 0.5; c->parametersMidiIn[i + 2] = 0.0; } #endif return Steinberg_kResultOk; } static Steinberg_tresult controllerTerminate(void* thisInterface) { TRACE("controller terminate\n"); controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)); c->context = NULL; return Steinberg_kResultOk; } static Steinberg_tresult controllerSetComponentState(void* thisInterface, struct Steinberg_IBStream* state) { TRACE("controller set component state %p %p\n", thisInterface, (void *)state); if (state == NULL) return Steinberg_kResultFalse; char *data; Steinberg_int64 length; if (stateRead(state, &data, &length) != 0) return Steinberg_kResultFalse; controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)); #ifdef DATA_STATE_DSP_CUSTOM plugin_state_callbacks cbs = { /* .handle = */ (void *)c, /* .lock = */ controllerStateLockCb, /* .unlock = */ controllerStateUnlockCb, /* .write = */ NULL, # if DATA_PRODUCT_PARAMETERS_IN_N > 0 /* .set_parameter = */ controllerStateSetParameterCb # endif }; int err = plugin_state_load(&cbs, -1.f, data, length); // -1.f means "does not apply" #else // we need to provide a default implementation because of certain hosts (e.g. Reaper) int err = 0; # if DATA_PRODUCT_PARAMETERS_IN_N > 0 for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) { union { float f; uint32_t u; } v; v.u = ((uint32_t *)data)[i]; if (IS_BIG_ENDIAN) v.u = SWAP_UINT32(v.u); if (isnan(v.f)) { free(data); return Steinberg_kResultFalse; } } for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) { union { float f; uint32_t u; } v; v.u = ((uint32_t *)data)[i]; if (IS_BIG_ENDIAN) v.u = SWAP_UINT32(v.u); c->parametersIn[i] = parameterAdjust(parameterInData + i, v.f); } # endif #endif if (data) free(data); if (err != 0) return Steinberg_kResultFalse; #if (DATA_PRODUCT_PARAMETERS_IN_N > 0) && defined(DATA_UI) if (c->views) for (size_t i = 0; i < DATA_PRODUCT_PARAMETERS_IN_N; i++) for (size_t j = 0; j < c->viewsCount; j++) plugViewUpdateParameterIn(c->views[j], i); #endif TRACE(" ok\n"); return Steinberg_kResultOk; } static Steinberg_tresult controllerSetState(void* thisInterface, struct Steinberg_IBStream* state) { (void)thisInterface; (void)state; TRACE("controller set state\n"); return Steinberg_kNotImplemented; } static Steinberg_tresult controllerGetState(void* thisInterface, struct Steinberg_IBStream* state) { (void)thisInterface; (void)state; TRACE("controller get state\n"); return Steinberg_kNotImplemented; } static Steinberg_int32 controllerGetParameterCount(void* thisInterface) { (void)thisInterface; TRACE("controller get parameter count\n"); return DATA_PRODUCT_PARAMETERS_N + 3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N; } static Steinberg_tresult controllerGetParameterInfo(void* thisInterface, Steinberg_int32 paramIndex, struct Steinberg_Vst_ParameterInfo* info) { (void)thisInterface; TRACE("controller get parameter info\n"); #if DATA_PRODUCT_PARAMETERS_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (paramIndex < 0 || paramIndex >= DATA_PRODUCT_PARAMETERS_N + 3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N) return Steinberg_kResultFalse; *info = parameterInfo[paramIndex]; return Steinberg_kResultTrue; #else (void)paramIndex; (void)info; return Steinberg_kResultFalse; #endif } static void dToStr(double v, Steinberg_Vst_String128 s, int precision) { int i = 0; if (v < 0.0) { s[0] = '-'; v = -v; i++; } if (v < 1.0) { s[i] = '0'; i++; } else { double x = 1.0; while (x <= v) x *= 10.0; x *= 0.1; while (x >= 1.0) { char c = v / x; s[i] = c + '0'; i++; v -= c * x; x *= 0.1; } } s[i] = '.'; i++; double x = 0.1; while (precision != 0) { char c = v / x; s[i] = c + '0'; i++; v -= c * x; x *= 0.1; precision--; } s[i] = '\0'; } static Steinberg_tresult controllerGetParamStringByValue(void* thisInterface, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue valueNormalized, Steinberg_Vst_String128 string) { (void)thisInterface; TRACE("controller get param string by value\n"); #if DATA_PRODUCT_PARAMETERS_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 int pi = parameterGetIndexById(id); if (pi >= DATA_PRODUCT_PARAMETERS_N + 3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N || pi < 0) return Steinberg_kResultFalse; dToStr(parameterMapByIndex(pi, valueNormalized), string, 2); return Steinberg_kResultTrue; #else (void)id; (void)valueNormalized; (void)string; return Steinberg_kResultFalse; #endif } void TCharToD(Steinberg_Vst_TChar* s, double *v) { int i = 0; *v = 0.0; if (s[0] == '-') { *v = -0.0; i++; } while (s[i] >= '0' && s[i] <= '9') { char d = s[i] - '0'; i++; *v = 10.0 * *v + d; } if (s[i] != '.') return; i++; double x = 1.0; while (s[i] >= '0' && s[i] <= '9') { char d = s[i] - '0'; i++; x *= 0.1; *v = *v + d * x; } } static Steinberg_tresult controllerGetParamValueByString(void* thisInterface, Steinberg_Vst_ParamID id, Steinberg_Vst_TChar* string, Steinberg_Vst_ParamValue* valueNormalized) { (void)thisInterface; TRACE("controller get param value by string\n"); #if DATA_PRODUCT_PARAMETERS_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 int pi = parameterGetIndexById(id); if (pi >= DATA_PRODUCT_PARAMETERS_N + 3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N || pi < 0) return Steinberg_kResultFalse; double v; TCharToD(string, &v); *valueNormalized = parameterUnmapByIndex(pi, v); return Steinberg_kResultTrue; #else (void)id; (void)string; (void)valueNormalized; return Steinberg_kResultFalse; #endif } static Steinberg_Vst_ParamValue controllerNormalizedParamToPlain(void* thisInterface, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue valueNormalized) { (void)thisInterface; TRACE("controller normalized param to plain\n"); #if DATA_PRODUCT_PARAMETERS_N > 0 int pi = parameterGetIndexById(id); return parameterMapByIndex(pi, valueNormalized); #else (void)id; (void)valueNormalized; return 0.0; #endif } static Steinberg_Vst_ParamValue controllerPlainParamToNormalized(void* thisInterface, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue plainValue) { (void)thisInterface; TRACE("controller plain param to normalized\n"); #if DATA_PRODUCT_PARAMETERS_N > 0 int pi = parameterGetIndexById(id); return parameterUnmapByIndex(pi, plainValue); #else (void)id; (void)plainValue; return 0.0; #endif } static Steinberg_Vst_ParamValue controllerGetParamNormalized(void* thisInterface, Steinberg_Vst_ParamID id) { TRACE("controller get param normalized\n"); #if DATA_PRODUCT_PARAMETERS_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)); int pi = parameterGetIndexById(id); # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 # if DATA_PRODUCT_PARAMETERS_N > 0 if (pi >= DATA_PRODUCT_PARAMETERS_N) # endif return c->parametersMidiIn[pi - DATA_PRODUCT_PARAMETERS_N]; # endif ParameterData *p; double *pv; controllerGetParamDataValuePtrs(c, pi, &p, &pv); return parameterUnmap(p, *pv); #else (void)thisInterface; (void)id; return 0.0; #endif } static Steinberg_tresult controllerSetParamNormalized(void* thisInterface, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue value) { TRACE("controller set param normalized\n"); #if DATA_PRODUCT_PARAMETERS_N + DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 int pi = parameterGetIndexById(id); if (pi >= DATA_PRODUCT_PARAMETERS_N + 3 * DATA_PRODUCT_BUSES_MIDI_INPUT_N || pi < 0) return Steinberg_kResultFalse; controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)); # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 if (pi >= DATA_PRODUCT_PARAMETERS_N) { c->parametersMidiIn[pi - DATA_PRODUCT_PARAMETERS_N] = value; } else { # endif # if DATA_PRODUCT_PARAMETERS_N > 0 ParameterData *p; double *pv; controllerGetParamDataValuePtrs(c, pi, &p, &pv); *pv = parameterAdjust(p, parameterMap(p, value)); # endif # if DATA_PRODUCT_BUSES_MIDI_INPUT_N > 0 } # endif # if defined(DATA_UI) && (DATA_PRODUCT_PARAMETERS_N > 0) if (pi < DATA_PRODUCT_PARAMETERS_N) for (size_t i = 0; i < c->viewsCount; i++) if(c->views[i]) { # if DATA_PRODUCT_PARAMETERS_IN_N == 0 plugViewUpdateParameterOut(c->views[i], parameterInfoToDataIndex[pi]); # elif DATA_PRODUCT_PARAMETERS_OUT_N == 0 plugViewUpdateParameterIn(c->views[i], parameterInfoToDataIndex[pi]); # else if (parameterInfo[pi].flags & Steinberg_Vst_ParameterInfo_ParameterFlags_kIsReadOnly) plugViewUpdateParameterOut(c->views[i], parameterInfoToDataIndex[pi]); else plugViewUpdateParameterIn(c->views[i], parameterInfoToDataIndex[pi]); # endif } # endif return Steinberg_kResultTrue; #else (void)thisInterface; (void)id; (void)value; return Steinberg_kResultFalse; #endif } static Steinberg_tresult controllerSetComponentHandler(void* thisInterface, struct Steinberg_Vst_IComponentHandler* handler) { TRACE("controller set component handler\n"); controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)); if (c->componentHandler != handler) { if (c->componentHandler != NULL) c->componentHandler->lpVtbl->release(c->componentHandler); c->componentHandler = handler; if (c->componentHandler != NULL) c->componentHandler->lpVtbl->addRef(c->componentHandler); } return Steinberg_kResultTrue; } static struct Steinberg_IPlugView* controllerCreateView(void* thisInterface, Steinberg_FIDString name) { TRACE("controller create view %s\n", name); #ifdef DATA_UI if (strcmp(name, "editor")) return NULL; controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIEditController)); size_t i; for (i = 0; i < c->viewsCount; i++) { if (c->views[i] != NULL) { TRACE("controllerCreateView: trying to create another view - not supported \n"); return NULL; } } plugView *view = malloc(sizeof(plugView)); if (view == NULL) return NULL; for (i = 0; i < c->viewsCount; i++) if (c->views[i] == NULL) break; if (i == c->viewsCount) { size_t cnt = i + 1; plugView **views = realloc(c->views, cnt * sizeof(plugView **)); if (views == NULL) { free(view); return NULL; } c->views = views; c->viewsCount = cnt; } c->views[i] = view; view->vtblIPlugView = &plugViewVtblIPlugView; view->refs = 1; view->frame = NULL; view->ui = NULL; view->ctrl = c; # ifdef __linux__ view->timer.vtblITimerHandler = &timerHandlerVtblITimerHandler; view->timer.refs = 1; view->timer.cb = plugViewOnTimer; view->timer.data = view; # endif return (struct Steinberg_IPlugView *)view; #else (void)thisInterface; (void)name; return NULL; #endif } static Steinberg_Vst_IEditControllerVtbl controllerVtblIEditController = { /* FUnknown */ /* .queryInterface = */ controllerIEditControllerQueryInterface, /* .addRef = */ controllerIEditControllerAddRef, /* .release = */ controllerIEditControllerRelease, /* IPluginBase */ /* .initialize = */ controllerInitialize, /* .terminate = */ controllerTerminate, /* IEditController */ /* .setComponentState = */ controllerSetComponentState, /* .setState = */ controllerSetState, /* .getState = */ controllerGetState, /* .getParameterCount = */ controllerGetParameterCount, /* .getParameterInfo = */ controllerGetParameterInfo, /* .getParamStringByValue = */ controllerGetParamStringByValue, /* .getParamValueByString = */ controllerGetParamValueByString, /* .normalizedParamToPlain = */ controllerNormalizedParamToPlain, /* .plainParamToNormalized = */ controllerPlainParamToNormalized, /* .getParamNormalized = */ controllerGetParamNormalized, /* .setParamNormalized = */ controllerSetParamNormalized, /* .setComponentHandler = */ controllerSetComponentHandler, /* .createView = */ controllerCreateView }; static Steinberg_tresult controllerIMidiMappingQueryInterface(void* thisInterface, const Steinberg_TUID iid, void** obj) { TRACE("controller IMidiMapping queryInterface %p\n", thisInterface); return controllerQueryInterface((controller *)((char *)thisInterface - offsetof(controller, vtblIMidiMapping)), iid, obj); } static Steinberg_uint32 controllerIMidiMappingAddRef(void* thisInterface) { TRACE("controller IMidiMapping addRef %p\n", thisInterface); return controllerAddRef((controller *)((char *)thisInterface - offsetof(controller, vtblIMidiMapping))); } static Steinberg_uint32 controllerIMidiMappingRelease(void* thisInterface) { TRACE("controller IMidiMapping release %p\n", thisInterface); return controllerRelease((controller *)((char *)thisInterface - offsetof(controller, vtblIMidiMapping))); } static Steinberg_tresult controllerGetMidiControllerAssignment(void* thisInterface, Steinberg_int32 busIndex, Steinberg_int16 channel, Steinberg_Vst_CtrlNumber midiControllerNumber, Steinberg_Vst_ParamID* id) { (void)thisInterface; (void)channel; TRACE("controller getMidiControllerAssignment\n"); if (busIndex < 0 || busIndex >= DATA_PRODUCT_BUSES_MIDI_INPUT_N) return Steinberg_kInvalidArgument; switch (midiControllerNumber) { case Steinberg_Vst_ControllerNumbers_kAfterTouch: *id = parameterInfo[DATA_PRODUCT_PARAMETERS_N + 3 * busIndex].id; return Steinberg_kResultTrue; break; case Steinberg_Vst_ControllerNumbers_kPitchBend: *id = parameterInfo[DATA_PRODUCT_PARAMETERS_N + 3 * busIndex + 1].id; return Steinberg_kResultTrue; break; case Steinberg_Vst_ControllerNumbers_kCtrlModWheel: *id = parameterInfo[DATA_PRODUCT_PARAMETERS_N + 3 * busIndex + 2].id; return Steinberg_kResultTrue; break; default: return Steinberg_kResultFalse; break; } } static Steinberg_Vst_IMidiMappingVtbl controllerVtblIMidiMapping = { /* FUnknown */ /* .queryInterface = */ controllerIMidiMappingQueryInterface, /* .addRef = */ controllerIMidiMappingAddRef, /* .release = */ controllerIMidiMappingRelease, /* IMidiMapping */ /* .getMidiControllerAssignment = */ controllerGetMidiControllerAssignment }; #ifdef DATA_MESSAGING static Steinberg_tresult controllerIConnectionPointQueryInterface(void* thisInterface, const Steinberg_TUID iid, void** obj) { TRACE("controller IConnectionPoint queryInterface %p\n", thisInterface); return controllerQueryInterface((controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint)), iid, obj); } static Steinberg_uint32 controllerIConnectionPointAddRef(void* thisInterface) { TRACE("controller IConnectionPoint addRef %p\n", thisInterface); return controllerAddRef((controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint))); } static Steinberg_uint32 controllerIConnectionPointRelease(void* thisInterface) { TRACE("controller IConnectionPoint release %p\n", thisInterface); return controllerRelease((controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint))); } static Steinberg_tresult controllerIConnectionPointConnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) { controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint)); if (!other) return Steinberg_kInvalidArgument; if (c->connectedPoint) return Steinberg_kResultFalse; c->connectedPoint = other; return Steinberg_kResultOk; } static Steinberg_tresult controllerIConnectionPointDisconnect(void* thisInterface, struct Steinberg_Vst_IConnectionPoint* other) { controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint)); if (c->connectedPoint && other == c->connectedPoint) { c->connectedPoint = NULL; return Steinberg_kResultOk; } return Steinberg_kResultFalse; } // This can get called after plugView creation but before attachment, so that ui is not ready static Steinberg_tresult controllerIConnectionPointNotify(void* thisInterface, struct Steinberg_Vst_IMessage* message) { controller *c = (controller *)((char *)thisInterface - offsetof(controller, vtblIConnectionPoint)); if (!message) return Steinberg_kResultFalse; Steinberg_Vst_IAttributeList *alist = message->lpVtbl->getAttributes(message); if (!alist) return Steinberg_kResultFalse; const void *data = NULL; unsigned int size = 0; alist->lpVtbl->getBinary(alist, "message_data", &data, &size); if (!data || size == 0) return Steinberg_kResultFalse; for (size_t i = 0; i < c->viewsCount; i++) { plugView *v = c->views[i]; if (!v || !v->ui) continue; plugin_ui_receive_from_dsp(v->ui, data, size); break; // Assuming there is only 1 view } return Steinberg_kResultOk; } static Steinberg_Vst_IConnectionPointVtbl controllerVtblIConnectionPoint = { /* FUnknown */ /* .queryInterface = */ controllerIConnectionPointQueryInterface, /* .addRef = */ controllerIConnectionPointAddRef, /* .release = */ controllerIConnectionPointRelease, /* IConnectionPoint */ /* .connect = */ controllerIConnectionPointConnect, /* .disconnect = */ controllerIConnectionPointDisconnect, /* .notify = */ controllerIConnectionPointNotify }; #endif static Steinberg_tresult factoryQueryInterface(void *thisInterface, const Steinberg_TUID iid, void ** obj) { TRACE("factory queryInterface\n"); if (memcmp(iid, Steinberg_FUnknown_iid, sizeof(Steinberg_TUID)) && memcmp(iid, Steinberg_IPluginFactory_iid, sizeof(Steinberg_TUID)) && memcmp(iid, Steinberg_IPluginFactory2_iid, sizeof(Steinberg_TUID)) && memcmp(iid, Steinberg_IPluginFactory3_iid, sizeof(Steinberg_TUID))) { TRACE(" not supported\n"); *obj = NULL; return Steinberg_kNoInterface; } *obj = thisInterface; return Steinberg_kResultOk; } static Steinberg_uint32 factoryAddRef(void *thisInterface) { (void)thisInterface; TRACE("factory add ref\n"); return 1; } static Steinberg_uint32 factoryRelease(void *thisInterface) { (void)thisInterface; TRACE("factory release\n"); return 1; } static Steinberg_tresult factoryGetFactoryInfo(void *thisInterface, struct Steinberg_PFactoryInfo * info) { (void)thisInterface; TRACE("getFactoryInfo\n"); strcpy(info->vendor, DATA_COMPANY_NAME); strcpy(info->url, DATA_COMPANY_URL); strcpy(info->email, DATA_COMPANY_EMAIL); info->flags = Steinberg_PFactoryInfo_FactoryFlags_kUnicode; return Steinberg_kResultOk; } static Steinberg_int32 factoryCountClasses(void *thisInterface) { (void)thisInterface; TRACE("countClasses\n"); return 2; } static Steinberg_tresult factoryGetClassInfo(void *thisInterface, Steinberg_int32 index, struct Steinberg_PClassInfo * info) { (void)thisInterface; TRACE("getClassInfo\n"); switch (index) { case 0: TRACE(" class 0\n"); memcpy(info->cid, dataPluginCID, sizeof(Steinberg_TUID)); info->cardinality = Steinberg_PClassInfo_ClassCardinality_kManyInstances; strcpy(info->category, "Audio Module Class"); strcpy(info->name, DATA_PRODUCT_NAME); break; case 1: TRACE(" class 1\n"); memcpy(info->cid, dataControllerCID, sizeof(Steinberg_TUID)); info->cardinality = Steinberg_PClassInfo_ClassCardinality_kManyInstances; strcpy(info->category, "Component Controller Class"); strcpy(info->name, DATA_PRODUCT_NAME " Controller"); break; default: return Steinberg_kInvalidArgument; break; } return Steinberg_kResultOk; } static Steinberg_tresult factoryCreateInstance(void *thisInterface, Steinberg_FIDString cid, Steinberg_FIDString iid, void ** obj) { (void)thisInterface; TRACE("createInstance\n"); if (memcmp(cid, dataPluginCID, sizeof(Steinberg_TUID)) == 0) { TRACE(" plugin\n"); if (memcmp(iid, Steinberg_FUnknown_iid, sizeof(Steinberg_TUID)) && memcmp(iid, Steinberg_IPluginBase_iid, sizeof(Steinberg_TUID)) && memcmp(iid, Steinberg_Vst_IComponent_iid, sizeof(Steinberg_TUID))) return Steinberg_kNoInterface; pluginInstance *p = malloc(sizeof(pluginInstance)); if (p == NULL) return Steinberg_kOutOfMemory; p->vtblIComponent = &pluginVtblIComponent; p->vtblIAudioProcessor = &pluginVtblIAudioProcessor; p->vtblIProcessContextRequirements = &pluginVtblIProcessContextRequirements; p->refs = 1; p->context = NULL; #ifdef DATA_MESSAGING p->vtblIConnectionPoint = &pluginVtblIConnectionPoint; p->connectedPoint = NULL; p->message_data_out_tosend = 0; p->message_data_in_size = 0; #endif *obj = p; TRACE(" instance: %p\n", (void *)p); } else if (memcmp(cid, dataControllerCID, sizeof(Steinberg_TUID)) == 0) { TRACE(" controller\n"); if (memcmp(iid, Steinberg_FUnknown_iid, sizeof(Steinberg_TUID)) && memcmp(iid, Steinberg_IPluginBase_iid, sizeof(Steinberg_TUID)) && memcmp(iid, Steinberg_Vst_IEditController_iid, sizeof(Steinberg_TUID))) return Steinberg_kNoInterface; controller *c = malloc(sizeof(controller)); if (c == NULL) return Steinberg_kOutOfMemory; c->vtblIEditController = &controllerVtblIEditController; c->vtblIMidiMapping = &controllerVtblIMidiMapping; #ifdef DATA_MESSAGING c->vtblIConnectionPoint = &controllerVtblIConnectionPoint; c->connectedPoint = NULL; #endif c->refs = 1; c->context = NULL; c->componentHandler = NULL; #ifdef DATA_UI c->views = NULL; c->viewsCount = 0; #endif *obj = c; TRACE(" instance: %p\n", (void *)c); } else { *obj = NULL; return Steinberg_kNoInterface; } return Steinberg_kResultOk; } static Steinberg_tresult factoryGetClassInfo2(void* thisInterface, Steinberg_int32 index, struct Steinberg_PClassInfo2* info) { (void)thisInterface; TRACE("getClassInfo2\n"); switch (index) { case 0: TRACE(" class 0\n"); memcpy(info->cid, dataPluginCID, sizeof(Steinberg_TUID)); info->cardinality = Steinberg_PClassInfo_ClassCardinality_kManyInstances; strcpy(info->category, "Audio Module Class"); strcpy(info->name, DATA_PRODUCT_NAME); info->classFlags = Steinberg_Vst_ComponentFlags_kDistributable; strcpy(info->subCategories, DATA_VST3_SUBCATEGORY); *info->vendor = '\0'; strcpy(info->version, DATA_PRODUCT_VERSION); strcpy(info->sdkVersion, "VST " DATA_VST3_SDK_VERSION " | Tibia"); break; case 1: TRACE(" class 1\n"); memcpy(info->cid, dataControllerCID, sizeof(Steinberg_TUID)); info->cardinality = Steinberg_PClassInfo_ClassCardinality_kManyInstances; strcpy(info->category, "Component Controller Class"); strcpy(info->name, DATA_PRODUCT_NAME " Controller"); info->classFlags = 0; *info->subCategories = '\0'; *info->vendor = '\0'; strcpy(info->version, DATA_PRODUCT_VERSION); strcpy(info->sdkVersion, "VST " DATA_VST3_SDK_VERSION " | Tibia"); break; default: return Steinberg_kInvalidArgument; break; } return Steinberg_kResultOk; } static Steinberg_tresult factoryGetClassInfoUnicode(void* thisInterface, Steinberg_int32 index, struct Steinberg_PClassInfoW* info) { (void)thisInterface; TRACE("getClassInfo unicode\n"); switch (index) { case 0: TRACE(" class 0\n"); memcpy(info->cid, dataPluginCID, sizeof(Steinberg_TUID)); info->cardinality = Steinberg_PClassInfo_ClassCardinality_kManyInstances; strcpy(info->category, "Audio Module Class"); memcpy(info->name, dataProductNameW, 64 * sizeof(Steinberg_char16)); info->classFlags = Steinberg_Vst_ComponentFlags_kDistributable; strcpy(info->subCategories, DATA_VST3_SUBCATEGORY); *info->vendor = '\0'; memcpy(info->version, dataProductVersionW, 64 * sizeof(Steinberg_char16)); memcpy(info->sdkVersion, dataVST3SDKVersionW, 64 * sizeof(Steinberg_char16)); break; case 1: TRACE(" class 1\n"); memcpy(info->cid, dataControllerCID, sizeof(Steinberg_TUID)); info->cardinality = Steinberg_PClassInfo_ClassCardinality_kManyInstances; strcpy(info->category, "Component Controller Class"); memcpy(info->name, dataVST3ControllerNameW, 64 * sizeof(Steinberg_char16)); info->classFlags = 0; *info->subCategories = '\0'; *info->vendor = '\0'; memcpy(info->version, dataProductVersionW, 64 * sizeof(Steinberg_char16)); memcpy(info->sdkVersion, dataVST3SDKVersionW, 64 * sizeof(Steinberg_char16)); break; default: return Steinberg_kInvalidArgument; break; } return Steinberg_kResultOk; } static Steinberg_tresult factorySetHostContext(void* thisInterface, struct Steinberg_FUnknown* context) { (void)thisInterface; (void)context; TRACE("factory set host context %p %p\n", thisInterface, (void*) context); return Steinberg_kNotImplemented; } static Steinberg_IPluginFactory3Vtbl factoryVtbl = { /* FUnknown */ /* .queryInterface = */ factoryQueryInterface, /* .addRef = */ factoryAddRef, /* .release = */ factoryRelease, /* IPluginFactory */ /* .getFactoryInfo = */ factoryGetFactoryInfo, /* .countClasses = */ factoryCountClasses, /* .getClassInfo = */ factoryGetClassInfo, /* .createInstance = */ factoryCreateInstance, /* IPluginFactory2 */ /* .getClassInfo2 = */ factoryGetClassInfo2, /* IPluginFactory3 */ /* .getClassInfoUnicode = */ factoryGetClassInfoUnicode, /* .setHostContext = */ factorySetHostContext }; static Steinberg_IPluginFactory3 factory = { &factoryVtbl }; #if defined(_WIN32) || defined(__CYGWIN__) # define EXPORT __declspec(dllexport) #else # define EXPORT __attribute__((visibility("default"))) #endif EXPORT Steinberg_IPluginFactory * GetPluginFactory(void) { return (Steinberg_IPluginFactory *)&factory; } static int refs = 0; static char vstExit(void) { refs--; if (refs == 0) { free(bindir); free(datadir); return 0; } return 1; } #if defined(_WIN32) || defined(__CYGWIN__) EXPORT APIENTRY BOOL DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { (void)hInstance; (void)lpReserved; char * path; if (dwReason == DLL_PROCESS_ATTACH) { if (refs == 0) { DWORD path_length; DWORD n_size; char * new_path; char * c; HMODULE hm; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)&bindir, &hm) == 0) { TRACE("GetModuleHandle failed, error = %lu\n", GetLastError()); goto err_handle; } path = NULL; n_size = MAX_PATH; while (1) { new_path = (char *)realloc(path, n_size); if (new_path == NULL) { TRACE("GetModuleFileName failed, not enough memory (requested %lu bytes)\n", n_size); goto err_realloc; } path = new_path; path_length = GetModuleFileNameA(hm, path, n_size); if (path_length == 0) { TRACE("GetModuleFileName failed, error = %lu\n", GetLastError()); goto err_filename; } if (path_length < n_size) break; n_size *= 2; } c = strrchr(path, '\\'); *c = '\0'; bindir = _strdup(path); if (bindir == NULL) { TRACE("bindir _strdup failed\n"); goto err_bindir; } c = strrchr(path, '\\'); *c = '\0'; datadir = (char *)malloc(strlen(path) + sizeof("\\Resources") + 1); if (datadir == NULL) { TRACE("datadir malloc failed\n"); goto err_datadir; } sprintf(datadir, "%s\\Resources", path); TRACE("bindir = %s \ndatadir = %s\n", bindir, datadir); } refs++; } else if (dwReason == DLL_PROCESS_DETACH) vstExit(); return 1; err_datadir: free(bindir); err_bindir: err_filename: err_realloc: if (path != NULL) free(path); err_handle: return 0; } #elif defined(__APPLE__) || defined(__linux__) EXPORT # if defined(__APPLE__) char bundleEntry(CFBundleRef ref) { (void)ref; # else char ModuleEntry(void *handle) { (void)handle; # endif char *file; if (refs == 0) { Dl_info info; union { void* d; char (*f)(void); } v; v.f = vstExit; if (dladdr((void*) v.d, &info) == 0) return 0; file = realpath(info.dli_fname, NULL); if (file == NULL) return 0; char *c = strrchr(file, '/'); *c = '\0'; bindir = strdup(file); if (bindir == NULL) goto err_bindir; c = strrchr(file, '/'); *c = '\0'; datadir = x_asprintf("%s/Resources", file); if (datadir == NULL) goto err_datadir; free(file); TRACE("bindir = %s \ndatadir = %s\n", bindir, datadir); } refs++; return 1; err_datadir: free(bindir); err_bindir: free(file); return 0; } EXPORT # if defined(__APPLE__) char bundleExit(void) { # else char ModuleExit(void) { # endif return vstExit(); } #endif