From adc132b5fc039a185be947de3309bd11f4dee823 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 1 Jan 2021 19:22:01 +0100 Subject: Implement EPN transmission for the midi backend --- backends/midi.c | 90 +++++++++++++++++++++++++++++++++++++++++++------------- backends/midi.h | 8 +++-- backends/midi.md | 10 +++++++ 3 files changed, 85 insertions(+), 23 deletions(-) diff --git a/backends/midi.c b/backends/midi.c index 1f0f2d5..8a8887a 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -13,7 +13,9 @@ enum /*_midi_channel_type*/ { cc, pressure, aftertouch, - pitchbend + pitchbend, + rpn, + nrpn }; static struct { @@ -99,6 +101,13 @@ static int midi_configure_instance(instance* inst, char* option, char* value){ data->write = strdup(value); return 0; } + else if(!strcmp(option, "epn-tx")){ + data->epn_tx_short = 0; + if(!strcmp(value, "short")){ + data->epn_tx_short = 1; + } + return 0; + } LOGPF("Unknown instance option %s", option); return 1; @@ -147,6 +156,14 @@ static channel* midi_channel(instance* inst, char* spec, uint8_t flags){ ident.fields.type = pressure; channel += 8; } + else if(!strncmp(channel, "rpn", 3)){ + ident.fields.type = rpn; + channel += 3; + } + else if(!strncmp(channel, "nrpn", 4)){ + ident.fields.type = nrpn; + channel += 4; + } else if(!strncmp(channel, "pitch", 5)){ ident.fields.type = pitchbend; } @@ -167,9 +184,37 @@ static channel* midi_channel(instance* inst, char* spec, uint8_t flags){ return NULL; } +static void midi_tx(int port, uint8_t type, uint8_t channel, uint8_t control, uint16_t value){ + snd_seq_event_t ev; + + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, port); + snd_seq_ev_set_subs(&ev); + snd_seq_ev_set_direct(&ev); + + switch(type){ + case note: + snd_seq_ev_set_noteon(&ev, channel, control, value); + break; + case cc: + snd_seq_ev_set_controller(&ev, channel, control, value); + break; + case pressure: + snd_seq_ev_set_keypress(&ev, channel, control, value); + break; + case pitchbend: + snd_seq_ev_set_pitchbend(&ev, channel, value); + break; + case aftertouch: + snd_seq_ev_set_chanpress(&ev, channel, value); + break; + } + + snd_seq_event_output(sequencer, &ev); +} + static int midi_set(instance* inst, size_t num, channel** c, channel_value* v){ size_t u; - snd_seq_event_t ev; midi_instance_data* data = (midi_instance_data*) inst->impl; midi_channel_ident ident = { .label = 0 @@ -178,30 +223,28 @@ static int midi_set(instance* inst, size_t num, channel** c, channel_value* v){ for(u = 0; u < num; u++){ ident.label = c[u]->ident; - snd_seq_ev_clear(&ev); - snd_seq_ev_set_source(&ev, data->port); - snd_seq_ev_set_subs(&ev); - snd_seq_ev_set_direct(&ev); - switch(ident.fields.type){ - case note: - snd_seq_ev_set_noteon(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); - break; - case cc: - snd_seq_ev_set_controller(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); - break; - case pressure: - snd_seq_ev_set_keypress(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); + case rpn: + case nrpn: + //transmit parameter number + midi_tx(data->port, cc, ident.fields.channel, (ident.fields.type == rpn) ? 101 : 99, (ident.fields.control & 0x3F80) >> 7); + midi_tx(data->port, cc, ident.fields.channel, (ident.fields.type == rpn) ? 100 : 98, ident.fields.control & 0x7F); + //transmit parameter value + midi_tx(data->port, cc, ident.fields.channel, 6, (((uint16_t) (v[u].normalised * 16383.0)) & 0x3F80) >> 7); + midi_tx(data->port, cc, ident.fields.channel, 38, ((uint16_t) (v[u].normalised * 16383.0)) & 0x7F); + + if(!data->epn_tx_short){ + //clear active parameter + midi_tx(data->port, cc, ident.fields.channel, 101, 127); + midi_tx(data->port, cc, ident.fields.channel, 100, 127); + } break; case pitchbend: - snd_seq_ev_set_pitchbend(&ev, ident.fields.channel, (v[u].normalised * 16383.0) - 8192); - break; - case aftertouch: - snd_seq_ev_set_chanpress(&ev, ident.fields.channel, v[u].normalised * 127.0); + midi_tx(data->port, ident.fields.type, ident.fields.channel, ident.fields.control, (v[u].normalised * 16383.0) - 8192); break; + default: + midi_tx(data->port, ident.fields.type, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); } - - snd_seq_event_output(sequencer, &ev); } snd_seq_drain_output(sequencer); @@ -216,6 +259,10 @@ static char* midi_type_name(uint8_t type){ return "note"; case cc: return "cc"; + case rpn: + return "rpn"; + case nrpn: + return "nrpn"; case pressure: return "pressure"; case aftertouch: @@ -248,6 +295,7 @@ static int midi_handle(size_t num, managed_fd* fds){ ident.fields.control = ev->data.note.note; val.normalised = (double) ev->data.note.velocity / 127.0; + //TODO (n)rpn RX switch(ev->type){ case SND_SEQ_EVENT_NOTEON: case SND_SEQ_EVENT_NOTEOFF: diff --git a/backends/midi.h b/backends/midi.h index dcee010..4e2ac09 100644 --- a/backends/midi.h +++ b/backends/midi.h @@ -14,14 +14,18 @@ typedef struct /*_midi_instance_data*/ { int port; char* read; char* write; + uint8_t epn_tx_short; + + uint16_t epn_control; + uint16_t epn_value; } midi_instance_data; typedef union { struct { - uint8_t pad[5]; + uint8_t pad[4]; uint8_t type; uint8_t channel; - uint8_t control; + uint16_t control; } fields; uint64_t label; } midi_channel_ident; diff --git a/backends/midi.md b/backends/midi.md index d3d6e33..3ac011e 100644 --- a/backends/midi.md +++ b/backends/midi.md @@ -15,6 +15,7 @@ The MIDI backend provides read-write access to the MIDI protocol via virtual por |---------------|-----------------------|-----------------------|-----------------------| | `read` | `20:0` | none | MIDI device to connect for input | | `write` | `DeviceName` | none | MIDI device to connect for output | +| `epn-tx` | `short` | `full` | Configures whether to clear the active parameter number after transmitting an `nrpn` or `rpn` parameter | MIDI device names may either be `client:port` portnames or prefixes of MIDI device names. Run `aconnect -i` to list input ports and `aconnect -o` to list output ports. @@ -30,6 +31,8 @@ The MIDI backend supports mapping different MIDI events to MIDIMonster channels. * `pressure` - Note pressure/aftertouch messages * `aftertouch` - Channel-wide aftertouch messages * `pitch` - Channel pitchbend messages +* `rpn` - Registered parameter numbers (14bit extension) +* `nrpn` - Non-registered parameter numbers (14bit extension) A MIDIMonster channel is specified using the syntax `channel.`. The shorthand `ch` may be used instead of the word `channel` (Note that `channel` here refers to the MIDI channel number). @@ -40,15 +43,22 @@ MIDI channels range from `0` to `15`. Each MIDI channel consists of 128 notes (n additionally each have a pressure control, 128 CC's (numbered likewise), a channel pressure control (also called 'channel aftertouch') and a pitch control which may all be mapped to individual MIDIMonster channels. +Every channel also provides `rpn` and `nrpn` controls, which are implemented on top of the MIDI protocol, using +the CC controls 101/100/99/98/38/6. Both control types have 14-bit IDs and 14-bit values. + Example mappings: ``` midi1.ch0.note9 > midi2.channel1.cc4 midi1.channel15.pressure1 > midi1.channel0.note0 midi1.ch1.aftertouch > midi2.ch2.cc0 midi1.ch0.pitch > midi2.ch1.pitch +midi1.ch0.nrpn900 > midi2.ch0.rpn1 ``` #### Known bugs / problems +Extended parameter numbers (`rpn` and `nrpn` control types) can currently only be transmitted, not properly +received as such. Support for this functionality is planned. + To access MIDI data, the user running MIDIMonster needs read & write access to the ALSA sequencer. This can usually be done by adding this user to the `audio` system group. -- cgit v1.2.3