diff options
| author | cbdev <cb@cbcdn.com> | 2019-09-07 14:20:00 +0200 | 
|---|---|---|
| committer | cbdev <cb@cbcdn.com> | 2019-09-07 14:20:00 +0200 | 
| commit | f19e3fe0d131c68d1e77fcd8706b260c28f508b8 (patch) | |
| tree | 2b946e6b5c43e043c2c1a8f608746825778b8d6a | |
| parent | 1a23b9339c0164ec18fe6dc5eb900185e4f9d886 (diff) | |
| download | midimonster-f19e3fe0d131c68d1e77fcd8706b260c28f508b8.tar.gz midimonster-f19e3fe0d131c68d1e77fcd8706b260c28f508b8.tar.bz2 midimonster-f19e3fe0d131c68d1e77fcd8706b260c28f508b8.zip | |
maweb I/O buffering
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | backends/maweb.c | 245 | ||||
| -rw-r--r-- | backends/maweb.h | 27 | 
3 files changed, 158 insertions, 120 deletions
| @@ -155,9 +155,9 @@ For Linux and OSX, just running `make` in the source directory should do the tri  Some backends have been marked as optional as they require rather large additional software to be installed,  for example the `ola` backend. To create a build including these, run `make full`. -To build on windows, install the crosscompiler package listed above and run -`make windows`. This will build `midimonster.exe` as well as a set of backends as DLL -files. +To build for Windows, you still need to compile on a Linux machine. +Install the crosscompiler package listed above and run `make windows`. +This will build `midimonster.exe` as well as a set of backends as DLL files.  ## Development diff --git a/backends/maweb.c b/backends/maweb.c index 2b7a37c..ca8a47d 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -14,9 +14,6 @@  #define WS_FLAG_FIN 0x80  #define WS_FLAG_MASK 0x80 -//TODO test using different pages simultaneously -//TODO test dot2 button virtual faders in fader view -  static uint64_t last_keepalive = 0;  static uint64_t update_interval = 50;  static uint64_t last_update = 0; @@ -104,11 +101,6 @@ int init(){  		.interval = maweb_interval  	}; -	if(sizeof(maweb_channel_ident) != sizeof(uint64_t)){ -		fprintf(stderr, "maweb channel identification union out of bounds\n"); -		return 1; -	} -  	//register backend  	if(mm_backend_register(maweb)){  		fprintf(stderr, "Failed to register maweb backend\n"); @@ -117,14 +109,31 @@ int init(){  	return 0;  } -static int channel_comparator(const void* raw_a, const void* raw_b){ -	maweb_channel_ident* a = (maweb_channel_ident*) raw_a; -	maweb_channel_ident* b = (maweb_channel_ident*) raw_b; - -	if(a->fields.page != b->fields.page){ -		return a->fields.page - b->fields.page; +static ssize_t maweb_channel_index(maweb_instance_data* data, maweb_channel_type type, uint16_t page, uint16_t index){ +	size_t n; +	for(n = 0; n < data->channels; n++){ +		if(data->channel[n].type == type +				&& data->channel[n].page == page +				&& data->channel[n].index == index){ +			return n; +		}  	} -	return a->fields.index - b->fields.index; +	return -1; +} + +static int channel_comparator(const void* raw_a, const void* raw_b){ +	maweb_channel_data* a = (maweb_channel_data*) raw_a; +	maweb_channel_data* b = (maweb_channel_data*) raw_b; +	 +	//this needs to take into account command line channels +	//they need to be sorted last so that the channel poll logic works properly +	if(a->type != b->type){ +		return a->type - b->type; +	} +	if(a->page != b->page){ +		return a->page - b->page; +	} +	return a->index - b->index;  }  static uint32_t maweb_interval(){ @@ -214,14 +223,14 @@ static instance* maweb_instance(){  static channel* maweb_channel(instance* inst, char* spec){  	maweb_instance_data* data = (maweb_instance_data*) inst->impl; -	maweb_channel_ident ident = { -		.label = 0 +	maweb_channel_data chan = { +		0  	};  	char* next_token = NULL;  	size_t n;  	if(!strncmp(spec, "page", 4)){ -		ident.fields.page = strtoul(spec + 4, &next_token, 10); +		chan.page = strtoul(spec + 4, &next_token, 10);  		if(*next_token != '.'){  			fprintf(stderr, "Failed to parse maweb channel spec %s: Missing separator\n", spec);  			return NULL; @@ -229,65 +238,57 @@ static channel* maweb_channel(instance* inst, char* spec){  		next_token++;  		if(!strncmp(next_token, "fader", 5)){ -			ident.fields.type = exec_fader; +			chan.type = exec_fader;  			next_token += 5;  		}  		else if(!strncmp(next_token, "upper", 5)){ -			ident.fields.type = exec_upper; +			chan.type = exec_upper;  			next_token += 5;  		}  		else if(!strncmp(next_token, "lower", 5)){ -			ident.fields.type = exec_lower; +			chan.type = exec_lower;  			next_token += 5;  		}  		else if(!strncmp(next_token, "flash", 5)){ -			ident.fields.type = exec_button; +			chan.type = exec_button;  			next_token += 5;  		}  		else if(!strncmp(next_token, "button", 6)){ -			ident.fields.type = exec_button; +			chan.type = exec_button;  			next_token += 6;  		} -		ident.fields.index = strtoul(next_token, NULL, 10); +		chan.index = strtoul(next_token, NULL, 10);  	}  	else{  		for(n = 0; n < sizeof(cmdline_keys) / sizeof(char*); n++){ -			if(!strcmp(spec, cmdline_keys[n])){ -				ident.fields.type = cmdline_button; -				ident.fields.index = n + 1; -				ident.fields.page = 1; +			//FIXME this is broken for layerMode +			if(!strcmp(spec, cmdline_keys[n]) || (*spec == 'l' && !strcmp(spec + 1, cmdline_keys[n]))){ +				chan.type = (*spec == 'l') ? cmdline_local : cmdline; +				chan.index = n + 1; +				chan.page = 1;  				break;  			}  		}  	} -	if(ident.fields.type && ident.fields.index && ident.fields.page){ +	if(chan.type && chan.index && chan.page){  		//actually, those are zero-indexed... -		ident.fields.index--; -		ident.fields.page--; - -		//check if the (exec/meta) channel is already known -		for(n = 0; n < data->input_channels; n++){ -			if(data->input_channel[n].fields.page == ident.fields.page -					&& data->input_channel[n].fields.index == ident.fields.index){ -				break; -			} -		} +		chan.index--; +		chan.page--; -		//FIXME only register channels that are mapped as outputs -		//only register exec channels for updates -		if(n == data->input_channels && ident.fields.type != cmdline_button){ -			data->input_channel = realloc(data->input_channel, (data->input_channels + 1) * sizeof(maweb_channel_ident)); -			if(!data->input_channel){ +		if(maweb_channel_index(data, chan.type, chan.page, chan.index) == -1){ +			data->channel = realloc(data->channel, (data->channels + 1) * sizeof(maweb_channel_data)); +			if(!data->channel){  				fprintf(stderr, "Failed to allocate memory\n");  				return NULL;  			} -			data->input_channel[n].label = ident.label; -			data->input_channels++; +			data->channel[data->channels] = chan; +			data->channels++;  		} -		return mm_channel(inst, ident.label, 1); +		return mm_channel(inst, maweb_channel_index(data, chan.type, chan.page, chan.index), 1);  	} +  	fprintf(stderr, "Failed to parse maweb channel spec %s\n", spec);  	return NULL;  } @@ -325,20 +326,18 @@ static int maweb_send_frame(instance* inst, maweb_operation op, uint8_t* payload  }  static int maweb_process_playback(instance* inst, int64_t page, maweb_channel_type metatype, char* payload, size_t payload_length){ +	maweb_instance_data* data = (maweb_instance_data*) inst->impl;  	size_t exec_blocks = json_obj_offset(payload, (metatype == 2) ? "executorBlocks" : "bottomButtons"), offset, block = 0, control; -	channel* chan = NULL;  	channel_value evt; -	maweb_channel_ident ident = { -		.fields.page = page - 1, -		.fields.index = json_obj_int(payload, "iExec", 191) -	}; +	int64_t exec_index = json_obj_int(payload, "iExec", 191); +	ssize_t channel_index;  	if(!exec_blocks){  		if(metatype == 3){  			//ignore unused buttons  			return 0;  		} -		fprintf(stderr, "maweb missing exec block data on exec %" PRIu64 ".%d\n", page, ident.fields.index); +		fprintf(stderr, "maweb missing exec block data on exec %" PRIu64 ".%" PRIu64 "\n", page, exec_index);  		return 1;  	} @@ -348,27 +347,41 @@ static int maweb_process_playback(instance* inst, int64_t page, maweb_channel_ty  	}  	//TODO detect unused faders -	//TODO state tracking for fader values / exec run state  	//iterate over executor blocks  	for(offset = json_array_offset(payload + exec_blocks, block); offset; offset = json_array_offset(payload + exec_blocks, block)){  		control = exec_blocks + offset + json_obj_offset(payload + exec_blocks + offset, "fader"); -		ident.fields.type = exec_fader; -		chan = mm_channel(inst, ident.label, 0); -		if(chan){ -			evt.normalised = json_obj_double(payload + control, "v", 0.0); -			mm_channel_event(chan, evt); + +		channel_index = maweb_channel_index(data, exec_fader, page - 1, exec_index); +		if(channel_index >= 0){ +			if(!data->channel[channel_index].input_blocked){ +				evt.normalised = json_obj_double(payload + control, "v", 0.0); +				if(evt.normalised != data->channel[channel_index].in){ +					mm_channel_event(mm_channel(inst, channel_index, 0), evt); +					data->channel[channel_index].in = evt.normalised; +				} +			} +			else{ +				data->channel[channel_index].input_blocked--; +			}  		} -		ident.fields.type = exec_button; -		chan = mm_channel(inst, ident.label, 0); -		if(chan){ -			evt.normalised = json_obj_int(payload, "isRun", 0); -			mm_channel_event(chan, evt); +		channel_index = maweb_channel_index(data, exec_button, page - 1, exec_index); +		if(channel_index >= 0){ +			if(!data->channel[channel_index].input_blocked){ +				evt.normalised = json_obj_int(payload, "isRun", 0); +				if(evt.normalised != data->channel[channel_index].in){ +					mm_channel_event(mm_channel(inst, channel_index, 0), evt); +					data->channel[channel_index].in = evt.normalised; +				} +			} +			else{ +				data->channel[channel_index].input_blocked--; +			}  		} -		DBGPF("maweb page %" PRIu64 " exec %d value %f running %" PRIu64 "\n", page, ident.fields.index, json_obj_double(payload + control, "v", 0.0), json_obj_int(payload, "isRun", 0)); -		ident.fields.index++; +		DBGPF("maweb page %" PRIu64 " exec %" PRIu64 " value %f running %" PRIu64 "\n", page, exec_index, json_obj_double(payload + control, "v", 0.0), json_obj_int(payload, "isRun", 0)); +		exec_index++;  		block++;  	} @@ -437,33 +450,37 @@ static int maweb_request_playbacks(instance* inst){  		return 0;  	} +	//TODO this needs to ignore non-metamoded execs  	//don't quote me on this whole segment -	for(channel = 0; channel < data->input_channels; channel++){ +	 +	//only request faders and buttons +	for(channel = 0; channel < data->channels && data->channel[channel].type <= exec_button; channel++){  		offsets[0] = offsets[1] = offsets[2] = 1; -		page_index = data->input_channel[channel].fields.page; +		page_index = data->channel[channel].page;  		if(data->peer_type == peer_dot2){  			//blocks 0, 100 & 200 have 21 execs and need to be queried from fader view -			view = (data->input_channel[channel].fields.index >= 300) ? 3 : 2; +			view = (data->channel[channel].index >= 300) ? 3 : 2; -			for(channel_offset = 1; channel + channel_offset <= data->input_channels; channel_offset++){ +			for(channel_offset = 1; channel + channel_offset <= data->channels && data->channel[channel + channel_offset - 1].type < exec_button; channel_offset++){  				channels = channel + channel_offset - 1;  				//find end for this exec block -				for(; channel + channel_offset < data->input_channels; channel_offset++){ -					if(data->input_channel[channel + channel_offset].fields.page != page_index -							|| (data->input_channel[channels].fields.index / 100) != (data->input_channel[channel + channel_offset].fields.index / 100)){ +				for(; channel + channel_offset < data->channels; channel_offset++){ +					if(data->channel[channel + channel_offset].page != page_index +							|| data->channel[channels].type != data->channel[channel + channel_offset].type +							|| (data->channel[channels].index / 100) != (data->channel[channel + channel_offset].index / 100)){  						break;  					}  				}  				//add request block for the exec block -				offsets[0] += snprintf(item_indices + offsets[0], sizeof(item_indices) - offsets[0], "%d,", data->input_channel[channels].fields.index); -				offsets[1] += snprintf(item_counts + offsets[1], sizeof(item_counts) - offsets[1], "%d,", data->input_channel[channel + channel_offset - 1].fields.index - data->input_channel[channels].fields.index + 1); -				offsets[2] += snprintf(item_types + offsets[2], sizeof(item_types) - offsets[2], "%d,", (data->input_channel[channels].fields.index < 100) ? 2 : 3); - -				//send on page boundary, metamode boundary, last channel -				if(channel + channel_offset >= data->input_channels -						|| data->input_channel[channel + channel_offset].fields.page != page_index -						|| (data->input_channel[channel].fields.index < 300) != (data->input_channel[channel + channel_offset].fields.index < 300)){ +				offsets[0] += snprintf(item_indices + offsets[0], sizeof(item_indices) - offsets[0], "%d,", data->channel[channels].index); +				offsets[1] += snprintf(item_counts + offsets[1], sizeof(item_counts) - offsets[1], "%d,", data->channel[channel + channel_offset - 1].index - data->channel[channels].index + 1); +				offsets[2] += snprintf(item_types + offsets[2], sizeof(item_types) - offsets[2], "%d,", (data->channel[channels].index < 100) ? 2 : 3); + +				//send on last channel, page boundary, metamode boundary +				if(channel + channel_offset >= data->channels +						|| data->channel[channel + channel_offset].page != page_index +						|| (data->channel[channel].index < 300) != (data->channel[channel + channel_offset].index < 300)){  					break;  				}  			} @@ -475,17 +492,18 @@ static int maweb_request_playbacks(instance* inst){  		}  		else{  			//for the ma, the view equals the exec type requested (we can query all button execs from button view, all fader execs from fader view) -			view = (data->input_channel[channel].fields.index >= 100) ? 3 : 2; +			view = (data->channel[channel].index >= 100) ? 3 : 2;  			snprintf(item_types, sizeof(item_types), "[%" PRIsize_t "]", view);  			//this channel must be included, so it must be in range for the first startindex -			snprintf(item_indices, sizeof(item_indices), "[%d]", (data->input_channel[channel].fields.index / 5) * 5); +			snprintf(item_indices, sizeof(item_indices), "[%d]", (data->channel[channel].index / 5) * 5); -			for(channel_offset = 1; channel + channel_offset < data->input_channels -					&& data->input_channel[channel].fields.page == data->input_channel[channel + channel_offset].fields.page -					&& data->input_channel[channel].fields.index / 100 == data->input_channel[channel + channel_offset].fields.index / 100; channel_offset++){ +			for(channel_offset = 1; channel + channel_offset < data->channels +					&& data->channel[channel].type == data->channel[channel + channel_offset].type +					&& data->channel[channel].page == data->channel[channel + channel_offset].page +					&& data->channel[channel].index / 100 == data->channel[channel + channel_offset].index / 100; channel_offset++){  			} -			channels = data->input_channel[channel + channel_offset - 1].fields.index - (data->input_channel[channel].fields.index / 5) * 5; +			channels = data->channel[channel + channel_offset - 1].index - (data->channel[channel].index / 5) * 5;  			snprintf(item_counts, sizeof(item_indices), "[%" PRIsize_t "]", ((channels / 5) * 5 + 5));  		} @@ -549,7 +567,12 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le  	DBGPF("maweb message (%" PRIsize_t "): %s\n", payload_length, payload);  	if(json_obj(payload, "session") == JSON_NUMBER){  		data->session = json_obj_int(payload, "session", data->session); -		fprintf(stderr, "maweb session id is now %" PRIu64 "\n", data->session); +		if(data->session < 0){ +				fprintf(stderr, "maweb login failed\n"); +				data->login = 0; +				return 0; +		} +		fprintf(stderr, "maweb session id is now %" PRId64 "\n", data->session);  	}  	if(json_obj_bool(payload, "forceLogin", 0)){ @@ -768,8 +791,8 @@ static int maweb_handle_fd(instance* inst){  static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){  	maweb_instance_data* data = (maweb_instance_data*) inst->impl; +	maweb_channel_data* chan = NULL;  	char xmit_buffer[MAWEB_XMIT_CHUNK]; -	maweb_channel_ident ident;  	size_t n;  	if(num && !data->login){ @@ -778,8 +801,23 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){  	}  	for(n = 0; n < num; n++){ -		ident.label = c[n]->ident; -		switch(ident.fields.type){ +		//sanity check +		if(c[n]->ident >= data->channels){ +			return 1; +		} +		chan = data->channel + c[n]->ident; + +		//channel state tracking +		if(chan->out == v[n].normalised){ +			continue; +		} +		chan->out = v[n].normalised; + +		//i/o value space separation +		chan->in = v[n].normalised; +		chan->input_blocked = 1; + +		switch(chan->type){  			case exec_fader:  				snprintf(xmit_buffer, sizeof(xmit_buffer),  						"{\"requestType\":\"playbacks_userInput\"," @@ -788,8 +826,7 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){  						"\"faderValue\":%f,"  						"\"type\":1,"  						"\"session\":%" PRIu64 -						"}", ident.fields.index, ident.fields.page, v[n].normalised, data->session); -				maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); +						"}", chan->index, chan->page, v[n].normalised, data->session);  				break;  			case exec_upper:  			case exec_lower: @@ -804,26 +841,26 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){  						"\"released\":%s,"  						"\"type\":0,"  						"\"session\":%" PRIu64 -						"}", ident.fields.index, ident.fields.page, -						(data->peer_type == peer_dot2 && ident.fields.type == exec_upper) ? 0 : (ident.fields.type - exec_button), +						"}", chan->index, chan->page, +						(data->peer_type == peer_dot2 && chan->type == exec_upper) ? 0 : (chan->type - exec_button),  						(v[n].normalised > 0.9) ? "true" : "false",  						(v[n].normalised > 0.9) ? "false" : "true",  						data->session); -				maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer));  				break; -			case cmdline_button: +			case cmdline:  				snprintf(xmit_buffer, sizeof(xmit_buffer),  						"{\"keyname\":\"%s\","  						//"\"autoSubmit\":false,"  						"\"value\":%d" -						"}", cmdline_keys[ident.fields.index], +						"}", cmdline_keys[chan->index],  						(v[n].normalised > 0.9) ? 1 : 0); -				maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer));  				break; +			//TODO cmdline_local  			default:  				fprintf(stderr, "maweb control not yet implemented\n"); -				break; +				return 1;  		} +		maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer));  	}  	return 0;  } @@ -912,7 +949,7 @@ static int maweb_start(){  	for(u = 0; u < n; u++){  		//sort channels  		data = (maweb_instance_data*) inst[u]->impl; -		qsort(data->input_channel, data->input_channels, sizeof(maweb_channel_ident), channel_comparator); +		qsort(data->channel, data->channels, sizeof(maweb_channel_data), channel_comparator);  		if(maweb_connect(inst[u])){  			fprintf(stderr, "Failed to open connection to MA Web Remote for instance %s\n", inst[u]->name); @@ -964,9 +1001,9 @@ static int maweb_shutdown(){  		data->offset = data->allocated = 0;  		data->state = ws_new; -		free(data->input_channel); -		data->input_channel = NULL; -		data->input_channels = 0; +		free(data->channel); +		data->channel = NULL; +		data->channels = 0;  	}  	free(inst); diff --git a/backends/maweb.h b/backends/maweb.h index a868426..3738367 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -25,7 +25,8 @@ typedef enum /*_maweb_channel_type*/ {  	exec_button = 2, //gma: 0 dot: 0  	exec_lower = 3, //gma: 1 dot: 1  	exec_upper = 4, //gma: 2 dot: 0 -	cmdline_button +	cmdline, +	cmdline_local  } maweb_channel_type;  typedef enum /*_maweb_peer_type*/ { @@ -49,15 +50,16 @@ typedef enum /*_ws_frame_op*/ {  	ws_pong = 10  } maweb_operation; -typedef union { -	struct { -		uint8_t padding[3]; -		uint8_t type; -		uint16_t page; -		uint16_t index; -	} fields; -	uint64_t label; -} maweb_channel_ident; +typedef struct /*_maweb_channel*/ { +	maweb_channel_type type; +	uint16_t page; +	uint16_t index; + +	uint8_t input_blocked; + +	double in; +	double out; +} maweb_channel_data;  typedef struct /*_maweb_instance_data*/ {  	char* host; @@ -69,9 +71,8 @@ typedef struct /*_maweb_instance_data*/ {  	int64_t session;  	maweb_peer_type peer_type; -	//need to keep an internal registry to optimize data polls -	size_t input_channels; -	maweb_channel_ident* input_channel; +	size_t channels; +	maweb_channel_data* channel;  	int fd;  	maweb_state state; | 
