From 43502018c5373ef64c44be7f697e1bc68d038acc Mon Sep 17 00:00:00 2001 From: zawz Date: Mon, 29 Jul 2019 15:29:22 +0200 Subject: [PATCH] 1.0 release +Add options +Add help +Add support for multiple identical devices *New README *Bug fixes --- README | 128 ------------------ README.md | 42 ++++++ gen_help_format.sh | 10 ++ help_format/command-tags | 57 ++++++++ help_format/file-format | 23 ++++ help_format/help_template_head | 2 + help_format/help_template_tail | 1 + help_format/shell-format | 23 ++++ include/Filedat.hpp | 96 +++++++------- include/device.hpp | 7 +- include/help.h | 6 + include/options.hpp | 63 +++++++-- include/system.hpp | 4 + src/Filedat.cpp | 232 ++++++++++++++++++++------------- src/device.cpp | 21 ++- src/main.cpp | 145 +++++++++++++++++---- src/options.cpp | 67 ++++++++-- src/system.cpp | 40 ++++-- 18 files changed, 623 insertions(+), 344 deletions(-) delete mode 100644 README create mode 100644 README.md create mode 100755 gen_help_format.sh create mode 100644 help_format/command-tags create mode 100644 help_format/file-format create mode 100644 help_format/help_template_head create mode 100644 help_format/help_template_tail create mode 100644 help_format/shell-format create mode 100644 include/help.h diff --git a/README b/README deleted file mode 100644 index c88f5ea..0000000 --- a/README +++ /dev/null @@ -1,128 +0,0 @@ - -Maps midi signals coming from ALSA midi devices to shell commands - -Build: `$ make && sudo make install` - -usage: midiMap -This is a daemon program, it does not start any background process by itself and needs to be constantly running for the mapping to be active - -This program is in early stage but is fully functional without any major errors - -Known issues: -- Doesn't support multiple identical devices - - -See 'example.mim' for an example config file - -To scan for devices use `$ aseqdump -l` -To scan a device's inputs use `$ aseqdump -p ` - --- [COMMAND FORMAT] -- - -Format is regular shell format - --- Environment - -[Note] -$id: id of the note -$channel: channel of the note -$velocity: velocity of the note - -[Controller] -$value: value of the controller (remapped) -$id: id of the controller -$channel: channel of the controller -$rawvalue: original value of the controller - -[Pitch] -$value: value of the bend (remapped) -$rawvalue: original value of the bend - -[System] -$code: hexadecimal code of the system signal - - --- [FILE FORMAT] -- - -[,] - -- format: -{ - name= - commands=[,] -} -- -*name: string referring to client name of the device - --- format (global): -{ - = - = - ... -} - - --- COMMAND TAGS - -[Global tags] - type= - shell= --- -*type: type of the signal: note/controller/pitch/system/connect/disconnect - > mandatory -*shell: shell command to be executed - > mandatory - -[Note tags] - id= - channel= - trigger= --- -*id: note id from 0 to 127 - > optional, default 0:127 -*channel: value from 0 to 16 for channel, * for any channel - > optional, default * -*trigger: note velocity from 0 to 127 that triggers the command - > optional, default 1:127 - -[Controller tags] - id= - channel= - range= - remap= - float= --- -*id: controller id from 0 to 127 - > optional, default 0:127 -*channel: value from 0 to 16 for channel, * for any channel - > optional, default * -*range: controller value from 0 to 127 that triggers command - > optional, default 0:127 -*remap: remaps the range to given interval - > optional, default same as range -*float: boolean value defining if output is a floating point value - > optional, default false - -[Pitch bend tags] - range= - remap= - float= --- -*range: controller value from -8192 to 8191 that triggers command - > optional, default -8192:8191 -*remap: remaps the range to given interval - > optional, default same as range -*float: boolean value defining if output is a floating point value - > optional, default false - ---Interval Format -x:y range from x to y -x single value x -* all possible values - - -Comments can be done with // - note: // in value lines will not be ignored - -A shell command can be written on multiple lines by containing it between '' right after = - note: additional ' in the command need to be escaped with \ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9214e2f --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# zmidimap + +Map midi signals coming from ALSA midi devices to shell commands + +Soft dependencies: alsa-utils +Hard dependencies: aseqdump + +## Installing + +### Package + +#### Debian/Ubuntu +Download the .deb package then run: `sudo dpkg -i zmidimap.deb ; sudo apt -f install` + +### Standalone + +Move executable `zmidimap` to your PATH directory + +### From source + +```sh +git clone https://github.com/zawwz/zmidimap.git +cd zmidimap +make && sudo make install +``` + +## Usage + +`zmidimap [options] ` +This is a daemon program, it does not start any background process by itself and needs to be constantly running for the mapping to be active + +## Map File / Configuration + +See `example.mim` for an example file +For more details see `zmidimap --help` + +## Options + +To scan for devices use `zmidimap -l` +To scan a device's inputs use `zmidimap -p ` + +For more details see `zmidimap --help` diff --git a/gen_help_format.sh b/gen_help_format.sh new file mode 100755 index 0000000..987ef15 --- /dev/null +++ b/gen_help_format.sh @@ -0,0 +1,10 @@ +FORMAT_FOLDER=help_format +IDIR=include + +cp $FORMAT_FOLDER/help_template_head $IDIR/help.h + +echo "#define FILE_FORMAT \"zmidimap$(sed -n -e 'H;${x;s/\n/\\n/g;s/^,//;p;}' $FORMAT_FOLDER/file-format)\"" >> $IDIR/help.h +echo "#define SHELL_FORMAT \"zmidimap$(sed -n -e 'H;${x;s/\n/\\n/g;s/^,//;p;}' $FORMAT_FOLDER/shell-format)\"" >> $IDIR/help.h +echo "#define COMMAND_TAGS \"zmidimap$(sed -n -e 'H;${x;s/\n/\\n/g;s/^,//;p;}' $FORMAT_FOLDER/command-tags)\"" >> $IDIR/help.h + +cat $FORMAT_FOLDER/help_template_tail >> $IDIR/help.h diff --git a/help_format/command-tags b/help_format/command-tags new file mode 100644 index 0000000..ecda04c --- /dev/null +++ b/help_format/command-tags @@ -0,0 +1,57 @@ +-- [COMMAND TAGS] -- + +[Global tags] + type= + shell= +-- + *type: type of the signal: note/controller/pitch/system/connect/disconnect + > mandatory + *shell: shell command to be executed + > mandatory + +[Note tags] + id= + channel= + trigger= +-- + *id: note id from 0 to 127 + > optional, default 0:127 + *channel: value from 0 to 16 for channel, * for any channel + > optional, default * + *trigger: note velocity from 0 to 127 that triggers the command + > optional, default 1:127 + +[Controller tags] + id= + channel= + range= + remap= + float= +-- + *id: controller id from 0 to 127 + > optional, default 0:127 + *channel: value from 0 to 16 for channel, * for any channel + > optional, default * + *range: controller value from 0 to 127 that triggers command + > optional, default 0:127 + *remap: remaps the range to given interval + > optional, default same as range + *float: boolean value defining if output is a floating point value + > optional, default false + +[Pitch bend tags] + range= + remap= + float= +-- + *range: controller value from -8192 to 8191 that triggers command + > optional, default -8192:8191 + *remap: remaps the range to given interval + > optional, default same as range + *float: boolean value defining if output is a floating point value + > optional, default false + +[Interval Format] + x:y range from x to y + x single value x + * all possible values diff --git a/help_format/file-format b/help_format/file-format new file mode 100644 index 0000000..c01717f --- /dev/null +++ b/help_format/file-format @@ -0,0 +1,23 @@ +-- [FILE FORMAT] -- + +[,] + + format + { + name= + commands=[,] + } +-- + *name: string referring to client name of the device + + format + { + = + = + ... + } +-- + value can be concatenated with \"\" or '' + see command-tags + +Full info on file format: https://github.com/zawwz/zFiledat diff --git a/help_format/help_template_head b/help_format/help_template_head new file mode 100644 index 0000000..ca17dac --- /dev/null +++ b/help_format/help_template_head @@ -0,0 +1,2 @@ +#ifndef HELP_H +#define HELP_H diff --git a/help_format/help_template_tail b/help_format/help_template_tail new file mode 100644 index 0000000..3fc5c66 --- /dev/null +++ b/help_format/help_template_tail @@ -0,0 +1 @@ +#endif //HELP_H diff --git a/help_format/shell-format b/help_format/shell-format new file mode 100644 index 0000000..d04e038 --- /dev/null +++ b/help_format/shell-format @@ -0,0 +1,23 @@ +-- [SHELL FORMAT] -- + +Shell command follows regular shell format + +-- [Environment] -- + +[Note] + $id: id of the note + $channel: channel of the note + $velocity: velocity of the note + +[Controller] + $value: value of the controller (remapped) + $id: id of the controller + $channel: channel of the controller + $rawvalue: original value of the controller + +[Pitch] + $value: value of the bend (remapped) + $rawvalue: original value of the bend + +[System] + $code: hexadecimal code of the system signal diff --git a/include/Filedat.hpp b/include/Filedat.hpp index 31adc1f..f626aaa 100644 --- a/include/Filedat.hpp +++ b/include/Filedat.hpp @@ -9,36 +9,24 @@ #include -class file_format_error : public std::exception +class format_error : public std::exception { public: - file_format_error(const std::string& what, const std::string& origin, const char* data, int where) { desc=what; index=where; filename=origin; cdat=data; } + format_error(const std::string& what, const std::string& origin, const std::string& data, int where) { desc=what; index=where; filename=origin; sdat=data; } const char * what () const throw () {return desc.c_str();} const int where () const throw () {return index;} - const char * data() const throw () {return cdat;} + const char * data() const throw () {return sdat.c_str();} const char * origin() const throw () {return filename.c_str();} private: std::string desc; int index; std::string filename; - const char* cdat; + std::string sdat; }; -class chunk_format_error : public std::exception -{ -public: - chunk_format_error(std::string const& what, int where) { desc=what; index=where; } - - const char * what () const throw () {return desc.c_str();} - const int where () const throw () {return index;} -private: - std::string desc; - int index; -}; - -void printFileException(file_format_error& exc); void printErrorIndex(const char* in, const int index, const std::string& message, const std::string& origin); +inline void printFormatException(format_error& exc) { printErrorIndex(exc.data(), exc.where(), exc.what(), exc.origin()); } class Filedat; @@ -59,12 +47,18 @@ protected: class Chunk { public: - Chunk(const char* in, const int in_size, int offset=0, Filedat* data=nullptr) - { m_achunk=nullptr; set(in, in_size, offset, data);} - Chunk(std::string const& in, int offset=0, Filedat* data=nullptr) - { m_achunk=nullptr; set(in, offset, data);} + Chunk() { m_achunk=nullptr; } - Chunk(Chunk const& in) { m_achunk=nullptr; set(in);} + Chunk(const char* in) + { m_achunk=nullptr; set(in, strlen(in), 0, nullptr); } + Chunk(std::string const& in) + { m_achunk=nullptr; set(in, 0, nullptr); } + Chunk(const char* in, const int in_size, int offset=0, Filedat* data=nullptr) + { m_achunk=nullptr; set(in, in_size, offset, data); } + Chunk(std::string const& in, int offset=0, Filedat* data=nullptr) + { m_achunk=nullptr; set(in, offset, data); } + + Chunk(Chunk const& in) { m_achunk=nullptr; set(in); } void clear() { if(m_achunk!=nullptr) delete m_achunk; m_achunk=nullptr; } ~Chunk() { clear(); } @@ -72,7 +66,6 @@ public: Filedat* parent() const { return m_parent; } int offset() const { return m_offset; } - void set(const char* in, const int in_size, int offset=0, Filedat* data=nullptr); void set(std::string const& in, int offset=0, Filedat* data=nullptr) { this->set(in.c_str(), in.size(), offset, data); } @@ -81,16 +74,16 @@ public: std::string strval(unsigned int alignment=0, std::string const& aligner="\t") const; - // bool concatenate(Chunk const& chk); //concatenates chunks - bool addToChunk(std::string const& name, Chunk const& val); //adds if datachunk - bool addToChunk(std::vector> const& vec); //adds if datachunk - bool addToList(Chunk const& val); //adds if list - bool addToList(std::vector const& vec); //adds if list - inline bool add(std::string const& name, Chunk const& val) { return addToChunk(name, val); } //adds if datachunk - inline bool add(std::pair const& pair) { return add(pair.first, pair.second); } //adds if datachunk - inline bool add(std::vector> const& vec) { return addToChunk(vec); } //adds if datachunk - inline bool add(Chunk const& val) { return addToList(val); } //adds if list - inline bool add(std::vector const& vec) { return addToList(vec); } //adds if list + void addToChunk(std::string const& name, Chunk const& val); //adds if datachunk + void addToChunk(std::vector> const& vec); //adds if datachunk + void addToList(Chunk const& val); //adds if list + void addToList(std::vector const& vec); //adds if list + inline void add(std::string const& name, Chunk const& val) { addToChunk(name, val); } //adds if datachunk + inline void add(std::pair const& pair) { add(pair.first, pair.second); } //adds if datachunk + inline void add(std::vector> const& vec) { addToChunk(vec); } //adds if datachunk + inline void add(Chunk const& val) { addToList(val); } //adds if list + inline void add(std::vector const& vec) { addToList(vec); } //adds if list + // void concatenate(Chunk const& chk); //concatenates chunks Chunk copy() const { return Chunk(*this); } Chunk* pcopy() const { return new Chunk(*this); } @@ -104,13 +97,13 @@ public: Chunk& subChunkRef(std::string const& a) const; //datachunk Chunk& subChunkRef(unsigned int a) const; //chunklist - Chunk& operator[](std::string const& a) const { return subChunkRef(a); } - Chunk& operator[](unsigned int a) const { return subChunkRef(a); } - Chunk& operator=(Chunk const& a) { set(a); return *this; } - inline bool operator+=(std::pair const& a) { return add(a); } - inline bool operator+=(std::vector> const& a) { return add(a); } - inline bool operator+=(Chunk const& a) { return add(a); } - inline bool operator+=(std::vector const& a) { return add(a); } + Chunk& operator[](std::string const& a) const { return subChunkRef(a); } + Chunk& operator[](unsigned int a) const { return subChunkRef(a); } + Chunk& operator=(Chunk const& a) { set(a); return *this; } + inline Chunk& operator+=(std::pair const& a) { add(a); return *this; } + inline Chunk& operator+=(std::vector> const& a) { add(a); return *this; } + inline Chunk& operator+=(Chunk const& a) { add(a); return *this; } + inline Chunk& operator+=(std::vector const& a) { add(a); return *this; } // inline bool operator*=(Chunk const& a) { concatenate(a); } //add operator+ and operator* @@ -162,8 +155,10 @@ public: bool readTest() const; - void importFile(); - bool exportFile(std::string const& path="", std::string const& aligner="\t") const; + void import_file(const std::string& path=""); + void import_stdin(); + void import_string(const std::string& data); + bool export_file(std::string const& path="", std::string const& aligner="\t") const; void clear(); @@ -172,8 +167,8 @@ public: std::string strval(std::string const& aligner="\t") const; - Chunk* pchunk() const { return m_dataChunk; } - Chunk& chunk() const { return *m_dataChunk; } + inline Chunk* pchunk() const { return m_dataChunk; } + inline Chunk& chunk() const { return *m_dataChunk; } inline Chunk* pdata() const { return m_dataChunk; } inline Chunk& data() const { return *m_dataChunk; } @@ -181,16 +176,13 @@ public: inline const std::string& stringdata() const { return m_data; } inline const char* c_data() const { return m_data.c_str(); } - Chunk& operator[](const std::string& index) - { - return m_dataChunk->subChunkRef(index); - } - Chunk& operator[](const int index) - { - return m_dataChunk->subChunkRef(index); - } + inline Chunk& operator[](const std::string& index) { return m_dataChunk->subChunkRef(index); } + inline Chunk& operator[](const int index) { return m_dataChunk->subChunkRef(index); } private: + //functions + void generateChunk(); + //attributes std::string m_filePath; std::string m_data; Chunk* m_dataChunk; diff --git a/include/device.hpp b/include/device.hpp index 6904c0d..dd3e617 100644 --- a/include/device.hpp +++ b/include/device.hpp @@ -6,9 +6,13 @@ #include #include "command.hpp" - #include "Filedat.hpp" +#define KILL_COMMAND_FH "kill -s INT $(pgrep -f \"aseqdump -p " +#define KILL_COMMAND_SH "\")" + +void sh(std::string const& string); + class Device { public: @@ -22,6 +26,7 @@ public: Chunk export_chunk(); std::string name; + int client_id; bool busy; uint32_t nb_command; diff --git a/include/help.h b/include/help.h new file mode 100644 index 0000000..c5f1d5d --- /dev/null +++ b/include/help.h @@ -0,0 +1,6 @@ +#ifndef HELP_H +#define HELP_H +#define FILE_FORMAT "zmidimap\n-- [FILE FORMAT] --\n\n[,]\n\n format\n {\n name=\n commands=[,]\n }\n--\n *name: string referring to client name of the device\n\n format\n {\n =\n =\n ...\n }\n--\n value can be concatenated with \"\" or ''\n see command-tags\n\nFull info on file format: https://github.com/zawwz/zFiledat" +#define SHELL_FORMAT "zmidimap\n-- [SHELL FORMAT] --\n\nShell command follows regular shell format\n\n-- [Environment] --\n\n[Note]\n $id: id of the note\n $channel: channel of the note\n $velocity: velocity of the note\n\n[Controller]\n $value: value of the controller (remapped)\n $id: id of the controller\n $channel: channel of the controller\n $rawvalue: original value of the controller\n\n[Pitch]\n $value: value of the bend (remapped)\n $rawvalue: original value of the bend\n\n[System]\n $code: hexadecimal code of the system signal" +#define COMMAND_TAGS "zmidimap\n-- [COMMAND TAGS] --\n\n[Global tags]\n type=\n shell=\n--\n *type: type of the signal: note/controller/pitch/system/connect/disconnect\n > mandatory\n *shell: shell command to be executed\n > mandatory\n\n[Note tags]\n id=\n channel=\n trigger=\n--\n *id: note id from 0 to 127\n > optional, default 0:127\n *channel: value from 0 to 16 for channel, * for any channel\n > optional, default *\n *trigger: note velocity from 0 to 127 that triggers the command\n > optional, default 1:127\n\n[Controller tags]\n id=\n channel=\n range=\n remap=\n float=\n--\n *id: controller id from 0 to 127\n > optional, default 0:127\n *channel: value from 0 to 16 for channel, * for any channel\n > optional, default *\n *range: controller value from 0 to 127 that triggers command\n > optional, default 0:127\n *remap: remaps the range to given interval\n > optional, default same as range\n *float: boolean value defining if output is a floating point value\n > optional, default false\n\n[Pitch bend tags]\n range=\n remap=\n float=\n--\n *range: controller value from -8192 to 8191 that triggers command\n > optional, default -8192:8191\n *remap: remaps the range to given interval\n > optional, default same as range\n *float: boolean value defining if output is a floating point value\n > optional, default false\n\n[Interval Format]\n x:y range from x to y\n x single value x\n * all possible values" +#endif //HELP_H diff --git a/include/options.hpp b/include/options.hpp index 4b73407..40a83d3 100644 --- a/include/options.hpp +++ b/include/options.hpp @@ -9,43 +9,82 @@ #include #include + +// Turn argc/argv into a vector std::vector argVector(int argc, char** argv); class Option { public: + /* CTOR/DTOR */ + //ctors Option(); - Option(char c, bool arg); - Option(std::string const& str, bool arg); - Option(char c, std::string const& str, bool arg); + Option(char c, bool arg, std::string helptext="", std::string argname="arg"); + Option(std::string const& str, bool arg, std::string helptext="", std::string argname="arg"); + Option(char c, std::string const& str, bool arg, std::string helptext="", std::string argname="arg"); + //dtors virtual ~Option(); - bool shortDef; + /* FUNCTIONS */ + + // Print command help. Puts leftpad spaces before printing, and rightpad space until help + void printHelp(int leftpad, int rightpad); + + /* PROPERTIES */ + + bool shortDef; // has a char definition char charName; - bool longDef; + bool longDef; // has a string definition std::string strName; - bool activated; + bool takesArgument; // option takes an argument + + std::string help_text; // text to display in printHelp + std::string arg_name; // name of the argument to display in printHelp + + /* PROCESSING STATUS */ + + bool activated; // option was activated + + std::string argument; // argument of the option - bool takesArgument; - std::string argument; }; class OptionSet { public: + /* CTOR/DTOR */ OptionSet(); virtual ~OptionSet(); - void addOption(Option opt) { m_options.push_back(opt); } - - Option* findOption(char c); - Option* findOption(std::string const& str); + /* PROPERTIES */ + // Stream on which errors are sent. Default stderr std::ostream* errStream; + /* FUNCTIONS */ + + /*CREATION FUNCTIONS*/ + //Adding an option. Refer to Option ctors + void addOption(Option opt) { m_options.push_back(opt); } + + /*PRINT FUNCTIONS*/ + // Print command help. Puts leftpad spaces before each line, and rightpad space until help + void printHelp(int leftpad=2, int rightpad=25); + + /*QUERY FUNCTIONS*/ + // Find an option with its charname + Option* findOption(char c); + // Find an option with its stringname + Option* findOption(std::string const& str); + + /*PROCESSING FUNCTIONS*/ + // Process through options. + // pair.first : vector with arguments that were not identified as options + // pair.second : bool indicating status. True if no error encountered, false if errors std::pair,bool> getOptions(std::vector input); + std::pair,bool> getOptions(int argc, char** argv) { return getOptions(argVector(argc, argv)); } private: std::vector