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 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 21 deletions(-) (limited to 'backends/midi.c') 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: -- cgit v1.2.3 From 26b91b849899976b455bc5d780688de6962569e1 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 2 Jan 2021 17:15:57 +0100 Subject: Implement EPN transmission for the winmidi backend --- backends/midi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'backends/midi.c') diff --git a/backends/midi.c b/backends/midi.c index 8a8887a..d581a01 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -109,7 +109,7 @@ static int midi_configure_instance(instance* inst, char* option, char* value){ return 0; } - LOGPF("Unknown instance option %s", option); + LOGPF("Unknown instance configuration option %s on instance %s", option, inst->name); return 1; } @@ -227,10 +227,10 @@ static int midi_set(instance* inst, size_t num, channel** c, channel_value* v){ 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) ? 101 : 99, (ident.fields.control >> 7) & 0x7F); 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, 6, (((uint16_t) (v[u].normalised * 16383.0)) >> 7) & 0x7F); midi_tx(data->port, cc, ident.fields.channel, 38, ((uint16_t) (v[u].normalised * 16383.0)) & 0x7F); if(!data->epn_tx_short){ -- cgit v1.2.3 From 50ce7276315671b72ecbfbcb7bebe6d34654639a Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 4 Jan 2021 01:16:28 +0100 Subject: Implement EPN reception for the midi backend --- backends/midi.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 8 deletions(-) (limited to 'backends/midi.c') diff --git a/backends/midi.c b/backends/midi.c index d581a01..7883662 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -273,11 +273,91 @@ static char* midi_type_name(uint8_t type){ return "unknown"; } +static void midi_handle_epn(instance* inst, uint8_t chan, uint16_t control, uint16_t value){ + midi_instance_data* data = (midi_instance_data*) inst->impl; + midi_channel_ident ident = { + .label = 0 + }; + channel* changed = NULL; + channel_value val; + //check for 3-byte update TODO + + //switching between nrpn and rpn clears all valid bits + if(((data->epn_status[chan] & EPN_NRPN) && (control == 101 || control == 100)) + || (!(data->epn_status[chan] & EPN_NRPN) && (control == 99 || control == 98))){ + data->epn_status[chan] &= ~(EPN_NRPN | EPN_PARAMETER_LO | EPN_PARAMETER_HI); + } + + //setting an address always invalidates the value valid bits + if(control >= 98 && control <= 101){ + data->epn_status[chan] &= ~(EPN_VALUE_HI /*| EPN_VALUE_LO*/); + } + + //parameter hi + if(control == 101 || control == 99){ + data->epn_control[chan] &= 0x7F; + data->epn_control[chan] |= value << 7; + data->epn_status[chan] |= EPN_PARAMETER_HI | ((control == 99) ? EPN_NRPN : 0); + if(control == 101 && value == 127){ + data->epn_status[chan] &= ~EPN_PARAMETER_HI; + } + } + + //parameter lo + if(control == 100 || control == 98){ + data->epn_control[chan] &= ~0x7F; + data->epn_control[chan] |= value & 0x7F; + data->epn_status[chan] |= EPN_PARAMETER_LO | ((control == 98) ? EPN_NRPN : 0); + if(control == 100 && value == 127){ + data->epn_status[chan] &= ~EPN_PARAMETER_LO; + } + } + + //value hi, clears low, mark as update candidate + if(control == 6 + //check if parameter is set before accepting value update + && ((data->epn_status[chan] & (EPN_PARAMETER_HI | EPN_PARAMETER_LO)) == (EPN_PARAMETER_HI | EPN_PARAMETER_LO))){ + data->epn_value[chan] = value << 7; + data->epn_status[chan] |= EPN_VALUE_HI; + } + + //FIXME is the update order for the value bits fixed? + //FIXME can there be standalone updates on CC 38? + + //value lo, flush the value + if(control == 38 + && data->epn_status[chan] & EPN_VALUE_HI){ + data->epn_value[chan] &= ~0x7F; + data->epn_value[chan] |= value & 0x7F; + //FIXME not clearing the valid bit would allow for fast low-order updates + data->epn_status[chan] &= ~EPN_VALUE_HI; + + if(midi_config.detect){ + LOGPF("Incoming EPN data on channel %s.ch%d.%s%d", inst->name, chan, data->epn_status[chan] & EPN_NRPN ? "nrpn" : "rpn", data->epn_control[chan]); + } + + //find the updated channel + ident.fields.type = data->epn_status[chan] & EPN_NRPN ? nrpn : rpn; + ident.fields.channel = chan; + ident.fields.control = data->epn_control[chan]; + val.normalised = (double) data->epn_value[chan] / 16383.0; + + //push the new value + changed = mm_channel(inst, ident.label, 0); + if(changed){ + mm_channel_event(changed, val); + } + } +} + static int midi_handle(size_t num, managed_fd* fds){ snd_seq_event_t* ev = NULL; instance* inst = NULL; + midi_instance_data* data = NULL; + channel* changed = NULL; channel_value val; + char* event_type = NULL; midi_channel_ident ident = { .label = 0 @@ -295,7 +375,14 @@ 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 + //scan for the instance before parsing incoming data, instance state is required for the EPN state machine + inst = mm_instance_find(BACKEND_NAME, ev->dest.port); + if(!inst){ + LOG("Delivered event did not match any instance"); + continue; + } + data = (midi_instance_data*) inst->impl; + switch(ev->type){ case SND_SEQ_EVENT_NOTEON: case SND_SEQ_EVENT_NOTEOFF: @@ -323,6 +410,15 @@ static int midi_handle(size_t num, managed_fd* fds){ ident.fields.channel = ev->data.control.channel; ident.fields.control = ev->data.control.param; val.normalised = (double) ev->data.control.value / 127.0; + + //check for EPN CCs and update the state machine + if((ident.fields.control <= 101 && ident.fields.control >= 98) + || ident.fields.control == 6 + || ident.fields.control == 38 + //if the high-order value bits are set, forward any control to the state machine for the short update form + || data->epn_status[ident.fields.channel] & EPN_VALUE_HI){ + midi_handle_epn(inst, ident.fields.channel, ident.fields.control, ev->data.control.value); + } break; default: LOG("Ignored event of unsupported type"); @@ -330,13 +426,6 @@ static int midi_handle(size_t num, managed_fd* fds){ } event_type = midi_type_name(ident.fields.type); - inst = mm_instance_find(BACKEND_NAME, ev->dest.port); - if(!inst){ - //FIXME might want to return failure - LOG("Delivered event did not match any instance"); - continue; - } - changed = mm_channel(inst, ident.label, 0); if(changed){ if(mm_channel_event(changed, val)){ -- cgit v1.2.3 From 41cb85a842a696e1183e1d55116c99b63099fde3 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 8 Jan 2021 21:38:43 +0100 Subject: Implement EPN reception for the winmidi backend --- backends/midi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'backends/midi.c') diff --git a/backends/midi.c b/backends/midi.c index 7883662..bddabb5 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -273,6 +273,7 @@ static char* midi_type_name(uint8_t type){ return "unknown"; } +//this state machine is used more-or-less verbatim in the winmidi and jack backends - fixes need to be applied there, too static void midi_handle_epn(instance* inst, uint8_t chan, uint16_t control, uint16_t value){ midi_instance_data* data = (midi_instance_data*) inst->impl; midi_channel_ident ident = { @@ -402,6 +403,7 @@ static int midi_handle(size_t num, managed_fd* fds){ break; case SND_SEQ_EVENT_PITCHBEND: ident.fields.type = pitchbend; + ident.fields.control = 0; ident.fields.channel = ev->data.control.channel; val.normalised = ((double) ev->data.control.value + 8192) / 16383.0; break; -- cgit v1.2.3 From 7902842bd10b17d8d5b6bfc586f440299646d48c Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 9 Jan 2021 15:21:24 +0100 Subject: Add source/target as aliases for MIDI read/write config options (#79) --- backends/midi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'backends/midi.c') diff --git a/backends/midi.c b/backends/midi.c index bddabb5..e32c975 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -83,7 +83,7 @@ static int midi_configure_instance(instance* inst, char* option, char* value){ midi_instance_data* data = (midi_instance_data*) inst->impl; //FIXME maybe allow connecting more than one device - if(!strcmp(option, "read")){ + if(!strcmp(option, "read") || !strcmp(option, "source")){ //connect input device if(data->read){ LOGPF("Instance %s was already connected to an input device", inst->name); @@ -92,7 +92,7 @@ static int midi_configure_instance(instance* inst, char* option, char* value){ data->read = strdup(value); return 0; } - else if(!strcmp(option, "write")){ + else if(!strcmp(option, "write") || !strcmp(option, "target")){ //connect output device if(data->write){ LOGPF("Instance %s was already connected to an output device", inst->name); -- cgit v1.2.3 From a3a893f6b8b6c10ff281fcdfe0b4a4ddafe89023 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 10 Jan 2021 17:30:12 +0100 Subject: Implement program change control for the midi, winmidi and jack backends (Fixes #79) --- backends/midi.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'backends/midi.c') diff --git a/backends/midi.c b/backends/midi.c index e32c975..10c8c4a 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -14,6 +14,7 @@ enum /*_midi_channel_type*/ { pressure, aftertouch, pitchbend, + program, rpn, nrpn }; @@ -167,6 +168,9 @@ static channel* midi_channel(instance* inst, char* spec, uint8_t flags){ else if(!strncmp(channel, "pitch", 5)){ ident.fields.type = pitchbend; } + else if(!strncmp(channel, "program", 7)){ + ident.fields.type = program; + } else if(!strncmp(channel, "aftertouch", 10)){ ident.fields.type = aftertouch; } @@ -208,6 +212,9 @@ static void midi_tx(int port, uint8_t type, uint8_t channel, uint8_t control, ui case aftertouch: snd_seq_ev_set_chanpress(&ev, channel, value); break; + case program: + snd_seq_ev_set_pgmchange(&ev, channel, value); + break; } snd_seq_event_output(sequencer, &ev); @@ -269,6 +276,8 @@ static char* midi_type_name(uint8_t type){ return "aftertouch"; case pitchbend: return "pitch"; + case program: + return "program"; } return "unknown"; } @@ -399,6 +408,7 @@ static int midi_handle(size_t num, managed_fd* fds){ case SND_SEQ_EVENT_CHANPRESS: ident.fields.type = aftertouch; ident.fields.channel = ev->data.control.channel; + ident.fields.control = 0; val.normalised = (double) ev->data.control.value / 127.0; break; case SND_SEQ_EVENT_PITCHBEND: @@ -407,6 +417,12 @@ static int midi_handle(size_t num, managed_fd* fds){ ident.fields.channel = ev->data.control.channel; val.normalised = ((double) ev->data.control.value + 8192) / 16383.0; break; + case SND_SEQ_EVENT_PGMCHANGE: + ident.fields.type = program; + ident.fields.control = 0; + ident.fields.channel = ev->data.control.channel; + val.normalised = (double) ev->data.control.value / 127.0; + break; case SND_SEQ_EVENT_CONTROLLER: ident.fields.type = cc; ident.fields.channel = ev->data.control.channel; @@ -437,7 +453,7 @@ static int midi_handle(size_t num, managed_fd* fds){ } if(midi_config.detect && event_type){ - if(ident.fields.type == pitchbend || ident.fields.type == aftertouch){ + if(ident.fields.type == pitchbend || ident.fields.type == aftertouch || ident.fields.type == program){ LOGPF("Incoming data on channel %s.ch%d.%s", inst->name, ident.fields.channel, event_type); } else{ -- cgit v1.2.3 From 35f4798673194733358cd3db19a4d2baf70887fd Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 15 Jan 2021 21:35:21 +0100 Subject: Implement EPN's for the rtpmidi backend --- backends/midi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'backends/midi.c') diff --git a/backends/midi.c b/backends/midi.c index 10c8c4a..4bf846a 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -247,6 +247,7 @@ static int midi_set(instance* inst, size_t num, channel** c, channel_value* v){ } break; case pitchbend: + //TODO check whether this actually works that well midi_tx(data->port, ident.fields.type, ident.fields.channel, ident.fields.control, (v[u].normalised * 16383.0) - 8192); break; default: @@ -282,7 +283,7 @@ static char* midi_type_name(uint8_t type){ return "unknown"; } -//this state machine is used more-or-less verbatim in the winmidi and jack backends - fixes need to be applied there, too +//this state machine is used more-or-less verbatim in the winmidi, rtpmidi and jack backends - fixes need to be applied there, too static void midi_handle_epn(instance* inst, uint8_t chan, uint16_t control, uint16_t value){ midi_instance_data* data = (midi_instance_data*) inst->impl; midi_channel_ident ident = { -- cgit v1.2.3