From fd99dafc5f883e7da41d1f90f8b4af632854c5cd Mon Sep 17 00:00:00 2001 From: zawz Date: Sun, 16 Jun 2019 14:47:55 +0200 Subject: [PATCH] Add Pitch bend support + some note fixes --- README | 39 ++++- example.mim | 3 - include/command.hpp | 19 ++- include/device.hpp | 4 +- src/command.cpp | 13 +- src/device.cpp | 358 ++++++++++++++++++++++++++++---------------- 6 files changed, 293 insertions(+), 143 deletions(-) diff --git a/README b/README index 20a9d22..7a713ca 100644 --- a/README +++ b/README @@ -27,15 +27,22 @@ Format is a regular shell format -- Environment - Global $channel: channel of the -$id: id of the note/controller + + - Note +$id: id of the note $velocity: velocity of the note - Controller +$id: id of the controller $value: value of the controller (remapped) $rawvalue: original value of the controller +- Pitch bend +$value: value of the bend (remapped) +$rawvalue: original value of the bend + -- FILE FORMAT -- [,] @@ -48,37 +55,39 @@ commands=[,] - *name: string referring to client name of the device -- command format (global): +- format (global): { -type= -id= +type= shell= channel=<*/x> } - -*id: value from 0 to 127 referring to id of note/controller - > mandatory *shell: shell command to be executed > mandatory *channel: value from 0 to 16 for channel. Can use * for any channel > optional, default * - - format (note) { +id= trigger= } - +*id: value from 0 to 127 referring to id of note/controller + > mandatory *trigger: note velocity from 0 to 127 that triggers the command. Can enter an interval x:y or single value > optional, default 1:127 -- format (note) +- format (controller) { +id= range= remap= float= } - +*id: value from 0 to 127 referring to id of note/controller + > mandatory *range: controller value from 0 to 127 that triggers command. Can enter an interval x:y or single value > optional, default 0:127 *remap: remaps the range to given interval. Interval can be inversed and float @@ -86,4 +95,18 @@ float= *float: boolean value defining if output is a floating point value > optional, default false +- format (pitch) +{ +range= +remap= +float= +} +- +*range: controller value from -8192 to 8192 that triggers command. Can enter an interval x:y or single value + > optional, default -8192:8192 +*remap: remaps the range to given interval. Interval can be inversed and float + > optional, default same as range +*float: boolean value defining if output is a floating point value + > optional, default false + - Comments can be written inside {} by doing //= diff --git a/example.mim b/example.mim index e370c51..2872c9a 100644 --- a/example.mim +++ b/example.mim @@ -6,14 +6,11 @@ { type=note id=9 - channel=0 - trigger=1:127 shell=echo "Note $id on ch$channel v$velocity" }, { type=note id=9 - channel=0 trigger=0 shell=echo "Note $id off ch$channel" }, diff --git a/include/command.hpp b/include/command.hpp index dba70c9..0b35f3f 100644 --- a/include/command.hpp +++ b/include/command.hpp @@ -9,10 +9,10 @@ class NoteCommand { public: - NoteCommand(uint8_t i, uint8_t ch, uint8_t l, uint8_t h, std::string sh); + NoteCommand(uint8_t i, int8_t ch, uint8_t l, uint8_t h, std::string sh); uint8_t id; - uint8_t channel; + int8_t channel; uint8_t low; uint8_t high; std::string shell; @@ -34,5 +34,20 @@ public: std::string shell; }; +class PitchCommand +{ +public: + + PitchCommand(uint8_t ch, int16_t l, int16_t h, float ml, float mh, bool fl, std::string sh); + + int8_t channel; + int16_t min; + int16_t max; + float mapMin; + float mapMax; + bool floating; + std::string shell; +}; + #endif //COMMAND_HPP diff --git a/include/device.hpp b/include/device.hpp index bb5c113..144f410 100644 --- a/include/device.hpp +++ b/include/device.hpp @@ -16,6 +16,7 @@ public: virtual ~Device(); bool start_loop(); + void run_signal(char* buff); bool import_chunk(Chunk const& ch); Chunk export_chunk(); @@ -26,7 +27,8 @@ public: uint32_t nb_command; std::vector noteCommands[128]; std::vector ctrlCommands[128]; - // std::vector systCommands; + std::vector pitchCommands; + // std::vector sysCommands; std::thread thread; private: diff --git a/src/command.cpp b/src/command.cpp index 5fbef82..a077726 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -1,6 +1,6 @@ #include "command.hpp" -NoteCommand::NoteCommand(uint8_t i, uint8_t ch, uint8_t l, uint8_t h, std::string sh) +NoteCommand::NoteCommand(uint8_t i, int8_t ch, uint8_t l, uint8_t h, std::string sh) { this->id=i; this->channel=ch; @@ -20,3 +20,14 @@ ControllerCommand::ControllerCommand(uint8_t i, int8_t ch, uint8_t l, uint8_t h, this->floating=fl; this->shell=sh; } + +PitchCommand::PitchCommand(uint8_t ch, int16_t l, int16_t h, float ml, float mh, bool fl, std::string sh) +{ + this->channel=ch; + this->min=l; + this->max=h; + this->mapMin=ml; + this->mapMax=mh; + this->floating=fl; + this->shell=sh; +} diff --git a/src/device.cpp b/src/device.cpp index 130cd93..9e2354a 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -54,12 +54,8 @@ bool Device::import_chunk(Chunk const& ch) else { int8_t channel; - uint8_t id; std::string shell; - //id - id=stoi(tch["id"].strval()); - //channel if(tch.subChunkPtr("channel") == nullptr || tch["channel"].strval()=="*") channel=-1; @@ -72,37 +68,50 @@ bool Device::import_chunk(Chunk const& ch) //type if(tstr == "note") //type note { - uint8_t low; - uint8_t high; + uint8_t id; + uint8_t low=1; + uint8_t high=127; std::string tt; + //id + id=stoi(tch["id"].strval()); + //trigger - tt=tch["trigger"].strval(); - auto tpos=tt.find(':'); - if(tpos == std::string::npos) + Chunk* ttch=tch.subChunkPtr("trigger"); + if(ttch != nullptr) { - //single value - low=stoi(tt); - high=low; - } - else - { - //range - low=stoi(tt.substr(0,tpos)); - tpos++; - high=stoi(tt.substr(tpos, tt.size()-tpos)); + tt=ttch->strval(); + auto tpos=tt.find(':'); + if(tpos == std::string::npos) + { + //single value + low=stoi(tt); + high=low; + } + else + { + //range + low=stoi(tt.substr(0,tpos)); + tpos++; + high=stoi(tt.substr(tpos, tt.size()-tpos)); + } } + printf("%d:%d\n",low,high); this->noteCommands[id].push_back(NoteCommand(id,channel,low,high,shell)); this->nb_command++; } else if(tstr == "controller") //type controller { + uint8_t id; uint8_t min=0; uint8_t max=127; float mapMin; float mapMax; bool floating=false; + //id + id=stoi(tch["id"].strval()); + //range Chunk* ttch=tch.subChunkPtr("range"); if(ttch != nullptr) @@ -152,6 +161,63 @@ bool Device::import_chunk(Chunk const& ch) this->ctrlCommands[id].push_back(ControllerCommand(id,channel,min,max,mapMin,mapMax,floating,shell)); this->nb_command++; } + else if(tstr == "pitch") //type pitch bend + { + int16_t min=-8192; + int16_t max=8192; + float mapMin; + float mapMax; + bool floating=false; + + //range + Chunk* ttch=tch.subChunkPtr("range"); + if(ttch != nullptr) + { + std::string range=ttch->strval(); + auto tpos=range.find(':'); + if(tpos == std::string::npos) + { + //single value + min=stoi(range); + max=min; + } + else + { + //range + min=stoi(range.substr(0,tpos)); + tpos++; + max=stoi(range.substr(tpos, range.size()-tpos)); + } + } + + //remap + ttch=tch.subChunkPtr("remap"); + if(ttch != nullptr) + { + std::string map=ttch->strval(); + auto tpos=map.find(':'); + mapMin=stof(map.substr(0,tpos)); + tpos++; + mapMax=stof(map.substr(tpos, map.size()-tpos)); + } + + //floating + ttch=tch.subChunkPtr("float"); + if(ttch != nullptr) + { + std::string tfl=ttch->strval(); + if( tfl == "true" || tfl == "yes" || tfl == "y") + floating=true; + } + else + { + mapMin=min; + mapMax=max; + } + + this->pitchCommands.push_back(PitchCommand(channel,min,max,mapMin,mapMax,floating,shell)); + this->nb_command++; + } else { throw std::runtime_error("Command type " + tstr + " doesn't exist"); @@ -162,6 +228,150 @@ bool Device::import_chunk(Chunk const& ch) return true; } +void Device::run_signal(char* buff) +{ + if ( (strstr(buff, "Port unsubscribed") != NULL) ) // distonnected + { + std::string kill_command=KILL_COMMAND_FH + this->name + KILL_COMMAND_SH; + system(kill_command.c_str()); // kill the process + this->busy=false; + } + + else if (index(buff, ':') != NULL) // MIDI command + { + if (strstr(buff, "System exclusive") != NULL) + { + printf("%s", buff); + //do stuff + } + else + { + int t; + char type; + int8_t channel; + int ctid; + int16_t value; + char* pos=NULL; + bool note_off=false; + + //note off special case + if (strstr(buff,"Note off") != NULL) + note_off=true; + + // type read + if (strstr(buff,"note") != NULL) + type='n'; + else if (strstr(buff,"controller") != NULL) + type='c'; + else if (strstr(buff,"Pitch") != NULL) + type='p'; + else + { + throw std::runtime_error("Unknown MIDI signal\n"); + return; + } + + //channel read + pos=index(buff, ','); + t=1; + while (*(pos-t-1) != ' ') + t++; + channel = std::stoi( std::string(pos-t, t) ); + pos+=2; + + //ctid read (only note anc controller) + if(type=='n' || type=='c') + { + while (*(pos) != ' ') + pos++; + pos++; + t=1; + while ( isNum(*(pos+t)) ) + t++; + ctid = std::stoi( std::string(pos, t) ); + pos+=t+2; + } + + //value read + if(!note_off) + { + while (*(pos) != ' ') + pos++; + pos++; + t=1; + while ( isNum(*(pos+t)) ) + t++; + value = std::stoi( std::string(pos, t)); + } + else + value=0; + + if (type == 'n') + { + for( auto it : this->noteCommands[ctid]) + { + if((it.channel == -1 || it.channel == channel) && it.low <= value && it.high >= value) + { + std::string command="id=" + std::to_string(ctid) + ";channel=" + std::to_string(channel) + ";velocity=" + std::to_string(value) + ";"; + command += it.shell; + std::thread(sh, command).detach(); + } + } + } + else if(type == 'c') + { + for( auto it : this->ctrlCommands[ctid]) + { + if((it.channel == -1 || it.channel == channel) && it.min <= value && it.max >= value) + { + //remapping of value + float result; + if(it.min == it.max) + result = it.mapMin; + else + result=(value-it.min)*(it.mapMax-it.mapMin)/(it.max-it.min)+it.mapMin; + + //command execution + std::string command="id=" + std::to_string(ctid) + ";channel=" + std::to_string(channel) + ";rawvalue=" + std::to_string(value) + ";value="; + if(it.floating) + command += std::to_string(result); + else + command += std::to_string((long int) result); + command += ";" + it.shell; + std::thread(sh, command).detach(); + } + } + } + else if(type == 'p') + { + for( auto it : this->pitchCommands) + { + if((it.channel == -1 || it.channel == channel) && it.min <= value && it.max >= value) + { + //remapping of value + float result; + if(it.min == it.max) + result = it.mapMin; + else + result=(value-it.min)*(it.mapMax-it.mapMin)/(it.max-it.min)+it.mapMin; + + //command execution + std::string command="id=" + std::to_string(ctid) + ";channel=" + std::to_string(channel) + ";rawvalue=" + std::to_string(value) + ";value="; + if(it.floating) + command += std::to_string(result); + else + command += std::to_string((long int) result); + command += ";" + it.shell; + std::thread(sh, command).detach(); + } + } + } // if type + + } //if system exclusive + } //while + +} + void Device::loop(Device* dev) { std::string command = "aseqdump -p '" + dev->name + '\''; @@ -171,115 +381,7 @@ void Device::loop(Device* dev) while (getline(&buff, &buff_size, stream) > 0) { - if ( (strstr(buff, "Port unsubscribed") != NULL) ) // distonnected - { - std::string kill_command=KILL_COMMAND_FH + dev->name + KILL_COMMAND_SH; - system(kill_command.c_str()); // kill the process - dev->busy=false; - } - - else if (index(buff, ':') != NULL) // MIDI command - { - if (strstr(buff, "System exclusive") != NULL) - { - printf("%s", buff); - //do stuff - } - else - { - int t; - char type; - int8_t channel; - int ctid; - uint8_t value; - char* pos=NULL; - bool note_off=false; - - if (strstr(buff,"Note off") != NULL) - note_off=true; - - //channel read - pos=index(buff, ','); - t=1; - while (*(pos-t-1) != ' ') - t++; - channel = std::stoi( std::string(pos-t, t) ); - pos+=2; - - //type - if (*pos == 'c') //controller signal - type='c'; - else if (*pos == 'n') //note signal - type='n'; - else - { - throw std::runtime_error("Unknown MIDI signal\n"); - return; - } - - //ctid read - while (*(pos) != ' ') - pos++; - pos++; - t=1; - while ( isNum(*(pos+t)) ) - t++; - ctid = std::stoi( std::string(pos, t) ); - pos+=t+2; - - - //value read - if(!note_off) - { - while (*(pos) != ' ') - pos++; - pos++; - t=1; - while ( isNum(*(pos+t)) ) - t++; - value = std::stoi( std::string(pos, t)); - } - else - value=0; - - if (type == 'n') - { - for( auto it : dev->noteCommands[ctid]) - { - if((it.channel == -1 || it.channel == channel) && it.low <= value && it.high >= value) - { - std::string command="id=" + std::to_string(ctid) + ";channel=" + std::to_string(channel) + ";velocity=" + std::to_string(value) + ";"; - command += it.shell; - std::thread(sh, command).detach(); - } - } - } - if(type == 'c') - { - for( auto it : dev->ctrlCommands[ctid]) - { - if((it.channel == -1 || it.channel == channel) && it.min <= value && it.max >= value) - { - //remapping of value - float result; - if(it.min == it.max) - result = it.mapMin; - else - result=(value-it.min)*(it.mapMax-it.mapMin)/(it.max-it.min)+it.mapMin; - - //command execution - std::string command="id=" + std::to_string(ctid) + ";channel=" + std::to_string(channel) + ";rawvalue=" + std::to_string(value) + ";value="; - if(it.floating) - command += std::to_string(result); - else - command += std::to_string((long int) result); - command += ";" + it.shell; - std::thread(sh, command).detach(); - } - } - } - } - } + dev->run_signal(buff); } printf("Device '%s' disconnected\n", dev->name.c_str());