diff --git a/TODO b/TODO index 6ef456f..8b4d8d7 100644 --- a/TODO +++ b/TODO @@ -13,3 +13,7 @@ * more mappings, lv2 port props * latency mechanism, see https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Workflow+Diagrams/Get+Latency+Call+Sequence.html, https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#af8884671ccefe68e0a86e72413a0fcf8, https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Workflow+Diagrams/Audio+Processor+Call+Sequence.html * vst3 don't include/link libm if not needed, etc. (reduce to minimum reqs) +* proper parameter automation +* proper sample accurate midi (see https://lv2plug.in/book/#_midi_gate) +* whole midi properly (see https://github.com/harryhaaren/lv2/blob/master/lv2/lv2plug.in/ns/ext/midi/midi.h, https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message, http://midi.teragonaudio.com/tech/midispec.htm) +* uneven midi pitch bend, what to do? diff --git a/templates/lv2/data/manifest.ttl b/templates/lv2/data/manifest.ttl index c088520..2c62f6a 100644 --- a/templates/lv2/data/manifest.ttl +++ b/templates/lv2/data/manifest.ttl @@ -20,6 +20,10 @@ ] ; lv2:minorVersion {{=/^([0-9]+)\./.exec(it.lv2.version)[1]}} ; lv2:microVersion {{=/^[0-9]+\.([0-9]+)/.exec(it.lv2.version)[1]}} ; +{{?it.tibia.lv2.ports.find(p => p.type == "midi")}} + lv2:requiredFeature urid:map ; + lv2:optionalFeature log:log ; +{{?}} lv2:optionalFeature lv2:hardRTCapable ; lv2:port [ {{~it.tibia.lv2.ports :p:i}} diff --git a/templates/lv2/src/.lv2.c.swp b/templates/lv2/src/.lv2.c.swp new file mode 100644 index 0000000..a8c32bb Binary files /dev/null and b/templates/lv2/src/.lv2.c.swp differ diff --git a/templates/lv2/src/data.h b/templates/lv2/src/data.h index 6e59cec..ee7ced4 100644 --- a/templates/lv2/src/data.h +++ b/templates/lv2/src/data.h @@ -7,6 +7,12 @@ #define DATA_PRODUCT_CONTROL_INPUTS_N {{=it.product.parameters.filter(x => x.direction == "input").length}} #define DATA_PRODUCT_CONTROL_OUTPUTS_N {{=it.product.parameters.filter(x => x.direction == "output").length}} +#if DATA_PRODUCT_MIDI_INPUTS_N > 0 +static uint32_t midi_in_index[DATA_PRODUCT_MIDI_INPUTS_N] = { + {{~it.tibia.lv2.ports.filter(x => x.type == "midi" && x.direction == "input") :p}}{{=p.busIndex}}, {{~}} +}; +#endif + #if DATA_PRODUCT_CONTROL_INPUTS_N > 0 # define DATA_PARAM_BYPASS 1 diff --git a/templates/lv2/src/lv2.c b/templates/lv2/src/lv2.c index dbc3f7a..2cd2390 100644 --- a/templates/lv2/src/lv2.c +++ b/templates/lv2/src/lv2.c @@ -1,36 +1,77 @@ -#include "lv2/core/lv2.h" #include +#include #include "data.h" #include "plugin.h" +#include "lv2/core/lv2.h" +#if DATA_PRODUCT_MIDI_INPUTS_N + DATA_PRODUCT_MIDI_OUTPUTS_N > 0 +#include "lv2/core/lv2_util.h" +#include "lv2/atom/util.h" +#include "lv2/atom/atom.h" +#include "lv2/log/log.h" +#include "lv2/log/logger.h" +#include "lv2/midi/midi.h" +#include "lv2/urid/urid.h" +#endif + #if defined(__i386__) || defined(__x86_64__) #include #include #endif typedef struct { - plugin p; + plugin p; #if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 - const float * x[DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N]; + const float * x[DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N]; #endif #if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0 - float * y[DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N]; + float * y[DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N]; +#endif +#if DATA_PRODUCT_MIDI_INPUTS_N > 0 + const LV2_Atom_Sequence * x_midi[DATA_PRODUCT_MIDI_INPUTS_N]; +#endif +#if DATA_PRODUCT_MIDI_OUTPUTS_N > 0 + LV2_Atom_Sequence * y_midi[DATA_PRODUCT_MIDI_OUTPUTS_N]; #endif #if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0 - float * c[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N]; + float * c[DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N]; #endif #if DATA_PRODUCT_CONTROL_INPUTS_N > 0 - float params[DATA_PRODUCT_CONTROL_INPUTS_N]; + float params[DATA_PRODUCT_CONTROL_INPUTS_N]; +#endif + void * mem; +#if DATA_PRODUCT_MIDI_INPUTS_N + DATA_PRODUCT_MIDI_OUTPUT_N > 0 + LV2_URID_Map * map; + LV2_Log_Logger logger; + LV2_URID uri_midi_MidiEvent; #endif - void * mem; } plugin_instance; static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double sample_rate, const char * bundle_path, const LV2_Feature * const * features) { plugin_instance *instance = malloc(sizeof(plugin_instance)); if (instance == NULL) return NULL; + +#if DATA_PRODUCT_MIDI_INPUTS_N + DATA_PRODUCT_MIDI_OUTPUT_N > 0 + // from https://lv2plug.in/book + const char* missing = lv2_features_query(features, + LV2_LOG__log, &instance->logger.log, false, + LV2_URID__map, &instance->map, true, + NULL); + + lv2_log_logger_set_map(&instance->logger, instance->map); + if (missing) { + lv2_log_error(&instance->logger, "Missing feature <%s>\n", missing); + free(instance); + return NULL; + } + + instance->uri_midi_MidiEvent = instance->map->map(instance->map->handle, LV2_MIDI__MidiEvent); +#endif + plugin_init(&instance->p); + plugin_set_sample_rate(&instance->p, sample_rate); size_t req = plugin_mem_req(&instance->p); if (req != 0) { @@ -42,6 +83,7 @@ static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double s plugin_mem_set(&instance->p, instance->mem); } else instance->mem = NULL; + #if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 for (uint32_t i = 0; i < DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N; i++) instance->x[i] = NULL; @@ -50,10 +92,19 @@ static LV2_Handle instantiate(const struct LV2_Descriptor * descriptor, double s for (uint32_t i = 0; i < DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N; i++) instance->y[i] = NULL; #endif +#if DATA_PRODUCT_MIDI_INPUTS_N > 0 + for (uint32_t i = 0; i < DATA_PRODUCT_MIDI_INPUTS_N; i++) + instance->x_midi[i] = NULL; +#endif +#if DATA_PRODUCT_MIDI_OUTPUTS_N > 0 + for (uint32_t i = 0; i < DATA_PRODUCT_MIDI_OUTPUTS_N; i++) + instance->y_midi[i] = NULL; +#endif #if (DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N) > 0 for (uint32_t i = 0; i < DATA_PRODUCT_CONTROL_INPUTS_N + DATA_PRODUCT_CONTROL_OUTPUTS_N; i++) instance->c[i] = NULL; #endif + return instance; } @@ -75,14 +126,14 @@ static void connect_port(LV2_Handle instance, uint32_t port, void * data_locatio #endif #if DATA_PRODUCT_MIDI_INPUTS_N > 0 if (port < DATA_PRODUCT_MIDI_INPUTS_N) { - // TBD + i->x_midi[port] = data_location; return; } port -= DATA_PRODUCT_MIDI_INPUTS_N; #endif #if DATA_PRODUCT_MIDI_OUTPUTS_N > 0 if (port < DATA_PRODUCT_MIDI_OUTPUTS_N) { - // TBD + i->y_midi[port] = data_location; return; } port -= DATA_PRODUCT_MIDI_OUTPUTS_N; @@ -146,6 +197,47 @@ static void run(LV2_Handle instance, uint32_t sample_count) { _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); #endif + // from https://lv2plug.in/book +#if DATA_PRODUCT_MIDI_INPUTS_N > 0 + for (size_t j = 0; j < DATA_PRODUCT_MIDI_INPUTS_N; j++) + LV2_ATOM_SEQUENCE_FOREACH(i->x_midi[j], ev) { + if (ev->body.type == i->uri_midi_MidiEvent) { + const uint8_t * const msg = (const uint8_t *)(ev + 1); + switch (lv2_midi_message_type(msg)) { + case LV2_MIDI_MSG_NOTE_ON: + if (msg[2] == 0) + plugin_note_off(&i->p, midi_in_index[j], msg[1], 64.f / 127.f); + else + plugin_note_on(&i->p, midi_in_index[j], msg[1], (1.f / 127.f) * msg[2]); + break; + case LV2_MIDI_MSG_NOTE_OFF: + plugin_note_off(&i->p, midi_in_index[j], msg[1], (1.f / 127.f) * msg[2]); + break; + case LV2_MIDI_MSG_CONTROLLER: + switch (msg[1]) { + case LV2_MIDI_CTL_ALL_SOUNDS_OFF: + plugin_all_sounds_off(&i->p, midi_in_index[j]); + break; + case LV2_MIDI_CTL_ALL_NOTES_OFF: + plugin_all_notes_off(&i->p, midi_in_index[j]); + break; + default: + break; + } + break; + case LV2_MIDI_MSG_CHANNEL_PRESSURE: + plugin_channel_pressure(&i->p, midi_in_index[j], (1.f / 127.f) * msg[1]); + break; + case LV2_MIDI_MSG_BENDER: + plugin_pitch_bend_change(&i->p, midi_in_index[j], (1.f / 8192.f) * (msg[2] << 7 | msg[1]) - 1.f); + break; + default: + break; + } + } + } +#endif + plugin_process(&i->p, i->x, i->y, sample_count); #if defined(__aarch64__) diff --git a/templates/lv2/tibia-index.js b/templates/lv2/tibia-index.js index 919b877..1f4fc48 100644 --- a/templates/lv2/tibia-index.js +++ b/templates/lv2/tibia-index.js @@ -7,12 +7,14 @@ module.exports = function (data, api) { { id: "atom", uri: "http://lv2plug.in/ns/ext/atom#" }, { id: "doap", uri: "http://usefulinc.com/ns/doap#" }, { id: "foaf", uri: "http://xmlns.com/foaf/0.1/" }, + { id: "log", uri: "http://lv2plug.in/ns/ext/log#" }, { id: "lv2", uri: "http://lv2plug.in/ns/lv2core#" }, { id: "midi", uri: "http://lv2plug.in/ns/ext/midi#" }, { id: "pprops", uri: "http://lv2plug.in/ns/ext/port-props#" }, { id: "rdf", uri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#" }, { id: "rdfs", uri: "http://www.w3.org/2000/01/rdf-schema#" }, - { id: "units", uri: "http://lv2plug.in/ns/extensions/units#" } + { id: "units", uri: "http://lv2plug.in/ns/extensions/units#" }, + { id: "urid", uri: "http://lv2plug.in/ns/ext/urid#" } ], units: { "bar": "@units:bar", @@ -64,14 +66,14 @@ module.exports = function (data, api) { for (; bi < audioBuses.length; bi++) { var b = audioBuses[bi]; if (b.channels == "mono") { - var e = { type: "audio", direction: b.direction, name: b.name, sidechain: b.sidechain, cv: b.cv, optional: b.optional }; + var e = { type: "audio", direction: b.direction, name: b.name, sidechain: b.sidechain, cv: b.cv, optional: b.optional, busIndex: bi }; e.symbol = data.lv2.busSymbols[bi]; ports.push(e); } else { - var e = { type: "audio", direction: b.direction, name: b.name + " Left", shortName: b.shortName + " L", sidechain: b.sidechain, cv: b.cv }; + var e = { type: "audio", direction: b.direction, name: b.name + " Left", shortName: b.shortName + " L", sidechain: b.sidechain, cv: b.cv, busIndex: bi }; e.symbol = data.lv2.busSymbols[bi] + "_L"; data.tibia.lv2.ports.push(e); - var e = { type: "audio", direction: b.direction, name: b.name + " Right", shortName: b.shortName + " R", sidechain: b.sidechain, cv: b.cv }; + var e = { type: "audio", direction: b.direction, name: b.name + " Right", shortName: b.shortName + " R", sidechain: b.sidechain, cv: b.cv, busIndex: bi }; e.symbol = data.lv2.busSymbols[bi] + "_R"; ports.push(e); } @@ -83,7 +85,7 @@ module.exports = function (data, api) { var ports = []; for (var i = 0; i < midiBuses.length; i++, bi++) { var b = midiBuses[i]; - var e = { type: "midi", direction: b.direction, name: b.name, sidechain: b.sidechain, control: b.control, optional: b.optional }; + var e = { type: "midi", direction: b.direction, name: b.name, sidechain: b.sidechain, control: b.control, optional: b.optional, busIndex: bi }; e.symbol = data.lv2.busSymbols[bi]; ports.push(e); } diff --git a/test/plugin.h b/test/plugin.h index cc1f9e4..3dde9f2 100644 --- a/test/plugin.h +++ b/test/plugin.h @@ -13,6 +13,7 @@ typedef struct plugin { float * delay_line; size_t delay_line_cur; float z1; + float cutoff_k; } plugin; static void plugin_init(plugin *instance) { @@ -38,6 +39,7 @@ static void plugin_reset(plugin *instance) { memset(instance->delay_line, 0, instance->delay_line_length * sizeof(float)); instance->delay_line_cur = 0; instance->z1 = 0.f; + instance->cutoff_k = 1.f; } static void plugin_set_parameter(plugin *instance, size_t index, float value) { @@ -68,7 +70,7 @@ static size_t calc_index(size_t cur, size_t delay, size_t len) { static void plugin_process(plugin *instance, const float **inputs, float **outputs, size_t n_samples) { size_t delay = roundf(instance->sample_rate * instance->delay); - const float mA1 = instance->sample_rate / (instance->sample_rate + 6.283185307179586f * instance->cutoff); + const float mA1 = instance->sample_rate / (instance->sample_rate + 6.283185307179586f * instance->cutoff * instance->cutoff_k); for (size_t i = 0; i < n_samples; i++) { instance->delay_line[instance->delay_line_cur] = inputs[0][i]; const float x = instance->delay_line[calc_index(instance->delay_line_cur, delay, instance->delay_line_length)]; @@ -80,3 +82,22 @@ static void plugin_process(plugin *instance, const float **inputs, float **outpu outputs[0][i] = instance->bypass ? inputs[0][i] : instance->gain * y; } } + +static void plugin_note_on(plugin *instance, size_t index, uint8_t note, float velocity) { + instance->cutoff_k = powf(2.f, (1.f / 12.f) * (note - 60)); +} + +static void plugin_note_off(plugin *instance, size_t index, uint8_t note, float velocity) { +} + +static void plugin_all_sounds_off(plugin *instance, size_t index) { +} + +static void plugin_all_notes_off(plugin *instance, size_t index) { +} + +static void plugin_channel_pressure(plugin *instance, size_t index, float value) { +} + +static void plugin_pitch_bend_change(plugin *instance, size_t index, float value) { +}