aboutsummaryrefslogtreecommitdiffhomepage
path: root/backends
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2021-01-01 19:22:01 +0100
committercbdev <cb@cbcdn.com>2021-01-01 19:22:01 +0100
commitadc132b5fc039a185be947de3309bd11f4dee823 (patch)
tree4d622ecee4a68fead80ddd67ae406ea60fff99a3 /backends
parentc85ba5aff2d670791589553073e4c519ef1d8434 (diff)
downloadmidimonster-adc132b5fc039a185be947de3309bd11f4dee823.tar.gz
midimonster-adc132b5fc039a185be947de3309bd11f4dee823.tar.bz2
midimonster-adc132b5fc039a185be947de3309bd11f4dee823.zip
Implement EPN transmission for the midi backend
Diffstat (limited to 'backends')
-rw-r--r--backends/midi.c90
-rw-r--r--backends/midi.h8
-rw-r--r--backends/midi.md10
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<channel>.<type><index>`. 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.