lv2 midi in

This commit is contained in:
Stefano D'Angelo 2024-01-16 15:37:08 +01:00
parent d8379dcb6d
commit 8b1345c662
7 changed files with 144 additions and 15 deletions

4
TODO
View File

@ -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?

View File

@ -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}}

Binary file not shown.

View File

@ -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

View File

@ -1,36 +1,77 @@
#include "lv2/core/lv2.h"
#include <stdlib.h>
#include <stdint.h>
#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 <xmmintrin.h>
#include <pmmintrin.h>
#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__)

View File

@ -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);
}

View File

@ -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) {
}