diff options
author | cbdev <cb@cbcdn.com> | 2020-08-09 14:16:03 +0200 |
---|---|---|
committer | cbdev <cb@cbcdn.com> | 2020-08-09 14:16:03 +0200 |
commit | ee055791d1430187ec175c3f065398460a5acf6b (patch) | |
tree | 29796c9de60e440e3d29333d68e4f7370a1c6e5a | |
parent | 7a00b8fda337ad38cfba4689dd5fc07686783158 (diff) | |
download | midimonster-ee055791d1430187ec175c3f065398460a5acf6b.tar.gz midimonster-ee055791d1430187ec175c3f065398460a5acf6b.tar.bz2 midimonster-ee055791d1430187ec175c3f065398460a5acf6b.zip |
Implement list-type multichannel specification (Fixes #67)
-rw-r--r-- | README.md | 19 | ||||
-rw-r--r-- | config.c | 114 | ||||
-rw-r--r-- | config.h | 9 |
3 files changed, 116 insertions, 26 deletions
@@ -122,18 +122,25 @@ The last line is a shorter way to create a bi-directional mapping. ### Multi-channel mapping -To make mapping large contiguous sets of channels easier, channel names may contain -expressions of the form `{<start>..<end>}`, with *start* and *end* being positive integers -delimiting a range of channels. Multiple such expressions may be used in one channel -specification, with the rightmost expression being incremented (or decremented) first for -evaluation. +To make mapping large contiguous sets of channels easier, channel names may contain certain +types of expressions specifying multiple channels at once. + +Expressions of the form `{<start>..<end>}`, with *start* and *end* being positive integers, +expand to a range of channels, with the expression replaced by the incrementing or decrementing +value. + +Expressions of the form `{value1,value2,value3}` (with any number of values separated by commas) +are replaced with each of the specified values in sequence. + +Multiple such expressions may be used in one channel specification, with the rightmost expression +being evaluated first. Both sides of a multi-channel assignment need to have the same number of channels, or one side must have exactly one channel. Example multi-channel mapping: ``` -instance-a.channel{1..10} > instance-b.{10..1} +instance-a.channel{1..5} > instance-b.{1,2,3,4,5} ``` ## Backend documentation @@ -7,6 +7,7 @@ #endif #define BACKEND_NAME "core/cfg" +#define DEBUG #include "midimonster.h" #include "config.h" #include "backend.h" @@ -99,9 +100,10 @@ static char* config_trim_line(char* in){ return in; } -static int config_glob_parse(channel_glob* glob, char* spec, size_t length){ - char* parse_offset = NULL; +static int config_glob_parse_range(channel_glob* glob, char* spec, size_t length){ //FIXME might want to allow negative delimiters at some point + char* parse_offset = NULL; + glob->type = glob_range; //first interval member glob->limits.u64[0] = strtoul(spec, &parse_offset, 10); @@ -130,6 +132,39 @@ static int config_glob_parse(channel_glob* glob, char* spec, size_t length){ return 0; } +static int config_glob_parse_list(channel_glob* glob, char* spec, size_t length){ + size_t u = 0; + glob->type = glob_list; + glob->values = 1; + + //count number of values in list + for(u = 0; u < length; u++){ + if(spec[u] == ','){ + glob->values++; + } + } + return 0; +} + +static int config_glob_parse(channel_glob* glob, char* spec, size_t length){ + size_t u = 0; + + //detect glob type + for(u = 0; u < length; u++){ + if(length - u > 2 && !strncmp(spec + u, "..", 2)){ + DBGPF("Detected glob %.*s as range type", (int) length, spec); + return config_glob_parse_range(glob, spec, length); + } + else if(spec[u] == ','){ + DBGPF("Detected glob %.*s as list type", (int) length, spec); + return config_glob_parse_list(glob, spec, length); + } + } + + LOGPF("Failed to detect glob type for spec %.*s", (int) length, spec); + return 1; +} + static int config_glob_scan(instance* inst, channel_spec* spec){ char* glob_start = spec->spec, *glob_end = NULL; size_t u; @@ -182,50 +217,89 @@ static int config_glob_scan(instance* inst, channel_spec* spec){ return 0; } +static ssize_t config_glob_resolve_range(char* spec, size_t length, channel_glob* glob, uint64_t n){ + uint64_t current_value = glob->limits.u64[0] + (n % glob->values); + //if counting down + if(glob->limits.u64[0] > glob->limits.u64[1]){ + current_value = glob->limits.u64[0] - (n % glob->values); + } + + //write out value + return snprintf(spec, length, "%" PRIu64, current_value); +} + +static ssize_t config_glob_resolve_list(char* spec, size_t length, channel_glob* glob, uint64_t n){ + uint64_t current_replacement = 0; + size_t replacement_length = 0; + char* source = spec + 1; + n %= glob->values; + + //find start of replacement value + DBGPF("Searching instance %" PRIu64 " of spec %.*s", n, (int) length, spec); + for(current_replacement = 0; current_replacement < n; current_replacement++){ + for(; source[0] != ','; source++){ + } + source++; + } + + //calculate replacement length + for(; source[replacement_length] != ',' && source[replacement_length] != '}'; replacement_length++){ + } + + //write out new value + memmove(spec, source, replacement_length); + return replacement_length; +} + static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t n, uint8_t map_direction){ size_t glob = 0, glob_length; ssize_t bytes = 0; - uint64_t current_value = 0; channel* result = NULL; char* resolved_spec = strdup(spec->spec); if(!resolved_spec){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return NULL; } //TODO if not internal, try to resolve externally - //iterate and resolve globs for(glob = spec->globs; glob > 0; glob--){ - current_value = spec->glob[glob - 1].limits.u64[0] + (n % spec->glob[glob - 1].values); - if(spec->glob[glob - 1].limits.u64[0] > spec->glob[glob - 1].limits.u64[1]){ - current_value = spec->glob[glob - 1].limits.u64[0] - (n % spec->glob[glob - 1].values); - } glob_length = spec->glob[glob - 1].offset[1] - spec->glob[glob - 1].offset[0]; - n /= spec->glob[glob - 1].values; - //write out value - bytes = snprintf(resolved_spec + spec->glob[glob - 1].offset[0], - glob_length, - "%" PRIu64, - current_value); - if(bytes > glob_length){ - fprintf(stderr, "Internal error resolving glob %s\n", spec->spec); - goto bail; + switch(spec->glob[glob - 1].type){ + case glob_range: + bytes = config_glob_resolve_range(resolved_spec + spec->glob[glob - 1].offset[0], + glob_length, + spec->glob + (glob - 1), + n); + break; + case glob_list: + bytes = config_glob_resolve_list(resolved_spec + spec->glob[glob - 1].offset[0], + glob_length, + spec->glob + (glob - 1), + n); + break; } + n /= spec->glob[glob - 1].values; + //move trailing data - if(bytes < glob_length){ + if(bytes > 0 && bytes < glob_length){ memmove(resolved_spec + spec->glob[glob - 1].offset[0] + bytes, resolved_spec + spec->glob[glob - 1].offset[1] + 1, strlen(spec->spec) - spec->glob[glob - 1].offset[1]); } + else{ + LOGPF("Failure parsing glob spec %s", resolved_spec); + goto bail; + } } + DBGPF("Resolved spec %s to %s", spec->spec, resolved_spec); result = inst->backend->channel(inst, resolved_spec, map_direction); if(spec->globs && !result){ - fprintf(stderr, "Failed to match multichannel evaluation %s to a channel\n", resolved_spec); + LOGPF("Failed to match multichannel evaluation %s to a channel", resolved_spec); } bail: @@ -1,4 +1,12 @@ /* + * Channel glob type + */ +enum /*_mm_channel_glob_type */ { + glob_range, + glob_list +}; + +/* * Channel specification glob */ typedef struct /*_mm_channel_glob*/ { @@ -7,6 +15,7 @@ typedef struct /*_mm_channel_glob*/ { void* impl; uint64_t u64[2]; } limits; + uint8_t type; uint64_t values; } channel_glob; |