aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2021-01-03 18:12:48 +0100
committercbdev <cb@cbcdn.com>2021-01-03 18:12:48 +0100
commit1e4a11bd9848c40e6cd19632bef1981bb33b3b3d (patch)
tree3ef293bced5a041306a2b8eb90df47ad61b64685
parent26b91b849899976b455bc5d780688de6962569e1 (diff)
downloadmidimonster-1e4a11bd9848c40e6cd19632bef1981bb33b3b3d.tar.gz
midimonster-1e4a11bd9848c40e6cd19632bef1981bb33b3b3d.tar.bz2
midimonster-1e4a11bd9848c40e6cd19632bef1981bb33b3b3d.zip
Implement EPN transmission for the jack backend
-rw-r--r--backends/jack.c99
-rw-r--r--backends/jack.h9
-rw-r--r--backends/jack.md7
3 files changed, 86 insertions, 29 deletions
diff --git a/backends/jack.c b/backends/jack.c
index c84ed0f..a3caf73 100644
--- a/backends/jack.c
+++ b/backends/jack.c
@@ -18,8 +18,6 @@
#endif
#endif
-//FIXME pitchbend range is somewhat oob
-
static struct /*_mmjack_backend_cfg*/ {
unsigned verbosity;
volatile sig_atomic_t jack_shutdown;
@@ -80,18 +78,42 @@ static int mmjack_midiqueue_append(mmjack_port* port, mmjack_channel_ident ident
return 0;
}
+static void mmjack_process_midiout(void* buffer, size_t sample_offset, uint8_t type, uint8_t channel, uint8_t control, uint16_t value){
+ jack_midi_data_t* event_data = jack_midi_event_reserve(buffer, sample_offset, (type == midi_aftertouch) ? 2 : 3);
+
+ if(!event_data){
+ LOG("Failed to reserve MIDI stream data");
+ return;
+ }
+
+ //build midi event
+ event_data[0] = channel | type;
+ event_data[1] = control & 0x7F;
+ event_data[2] = value & 0x7F;
+
+ if(type == midi_pitchbend){
+ event_data[1] = value & 0x7F;
+ event_data[2] = (value >> 7) & 0x7F;
+ }
+ else if(type == midi_aftertouch){
+ event_data[1] = value & 0x7F;
+ event_data[2] = 0;
+ }
+}
+
static int mmjack_process_midi(instance* inst, mmjack_port* port, size_t nframes, size_t* mark){
+ mmjack_instance_data* data = (mmjack_instance_data*) inst->impl;
void* buffer = jack_port_get_buffer(port->port, nframes);
jack_nframes_t event_count = jack_midi_get_event_count(buffer);
jack_midi_event_t event;
- jack_midi_data_t* event_data;
mmjack_channel_ident ident;
- size_t u;
+ size_t u, frame;
uint16_t value;
if(port->input){
if(event_count){
DBGPF("Reading %u MIDI events from port %s", event_count, port->name);
+ //TODO (n)rpn RX
for(u = 0; u < event_count; u++){
ident.label = 0;
//read midi data from stream
@@ -124,30 +146,33 @@ static int mmjack_process_midi(instance* inst, mmjack_port* port, size_t nframes
//clear buffer
jack_midi_clear_buffer(buffer);
+ frame = 0;
for(u = 0; u < port->queue_len; u++){
- //build midi event
ident.label = port->queue[u].ident.label;
- event_data = jack_midi_event_reserve(buffer, u, (ident.fields.sub_type == midi_aftertouch) ? 2 : 3);
- if(!event_data){
- LOG("Failed to reserve MIDI stream data");
- return 1;
- }
- event_data[0] = ident.fields.sub_channel | ident.fields.sub_type;
- if(ident.fields.sub_type == midi_pitchbend){
- event_data[1] = port->queue[u].raw & 0x7F;
- event_data[2] = (port->queue[u].raw >> 7) & 0x7F;
- }
- else if(ident.fields.sub_type == midi_aftertouch){
- event_data[1] = port->queue[u].raw & 0x7F;
+
+ if(ident.fields.sub_type == midi_rpn
+ || ident.fields.sub_type == midi_nrpn){
+ //transmit parameter number
+ mmjack_process_midiout(buffer, frame++, midi_cc, ident.fields.sub_channel, (ident.fields.sub_type == midi_rpn) ? 101 : 99, (ident.fields.sub_control >> 7) & 0x7F);
+ mmjack_process_midiout(buffer, frame++, midi_cc, ident.fields.sub_channel, (ident.fields.sub_type == midi_rpn) ? 100 : 98, ident.fields.sub_control & 0x7F);
+
+ //transmit parameter value
+ mmjack_process_midiout(buffer, frame++, midi_cc, ident.fields.sub_channel, 6, (port->queue[u].raw >> 7) & 0x7F);
+ mmjack_process_midiout(buffer, frame++, midi_cc, ident.fields.sub_channel, 38, port->queue[u].raw & 0x7F);
+
+ if(!data->midi_epn_tx_short){
+ //clear active parameter
+ mmjack_process_midiout(buffer, frame++, midi_cc, ident.fields.sub_channel, 101, 127);
+ mmjack_process_midiout(buffer, frame++, midi_cc, ident.fields.sub_channel, 100, 127);
+ }
}
else{
- event_data[1] = ident.fields.sub_control;
- event_data[2] = port->queue[u].raw & 0x7F;
+ mmjack_process_midiout(buffer, frame++, ident.fields.sub_type, ident.fields.sub_channel, ident.fields.sub_control, port->queue[u].raw);
}
}
- if(port->queue_len){
- DBGPF("Wrote %" PRIsize_t " MIDI events to port %s", port->queue_len, port->name);
+ if(frame){
+ DBGPF("Wrote %" PRIsize_t " MIDI events to port %s", frame, port->name);
}
port->queue_len = 0;
}
@@ -305,6 +330,13 @@ static int mmjack_configure_instance(instance* inst, char* option, char* value){
data->server_name = strdup(value);
return 0;
}
+ else if(!strcmp(option, "epn-tx")){
+ data->midi_epn_tx_short = 0;
+ if(!strcmp(value, "short")){
+ data->midi_epn_tx_short = 1;
+ }
+ return 0;
+ }
//register new port, first check for unique name
for(p = 0; p < data->ports; p++){
@@ -385,6 +417,14 @@ static int mmjack_parse_midispec(mmjack_channel_ident* ident, char* spec){
ident->fields.sub_type = midi_pressure;
next_token += 8;
}
+ else if(!strncmp(next_token, "rpn", 3)){
+ ident->fields.sub_type = midi_rpn;
+ next_token += 3;
+ }
+ else if(!strncmp(next_token, "nrpn", 4)){
+ ident->fields.sub_type = midi_nrpn;
+ next_token += 4;
+ }
else if(!strncmp(next_token, "pitch", 5)){
ident->fields.sub_type = midi_pitchbend;
}
@@ -399,7 +439,9 @@ static int mmjack_parse_midispec(mmjack_channel_ident* ident, char* spec){
ident->fields.sub_control = strtoul(next_token, NULL, 10);
if(ident->fields.sub_type == midi_none
- || ident->fields.sub_control > 127){
+ || (ident->fields.sub_type != midi_nrpn
+ && ident->fields.sub_type != midi_rpn
+ && ident->fields.sub_control > 127)){
LOGPF("Invalid MIDI spec %s", spec);
return 1;
}
@@ -467,9 +509,12 @@ static int mmjack_set(instance* inst, size_t num, channel** c, channel_value* v)
break;
case port_midi:
value = v[u].normalised * 127.0;
- if(ident.fields.sub_type == midi_pitchbend){
- value = ((uint16_t)(v[u].normalised * 16384.0));
+ if(ident.fields.sub_type == midi_pitchbend
+ || ident.fields.sub_type == midi_nrpn
+ || ident.fields.sub_type == midi_rpn){
+ value = ((uint16_t)(v[u].normalised * 16383.0));
}
+
if(mmjack_midiqueue_append(data->port + ident.fields.port, ident, value)){
pthread_mutex_unlock(&data->port[ident.fields.port].lock);
return 1;
@@ -494,8 +539,10 @@ static void mmjack_handle_midi(instance* inst, size_t index, mmjack_port* port){
port->queue[u].ident.fields.port = index;
chan = mm_channel(inst, port->queue[u].ident.label, 0);
if(chan){
- if(port->queue[u].ident.fields.sub_type == midi_pitchbend){
- val.normalised = ((double)port->queue[u].raw) / 16384.0;
+ if(port->queue[u].ident.fields.sub_type == midi_pitchbend
+ || port->queue[u].ident.fields.sub_type == midi_rpn
+ || port->queue[u].ident.fields.sub_type == midi_nrpn){
+ val.normalised = ((double)port->queue[u].raw) / 16383.0;
}
else{
val.normalised = ((double)port->queue[u].raw) / 127.0;
diff --git a/backends/jack.h b/backends/jack.h
index 03ce052..ca62ea5 100644
--- a/backends/jack.h
+++ b/backends/jack.h
@@ -22,16 +22,17 @@ enum /*mmjack_midi_channel_type*/ {
midi_cc = 0xB0,
midi_pressure = 0xA0,
midi_aftertouch = 0xD0,
- midi_pitchbend = 0xE0
+ midi_pitchbend = 0xE0,
+ midi_rpn = 0xF0,
+ midi_nrpn = 0xF1
};
typedef union {
struct {
uint32_t port;
- uint8_t pad;
uint8_t sub_type;
uint8_t sub_channel;
- uint8_t sub_control;
+ uint16_t sub_control;
} fields;
uint64_t label;
} mmjack_channel_ident;
@@ -70,6 +71,8 @@ typedef struct /*_jack_instance_data*/ {
char* client_name;
int fd;
+ uint8_t midi_epn_tx_short;
+
jack_client_t* client;
size_t ports;
mmjack_port* port;
diff --git a/backends/jack.md b/backends/jack.md
index b6ff5a9..3d426f3 100644
--- a/backends/jack.md
+++ b/backends/jack.md
@@ -16,6 +16,7 @@ transport of control data via either JACK midi ports or control voltage (CV) inp
|---------------|-----------------------|-----------------------|-----------------------|
| `name` | `Controller` | `MIDIMonster` | Client name for the JACK connection |
| `server` | `jackserver` | `default` | JACK server identifier to connect to |
+| `epn-tx` | `short` | `full` | Configure whether to clear the active parameter number after transmitting a MIDI `nrpn` or `rpn` parameter. |
Channels (corresponding to JACK ports) need to be configured with their type and, if applicable, value limits.
To configure a port, specify it in the instance configuration using the following syntax:
@@ -65,6 +66,8 @@ The following values are recognized for `type`:
* `pressure` - Note pressure/aftertouch messages
* `aftertouch` - Channel-wide aftertouch messages
* `pitch` - Channel pitchbend messages
+* `rpn` - Registered parameter numbers (14-bit extension)
+* `nrpn` - Non-registered parameter numbers (14-bit extension)
The `pitch` and `aftertouch` events are channel-wide, thus they can be specified as `channel<channel>.<type>`.
@@ -72,6 +75,7 @@ Example mappings:
```
jack1.cv_in > jack1.midi_out.ch0.note3
jack1.midi_in.ch0.pitch > jack1.cv_out
+jack2.midi_in.ch0.nrpn900 > jack1.midi_out.ch1.rpn1
```
The MIDI subchannel syntax is intentionally kept compatible to the different MIDI backends also supported
@@ -79,6 +83,9 @@ by the MIDIMonster
#### 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.
+
While JACK has rudimentary capabilities for transporting OSC messages, configuring and parsing such channels
with this backend would take a great amount of dedicated syntax & code. CV ports can provide fine-grained single
control channels as an alternative to MIDI. This feature may be implemented at some point in the future.