diff options
| -rw-r--r-- | backends/midi.c | 6 | ||||
| -rw-r--r-- | backends/midi.md | 6 | ||||
| -rw-r--r-- | backends/winmidi.c | 85 | ||||
| -rw-r--r-- | backends/winmidi.h | 9 | ||||
| -rw-r--r-- | backends/winmidi.md | 10 | 
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. | 
