Nils
2 months ago
4 changed files with 2 additions and 587 deletions
@ -1,587 +0,0 @@ |
|||
// https://github.com/free-audio/clap/blob/main/src/plugin-template.c
|
|||
//gcc -fPIC -shared -g -Wall -Wextra -Wno-unused-parameter -lcalfbox-lss-static `pkg-config --cflags --libs glib-2.0` -lm -luuid -lsndfile -o sfzload.clap clap-sfzload.c
|
|||
//gcc -fPIC -shared -g -lcalfbox-lss-static `pkg-config --cflags --libs glib-2.0` -lm -luuid -lsndfile -o sfzload.clap clap-sfzload.c
|
|||
// Symlink sfzload.clap to ~/.clap
|
|||
|
|||
#include <string.h> |
|||
#include <stdlib.h> |
|||
#include <stdio.h> |
|||
#include <math.h> |
|||
|
|||
#include <calfbox/module.h> |
|||
#include <calfbox/engine.h> |
|||
#include <calfbox/sampler.h> |
|||
#include <calfbox/instr.h> |
|||
#include <calfbox/layer.h> |
|||
#include <calfbox/scene.h> |
|||
#include <calfbox/sfzloader.h> |
|||
|
|||
#include <clap/clap.h> |
|||
|
|||
/////////////////////////////
|
|||
////////// CALFBOX //////////
|
|||
/////////////////////////////
|
|||
|
|||
|
|||
struct cbox_environment |
|||
{ |
|||
struct cbox_document *document; |
|||
struct cbox_engine *engine; |
|||
struct cbox_rt *realtime; |
|||
struct cbox_io io; |
|||
struct cbox_open_params params; |
|||
|
|||
struct cbox_layer *layer; |
|||
struct cbox_scene *scene; |
|||
|
|||
void *arg; |
|||
gchar *context; |
|||
}; |
|||
struct cbox_environment env; |
|||
|
|||
struct program_double_buffer |
|||
{ |
|||
gboolean left_buffer_active; |
|||
char directory[PATH_MAX]; |
|||
char abs_path[PATH_MAX]; |
|||
struct sampler_module * module; |
|||
struct sampler_program *pgm_left; |
|||
struct sampler_program *pgm_right; |
|||
struct sampler_program *pgm_active; |
|||
}; |
|||
struct program_double_buffer prg_buf; |
|||
|
|||
|
|||
void switch_program_buffer() { |
|||
|
|||
GError *error = NULL; |
|||
if (prg_buf.left_buffer_active) { |
|||
//printf("switching to right buffer\n");
|
|||
prg_buf.pgm_active = prg_buf.pgm_right; |
|||
} |
|||
else { |
|||
//printf("switching to left buffer\n");
|
|||
prg_buf.pgm_active = prg_buf.pgm_left; |
|||
} |
|||
|
|||
sampler_module_load_program_sfz(prg_buf.module, prg_buf.pgm_active, prg_buf.abs_path, 0, &error); //0 means "is from string"
|
|||
if (error) fprintf(stderr, "Cannot load program from sfz: %s\n", error ? error->message : "unknown error"); |
|||
|
|||
//Registering programs needs to be done after loading the programs from sfz
|
|||
sampler_register_program(prg_buf.module, prg_buf.pgm_active); |
|||
|
|||
sampler_select_program(prg_buf.module, 0, prg_buf.pgm_active->name, &error); |
|||
if (error) fprintf(stderr, "Cannot select program: %s\n", error ? error->message : "unknown error"); |
|||
|
|||
//Setting the channel to a program needs to be done after registering programs
|
|||
sampler_channel_set_program(&prg_buf.module->channels[0], prg_buf.pgm_active); |
|||
|
|||
prg_buf.left_buffer_active = !prg_buf.left_buffer_active; |
|||
|
|||
//Clean inactive buffer and replace with empty one for next switch
|
|||
if (prg_buf.left_buffer_active) { |
|||
//printf("cleaning right buffer\n");
|
|||
//CBOX_DELETE(prg_buf.pgm_right); segfault from time to time
|
|||
prg_buf.pgm_right = sampler_program_new(prg_buf.module, 0, "sfzload_right", NULL, prg_buf.directory, &error); //program number 0. tarfile is NULL. We only give the sample dir here, the actual sfz is in sampler_module_load_program_sfz
|
|||
if (error) fprintf(stderr, "Cannot create new program: %s\n", error ? error->message : "unknown error"); |
|||
|
|||
} |
|||
else { |
|||
//printf("cleaning left buffer\n");
|
|||
//CBOX_DELETE(prg_buf.pgm_left); segfault from time to time
|
|||
prg_buf.pgm_left = sampler_program_new(prg_buf.module, 0, "sfzload_left", NULL, prg_buf.directory, &error); //program number 0. tarfile is NULL. We only give the sample dir here, the actual sfz is in sampler_module_load_program_sfz
|
|||
if (error) fprintf(stderr, "Cannot create new program: %s\n", error ? error->message : "unknown error"); |
|||
} |
|||
|
|||
if (error) g_error_free(error); |
|||
error = NULL; |
|||
|
|||
} |
|||
|
|||
gboolean on_idle(struct cbox_command_target *fb, GError **error) |
|||
{ |
|||
if (env.realtime) |
|||
{ |
|||
// Process results of asynchronous commands
|
|||
cbox_rt_handle_cmd_queue(env.realtime); |
|||
|
|||
if (!cbox_midi_appsink_send_to(&env.engine->appsink, fb, error)) |
|||
return FALSE; |
|||
} |
|||
return TRUE; |
|||
} |
|||
|
|||
|
|||
void cleanup_resources(struct cbox_environment *env) |
|||
{ |
|||
if (env == NULL) return; |
|||
|
|||
printf("Starting cleanup...\n"); |
|||
|
|||
if (env->realtime) { |
|||
cbox_rt_stop(env->realtime); |
|||
CBOX_DELETE(env->realtime); |
|||
env->realtime = NULL; |
|||
} |
|||
|
|||
if (env->engine) { |
|||
CBOX_DELETE(env->engine); |
|||
env->engine = NULL; |
|||
} |
|||
|
|||
if (env->document) { |
|||
cbox_document_destroy(env->document); |
|||
env->document = NULL; |
|||
} |
|||
|
|||
cbox_wavebank_close(); |
|||
cbox_config_close(); |
|||
|
|||
if (env->context) { |
|||
g_free(env->context); |
|||
env->context = NULL; |
|||
} |
|||
|
|||
printf("Cleanup complete.\n"); |
|||
} |
|||
|
|||
/////////////////////////////
|
|||
//////////// CLAP ///////////
|
|||
/////////////////////////////
|
|||
|
|||
|
|||
|
|||
static const clap_plugin_descriptor_t s_my_plug_desc = { |
|||
.clap_version = CLAP_VERSION_INIT, |
|||
.id = "org.laborejo.sfzloader", |
|||
.name = "SFZ Loader", |
|||
.vendor = "Laborejo Software Suite", |
|||
.url = "https://git.laborejo.org/lss/libcalfbox-lss", |
|||
.manual_url = "https://git.laborejo.org/lss/libcalfbox-lss", |
|||
.support_url = "https://laborejo.org/", |
|||
.version = "1.0.0", |
|||
.description = "Load and play a single SFZ file.", |
|||
.features = (const char *[]){CLAP_PLUGIN_FEATURE_INSTRUMENT, CLAP_PLUGIN_FEATURE_STEREO, NULL}, |
|||
}; |
|||
|
|||
typedef struct { |
|||
clap_plugin_t plugin; |
|||
const clap_host_t *host; |
|||
const clap_host_latency_t *host_latency; |
|||
const clap_host_log_t *host_log; |
|||
const clap_host_thread_check_t *host_thread_check; |
|||
const clap_host_state_t *host_state; |
|||
float sampleRate; |
|||
int midiKey; |
|||
|
|||
uint32_t latency; |
|||
} my_plug_t; |
|||
|
|||
/////////////////////////////
|
|||
// clap_plugin_audio_ports //
|
|||
/////////////////////////////
|
|||
|
|||
static uint32_t my_plug_audio_ports_count(const clap_plugin_t *plugin, bool is_input) { |
|||
// We just declare 1 audio input and 1 audio output
|
|||
return 1; |
|||
} |
|||
|
|||
static bool my_plug_audio_ports_get(const clap_plugin_t *plugin, |
|||
uint32_t index, |
|||
bool is_input, |
|||
clap_audio_port_info_t *info) { |
|||
if (index > 0) |
|||
return false; |
|||
info->id = 0; |
|||
snprintf(info->name, sizeof(info->name), "%s", "My Port Name"); |
|||
info->channel_count = 2; |
|||
info->flags = CLAP_AUDIO_PORT_IS_MAIN; |
|||
info->port_type = CLAP_PORT_STEREO; |
|||
info->in_place_pair = CLAP_INVALID_ID; |
|||
return true; |
|||
} |
|||
|
|||
static const clap_plugin_audio_ports_t s_my_plug_audio_ports = { |
|||
.count = my_plug_audio_ports_count, |
|||
.get = my_plug_audio_ports_get, |
|||
}; |
|||
|
|||
////////////////////////////
|
|||
// clap_plugin_note_ports //
|
|||
////////////////////////////
|
|||
|
|||
static uint32_t my_plug_note_ports_count(const clap_plugin_t *plugin, bool is_input) { |
|||
// We just declare 1 note input
|
|||
return 1; |
|||
} |
|||
|
|||
static bool my_plug_note_ports_get(const clap_plugin_t *plugin, |
|||
uint32_t index, |
|||
bool is_input, |
|||
clap_note_port_info_t *info) { |
|||
if (index > 0) |
|||
return false; |
|||
info->id = 0; |
|||
snprintf(info->name, sizeof(info->name), "%s", "My Port Name"); |
|||
info->supported_dialects = |
|||
CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI_MPE | CLAP_NOTE_DIALECT_MIDI2; |
|||
info->preferred_dialect = CLAP_NOTE_DIALECT_CLAP; |
|||
return true; |
|||
} |
|||
|
|||
static const clap_plugin_note_ports_t s_my_plug_note_ports = { |
|||
.count = my_plug_note_ports_count, |
|||
.get = my_plug_note_ports_get, |
|||
}; |
|||
|
|||
//////////////////
|
|||
// clap_latency //
|
|||
//////////////////
|
|||
|
|||
uint32_t my_plug_latency_get(const clap_plugin_t *plugin) { |
|||
my_plug_t *plug = plugin->plugin_data; |
|||
return plug->latency; |
|||
} |
|||
|
|||
static const clap_plugin_latency_t s_my_plug_latency = { |
|||
.get = my_plug_latency_get, |
|||
}; |
|||
|
|||
////////////////
|
|||
// clap_state //
|
|||
////////////////
|
|||
|
|||
bool my_plug_state_save(const clap_plugin_t *plugin, const clap_ostream_t *stream) { |
|||
my_plug_t *plug = plugin->plugin_data; |
|||
// TODO: write the state into stream
|
|||
return true; |
|||
} |
|||
|
|||
bool my_plug_state_load(const clap_plugin_t *plugin, const clap_istream_t *stream) { |
|||
my_plug_t *plug = plugin->plugin_data; |
|||
// TODO: read the state from stream
|
|||
return true; |
|||
} |
|||
|
|||
static const clap_plugin_state_t s_my_plug_state = { |
|||
.save = my_plug_state_save, |
|||
.load = my_plug_state_load, |
|||
}; |
|||
|
|||
/////////////////
|
|||
// clap_plugin //
|
|||
/////////////////
|
|||
|
|||
static bool my_plug_init(const struct clap_plugin *plugin) { |
|||
my_plug_t *plug = plugin->plugin_data; |
|||
|
|||
// Fetch host's extensions here
|
|||
// Make sure to check that the interface functions are not null pointers
|
|||
plug->host_log = (const clap_host_log_t *)plug->host->get_extension(plug->host, CLAP_EXT_LOG); |
|||
plug->host_thread_check = (const clap_host_thread_check_t *)plug->host->get_extension(plug->host, CLAP_EXT_THREAD_CHECK); |
|||
plug->host_latency = (const clap_host_latency_t *)plug->host->get_extension(plug->host, CLAP_EXT_LATENCY); |
|||
plug->host_state = (const clap_host_state_t *)plug->host->get_extension(plug->host, CLAP_EXT_STATE); |
|||
return true; |
|||
} |
|||
|
|||
static void my_plug_destroy(const struct clap_plugin *plugin) { |
|||
my_plug_t *plug = plugin->plugin_data; |
|||
free(plug); |
|||
} |
|||
|
|||
static bool my_plug_activate(const struct clap_plugin *plugin, |
|||
double sample_rate, |
|||
uint32_t min_frames_count, |
|||
uint32_t max_frames_count) { |
|||
|
|||
my_plug_t *plug = plugin->plugin_data; |
|||
plug->sampleRate = sample_rate; |
|||
plug->midiKey = 59; |
|||
return true; |
|||
} |
|||
|
|||
static void my_plug_deactivate(const struct clap_plugin *plugin) {} |
|||
|
|||
static bool my_plug_start_processing(const struct clap_plugin *plugin) { return true; } |
|||
|
|||
static void my_plug_stop_processing(const struct clap_plugin *plugin) {} |
|||
|
|||
static void my_plug_reset(const struct clap_plugin *plugin) {} |
|||
|
|||
static void my_plug_process_event(my_plug_t *plug, const clap_event_header_t *hdr) { |
|||
if (hdr->space_id == CLAP_CORE_EVENT_SPACE_ID) { |
|||
switch (hdr->type) { |
|||
case CLAP_EVENT_NOTE_ON: { |
|||
const clap_event_note_t *ev = (const clap_event_note_t *)hdr; |
|||
// TODO: handle note on
|
|||
plug->midiKey = ev->key; |
|||
printf("Note On: MIDI Key = %d\n", plug->midiKey); |
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_NOTE_OFF: { |
|||
const clap_event_note_t *ev = (const clap_event_note_t *)hdr; |
|||
// TODO: handle note off
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_NOTE_CHOKE: { |
|||
const clap_event_note_t *ev = (const clap_event_note_t *)hdr; |
|||
// TODO: handle note choke
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_NOTE_EXPRESSION: { |
|||
const clap_event_note_expression_t *ev = (const clap_event_note_expression_t *)hdr; |
|||
// TODO: handle note expression
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_PARAM_VALUE: { |
|||
const clap_event_param_value_t *ev = (const clap_event_param_value_t *)hdr; |
|||
// TODO: handle parameter change
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_PARAM_MOD: { |
|||
const clap_event_param_mod_t *ev = (const clap_event_param_mod_t *)hdr; |
|||
// TODO: handle parameter modulation
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_TRANSPORT: { |
|||
const clap_event_transport_t *ev = (const clap_event_transport_t *)hdr; |
|||
// TODO: handle transport event
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_MIDI: { |
|||
const clap_event_midi_t *ev = (const clap_event_midi_t *)hdr; |
|||
// TODO: handle MIDI event
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_MIDI_SYSEX: { |
|||
const clap_event_midi_sysex_t *ev = (const clap_event_midi_sysex_t *)hdr; |
|||
// TODO: handle MIDI Sysex event
|
|||
break; |
|||
} |
|||
|
|||
case CLAP_EVENT_MIDI2: { |
|||
const clap_event_midi2_t *ev = (const clap_event_midi2_t *)hdr; |
|||
// TODO: handle MIDI2 event
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
static double phase = 0.0; |
|||
|
|||
static clap_process_status my_plug_process(const struct clap_plugin *plugin, |
|||
const clap_process_t *process) { |
|||
my_plug_t *plug = plugin->plugin_data; |
|||
const uint32_t nframes = process->frames_count; |
|||
const uint32_t nev = process->in_events->size(process->in_events); |
|||
uint32_t ev_index = 0; |
|||
uint32_t next_ev_frame = nev > 0 ? 0 : nframes; |
|||
|
|||
double frequency = 440 * pow(2, (plug->midiKey - 69) / 12.0); |
|||
const double phase_step = (2.0 * M_PI * frequency) / plug->sampleRate; |
|||
|
|||
/*
|
|||
|
|||
for (uint32_t i = 0; i < nframes;) { |
|||
// handle every events that happrens at the frame "i"
|
|||
while (ev_index < nev && next_ev_frame == i) { |
|||
const clap_event_header_t *hdr = process->in_events->get(process->in_events, ev_index); |
|||
if (hdr->time != i) { |
|||
next_ev_frame = hdr->time; |
|||
break; |
|||
} |
|||
|
|||
my_plug_process_event(plug, hdr); |
|||
++ev_index; |
|||
|
|||
if (ev_index == nev) { |
|||
// we reached the end of the event list
|
|||
next_ev_frame = nframes; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// process every samples until the next event
|
|||
for (; i < next_ev_frame; ++i) { |
|||
// fetch input samples
|
|||
const float in_l = process->audio_inputs[0].data32[0][i]; |
|||
const float in_r = process->audio_inputs[0].data32[1][i]; |
|||
|
|||
// TODO: process samples, here we simply swap left and right channels
|
|||
const float out_l = in_r; |
|||
const float out_r = in_l; |
|||
|
|||
// store output samples
|
|||
process->audio_outputs[0].data32[0][i] = out_l; |
|||
process->audio_outputs[0].data32[1][i] = out_r; |
|||
} |
|||
} |
|||
*/ |
|||
|
|||
for (uint32_t i = 0; i < nframes; ++i) { |
|||
// handle every events that happens at the frame "i"
|
|||
while (ev_index < nev && next_ev_frame == i) { |
|||
const clap_event_header_t *hdr = process->in_events->get(process->in_events, ev_index); |
|||
if (hdr->time != i) { |
|||
next_ev_frame = hdr->time; |
|||
break; |
|||
} |
|||
|
|||
my_plug_process_event(plug, hdr); |
|||
++ev_index; |
|||
|
|||
if (ev_index == nev) { |
|||
// we reached the end of the event list
|
|||
next_ev_frame = nframes; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Generate and output the sine wave
|
|||
float sample = sin(phase); |
|||
phase += phase_step; |
|||
if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI; |
|||
|
|||
// Output the same sine wave on both left and right channels
|
|||
process->audio_outputs[0].data32[0][i] = sample * 0.5; // Left channel
|
|||
process->audio_outputs[0].data32[1][i] = sample * 0.5; // Right channel
|
|||
} |
|||
|
|||
|
|||
return CLAP_PROCESS_CONTINUE; |
|||
} |
|||
|
|||
static const void *my_plug_get_extension(const struct clap_plugin *plugin, const char *id) { |
|||
if (!strcmp(id, CLAP_EXT_LATENCY)) |
|||
return &s_my_plug_latency; |
|||
if (!strcmp(id, CLAP_EXT_AUDIO_PORTS)) |
|||
return &s_my_plug_audio_ports; |
|||
if (!strcmp(id, CLAP_EXT_NOTE_PORTS)) |
|||
return &s_my_plug_note_ports; |
|||
if (!strcmp(id, CLAP_EXT_STATE)) |
|||
return &s_my_plug_state; |
|||
// TODO: add support to CLAP_EXT_PARAMS
|
|||
return NULL; |
|||
} |
|||
|
|||
static void my_plug_on_main_thread(const struct clap_plugin *plugin) {} |
|||
|
|||
clap_plugin_t *my_plug_create(const clap_host_t *host) { |
|||
my_plug_t *p = calloc(1, sizeof(*p)); |
|||
p->host = host; |
|||
p->plugin.desc = &s_my_plug_desc; |
|||
p->plugin.plugin_data = p; |
|||
p->plugin.init = my_plug_init; |
|||
p->plugin.destroy = my_plug_destroy; |
|||
p->plugin.activate = my_plug_activate; |
|||
p->plugin.deactivate = my_plug_deactivate; |
|||
p->plugin.start_processing = my_plug_start_processing; |
|||
p->plugin.stop_processing = my_plug_stop_processing; |
|||
p->plugin.reset = my_plug_reset; |
|||
p->plugin.process = my_plug_process; |
|||
p->plugin.get_extension = my_plug_get_extension; |
|||
p->plugin.on_main_thread = my_plug_on_main_thread; |
|||
|
|||
// Don't call into the host here
|
|||
|
|||
return &p->plugin; |
|||
} |
|||
|
|||
/////////////////////////
|
|||
// clap_plugin_factory //
|
|||
/////////////////////////
|
|||
|
|||
static struct { |
|||
const clap_plugin_descriptor_t *desc; |
|||
clap_plugin_t *(CLAP_ABI *create)(const clap_host_t *host); |
|||
} s_plugins[] = { |
|||
{ |
|||
.desc = &s_my_plug_desc, |
|||
.create = my_plug_create, |
|||
}, |
|||
}; |
|||
|
|||
static uint32_t plugin_factory_get_plugin_count(const struct clap_plugin_factory *factory) { |
|||
return sizeof(s_plugins) / sizeof(s_plugins[0]); |
|||
} |
|||
|
|||
static const clap_plugin_descriptor_t * |
|||
plugin_factory_get_plugin_descriptor(const struct clap_plugin_factory *factory, uint32_t index) { |
|||
return s_plugins[index].desc; |
|||
} |
|||
|
|||
static const clap_plugin_t *plugin_factory_create_plugin(const struct clap_plugin_factory *factory, |
|||
const clap_host_t *host, |
|||
const char *plugin_id) { |
|||
if (!clap_version_is_compatible(host->clap_version)) { |
|||
return NULL; |
|||
} |
|||
|
|||
const int N = sizeof(s_plugins) / sizeof(s_plugins[0]); |
|||
for (int i = 0; i < N; ++i) |
|||
if (!strcmp(plugin_id, s_plugins[i].desc->id)) |
|||
return s_plugins[i].create(host); |
|||
|
|||
return NULL; |
|||
} |
|||
|
|||
static const clap_plugin_factory_t s_plugin_factory = { |
|||
.get_plugin_count = plugin_factory_get_plugin_count, |
|||
.get_plugin_descriptor = plugin_factory_get_plugin_descriptor, |
|||
.create_plugin = plugin_factory_create_plugin, |
|||
}; |
|||
|
|||
////////////////
|
|||
// clap_entry //
|
|||
////////////////
|
|||
|
|||
static bool entry_init(const char *plugin_path) { |
|||
// called only once, and very first
|
|||
|
|||
cbox_dom_init(); |
|||
|
|||
env.document = cbox_document_new(); |
|||
env.realtime = cbox_rt_new(env.document); |
|||
env.engine = cbox_engine_new(env.document, env.realtime); |
|||
env.context = NULL; |
|||
env.realtime->engine = env.engine; //inject engine into realtime struct
|
|||
|
|||
cbox_config_init(""); |
|||
|
|||
//Autoconnect our jack outputs to system out
|
|||
cbox_config_set_string("io", "out_1", "#1"); |
|||
cbox_config_set_string("io", "out_2", "#2"); |
|||
cbox_config_set_string("io", "midi", "*a2j:.*"); |
|||
cbox_config_set_string("io", "client_name", "sfzload"); |
|||
cbox_config_set_int("io", "enable_common_midi_input", 1); //last is a bool. This is the default, let's call it anyway.
|
|||
|
|||
return true; |
|||
} |
|||
|
|||
static void entry_deinit(void) { |
|||
// called before unloading the DSO
|
|||
} |
|||
|
|||
static const void *entry_get_factory(const char *factory_id) { |
|||
if (!strcmp(factory_id, CLAP_PLUGIN_FACTORY_ID)) |
|||
return &s_plugin_factory; |
|||
return NULL; |
|||
} |
|||
|
|||
// This symbol will be resolved by the host
|
|||
CLAP_EXPORT const clap_plugin_entry_t clap_entry = { |
|||
.clap_version = CLAP_VERSION_INIT, |
|||
.init = entry_init, |
|||
.deinit = entry_deinit, |
|||
.get_factory = entry_get_factory, |
|||
}; |
Binary file not shown.
Loading…
Reference in new issue