From 1e4a11bd9848c40e6cd19632bef1981bb33b3b3d Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 3 Jan 2021 18:12:48 +0100 Subject: Implement EPN transmission for the jack backend --- backends/jack.c | 99 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 26 deletions(-) (limited to 'backends/jack.c') 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; -- cgit v1.2.3 From 00ba26c238a2e75c5b7d2e32469eae02179efde9 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 8 Jan 2021 23:03:11 +0100 Subject: Implement EPN reception for the jack backend --- TODO | 1 + backends/jack.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++- backends/jack.h | 10 ++++++++ backends/jack.md | 7 ++++-- backends/winmidi.c | 2 +- 5 files changed, 88 insertions(+), 4 deletions(-) (limited to 'backends/jack.c') diff --git a/TODO b/TODO index 5662479..2ab5f10 100644 --- a/TODO +++ b/TODO @@ -5,6 +5,7 @@ make event collectors threadsafe to stop marshalling data... collect & check backend API version move all connection establishment to _start to be able to hot-stop/start all backends event deduplication in core? +move all typenames to _t per-channel filters * invert diff --git a/backends/jack.c b/backends/jack.c index a3caf73..176144f 100644 --- a/backends/jack.c +++ b/backends/jack.c @@ -101,6 +101,68 @@ static void mmjack_process_midiout(void* buffer, size_t sample_offset, uint8_t t } } +//this state machine was copied more-or-less verbatim from the alsa midi implementation - fixes there will need to be integrated +static void mmjack_handle_epn(mmjack_port* port, uint8_t chan, uint16_t control, uint16_t value){ + mmjack_channel_ident ident = { + .label = 0 + }; + + //switching between nrpn and rpn clears all valid bits + if(((port->epn_status[chan] & EPN_NRPN) && (control == 101 || control == 100)) + || (!(port->epn_status[chan] & EPN_NRPN) && (control == 99 || control == 98))){ + port->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){ + port->epn_status[chan] &= ~EPN_VALUE_HI; + } + + //parameter hi + if(control == 101 || control == 99){ + port->epn_control[chan] &= 0x7F; + port->epn_control[chan] |= value << 7; + port->epn_status[chan] |= EPN_PARAMETER_HI | ((control == 99) ? EPN_NRPN : 0); + if(control == 101 && value == 127){ + port->epn_status[chan] &= ~EPN_PARAMETER_HI; + } + } + + //parameter lo + if(control == 100 || control == 98){ + port->epn_control[chan] &= ~0x7F; + port->epn_control[chan] |= value & 0x7F; + port->epn_status[chan] |= EPN_PARAMETER_LO | ((control == 98) ? EPN_NRPN : 0); + if(control == 100 && value == 127){ + port->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 + && ((port->epn_status[chan] & (EPN_PARAMETER_HI | EPN_PARAMETER_LO)) == (EPN_PARAMETER_HI | EPN_PARAMETER_LO))){ + port->epn_value[chan] = value << 7; + port->epn_status[chan] |= EPN_VALUE_HI; + } + + //value lo, flush the value + if(control == 38 + && port->epn_status[chan] & EPN_VALUE_HI){ + port->epn_value[chan] &= ~0x7F; + port->epn_value[chan] |= value & 0x7F; + port->epn_status[chan] &= ~EPN_VALUE_HI; + + //find the updated channel + ident.fields.sub_type = port->epn_status[chan] & EPN_NRPN ? midi_nrpn : midi_rpn; + ident.fields.sub_channel = chan; + ident.fields.sub_control = port->epn_control[chan]; + + //ident.fields.port set on output in mmjack_handle_midi + mmjack_midiqueue_append(port, ident, port->epn_value[chan]); + } +} + 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); @@ -113,7 +175,6 @@ static int mmjack_process_midi(instance* inst, mmjack_port* port, size_t nframes 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 @@ -135,6 +196,15 @@ static int mmjack_process_midi(instance* inst, mmjack_port* port, size_t nframes ident.fields.sub_control = 0; value = event.buffer[1]; } + + //forward the EPN CCs to the EPN state machine + if(ident.fields.sub_type == midi_cc + && ((ident.fields.sub_control <= 101 && ident.fields.sub_control >= 98) + || ident.fields.sub_control == 6 + || ident.fields.sub_control == 38)){ + mmjack_handle_epn(port, ident.fields.sub_channel, ident.fields.sub_control, value); + } + //append midi data mmjack_midiqueue_append(port, ident, value); } diff --git a/backends/jack.h b/backends/jack.h index ca62ea5..762282b 100644 --- a/backends/jack.h +++ b/backends/jack.h @@ -16,6 +16,11 @@ static int mmjack_shutdown(size_t n, instance** inst); #define JACK_DEFAULT_SERVER_NAME "default" #define JACK_MIDIQUEUE_CHUNK 10 +#define EPN_NRPN 8 +#define EPN_PARAMETER_HI 4 +#define EPN_PARAMETER_LO 2 +#define EPN_VALUE_HI 1 + enum /*mmjack_midi_channel_type*/ { midi_none = 0, midi_note = 0x90, @@ -59,10 +64,15 @@ typedef struct /*_mmjack_port_data*/ { double min; uint8_t mark; double last; + size_t queue_len; size_t queue_alloc; mmjack_midiqueue* queue; + uint16_t epn_control[16]; + uint16_t epn_value[16]; + uint8_t epn_status[16]; + pthread_mutex_t lock; } mmjack_port; diff --git a/backends/jack.md b/backends/jack.md index 3d426f3..4ff77f6 100644 --- a/backends/jack.md +++ b/backends/jack.md @@ -83,8 +83,11 @@ 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. +MIDI extended parameter numbers (EPNs, the `rpn` and `nrpn` control types) will also generate events on the controls (CC 101 through +98, 38 and 6) that are used as the lower layer transport. When using EPNs, mapping those controls is probably not useful. + +EPN control types support only the full 14-bit transfer encoding, not the shorter variant transmitting only the 7 +high-order bits. This may be changed if there is sufficient interest in the functionality. 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 diff --git a/backends/winmidi.c b/backends/winmidi.c index 66456e8..d12dc71 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -402,7 +402,7 @@ static void winmidi_handle_epn(instance* inst, uint8_t chan, uint16_t control, u ident.fields.control = data->epn_control[chan]; val.normalised = (double) data->epn_value[chan] / 16383.0; - winmidi_enqueue_input(inst, ident,val); + winmidi_enqueue_input(inst, ident, val); } } -- 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/jack.c | 9 ++++++--- backends/jack.h | 7 ++++--- backends/jack.md | 7 ++++++- backends/midi.c | 18 +++++++++++++++++- backends/midi.md | 4 +++- backends/winmidi.c | 12 +++++++++--- backends/winmidi.h | 7 ++++--- backends/winmidi.md | 4 +++- 8 files changed, 52 insertions(+), 16 deletions(-) (limited to 'backends/jack.c') diff --git a/backends/jack.c b/backends/jack.c index 176144f..fe74a80 100644 --- a/backends/jack.c +++ b/backends/jack.c @@ -79,7 +79,7 @@ static int mmjack_midiqueue_append(mmjack_port* port, mmjack_channel_ident ident } 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); + jack_midi_data_t* event_data = jack_midi_event_reserve(buffer, sample_offset, (type == midi_aftertouch || type == midi_program) ? 2 : 3); if(!event_data){ LOG("Failed to reserve MIDI stream data"); @@ -95,7 +95,7 @@ static void mmjack_process_midiout(void* buffer, size_t sample_offset, uint8_t t event_data[1] = value & 0x7F; event_data[2] = (value >> 7) & 0x7F; } - else if(type == midi_aftertouch){ + else if(type == midi_aftertouch || type == midi_program){ event_data[1] = value & 0x7F; event_data[2] = 0; } @@ -192,7 +192,7 @@ static int mmjack_process_midi(instance* inst, mmjack_port* port, size_t nframes ident.fields.sub_control = 0; value = event.buffer[1] | (event.buffer[2] << 7); } - else if(ident.fields.sub_type == midi_aftertouch){ + else if(ident.fields.sub_type == midi_aftertouch || ident.fields.sub_type == midi_program){ ident.fields.sub_control = 0; value = event.buffer[1]; } @@ -501,6 +501,9 @@ static int mmjack_parse_midispec(mmjack_channel_ident* ident, char* spec){ else if(!strncmp(next_token, "aftertouch", 10)){ ident->fields.sub_type = midi_aftertouch; } + else if(!strncmp(next_token, "program", 7)){ + ident->fields.sub_type = midi_program; + } else{ LOGPF("Unknown MIDI control type in spec %s", spec); return 1; diff --git a/backends/jack.h b/backends/jack.h index 762282b..42905f1 100644 --- a/backends/jack.h +++ b/backends/jack.h @@ -24,12 +24,13 @@ static int mmjack_shutdown(size_t n, instance** inst); enum /*mmjack_midi_channel_type*/ { midi_none = 0, midi_note = 0x90, - midi_cc = 0xB0, midi_pressure = 0xA0, + midi_cc = 0xB0, + midi_program = 0xC0, midi_aftertouch = 0xD0, midi_pitchbend = 0xE0, - midi_rpn = 0xF0, - midi_nrpn = 0xF1 + midi_rpn = 0xF1, + midi_nrpn = 0xF2 }; typedef union { diff --git a/backends/jack.md b/backends/jack.md index 4ff77f6..c67f060 100644 --- a/backends/jack.md +++ b/backends/jack.md @@ -56,6 +56,9 @@ MIDI ports provide subchannels for the various MIDI controls available. Each MID corresponding pressure controls for each note, 128 control change (CC) controls (numbered likewise), one channel wide "aftertouch" control and one channel-wide pitchbend control. +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. + A MIDI port subchannel is specified using the syntax `channel.`. The shorthand `ch` may be used instead of the word `channel` (Note that `channel` here refers to the MIDI channel number). @@ -66,16 +69,18 @@ The following values are recognized for `type`: * `pressure` - Note pressure/aftertouch messages * `aftertouch` - Channel-wide aftertouch messages * `pitch` - Channel pitchbend messages +* `program` - Channel program change 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.`. +The `pitch`, `aftertouch` and `program` messages/events are channel-wide, thus they can be specified as `channel.`. 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 +jack1.midi_in.ch15.note1 > jack1.midi_out.ch4.program ``` The MIDI subchannel syntax is intentionally kept compatible to the different MIDI backends also supported 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{ diff --git a/backends/midi.md b/backends/midi.md index 60a4d06..6280205 100644 --- a/backends/midi.md +++ b/backends/midi.md @@ -31,13 +31,14 @@ 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 +* `program` - Channel program change 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.`. The shorthand `ch` may be used instead of the word `channel` (Note that `channel` here refers to the MIDI channel number). -The `pitch` and `aftertouch` events are channel-wide, thus they can be specified as `channel.`. +The `pitch`, `aftertouch` and `program` messages/events are channel-wide, thus they can be specified as `channel.`. MIDI channels range from `0` to `15`. Each MIDI channel consists of 128 notes (numbered `0` through `127`), which additionally each have a pressure control, 128 CC's (numbered likewise), a channel pressure control (also called @@ -53,6 +54,7 @@ midi1.channel15.pressure1 > midi1.channel0.note0 midi1.ch1.aftertouch > midi2.ch2.cc0 midi1.ch0.pitch > midi2.ch1.pitch midi1.ch0.nrpn900 > midi2.ch0.rpn1 +midi2.ch15.note1 > midi1.ch2.program ``` #### Known bugs / problems diff --git a/backends/winmidi.c b/backends/winmidi.c index ec0fb44..a1fa686 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -169,6 +169,9 @@ static channel* winmidi_channel(instance* inst, char* spec, uint8_t flags){ 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; @@ -203,7 +206,7 @@ static void winmidi_tx(HMIDIOUT port, uint8_t type, uint8_t channel, uint8_t con output.components.data1 = value & 0x7F; output.components.data2 = (value >> 7) & 0x7F; } - else if(type == aftertouch){ + else if(type == aftertouch || type == program){ output.components.data1 = value; output.components.data2 = 0; } @@ -270,6 +273,8 @@ static char* winmidi_type_name(uint8_t typecode){ return "aftertouch"; case pitchbend: return "pitch"; + case program: + return "program"; } return "unknown"; } @@ -294,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, @@ -450,7 +456,7 @@ static void CALLBACK winmidi_input_callback(HMIDIIN device, unsigned message, DW 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; diff --git a/backends/winmidi.h b/backends/winmidi.h index 4d3e2dd..40b3554 100644 --- a/backends/winmidi.h +++ b/backends/winmidi.h @@ -31,12 +31,13 @@ typedef struct /*_winmidi_instance_data*/ { enum /*_winmidi_channel_type*/ { none = 0, note = 0x90, - cc = 0xB0, pressure = 0xA0, + cc = 0xB0, + program = 0xC0, aftertouch = 0xD0, pitchbend = 0xE0, - rpn = 0xF0, - nrpn = 0xF1 + rpn = 0xF1, + nrpn = 0xF2 }; typedef union { diff --git a/backends/winmidi.md b/backends/winmidi.md index 4de792a..9e7d9cc 100644 --- a/backends/winmidi.md +++ b/backends/winmidi.md @@ -33,13 +33,14 @@ 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 +* `program` - Channel program change 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.`. The shorthand `ch` may be used instead of the word `channel` (Note that `channel` here refers to the MIDI channel number). -The `pitch` and `aftertouch` events are channel-wide, thus they can be specified as `channel.`. +The `pitch`, `aftertouch` and `program` messages/events are channel-wide, thus they can be specified as `channel.`. MIDI channels range from `0` to `15`. Each MIDI channel consists of 128 notes (numbered `0` through `127`), which additionally each have a pressure control, 128 CC's (numbered likewise), a channel pressure control (also called @@ -55,6 +56,7 @@ midi1.channel15.pressure1 > midi1.channel0.note0 midi1.ch1.aftertouch > midi2.ch2.cc0 midi1.ch0.pitch > midi2.ch1.pitch midi2.ch0.nrpn900 > midi1.ch1.rpn1 +midi2.ch15.note1 > midi1.ch2.program ``` #### Known bugs / problems -- cgit v1.2.3