#define _POSIX_C_SOURCE 200809L #include "modern.h" #include "vst2_abi.h" #include "xtc/xtc_dsp.h" #include "xtc/xtc_version.h" #include #include #include #include #include #include #include #include #include /* 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; 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 callback, restart; struct xtc_modern *next; }; static xtc_modern *instances; 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) { (void)list; return 0; } static const clap_event_header_t *event_get(const clap_input_events_t *list, uint32_t index) { (void)list; (void)index; return 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,true) != 1 || ports->count(p->clap,false) != 1 || !ports->get(p->clap,0,true,&info) || info.channel_count != 1 || !ports->get(p->clap,0,false,&info) || info.channel_count != 1) return XTC_ERR_UNSUPPORTED; { const clap_plugin_note_ports_t *notes = p->clap->get_extension(p->clap,CLAP_EXT_NOTE_PORTS); if (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 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->inputs != 1 || p->v2->outputs != 1 || !p->v2->replace || !(p->v2->flags & 16) || (p->v2->flags & 256) || p->v2->initial_delay) return XTC_ERR_UNSUPPORTED; p->v2->dispatch(p->v2,45,0,0,name,0); name[63] = 0; if (!name[0]) return XTC_ERR_UNSUPPORTED; 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; if (p->component->lpVtbl->getBusCount(p->component,0,0) != 1 || p->component->lpVtbl->getBusCount(p->component,0,1) != 1 || p->component->lpVtbl->getBusCount(p->component,1,0) || p->component->lpVtbl->getBusCount(p->component,1,1) || p->processor->lpVtbl->canProcessSampleSize(p->processor,0) != 0 || p->processor->lpVtbl->setBusArrangements(p->processor,&mono,1,&mono,1) != 0) return XTC_ERR_UNSUPPORTED; for (i = 0; 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->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 = {NULL,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,&in,&out,1,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) p->v2->replace(p->v2,&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 = data.numOutputs = 1; data.inputs = ∈ data.outputs = &out; 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; }