diff options
| author | cbdev <cb@cbcdn.com> | 2021-01-01 19:22:01 +0100 | 
|---|---|---|
| committer | cbdev <cb@cbcdn.com> | 2021-01-01 19:22:01 +0100 | 
| commit | adc132b5fc039a185be947de3309bd11f4dee823 (patch) | |
| tree | 4d622ecee4a68fead80ddd67ae406ea60fff99a3 | |
| parent | c85ba5aff2d670791589553073e4c519ef1d8434 (diff) | |
| download | midimonster-adc132b5fc039a185be947de3309bd11f4dee823.tar.gz midimonster-adc132b5fc039a185be947de3309bd11f4dee823.tar.bz2 midimonster-adc132b5fc039a185be947de3309bd11f4dee823.zip | |
Implement EPN transmission for the midi backend
| -rw-r--r-- | backends/midi.c | 90 | ||||
| -rw-r--r-- | backends/midi.h | 8 | ||||
| -rw-r--r-- | backends/midi.md | 10 | 
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. | 
