diff options
Diffstat (limited to 'backends/winmidi.c')
| -rw-r--r-- | backends/winmidi.c | 218 | 
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  | 
