aboutsummaryrefslogtreecommitdiffhomepage
path: root/backends/winmidi.c
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2021-06-21 21:02:20 +0200
committercbdev <cb@cbcdn.com>2021-06-21 21:02:20 +0200
commit30268b29abe37847eab1770897e3a7f502ca8bda (patch)
treeaca60b934fe46b9cf0ddd72d083b40147f5b3133 /backends/winmidi.c
parentfca46bef7dd8448216d44f0777f0b5ef31ac5883 (diff)
parent91764dfc3ad86994ce27e5c80a92c034e12b849c (diff)
downloadmidimonster-mqtt.tar.gz
midimonster-mqtt.tar.bz2
midimonster-mqtt.zip
Merge branch 'master' into mqttmqtt
Diffstat (limited to 'backends/winmidi.c')
-rw-r--r--backends/winmidi.c218
1 files changed, 177 insertions, 41 deletions
diff --git a/backends/winmidi.c b/backends/winmidi.c
index 030062d..649af2e 100644
--- a/backends/winmidi.c
+++ b/backends/winmidi.c
@@ -74,7 +74,7 @@ static int winmidi_configure(char* option, char* value){
static int winmidi_configure_instance(instance* inst, char* option, char* value){
winmidi_instance_data* data = (winmidi_instance_data*) inst->impl;
- if(!strcmp(option, "read")){
+ if(!strcmp(option, "read") || !strcmp(option, "source")){
if(data->read){
LOGPF("Instance %s already connected to an input device", inst->name);
return 1;
@@ -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") || !strcmp(option, "target")){
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,12 +155,23 @@ 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;
}
else if(!strncmp(next_token, "aftertouch", 10)){
ident.fields.type = aftertouch;
}
+ else if(!strncmp(next_token, "program", 7)){
+ ident.fields.type = program;
+ }
else{
LOGPF("Unknown control type in %s", spec);
return NULL;
@@ -167,11 +185,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 +197,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 & 0x7F;
+
+ if(type == pitchbend){
+ output.components.data1 = value & 0x7F;
+ output.components.data2 = (value >> 7) & 0x7F;
+ }
+ else if(type == aftertouch || type == program){
+ 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 +229,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,12 +263,18 @@ 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:
return "aftertouch";
case pitchbend:
return "pitch";
+ case program:
+ return "program";
}
return "unknown";
}
@@ -248,7 +299,8 @@ static int winmidi_handle(size_t num, managed_fd* fds){
if(backend_config.detect){
//pretty-print channel-wide events
if(backend_config.event[u].channel.fields.type == pitchbend
- || backend_config.event[u].channel.fields.type == aftertouch){
+ || backend_config.event[u].channel.fields.type == aftertouch
+ || backend_config.event[u].channel.fields.type == program){
LOGPF("Incoming data on channel %s.ch%d.%s, value %f",
backend_config.event[u].inst->name,
backend_config.event[u].channel.fields.channel,
@@ -275,11 +327,98 @@ static int winmidi_handle(size_t num, managed_fd* fds){
return 0;
}
-static void CALLBACK winmidi_input_callback(HMIDIIN device, unsigned message, DWORD_PTR inst, DWORD param1, DWORD param2){
+static int winmidi_enqueue_input(instance* inst, winmidi_channel_ident ident, channel_value val){
+ EnterCriticalSection(&backend_config.push_events);
+ if(backend_config.events_alloc <= backend_config.events_active){
+ backend_config.event = realloc((void*) backend_config.event, (backend_config.events_alloc + 1) * sizeof(winmidi_event));
+ if(!backend_config.event){
+ LOG("Failed to allocate memory");
+ backend_config.events_alloc = 0;
+ backend_config.events_active = 0;
+ LeaveCriticalSection(&backend_config.push_events);
+ return 1;
+ }
+ backend_config.events_alloc++;
+ }
+ backend_config.event[backend_config.events_active].inst = inst;
+ backend_config.event[backend_config.events_active].channel.label = ident.label;
+ backend_config.event[backend_config.events_active].value = val;
+ backend_config.events_active++;
+ LeaveCriticalSection(&backend_config.push_events);
+ return 0;
+}
+
+//this state machine was copied more-or-less verbatim from the alsa midi implementation - fixes there will need to be integrated
+static void winmidi_handle_epn(instance* inst, uint8_t chan, uint16_t control, uint16_t value){
+ winmidi_instance_data* data = (winmidi_instance_data*) inst->impl;
winmidi_channel_ident ident = {
.label = 0
};
channel_value val;
+
+ //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;
+ }
+
+ //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;
+ }
+
+ //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;
+ data->epn_status[chan] &= ~EPN_VALUE_HI;
+
+ //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;
+
+ winmidi_enqueue_input(inst, ident, val);
+ }
+}
+
+static void CALLBACK winmidi_input_callback(HMIDIIN device, unsigned message, DWORD_PTR inst, DWORD param1, DWORD param2){
+ winmidi_channel_ident ident = {
+ .label = 0
+ };
+ channel_value val = {
+ 0
+ };
union {
struct {
uint8_t status;
@@ -305,18 +444,22 @@ static void CALLBACK winmidi_input_callback(HMIDIIN device, unsigned message, DW
ident.fields.type = input.components.status & 0xF0;
ident.fields.control = input.components.data1;
val.normalised = (double) input.components.data2 / 127.0;
+ val.raw.u64 = input.components.data2;
if(ident.fields.type == 0x80){
ident.fields.type = note;
val.normalised = 0;
+ val.raw.u64 = 0;
}
else if(ident.fields.type == pitchbend){
ident.fields.control = 0;
- val.normalised = (double)((input.components.data2 << 7) | input.components.data1) / 16384.0;
+ val.normalised = (double) ((input.components.data2 << 7) | input.components.data1) / 16383.0;
+ val.raw.u64 = input.components.data2 << 7 | input.components.data1;
}
- else if(ident.fields.type == aftertouch){
+ else if(ident.fields.type == aftertouch || ident.fields.type == program){
ident.fields.control = 0;
val.normalised = (double) input.components.data1 / 127.0;
+ val.raw.u64 = input.components.data1;
}
break;
case MIM_LONGDATA:
@@ -332,26 +475,19 @@ static void CALLBACK winmidi_input_callback(HMIDIIN device, unsigned message, DW
return;
}
+ //pass changes in the (n)rpn CCs to the EPN state machine
+ if(ident.fields.type == cc
+ && ((ident.fields.control <= 101 && ident.fields.control >= 98)
+ || ident.fields.control == 6
+ || ident.fields.control == 38)){
+ winmidi_handle_epn((instance*) inst, ident.fields.channel, ident.fields.control, val.raw.u64);
+ }
+
DBGPF("Incoming message type %d channel %d control %d value %f",
ident.fields.type, ident.fields.channel, ident.fields.control, val.normalised);
-
- EnterCriticalSection(&backend_config.push_events);
- if(backend_config.events_alloc <= backend_config.events_active){
- backend_config.event = realloc((void*) backend_config.event, (backend_config.events_alloc + 1) * sizeof(winmidi_event));
- if(!backend_config.event){
- LOG("Failed to allocate memory");
- backend_config.events_alloc = 0;
- backend_config.events_active = 0;
- LeaveCriticalSection(&backend_config.push_events);
- return;
- }
- backend_config.events_alloc++;
+ if(winmidi_enqueue_input((instance*) inst, ident, val)){
+ LOG("Failed to enqueue incoming data");
}
- backend_config.event[backend_config.events_active].inst = (instance*) inst;
- backend_config.event[backend_config.events_active].channel.label = ident.label;
- backend_config.event[backend_config.events_active].value = val;
- backend_config.events_active++;
- LeaveCriticalSection(&backend_config.push_events);
if(message != MIM_MOREDATA){
//alert the main loop