428 lines
24 KiB
C
428 lines
24 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include "modern.h"
|
|
#include "vst2_abi.h"
|
|
#include "xtc/xtc_dsp.h"
|
|
#include "xtc/xtc_version.h"
|
|
#include <clap/clap.h>
|
|
#include <clap/ext/thread-check.h>
|
|
#include <vst3_c_api.h>
|
|
#include <dlfcn.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* Module cache is control-thread owned. Entry initialization is once per loaded
|
|
* module, even when stereo preparation and retired plans overlap. */
|
|
typedef struct module {
|
|
void *handle;
|
|
xtc_plugin_format format;
|
|
unsigned int references;
|
|
const clap_plugin_entry_t *entry;
|
|
bool (*exit3)(void);
|
|
struct module *next;
|
|
} module;
|
|
static module *modules;
|
|
typedef union { clap_event_header_t header; clap_event_note_t note; clap_event_midi_t midi; } clap_note;
|
|
typedef struct { Steinberg_Vst_IEventList api; struct xtc_modern *owner; } vst_events;
|
|
struct xtc_modern {
|
|
module *module;
|
|
xtc_plugin_format format;
|
|
const clap_plugin_t *clap;
|
|
clap_host_t host;
|
|
xtc_v2_effect *v2;
|
|
Steinberg_IPluginFactory *factory;
|
|
Steinberg_Vst_IComponent *component;
|
|
Steinberg_Vst_IAudioProcessor *processor;
|
|
float *input, *output;
|
|
size_t frames;
|
|
double rate;
|
|
pthread_t control_thread;
|
|
int initialized, active, started;
|
|
unsigned int instrument, clap_midi;
|
|
size_t event_count, clap_count;
|
|
clap_note clap_events[XTC_MIDI_MAX_EVENTS];
|
|
xtc_v2_midi v2_events[XTC_MIDI_MAX_EVENTS];
|
|
struct Steinberg_Vst_Event vst_events[2304];
|
|
size_t vst_count;
|
|
unsigned char keys[16][128];
|
|
vst_events event_list;
|
|
unsigned int callback, restart;
|
|
struct xtc_modern *next;
|
|
};
|
|
static xtc_modern *instances;
|
|
static Steinberg_Vst_IEventListVtbl event_vtable;
|
|
static __thread int in_audio;
|
|
static __thread xtc_modern *constructing;
|
|
|
|
static int symbol(void *handle, const char *name, void *out, size_t size)
|
|
{
|
|
void *value = dlsym(handle,name);
|
|
if (!value || size != sizeof(value)) return 0;
|
|
memcpy(out,&value,size); return 1;
|
|
}
|
|
static void module_release(module *m)
|
|
{
|
|
module **slot;
|
|
if (!m || --m->references) return;
|
|
for (slot = &modules; *slot && *slot != m; slot = &(*slot)->next) {}
|
|
if (*slot) *slot = m->next;
|
|
if (m->entry) m->entry->deinit();
|
|
if (m->exit3) m->exit3();
|
|
dlclose(m->handle); free(m);
|
|
}
|
|
static xtc_result module_open(xtc_plugin_format format, const char *path, module **out)
|
|
{
|
|
void *handle = dlopen(path,RTLD_NOW | RTLD_LOCAL);
|
|
module *m; xtc_result r = XTC_ERR_UNSUPPORTED;
|
|
if (!handle) return XTC_ERR_NOT_FOUND;
|
|
for (m = modules; m; m = m->next) if (m->handle == handle && m->format == format) {
|
|
++m->references; dlclose(handle); *out = m; return XTC_OK;
|
|
}
|
|
m = calloc(1,sizeof(*m));
|
|
if (!m) { dlclose(handle); return XTC_ERR_OUT_OF_MEMORY; }
|
|
m->handle = handle; m->format = format; m->references = 1;
|
|
if (format == XTC_PLUGIN_CLAP) {
|
|
const clap_plugin_entry_t *entry = dlsym(handle,"clap_entry");
|
|
if (!entry || !clap_version_is_compatible(entry->clap_version) || !entry->init || !entry->deinit || !entry->get_factory) goto fail;
|
|
if (!entry->init(path)) { r = XTC_ERR_INVALID_STATE; goto fail; }
|
|
m->entry = entry;
|
|
} else if (format == XTC_PLUGIN_VST3) {
|
|
bool (*entry)(void *) = NULL; bool (*exit_module)(void) = NULL;
|
|
if (!symbol(handle,"ModuleEntry",&entry,sizeof(entry)) || !symbol(handle,"ModuleExit",&exit_module,sizeof(exit_module))) goto fail;
|
|
if (!entry(handle)) { r = XTC_ERR_INVALID_STATE; goto fail; }
|
|
m->exit3 = exit_module;
|
|
}
|
|
m->next = modules; modules = m; *out = m; return XTC_OK;
|
|
fail:
|
|
module_release(m); return r;
|
|
}
|
|
static void request_callback(const clap_host_t *h)
|
|
{ xtc_modern *p = h->host_data; __atomic_store_n(&p->callback,1U,__ATOMIC_RELEASE); }
|
|
static void request_restart(const clap_host_t *h)
|
|
{ xtc_modern *p = h->host_data; __atomic_store_n(&p->restart,1U,__ATOMIC_RELEASE); }
|
|
static void request_process(const clap_host_t *h) { (void)h; /* Always processed when transport runs. */ }
|
|
static bool is_main(const clap_host_t *h)
|
|
{ const xtc_modern *p = h->host_data; return !in_audio && pthread_equal(pthread_self(),p->control_thread); }
|
|
static bool is_audio(const clap_host_t *h) { (void)h; return in_audio != 0; }
|
|
static const void *host_extension(const clap_host_t *h, const char *id)
|
|
{
|
|
static const clap_host_thread_check_t check = {is_main,is_audio};
|
|
(void)h; return id && !strcmp(id,CLAP_EXT_THREAD_CHECK) ? &check : NULL;
|
|
}
|
|
void xtc_plugin_idle(void)
|
|
{
|
|
xtc_modern *p;
|
|
for (p = instances; p; p = p->next)
|
|
if (p->clap && __atomic_exchange_n(&p->callback,0U,__ATOMIC_ACQ_REL)) p->clap->on_main_thread(p->clap);
|
|
}
|
|
static uint32_t event_count(const clap_input_events_t *list)
|
|
{ const xtc_modern *p = list->ctx; return (uint32_t)p->clap_count; }
|
|
static const clap_event_header_t *event_get(const clap_input_events_t *list, uint32_t index)
|
|
{ const xtc_modern *p = list->ctx; return index < p->clap_count ? &p->clap_events[index].header : NULL; }
|
|
static bool event_push(const clap_output_events_t *list, const clap_event_header_t *event)
|
|
{ (void)list; (void)event; return false; }
|
|
static xtc_result open_clap(xtc_modern *p, unsigned int index, xtc_plugin_spec *s)
|
|
{
|
|
const clap_plugin_factory_t *factory = p->module->entry->get_factory(CLAP_PLUGIN_FACTORY_ID);
|
|
const clap_plugin_descriptor_t *d; const clap_plugin_audio_ports_t *ports;
|
|
clap_audio_port_info_t info;
|
|
if (!factory || !factory->get_plugin_count || !factory->get_plugin_descriptor || !factory->create_plugin) return XTC_ERR_UNSUPPORTED;
|
|
if (index >= factory->get_plugin_count(factory)) return XTC_ERR_NOT_FOUND;
|
|
d = factory->get_plugin_descriptor(factory,index);
|
|
if (!d || !clap_version_is_compatible(d->clap_version) || !d->id || !d->name ||
|
|
!d->id[0] || strlen(d->id) >= sizeof(s->identifier) || !d->name[0]) return XTC_ERR_UNSUPPORTED;
|
|
snprintf(s->identifier,sizeof(s->identifier),"%s",d->id); snprintf(s->label,sizeof(s->label),"%s",d->name);
|
|
p->host = (clap_host_t){CLAP_VERSION,p,"xtc","xtc","",XTC_VERSION_STRING,host_extension,request_restart,request_process,request_callback};
|
|
p->clap = factory->create_plugin(factory,&p->host,d->id);
|
|
if (!p->clap) return XTC_ERR_INVALID_STATE;
|
|
if (!p->clap->init || !p->clap->destroy || !p->clap->activate || !p->clap->deactivate ||
|
|
!p->clap->start_processing || !p->clap->stop_processing || !p->clap->process || !p->clap->get_extension || !p->clap->on_main_thread)
|
|
return XTC_ERR_UNSUPPORTED;
|
|
if (!p->clap->init(p->clap)) return XTC_ERR_INVALID_STATE;
|
|
ports = p->clap->get_extension(p->clap,CLAP_EXT_AUDIO_PORTS);
|
|
if (!ports || !ports->count || !ports->get || ports->count(p->clap,false) != 1 ||
|
|
!ports->get(p->clap,0,false,&info) || info.channel_count != 1) return XTC_ERR_UNSUPPORTED;
|
|
{ uint32_t inputs = ports->count(p->clap,true);
|
|
const clap_plugin_note_ports_t *notes = p->clap->get_extension(p->clap,CLAP_EXT_NOTE_PORTS);
|
|
if (inputs == 0 && notes && notes->count && notes->get && notes->count(p->clap,true) == 1 && !notes->count(p->clap,false)) {
|
|
clap_note_port_info_t note;
|
|
if (!notes->get(p->clap,0,true,¬e) || !(note.supported_dialects & (CLAP_NOTE_DIALECT_CLAP|CLAP_NOTE_DIALECT_MIDI))) return XTC_ERR_UNSUPPORTED;
|
|
p->instrument = s->instrument = 1; s->inputs = 0;
|
|
p->clap_midi = (note.supported_dialects & CLAP_NOTE_DIALECT_MIDI) != 0;
|
|
} else if (inputs != 1 || !ports->get(p->clap,0,true,&info) || info.channel_count != 1 ||
|
|
(notes && (!notes->count || notes->count(p->clap,true) || notes->count(p->clap,false)))) return XTC_ERR_UNSUPPORTED;
|
|
}
|
|
if (!p->clap->activate(p->clap,p->rate,1,(uint32_t)p->frames)) return XTC_ERR_INVALID_STATE;
|
|
p->active = 1;
|
|
{ const clap_plugin_latency_t *latency = p->clap->get_extension(p->clap,CLAP_EXT_LATENCY);
|
|
if (latency && (!latency->get || latency->get(p->clap))) return XTC_ERR_UNSUPPORTED; }
|
|
return XTC_OK;
|
|
}
|
|
static intptr_t v2_host(xtc_v2_effect *effect, int32_t opcode, int32_t index, intptr_t value, void *ptr, float option)
|
|
{
|
|
xtc_modern *p = constructing ? constructing : effect ? effect->user : NULL;
|
|
(void)index; (void)value; (void)option;
|
|
switch (opcode) {
|
|
case 1: return 2400; /* host API version */
|
|
case 6: return 1; /* Legacy MIDI delivery request. */
|
|
case 37: return ptr && (!strcmp(ptr,"sendVstEvents") || !strcmp(ptr,"sendVstMidiEvent")) ? 1 : 0;
|
|
case 13: if (p) __atomic_store_n(&p->restart,1U,__ATOMIC_RELEASE); return 0;
|
|
case 16: return p ? (intptr_t)p->rate : 48000;
|
|
case 17: return p ? (intptr_t)p->frames : 256;
|
|
case 32: if (ptr) memcpy(ptr,"xtc",4); return 1;
|
|
case 33: if (ptr) memcpy(ptr,"xtc",4); return 1;
|
|
case 34: return 1;
|
|
case 23: return in_audio ? 2 : 1; /* Realtime or user/main process level. */
|
|
default: return 0;
|
|
}
|
|
}
|
|
static xtc_result open_v2(xtc_modern *p, unsigned int index, xtc_plugin_spec *s)
|
|
{
|
|
xtc_v2_effect *(*entry)(xtc_v2_host) = NULL;
|
|
char name[64] = {0};
|
|
if (index) return XTC_ERR_NOT_FOUND;
|
|
if (!symbol(p->module->handle,"VSTPluginMain",&entry,sizeof(entry))) return XTC_ERR_UNSUPPORTED;
|
|
constructing = p; p->v2 = entry(v2_host); constructing = NULL;
|
|
if (!p->v2 || p->v2->magic != XTC_V2_MAGIC || !p->v2->dispatch) return XTC_ERR_UNSUPPORTED;
|
|
p->v2->user = p;
|
|
p->v2->dispatch(p->v2,0,0,0,NULL,0); p->initialized = 1;
|
|
if (p->v2->outputs != 1 || !p->v2->replace || !(p->v2->flags & 16) || p->v2->initial_delay ||
|
|
((p->v2->flags & 256) ? p->v2->inputs != 0 : p->v2->inputs != 1)) return XTC_ERR_UNSUPPORTED;
|
|
p->v2->dispatch(p->v2,45,0,0,name,0); name[63] = 0;
|
|
if (!name[0]) return XTC_ERR_UNSUPPORTED;
|
|
if (p->v2->flags & 256) { p->instrument = s->instrument = 1; s->inputs = 0; }
|
|
snprintf(s->label,sizeof(s->label),"%s",name); s->unique_id = (unsigned int)p->v2->unique_id;
|
|
p->v2->dispatch(p->v2,10,0,0,NULL,(float)p->rate);
|
|
p->v2->dispatch(p->v2,11,0,(intptr_t)p->frames,NULL,0);
|
|
p->v2->dispatch(p->v2,12,0,1,NULL,0); p->active = 1;
|
|
p->v2->dispatch(p->v2,71,0,0,NULL,0); p->started = 1;
|
|
return XTC_OK;
|
|
}
|
|
static Steinberg_tresult host_query(void *self, const Steinberg_TUID iid, void **out)
|
|
{
|
|
if (!out || !iid) return Steinberg_kInvalidArgument;
|
|
*out = NULL;
|
|
if (!memcmp(iid,Steinberg_FUnknown_iid,16) || !memcmp(iid,Steinberg_Vst_IHostApplication_iid,16)) { *out = self; return 0; }
|
|
return Steinberg_kNoInterface;
|
|
}
|
|
static Steinberg_uint32 host_ref(void *self) { (void)self; return 1; }
|
|
static Steinberg_tresult host_name(void *self, Steinberg_Vst_String128 name)
|
|
{ (void)self; if (!name) return Steinberg_kInvalidArgument; memset(name,0,sizeof(Steinberg_Vst_String128)); name[0] = 'x'; name[1] = 't'; name[2] = 'c'; return 0; }
|
|
static Steinberg_tresult host_create(void *self, Steinberg_TUID cid, Steinberg_TUID iid, void **out)
|
|
{ (void)self; (void)cid; (void)iid; if (!out) return Steinberg_kInvalidArgument; *out = NULL; return Steinberg_kNoInterface; }
|
|
static Steinberg_Vst_IHostApplicationVtbl host_vtable = {host_query,host_ref,host_ref,host_name,host_create};
|
|
static Steinberg_Vst_IHostApplication application = {&host_vtable};
|
|
static xtc_result open_v3(xtc_modern *p, unsigned int index, xtc_plugin_spec *s)
|
|
{
|
|
Steinberg_IPluginFactory *(*entry)(void) = NULL;
|
|
struct Steinberg_PClassInfo info;
|
|
struct Steinberg_Vst_ProcessSetup setup = {0,0,(Steinberg_int32)p->frames,p->rate};
|
|
Steinberg_Vst_SpeakerArrangement mono = Steinberg_Vst_SpeakerArr_kMono;
|
|
unsigned int i;
|
|
if (!symbol(p->module->handle,"GetPluginFactory",&entry,sizeof(entry))) return XTC_ERR_UNSUPPORTED;
|
|
p->factory = entry();
|
|
if (!p->factory || !p->factory->lpVtbl || !p->factory->lpVtbl->release ||
|
|
!p->factory->lpVtbl->countClasses || !p->factory->lpVtbl->getClassInfo || !p->factory->lpVtbl->createInstance) return XTC_ERR_UNSUPPORTED;
|
|
if ((Steinberg_int32)index >= p->factory->lpVtbl->countClasses(p->factory)) return XTC_ERR_NOT_FOUND;
|
|
memset(&info,0,sizeof(info));
|
|
if (p->factory->lpVtbl->getClassInfo(p->factory,(Steinberg_int32)index,&info) != 0 ||
|
|
!memchr(info.category,0,sizeof(info.category)) || strcmp(info.category,"Audio Module Class") ||
|
|
!memchr(info.name,0,sizeof(info.name)) || !info.name[0]) return XTC_ERR_UNSUPPORTED;
|
|
for (i = 0; i < 16; ++i) snprintf(s->identifier+i*2,3,"%02x",(unsigned char)info.cid[i]);
|
|
snprintf(s->label,sizeof(s->label),"%s",info.name);
|
|
if (p->factory->lpVtbl->createInstance(p->factory,info.cid,Steinberg_Vst_IComponent_iid,(void **)&p->component) != 0 || !p->component)
|
|
return XTC_ERR_INVALID_STATE;
|
|
if (!p->component->lpVtbl || !p->component->lpVtbl->initialize || !p->component->lpVtbl->terminate ||
|
|
!p->component->lpVtbl->queryInterface || !p->component->lpVtbl->release || !p->component->lpVtbl->getBusCount ||
|
|
!p->component->lpVtbl->getBusInfo || !p->component->lpVtbl->activateBus || !p->component->lpVtbl->setActive) return XTC_ERR_UNSUPPORTED;
|
|
if (p->component->lpVtbl->initialize(p->component,(Steinberg_FUnknown *)&application) != 0) return XTC_ERR_INVALID_STATE;
|
|
p->initialized = 1;
|
|
if (p->component->lpVtbl->queryInterface(p->component,Steinberg_Vst_IAudioProcessor_iid,(void **)&p->processor) != 0 || !p->processor)
|
|
return XTC_ERR_UNSUPPORTED;
|
|
if (!p->processor->lpVtbl || !p->processor->lpVtbl->release || !p->processor->lpVtbl->canProcessSampleSize ||
|
|
!p->processor->lpVtbl->setBusArrangements || !p->processor->lpVtbl->setupProcessing || !p->processor->lpVtbl->getLatencySamples ||
|
|
!p->processor->lpVtbl->setProcessing || !p->processor->lpVtbl->process) return XTC_ERR_UNSUPPORTED;
|
|
{ Steinberg_int32 inputs = p->component->lpVtbl->getBusCount(p->component,0,0);
|
|
Steinberg_int32 event_inputs = p->component->lpVtbl->getBusCount(p->component,1,0);
|
|
if (inputs == 0 && event_inputs == 1) { p->instrument = s->instrument = 1; s->inputs = 0; }
|
|
else if (inputs != 1 || event_inputs != 0) return XTC_ERR_UNSUPPORTED;
|
|
if (p->component->lpVtbl->getBusCount(p->component,0,1) != 1 ||
|
|
p->component->lpVtbl->getBusCount(p->component,1,1) ||
|
|
p->processor->lpVtbl->canProcessSampleSize(p->processor,0) != 0 ||
|
|
p->processor->lpVtbl->setBusArrangements(p->processor,inputs ? &mono : NULL,inputs,&mono,1) != 0) return XTC_ERR_UNSUPPORTED;
|
|
if (event_inputs && p->component->lpVtbl->activateBus(p->component,1,0,0,1) != 0) return XTC_ERR_UNSUPPORTED;
|
|
}
|
|
for (i = p->instrument ? 1U : 0U; i < 2; ++i) {
|
|
struct Steinberg_Vst_BusInfo bus;
|
|
if (p->component->lpVtbl->getBusInfo(p->component,0,(Steinberg_int32)i,0,&bus) != 0 || bus.channelCount != 1 ||
|
|
p->component->lpVtbl->activateBus(p->component,0,(Steinberg_int32)i,0,1) != 0) return XTC_ERR_UNSUPPORTED;
|
|
}
|
|
if (p->processor->lpVtbl->setupProcessing(p->processor,&setup) != 0 || p->processor->lpVtbl->getLatencySamples(p->processor)) return XTC_ERR_UNSUPPORTED;
|
|
if (p->component->lpVtbl->setActive(p->component,1) != 0) return XTC_ERR_INVALID_STATE;
|
|
p->active = 1;
|
|
if (p->processor->lpVtbl->setProcessing(p->processor,1) != 0) return XTC_ERR_INVALID_STATE;
|
|
p->started = 1; return XTC_OK;
|
|
}
|
|
xtc_result xtc_modern_open(xtc_plugin_format format, const char *path, unsigned int index, double rate, size_t frames, xtc_modern **out, xtc_plugin_spec *spec)
|
|
{
|
|
xtc_modern *p; xtc_plugin_spec s = {0}; xtc_result r;
|
|
if (!__atomic_always_lock_free(sizeof(unsigned int),0)) return XTC_ERR_UNSUPPORTED;
|
|
p = calloc(1,sizeof(*p));
|
|
if (!p) return XTC_ERR_OUT_OF_MEMORY;
|
|
p->format = format; p->frames = frames; p->rate = rate; p->control_thread = pthread_self();
|
|
p->event_list.api.lpVtbl = &event_vtable; p->event_list.owner = p;
|
|
p->input = calloc(frames,sizeof(float)); p->output = calloc(frames,sizeof(float));
|
|
if (!p->input || !p->output) { xtc_modern_destroy(p); return XTC_ERR_OUT_OF_MEMORY; }
|
|
r = module_open(format,path,&p->module);
|
|
s.format = format; s.index = index; s.inputs = 1; s.tempo = 120000;
|
|
snprintf(s.path,sizeof(s.path),"%s",path);
|
|
if (r == XTC_OK) r = format == XTC_PLUGIN_CLAP ? open_clap(p,index,&s) : format == XTC_PLUGIN_VST2 ? open_v2(p,index,&s) : open_v3(p,index,&s);
|
|
if (r != XTC_OK) { xtc_modern_destroy(p); return r; }
|
|
p->next = instances; instances = p; *out = p; *spec = s; return XTC_OK;
|
|
}
|
|
void xtc_modern_destroy(xtc_modern *p)
|
|
{
|
|
xtc_modern **slot;
|
|
if (!p) return;
|
|
for (slot = &instances; *slot && *slot != p; slot = &(*slot)->next) {}
|
|
if (*slot) *slot = p->next;
|
|
if (p->clap) {
|
|
if (p->started) { in_audio = 1; p->clap->stop_processing(p->clap); in_audio = 0; }
|
|
if (p->active) p->clap->deactivate(p->clap);
|
|
if (p->clap->destroy) p->clap->destroy(p->clap);
|
|
}
|
|
if (p->v2 && p->v2->magic == XTC_V2_MAGIC && p->v2->dispatch && p->initialized) {
|
|
if (p->started) p->v2->dispatch(p->v2,72,0,0,NULL,0);
|
|
if (p->active) p->v2->dispatch(p->v2,12,0,0,NULL,0);
|
|
p->v2->dispatch(p->v2,1,0,0,NULL,0);
|
|
}
|
|
if (p->processor && p->processor->lpVtbl) {
|
|
if (p->started) p->processor->lpVtbl->setProcessing(p->processor,0);
|
|
if (p->active) p->component->lpVtbl->setActive(p->component,0);
|
|
if (p->processor->lpVtbl->release) p->processor->lpVtbl->release(p->processor);
|
|
}
|
|
if (p->component && p->component->lpVtbl) {
|
|
if (p->initialized) p->component->lpVtbl->terminate(p->component);
|
|
if (p->component->lpVtbl->release) p->component->lpVtbl->release(p->component);
|
|
}
|
|
if (p->factory && p->factory->lpVtbl && p->factory->lpVtbl->release) p->factory->lpVtbl->release(p->factory);
|
|
module_release(p->module); free(p->input); free(p->output); free(p);
|
|
}
|
|
xtc_result xtc_modern_process(xtc_modern *p, const float *input, float *output, size_t frames)
|
|
{
|
|
size_t i; int ok = 1;
|
|
if (!p || !output || frames > p->frames) return XTC_ERR_INVALID_ARGUMENT;
|
|
if (!frames) return XTC_OK;
|
|
if (__atomic_load_n(&p->restart,__ATOMIC_ACQUIRE)) { memset(output,0,frames*sizeof(float)); return XTC_ERR_INVALID_STATE; }
|
|
for (i = 0; i < frames; ++i) p->input[i] = input ? xtc_dsp_finite(input[i]) : 0;
|
|
memset(p->output,0,frames*sizeof(float));
|
|
in_audio = 1;
|
|
if (p->clap) {
|
|
clap_input_events_t events = {p,event_count,event_get}; clap_output_events_t sink = {NULL,event_push};
|
|
clap_audio_buffer_t in = {&p->input,NULL,1,0,0}, out = {&p->output,NULL,1,0,0};
|
|
clap_process_t block = {-1,(uint32_t)frames,NULL,p->instrument ? NULL : &in,&out,p->instrument ? 0U : 1U,1,&events,&sink};
|
|
if (!p->started) { p->started = p->clap->start_processing(p->clap); if (!p->started) ok = 0; }
|
|
if (ok) {
|
|
clap_process_status status = p->clap->process(p->clap,&block);
|
|
ok = status >= CLAP_PROCESS_CONTINUE && status <= CLAP_PROCESS_SLEEP;
|
|
}
|
|
} else if (p->v2) {
|
|
if (p->event_count) {
|
|
xtc_v2_events events; memset(&events,0,sizeof(events)); events.count = (int32_t)p->event_count;
|
|
for (i = 0; i < p->event_count; ++i) events.events[i] = p->v2_events+i;
|
|
p->v2->dispatch(p->v2,25,0,0,&events,0);
|
|
}
|
|
p->v2->replace(p->v2,p->instrument ? NULL : &p->input,&p->output,(int32_t)frames);
|
|
}
|
|
else {
|
|
struct Steinberg_Vst_AudioBusBuffers in = {0}, out = {0};
|
|
struct Steinberg_Vst_ProcessData data = {0};
|
|
in.numChannels = out.numChannels = 1;
|
|
in.Steinberg_Vst_AudioBusBuffers_channelBuffers32 = &p->input;
|
|
out.Steinberg_Vst_AudioBusBuffers_channelBuffers32 = &p->output;
|
|
data.numSamples = (Steinberg_int32)frames; data.numInputs = p->instrument ? 0 : 1; data.numOutputs = 1; data.inputs = p->instrument ? NULL : ∈ data.outputs = &out;
|
|
if (p->instrument) data.inputEvents = &p->event_list.api;
|
|
ok = p->processor->lpVtbl->process(p->processor,&data) == 0;
|
|
}
|
|
in_audio = 0;
|
|
for (i = 0; i < frames; ++i) output[i] = ok ? xtc_dsp_finite(p->output[i]) : 0;
|
|
return ok ? XTC_OK : XTC_ERR_INVALID_STATE;
|
|
}
|
|
|
|
static Steinberg_tresult list_query(void *self, const Steinberg_TUID iid, void **out)
|
|
{
|
|
if (!out || !iid) return Steinberg_kInvalidArgument;
|
|
*out = NULL;
|
|
if (!memcmp(iid,Steinberg_Vst_IEventList_iid,16) || !memcmp(iid,Steinberg_FUnknown_iid,16)) { *out = self; return 0; }
|
|
return Steinberg_kNoInterface;
|
|
}
|
|
static Steinberg_int32 list_count(void *self) { return (Steinberg_int32)((vst_events *)self)->owner->vst_count; }
|
|
static Steinberg_tresult list_get(void *self, Steinberg_int32 index, struct Steinberg_Vst_Event *out)
|
|
{
|
|
xtc_modern *p = ((vst_events *)self)->owner;
|
|
if (!out || index < 0 || (size_t)index >= p->vst_count) return Steinberg_kInvalidArgument;
|
|
*out = p->vst_events[index]; return 0;
|
|
}
|
|
static Steinberg_tresult list_add(void *self, struct Steinberg_Vst_Event *event)
|
|
{ (void)self; (void)event; return Steinberg_kNotImplemented; }
|
|
static Steinberg_Vst_IEventListVtbl event_vtable = {list_query,host_ref,host_ref,list_count,list_get,list_add};
|
|
static void vst_note(xtc_modern *p, unsigned int offset, unsigned int channel, unsigned int pitch, unsigned int velocity, int on)
|
|
{
|
|
struct Steinberg_Vst_Event *v = p->vst_events+p->vst_count++;
|
|
memset(v,0,sizeof(*v)); v->sampleOffset = (Steinberg_int32)offset; v->type = on ? 0 : 1;
|
|
if (on) {
|
|
v->Steinberg_Vst_Event_noteOn.channel = (Steinberg_int16)channel;
|
|
v->Steinberg_Vst_Event_noteOn.pitch = (Steinberg_int16)pitch;
|
|
v->Steinberg_Vst_Event_noteOn.velocity = (float)velocity/127;
|
|
v->Steinberg_Vst_Event_noteOn.noteId = -1;
|
|
} else {
|
|
v->Steinberg_Vst_Event_noteOff.channel = (Steinberg_int16)channel;
|
|
v->Steinberg_Vst_Event_noteOff.pitch = (Steinberg_int16)pitch;
|
|
v->Steinberg_Vst_Event_noteOff.noteId = -1;
|
|
}
|
|
p->keys[channel][pitch] = (unsigned char)on;
|
|
}
|
|
xtc_result xtc_modern_process_midi(xtc_modern *p, const xtc_midi_event *events, size_t count, float *output, size_t frames)
|
|
{
|
|
size_t i; xtc_result r;
|
|
if (!p || !p->instrument || !output || (count && !events) || count > XTC_MIDI_MAX_EVENTS || frames > p->frames) return XTC_ERR_INVALID_ARGUMENT;
|
|
for (i = 0; i < count; ++i) {
|
|
const xtc_midi_event *e = events+i;
|
|
if (e->offset >= frames || (i && e->offset < events[i-1].offset) || e->channel > 15 || e->pitch > 127 || e->velocity > 127 ||
|
|
e->type < XTC_MIDI_NOTE_ON || e->type > XTC_MIDI_CONTROL || (e->type == XTC_MIDI_NOTE_ON && !e->velocity)) return XTC_ERR_INVALID_ARGUMENT;
|
|
}
|
|
p->event_count = count; p->vst_count = 0; p->clap_count = 0;
|
|
p->event_list.api.lpVtbl = &event_vtable; p->event_list.owner = p;
|
|
for (i = 0; i < count; ++i) {
|
|
const xtc_midi_event *e = events+i;
|
|
unsigned char status = (unsigned char)((e->type == XTC_MIDI_NOTE_ON ? 144 : e->type == XTC_MIDI_NOTE_OFF ? 128 : 176) | e->channel);
|
|
unsigned char key = (unsigned char)(e->type == XTC_MIDI_ALL_OFF ? 120 : e->pitch);
|
|
unsigned char value = (unsigned char)(e->type == XTC_MIDI_ALL_OFF ? 0 : e->velocity);
|
|
clap_note *c = p->clap_events+p->clap_count; memset(c,0,sizeof(*c));
|
|
if (p->clap_midi) {
|
|
c->midi.header = (clap_event_header_t){sizeof(c->midi),e->offset,CLAP_CORE_EVENT_SPACE_ID,CLAP_EVENT_MIDI,0};
|
|
c->midi.data[0] = status; c->midi.data[1] = key; c->midi.data[2] = value;
|
|
} else {
|
|
c->note.header = (clap_event_header_t){sizeof(c->note),e->offset,CLAP_CORE_EVENT_SPACE_ID,
|
|
e->type == XTC_MIDI_NOTE_ON ? CLAP_EVENT_NOTE_ON : e->type == XTC_MIDI_NOTE_OFF ? CLAP_EVENT_NOTE_OFF : CLAP_EVENT_NOTE_CHOKE,0};
|
|
c->note.note_id = -1; c->note.port_index = 0; c->note.channel = (int16_t)e->channel;
|
|
c->note.key = e->type == XTC_MIDI_ALL_OFF ? -1 : (int16_t)e->pitch; c->note.velocity = (double)e->velocity/127;
|
|
/* No generic controller mapping in native-note CLAP or VST3. */
|
|
if (e->type == XTC_MIDI_CONTROL) c->note.header.type = UINT16_MAX;
|
|
}
|
|
if (p->clap_midi || e->type != XTC_MIDI_CONTROL) ++p->clap_count;
|
|
memset(p->v2_events+i,0,sizeof(*p->v2_events));
|
|
p->v2_events[i].type = 1; p->v2_events[i].bytes = sizeof(xtc_v2_midi); p->v2_events[i].offset = (int32_t)e->offset;
|
|
p->v2_events[i].data[0] = status; p->v2_events[i].data[1] = key; p->v2_events[i].data[2] = value;
|
|
if (e->type == XTC_MIDI_NOTE_ON || e->type == XTC_MIDI_NOTE_OFF)
|
|
vst_note(p,e->offset,e->channel,e->pitch,e->velocity,e->type == XTC_MIDI_NOTE_ON);
|
|
else if (e->type == XTC_MIDI_ALL_OFF) {
|
|
unsigned int k;
|
|
for (k = 0; k < 128; ++k) if (p->keys[e->channel][k]) vst_note(p,e->offset,e->channel,k,0,0);
|
|
}
|
|
}
|
|
r = xtc_modern_process(p,NULL,output,frames);
|
|
p->event_count = 0; p->clap_count = 0; p->vst_count = 0; return r;
|
|
}
|