aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2021-01-02 17:15:57 +0100
committercbdev <cb@cbcdn.com>2021-01-02 17:15:57 +0100
commit26b91b849899976b455bc5d780688de6962569e1 (patch)
tree46b191b0dca80d56fc25cc499ba349bf3396bc89
parentadc132b5fc039a185be947de3309bd11f4dee823 (diff)
downloadmidimonster-26b91b849899976b455bc5d780688de6962569e1.tar.gz
midimonster-26b91b849899976b455bc5d780688de6962569e1.tar.bz2
midimonster-26b91b849899976b455bc5d780688de6962569e1.zip
Implement EPN transmission for the winmidi backend
-rw-r--r--backends/midi.c6
-rw-r--r--backends/midi.md6
-rw-r--r--backends/winmidi.c85
-rw-r--r--backends/winmidi.h9
-rw-r--r--backends/winmidi.md10
5 files changed, 88 insertions, 28 deletions
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){
diff --git a/backends/midi.md b/backends/midi.md
index 3ac011e..4732452 100644
--- a/backends/midi.md
+++ b/backends/midi.md
@@ -31,8 +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)
+* `rpn` - Registered parameter numbers (14-bit extension)
+* `nrpn` - Non-registered parameter numbers (14-bit 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).
@@ -43,7 +43,7 @@ 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
+Every MIDI 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:
diff --git a/backends/winmidi.c b/backends/winmidi.c
index 030062d..c89a098 100644
--- a/backends/winmidi.c
+++ b/backends/winmidi.c
@@ -82,7 +82,7 @@ static int winmidi_configure_instance(instance* inst, char* option, char* value)
data->read = strdup(value);
return 0;
}
- if(!strcmp(option, "write")){
+ else if(!strcmp(option, "write")){
if(data->write){
LOGPF("Instance %s already connected to an output device", inst->name);
return 1;
@@ -90,6 +90,13 @@ static int winmidi_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 configuration option %s on instance %s", option, inst->name);
return 1;
@@ -148,6 +155,14 @@ static channel* winmidi_channel(instance* inst, char* spec, uint8_t flags){
ident.fields.type = pressure;
next_token += 8;
}
+ else if(!strncmp(next_token, "rpn", 3)){
+ ident.fields.type = rpn;
+ next_token += 3;
+ }
+ else if(!strncmp(next_token, "nrpn", 4)){
+ ident.fields.type = nrpn;
+ next_token += 4;
+ }
else if(!strncmp(next_token, "pitch", 5)){
ident.fields.type = pitchbend;
}
@@ -167,11 +182,7 @@ static channel* winmidi_channel(instance* inst, char* spec, uint8_t flags){
return NULL;
}
-static int winmidi_set(instance* inst, size_t num, channel** c, channel_value* v){
- winmidi_instance_data* data = (winmidi_instance_data*) inst->impl;
- winmidi_channel_ident ident = {
- .label = 0
- };
+static void winmidi_tx(HMIDIOUT port, uint8_t type, uint8_t channel, uint8_t control, uint16_t value){
union {
struct {
uint8_t status;
@@ -183,6 +194,28 @@ static int winmidi_set(instance* inst, size_t num, channel** c, channel_value* v
} output = {
.dword = 0
};
+
+ output.components.status = type | channel;
+ output.components.data1 = control;
+ output.components.data2 = value;
+
+ if(type == pitchbend){
+ output.components.data1 = value & 0x7F;
+ output.components.data2 = (value >> 7) & 0x7F;
+ }
+ else if(type == aftertouch){
+ output.components.data1 = value;
+ output.components.data2 = 0;
+ }
+
+ midiOutShortMsg(port, output.dword);
+}
+
+static int winmidi_set(instance* inst, size_t num, channel** c, channel_value* v){
+ winmidi_instance_data* data = (winmidi_instance_data*) inst->impl;
+ winmidi_channel_ident ident = {
+ .label = 0
+ };
size_t u;
if(!data->device_out){
@@ -193,20 +226,29 @@ static int winmidi_set(instance* inst, size_t num, channel** c, channel_value* v
for(u = 0; u < num; u++){
ident.label = c[u]->ident;
- //build output message
- output.components.status = ident.fields.type | ident.fields.channel;
- output.components.data1 = ident.fields.control;
- output.components.data2 = v[u].normalised * 127.0;
- if(ident.fields.type == pitchbend){
- output.components.data1 = ((int)(v[u].normalised * 16384.0)) & 0x7F;
- output.components.data2 = (((int)(v[u].normalised * 16384.0)) >> 7) & 0x7F;
- }
- else if(ident.fields.type == aftertouch){
- output.components.data1 = v[u].normalised * 127.0;
- output.components.data2 = 0;
+ switch(ident.fields.type){
+ case rpn:
+ case nrpn:
+ //transmit parameter number
+ winmidi_tx(data->device_out, cc, ident.fields.channel, (ident.fields.type == rpn) ? 101 : 99, (ident.fields.control >> 7) & 0x7F);
+ winmidi_tx(data->device_out, cc, ident.fields.channel, (ident.fields.type == rpn) ? 100 : 98, ident.fields.control & 0x7F);
+
+ //transmit parameter value
+ winmidi_tx(data->device_out, cc, ident.fields.channel, 6, (((uint16_t) (v[u].normalised * 16383.0)) >> 7) & 0x7F);
+ winmidi_tx(data->device_out, cc, ident.fields.channel, 38, ((uint16_t) (v[u].normalised * 16383.0)) & 0x7F);
+
+ if(!data->epn_tx_short){
+ //clear active parameter
+ winmidi_tx(data->device_out, cc, ident.fields.channel, 101, 127);
+ winmidi_tx(data->device_out, cc, ident.fields.channel, 100, 127);
+ }
+ break;
+ case pitchbend:
+ winmidi_tx(data->device_out, ident.fields.type, ident.fields.channel, ident.fields.control, v[u].normalised * 16383.0);
+ break;
+ default:
+ winmidi_tx(data->device_out, ident.fields.type, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0);
}
-
- midiOutShortMsg(data->device_out, output.dword);
}
return 0;
@@ -218,6 +260,10 @@ static char* winmidi_type_name(uint8_t typecode){
return "note";
case cc:
return "cc";
+ case rpn:
+ return "rpn";
+ case nrpn:
+ return "nrpn";
case pressure:
return "pressure";
case aftertouch:
@@ -295,6 +341,7 @@ static void CALLBACK winmidi_input_callback(HMIDIIN device, unsigned message, DW
//callbacks may run on different threads, so we queue all events and alert the main thread via the feedback socket
DBGPF("Input callback on thread %ld", GetCurrentThreadId());
+ //TODO handle (n)rpn RX
switch(message){
case MIM_MOREDATA:
//processing too slow, do not immediately alert the main loop
diff --git a/backends/winmidi.h b/backends/winmidi.h
index 4c740ea..fbb2c94 100644
--- a/backends/winmidi.h
+++ b/backends/winmidi.h
@@ -13,6 +13,7 @@ static int winmidi_shutdown(size_t n, instance** inst);
typedef struct /*_winmidi_instance_data*/ {
char* read;
char* write;
+ uint8_t epn_tx_short;
HMIDIIN device_in;
HMIDIOUT device_out;
} winmidi_instance_data;
@@ -23,15 +24,17 @@ enum /*_winmidi_channel_type*/ {
cc = 0xB0,
pressure = 0xA0,
aftertouch = 0xD0,
- pitchbend = 0xE0
+ pitchbend = 0xE0,
+ rpn = 0xF0,
+ nrpn = 0xF1
};
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;
} winmidi_channel_ident;
diff --git a/backends/winmidi.md b/backends/winmidi.md
index 25a6378..6b0fa98 100644
--- a/backends/winmidi.md
+++ b/backends/winmidi.md
@@ -19,6 +19,7 @@ some deviations may still be present.
|---------------|-----------------------|-----------------------|-----------------------|
| `read` | `2` | none | MIDI device to connect for input |
| `write` | `DeviceName` | none | MIDI device to connect for output |
+| `epn-tx` | `short` | `full` | Configure whether to clear the active parameter number after transmitting an `nrpn` or `rpn` parameter. |
Input/output device names may either be prefixes of MIDI device names or numeric indices corresponding
to the listing shown at startup when using the global `list` option.
@@ -32,6 +33,8 @@ The `winmidi` backend supports mapping different MIDI events as MIDIMonster chan
* `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)
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).
@@ -42,16 +45,23 @@ 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 MIDI 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
+midi2.ch0.nrpn900 > midi1.ch1.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.
+
Currently, no Note Off messages are sent (instead, Note On messages with a velocity of 0 are
generated, which amount to the same thing according to the spec). This may be implemented as
a configuration option at a later time.