tibia/templates/vst3/src/vst3.c
2025-07-08 12:01:43 +02:00

3032 lines
98 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* File author: Stefano D'Angelo, Paolo Marrone
*/
#include <stdlib.h>
#include <stdint.h>
#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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <math.h>
#if defined(_WIN32) || defined(__CYGWIN__)
# include <windows.h>
#else
# include <dlfcn.h>
#endif
#ifdef __APPLE__
# include <CoreFoundation/CoreFoundation.h>
#endif
#if defined(__i386__) || defined(__x86_64__)
#include <xmmintrin.h>
#include <pmmintrin.h>
#endif
#if DATA_PRODUCT_PARAMETERS_IN_N > 0
# include <stdatomic.h>
# if defined(_WIN32) || defined(__CYGWIN__)
# include <processthreadsapi.h>
# define yield SwitchToThread
# else
# include <sched.h>
# define yield sched_yield
# endif
#endif
#ifdef DATA_MESSAGING
# include <tinycthread.h>
#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 <X11/Xlib.h>
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 <objc/objc.h>
# include <objc/runtime.h>
# include <objc/message.h>
# 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