1.0 release

+Add options
+Add help
+Add support for multiple identical devices
*New README
*Bug fixes
This commit is contained in:
zawz 2019-07-29 15:29:22 +02:00
parent 09c1f1386a
commit 43502018c5
18 changed files with 623 additions and 344 deletions

128
README
View file

@ -1,128 +0,0 @@
Maps midi signals coming from ALSA midi devices to shell commands
Build: `$ make && sudo make install`
usage: midiMap <config file>
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 <client name>`
-- [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] --
[<device>,<device>]
- <device> format:
{
name=<name>
commands=[<command>,<command>]
}
-
*name: string referring to client name of the device
--<command> format (global):
{
<tag>=<value>
<tag>=<value>
...
}
-- COMMAND TAGS
[Global tags]
type=<note/controller/pitch/system/connect/disconnect>
shell=<shell command>
--
*type: type of the signal: note/controller/pitch/system/connect/disconnect
> mandatory
*shell: shell command to be executed
> mandatory
[Note tags]
id=<interval>
channel=<x/*>
trigger=<interval>
--
*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=<interval>
channel=<x/*>
range=<interval>
remap=<interval>
float=<true/false>
--
*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=<interval>
remap=<interval>
float=<true/false>
--
*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 \

42
README.md Normal file
View file

@ -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] <map file>`
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 <client name>`
For more details see `zmidimap --help`

10
gen_help_format.sh Executable file
View file

@ -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

57
help_format/command-tags Normal file
View file

@ -0,0 +1,57 @@
-- [COMMAND TAGS] --
[Global tags]
type=<type>
shell=<shell command>
--
*type: type of the signal: note/controller/pitch/system/connect/disconnect
> mandatory
*shell: shell command to be executed
> mandatory
[Note tags]
id=<interval>
channel=<x/*>
trigger=<interval>
--
*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=<interval>
channel=<x/*>
range=<interval>
remap=<interval>
float=<true/false>
--
*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=<interval>
remap=<interval>
float=<true/false>
--
*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

23
help_format/file-format Normal file
View file

@ -0,0 +1,23 @@
-- [FILE FORMAT] --
[<device>,<device>]
<device> format
{
name=<name>
commands=[<command>,<command>]
}
--
*name: string referring to client name of the device
<command> format
{
<tag>=<value>
<tag>=<value>
...
}
--
value can be concatenated with \"\" or ''
see command-tags
Full info on file format: https://github.com/zawwz/zFiledat

View file

@ -0,0 +1,2 @@
#ifndef HELP_H
#define HELP_H

View file

@ -0,0 +1 @@
#endif //HELP_H

23
help_format/shell-format Normal file
View file

@ -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

View file

@ -9,36 +9,24 @@
#include <cstring>
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<std::pair<std::string, Chunk>> const& vec); //adds if datachunk
bool addToList(Chunk const& val); //adds if list
bool addToList(std::vector<Chunk> 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<std::string, Chunk> const& pair) { return add(pair.first, pair.second); } //adds if datachunk
inline bool add(std::vector<std::pair<std::string, Chunk>> 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<Chunk> const& vec) { return addToList(vec); } //adds if list
void addToChunk(std::string const& name, Chunk const& val); //adds if datachunk
void addToChunk(std::vector<std::pair<std::string, Chunk>> const& vec); //adds if datachunk
void addToList(Chunk const& val); //adds if list
void addToList(std::vector<Chunk> 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<std::string, Chunk> const& pair) { add(pair.first, pair.second); } //adds if datachunk
inline void add(std::vector<std::pair<std::string, Chunk>> const& vec) { addToChunk(vec); } //adds if datachunk
inline void add(Chunk const& val) { addToList(val); } //adds if list
inline void add(std::vector<Chunk> 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<std::string, Chunk> const& a) { return add(a); }
inline bool operator+=(std::vector<std::pair<std::string, Chunk>> const& a) { return add(a); }
inline bool operator+=(Chunk const& a) { return add(a); }
inline bool operator+=(std::vector<Chunk> 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<std::string, Chunk> const& a) { add(a); return *this; }
inline Chunk& operator+=(std::vector<std::pair<std::string, Chunk>> const& a) { add(a); return *this; }
inline Chunk& operator+=(Chunk const& a) { add(a); return *this; }
inline Chunk& operator+=(std::vector<Chunk> 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;

View file

@ -6,9 +6,13 @@
#include <thread>
#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;

6
include/help.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef HELP_H
#define HELP_H
#define FILE_FORMAT "zmidimap\n-- [FILE FORMAT] --\n\n[<device>,<device>]\n\n<device> format\n {\n name=<name>\n commands=[<command>,<command>]\n }\n--\n *name: string referring to client name of the device\n\n<command> format\n {\n <tag>=<value>\n <tag>=<value>\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=<type>\n shell=<shell command>\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=<interval>\n channel=<x/*>\n trigger=<interval>\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=<interval>\n channel=<x/*>\n range=<interval>\n remap=<interval>\n float=<true/false>\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=<interval>\n remap=<interval>\n float=<true/false>\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

View file

@ -9,43 +9,82 @@
#include <string>
#include <utility>
// Turn argc/argv into a vector<string>
std::vector<std::string> 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<std::vector<std::string>,bool> getOptions(std::vector<std::string> input);
std::pair<std::vector<std::string>,bool> getOptions(int argc, char** argv) { return getOptions(argVector(argc, argv)); }
private:
std::vector<Option> m_options;

View file

@ -1,6 +1,10 @@
#ifndef SYSTEM_HPP
#define SYSTEM_HPP
#define ANNOUNCE_COMMAND "aseqdump -p System:1"
#define LIST_COMMAND "aseqdump -l | tail -n +2 | cut -c10-42 | tr -s ' '"
#define LIST_EXTENDED_COMMAND "aseqdump -l | tail -n +2 | cut -c-42"
void device_check();
void announce_loop();

View file

@ -18,57 +18,72 @@ std::string _repeatString(const std::string& str, const unsigned int n)
return ret;
}
void printFileException(file_format_error& exc)
{
printErrorIndex(exc.data(), exc.where(), exc.what(), exc.origin());
}
void printErrorIndex(const char* in, const int index, const std::string& message, const std::string& origin)
{
int i=0, j=0; // j: last newline
int line=1; //n: line #
int in_size=strlen(in);
while(i < in_size && i < index)
if(index >= 0)
{
if(in[i] == '\n')
while(i < in_size && i < index)
{
line++;
j=i+1;
if(in[i] == '\n')
{
line++;
j=i+1;
}
i++;
}
while(i < in_size && in[i]!='\n')
{
i++;
}
i++;
}
while(i < in_size && in[i]!='\n')
if(origin != "")
{
i++;
std::cerr << origin << ": Error\nLine " << line << " col " << index-j+1 << ": " << message << std::endl;
std::cerr << std::string(in+j, i-j) << std::endl;
std::cerr << _repeatString(" ", index-j) << '^' << std::endl;
}
else
{
std::cerr << "Format Error: " << message << std::endl;
if(index >= 0)
{
std::cerr << std::string(in, i) << std::endl;
std::cerr << _repeatString(" ", index-j) << '^' << std::endl;
}
else
std::cerr << in << std::endl;
}
std::cerr << origin << ": Error\nLine " << line << " col " << index-j << ": " << message << std::endl;
std::cerr << std::string(in+j, i-j) << std::endl;
std::cerr << _repeatString(" ", index-j) << '^' << std::endl;
}
Filedat::Filedat()
{
m_dataChunk = nullptr;
m_dataChunk = new Chunk();
}
Filedat::Filedat(std::string const& in)
{
m_dataChunk = nullptr;
m_dataChunk = new Chunk();
m_filePath=in;
}
Filedat::~Filedat()
{
this->clear();
if(m_dataChunk!=nullptr)
delete m_dataChunk;
}
void Filedat::clear()
{
m_data="";
if(m_dataChunk!=nullptr)
{
delete m_dataChunk;
m_dataChunk = nullptr;
m_dataChunk = new Chunk();
}
}
@ -81,35 +96,47 @@ bool Filedat::readTest() const
return true;
}
void Filedat::importFile()
void Filedat::import_file(const std::string& path)
{
std::ifstream stream(m_filePath);
if(!stream)
{
throw std::runtime_error("Cannot open file '" + m_filePath + '\'');
}
m_data="";
std::string line;
while(stream)
{
getline(stream, line);
m_data += (line + '\n');
}
if(path != "")
m_filePath=path;
std::ifstream st(m_filePath);
if(!st)
throw std::runtime_error("Cannot read file '" + m_filePath + '\'');
this->clear();
try
std::string line;
while(st)
{
m_dataChunk = new Chunk(m_data.c_str(), m_data.size(), 0, this);
}
catch(chunk_format_error& e)
{
throw file_format_error(e.what(), m_filePath, m_data.c_str(), e.where());
getline(st, line);
m_data += (line + '\n');
}
this->generateChunk();
}
bool Filedat::exportFile(std::string const& path, std::string const& aligner) const
void Filedat::import_stdin()
{
m_filePath="stdin";
this->clear();
std::string line;
while(std::cin)
{
getline(std::cin, line);
m_data += (line + '\n');
}
this->generateChunk();
}
void Filedat::import_string(const std::string& data)
{
this->clear();
m_data=data;
m_filePath="";
this->generateChunk();
}
bool Filedat::export_file(std::string const& path, std::string const& aligner) const
{
std::ofstream stream;
if(path=="")
@ -130,6 +157,20 @@ std::string Filedat::strval(std::string const& aligner) const
return m_dataChunk->strval(0, aligner);
}
void Filedat::generateChunk()
{
try
{
if(m_dataChunk != nullptr)
delete m_dataChunk;
m_dataChunk = new Chunk(m_data.c_str(), m_data.size(), 0, this);
}
catch(format_error& e)
{
throw format_error(e.what(), m_filePath, m_data, e.where());
}
}
std::string _getname(const char* in, const int in_size, int* start, int* val_size, int* end)
{
int i=0;
@ -156,12 +197,10 @@ std::string _getname(const char* in, const int in_size, int* start, int* val_siz
while(i<in_size && in[i] != '=') //skip to =
i++;
if(i >= in_size) //no =
{
throw chunk_format_error("Tag has no value", j);
}
throw format_error("Tag has no value", "", std::string(in, in_size), j);
if(i == j) //nothing preceding =
throw chunk_format_error("Value has no tag", i);
throw format_error("Value has no tag", "", std::string(in, in_size), i);
int k=i-1; //name end
while( !_isRead(in[k]) )
@ -190,7 +229,7 @@ std::string _getname(const char* in, const int in_size, int* start, int* val_siz
j++;
}
if(i+j >= in_size) // no closing "
throw chunk_format_error("Double quote does not close", i-1);
throw format_error("Double quote does not close", "", std::string(in, in_size), i-1);
*val_size=j;
*end=i+j+1;
return name;
@ -207,7 +246,7 @@ std::string _getname(const char* in, const int in_size, int* start, int* val_siz
j++;
}
if(i+j >= in_size) // no closing '
throw chunk_format_error("Single quote does not close", i-1);
throw format_error("Single quote does not close", "", std::string(in, in_size), i-1);
*val_size=j;
*end=i+j+1;
return name;
@ -231,7 +270,7 @@ std::string _getname(const char* in, const int in_size, int* start, int* val_siz
j++;
}
if(i+j >= in_size) //reached end without closing
throw chunk_format_error("Brace does not close", i);
throw format_error("Brace does not close", "", std::string(in, in_size), i);
j++;
*val_size=j;
*end=i+j;
@ -256,7 +295,7 @@ std::string _getname(const char* in, const int in_size, int* start, int* val_siz
j++;
}
if(i+j >= in_size) //reached end without closing
throw chunk_format_error("Bracket does not close", i);
throw format_error("Bracket does not close", "", std::string(in, in_size), i);
j++;
*val_size=j;
*end=i+j;
@ -322,7 +361,7 @@ std::string _getlist(const char* in, const int in_size, int* start, int* end)
j++;
}
if(i+j >= in_size) // no closing "
throw chunk_format_error("Double quote does not close", i-1);
throw format_error("Double quote does not close", "", std::string(in, in_size), i-1);
ret = std::string(in+i, j);
*end=i+j+1;
}
@ -337,7 +376,7 @@ std::string _getlist(const char* in, const int in_size, int* start, int* end)
j++;
}
if(i+j >= in_size) // no closing '
throw chunk_format_error("Single quote does not close", i-1);
throw format_error("Single quote does not close", "", std::string(in, in_size), i-1);
ret = std::string(in+i, j);
*end=i+j+1;
}
@ -359,7 +398,7 @@ std::string _getlist(const char* in, const int in_size, int* start, int* end)
j++;
}
if(i+j >= in_size) //reached end without closing
throw chunk_format_error("Brace does not close", i);
throw format_error("Brace does not close", "", std::string(in, in_size), i);
j++;
ret = std::string(in+i, j);
*end=i+j;
@ -382,7 +421,7 @@ std::string _getlist(const char* in, const int in_size, int* start, int* end)
j++;
}
if(i+j >= in_size) //reached end without closing
throw chunk_format_error("Bracket does not close", i);
throw format_error("Bracket does not close", "", std::string(in, in_size), i);
j++;
ret = std::string(in+i, j);
*end=i+j;
@ -419,7 +458,7 @@ std::string _getlist(const char* in, const int in_size, int* start, int* end)
return ret;
}
else //Unexpected char
throw chunk_format_error("Expecting comma", i);
throw format_error("Expecting comma", "", std::string(in, in_size), i);
}
@ -448,7 +487,7 @@ void Chunk::set(const char* in, const int in_size, int offset, Filedat* data)
while(!_isRead(in[val_end])) //skip unread char
val_end--;
if(in[val_end] != '}')
throw chunk_format_error("Expecting closing brace", val_end-1);
throw format_error("Expecting closing brace", "", std::string(in, in_size), val_end+1);
DataChunk* tch = new DataChunk();
m_achunk = tch;
@ -461,15 +500,18 @@ void Chunk::set(const char* in, const int in_size, int offset, Filedat* data)
int _size=0;
int end=0;
while(!_isRead(in[i]))
i++;
std::string newstr=std::string(in+i, val_end-i);
try
{
name = _getname(newstr.c_str(), newstr.size(), &start, &_size, &end);
val = newstr.substr(start, _size);
}
catch(chunk_format_error& e)
catch(format_error& e)
{
throw chunk_format_error(e.what(), e.where()+i);
throw format_error(e.what(), "", std::string(in, in_size), e.where()+i);
}
if( name == "" ) //no more values
@ -477,11 +519,16 @@ void Chunk::set(const char* in, const int in_size, int offset, Filedat* data)
try
{
tch->values.insert(std::make_pair(name, new Chunk(val.c_str(),val.size(), offset + start+i, m_parent) ));
Chunk* chk = new Chunk(val.c_str(),val.size(), offset + start+i, m_parent);
if(!tch->values.insert( std::make_pair(name, chk ) ).second)
{
delete chk;
throw format_error("Key '" + name + "' already present", "", std::string(in, in_size), 0 - start );
}
}
catch(chunk_format_error& e)
catch(format_error& e)
{
throw chunk_format_error(e.what(), e.where() + start + i );
throw format_error(e.what(), "", std::string(in, in_size), e.where() + start + i );
}
i += end;
@ -497,7 +544,7 @@ void Chunk::set(const char* in, const int in_size, int offset, Filedat* data)
while(!_isRead(in[val_end])) //skip unread char
val_end--;
if(in[val_end] != ']')
throw chunk_format_error("Expecting closing bracket", val_end-1);
throw format_error("Expecting closing bracket", "", std::string(in, in_size), val_end+1);
ChunkList* tch = new ChunkList();
m_achunk = tch;
@ -511,18 +558,18 @@ void Chunk::set(const char* in, const int in_size, int offset, Filedat* data)
{
val = _getlist(newstr.c_str(), newstr.size(), &start, &end);
}
catch(chunk_format_error& e)
catch(format_error& e)
{
throw chunk_format_error(e.what(), e.where()+i);
throw format_error(e.what(), "", std::string(in, in_size), e.where()+i);
}
try
{
tch->list.push_back(new Chunk(val.c_str(),val.size(), offset + start+i, m_parent) );
}
catch(chunk_format_error& e)
catch(format_error& e)
{
throw chunk_format_error(e.what(), e.where() + start + i );
throw format_error(e.what(), "", std::string(in, in_size), e.where() + start + i );
}
i+=end;
@ -548,60 +595,59 @@ void Chunk::set(const char* in, const int in_size, int offset, Filedat* data)
}
}
bool Chunk::addToChunk(std::string const& name, Chunk const& val)
void Chunk::addToChunk(std::string const& name, Chunk const& val)
{
if(this->type()==AbstractChunk::chunk)
{
DataChunk* cp = dynamic_cast<DataChunk*>(m_achunk);
cp->values.insert(std::make_pair(name , new Chunk(val)));
return true;
Chunk* chk = new Chunk(val);
if( !cp->values.insert( std::make_pair(name,chk) ).second )
{
delete chk;
throw format_error("Key '" + name + "' already present", "", this->strval(), -1);
}
}
else if(this->type() == AbstractChunk::none)
{
DataChunk* cp = new DataChunk();
cp->values.insert(std::make_pair(name , new Chunk(val)));
m_achunk=cp;
return true;
cp->values.insert(std::make_pair(name , new Chunk(val)));
}
else
return false;
{
throw format_error("Cannot add keys to non-map chunks", "", this->strval(), -1);
}
}
bool Chunk::addToChunk(std::vector<std::pair<std::string, Chunk>> const& vec)
void Chunk::addToChunk(std::vector<std::pair<std::string, Chunk>> const& vec)
{
if(this->type()!=AbstractChunk::chunk && this->type()!=AbstractChunk::none)
return false;
for(auto it : vec)
this->add(it.first, it.second);
return true;
this->addToChunk(it.first, it.second);
}
bool Chunk::addToList(Chunk const& val)
void Chunk::addToList(Chunk const& val)
{
if(this->type()==AbstractChunk::list)
{
ChunkList* lp = dynamic_cast<ChunkList*>(m_achunk);
lp->list.push_back(new Chunk(val));
return true;
}
else if(this->type() == AbstractChunk::none)
{
ChunkList* lp = new ChunkList();
lp->list.push_back(new Chunk(val));
m_achunk=lp;
return true;
lp->list.push_back(new Chunk(val));
}
else
return false;
{
throw format_error("Cannot add elements to non-list chunks", "", this->strval(), -1);
}
}
bool Chunk::addToList(std::vector<Chunk> const& vec)
void Chunk::addToList(std::vector<Chunk> const& vec)
{
if(this->type()!=AbstractChunk::chunk && this->type()!=AbstractChunk::none)
return false;
for(auto it : vec)
this->add(it);
return true;
this->addToList(it);
}
std::string Chunk::strval(unsigned int alignment, std::string const& aligner) const
@ -693,11 +739,11 @@ Chunk& Chunk::subChunkRef(std::string const& in) const
{
if(m_parent != nullptr)
{
throw file_format_error("Element isn't a {}", m_parent->filePath(), m_parent->c_data(), m_offset );
throw format_error("Chunk isn't a map", m_parent->filePath(), m_parent->stringdata(), m_offset );
}
else
{
throw chunk_format_error("Chunk isn't a {}", m_offset);
throw format_error("Chunk isn't a map", "", this->strval(), -1);
}
}
DataChunk* dc = dynamic_cast<DataChunk*>(m_achunk);
@ -706,11 +752,11 @@ Chunk& Chunk::subChunkRef(std::string const& in) const
{
if(m_parent != nullptr)
{
throw file_format_error("Chunk doesn't have '" + in + "' flag", m_parent->filePath(), m_parent->c_data(), m_offset );
throw format_error("Map doesn't have '" + in + "' flag", m_parent->filePath(), m_parent->stringdata(), m_offset );
}
else
{
throw chunk_format_error("Chunk doesn't have '" + in + "' flag", m_offset );
throw format_error("Map doesn't have '" + in + "' flag", "", this->strval(), -1);
}
}
return *fi->second;
@ -722,11 +768,11 @@ Chunk& Chunk::subChunkRef(unsigned int a) const
{
if(m_parent != nullptr)
{
throw file_format_error("Element isn't a {}", m_parent->filePath(), m_parent->c_data(), m_offset );
throw format_error("Chunk isn't a list", m_parent->filePath(), m_parent->stringdata(), m_offset );
}
else
{
throw chunk_format_error("Chunk isn't a {}", m_offset);
throw format_error("Chunk isn't a list", "", this->strval(), -1);
}
}
ChunkList* cl = dynamic_cast<ChunkList*>(m_achunk);
@ -734,11 +780,11 @@ Chunk& Chunk::subChunkRef(unsigned int a) const
{
if(m_parent != nullptr)
{
throw file_format_error("List size is below " + std::to_string(a), m_parent->filePath(), m_parent->c_data(), m_offset );
throw format_error("List size is below " + std::to_string(a), m_parent->filePath(), m_parent->stringdata(), m_offset );
}
else
{
throw chunk_format_error("List size is below " + std::to_string(a), m_offset );
throw format_error("List size is below " + std::to_string(a), "", this->strval(), -1);
}
}
return *cl->list[a];

View file

@ -6,25 +6,23 @@
#include <iostream>
#define KILL_COMMAND_FH "kill -s INT $(ps -f | grep \"aseqdump -p "
#define KILL_COMMAND_SH "\" | grep -v grep | awk '{print $2}' | head -n1)"
std::vector<Device*> device_list;
static void sh(std::string const& string)
{
system(string.c_str());
}
static bool _isNum(char a)
{
return (a>='0' && a<='9');
}
void sh(std::string const& string)
{
system(string.c_str());
}
Device::Device()
{
busy=false;
nb_command=0;
client_id=-1;
}
Device::~Device()
@ -247,9 +245,10 @@ 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;
std::string kill_command=KILL_COMMAND_FH + std::to_string(this->client_id) + KILL_COMMAND_SH;
system(kill_command.c_str()); // kill the process
this->busy=false;
this->client_id=-1;
}
else if (index(buff, ':') != NULL) // MIDI command
@ -391,7 +390,7 @@ void Device::run_signal(char* buff)
result=(value-it.min)*(it.mapMax-it.mapMin)/(it.max-it.min)+it.mapMin;
//command execution
std::string command=";channel=" + std::to_string(channel)
std::string command="channel=" + std::to_string(channel)
+ ";rawvalue=" + std::to_string(value)
+ ";value=";
if(it.floating)
@ -412,7 +411,7 @@ void Device::run_signal(char* buff)
void Device::loop(Device* dev)
{
std::string command = "aseqdump -p '" + dev->name + '\'';
std::string command = "aseqdump -p '" + std::to_string(dev->client_id) + '\'';
FILE *stream = popen(command.c_str(), "r");
char* buff = NULL;
size_t buff_size = 0;

View file

@ -1,5 +1,6 @@
#include <signal.h>
#include <stdio.h>
#include <cstring>
#include "device.hpp"
#include "system.hpp"
@ -7,30 +8,127 @@
#include "Filedat.hpp"
#include "options.hpp"
#include <cstring>
#include "help.h"
OptionSet options;
void help()
{
printf("zmidimap [options] <midimap file>\n\nOptions:\n");
options.printHelp(2, 25);
printf("\nSee --file-format --command-tags --shell-format options for details on map file format\n");
}
void option_p(const std::string& port)
{
std::string command="aseqdump -p '" + port + '\'';
FILE *stream = popen(command.c_str(), "r");
char* buff = NULL;
size_t buff_size = 0;
while (getline(&buff, &buff_size, stream) > 0)
{
if ( (strstr(buff, "Port unsubscribed") != NULL) ) // distonnected
{
std::string kill_command=KILL_COMMAND_FH + port + KILL_COMMAND_SH;
system(kill_command.c_str()); // kill the process
}
else
printf("%s", buff);
}
}
void cleanup()
{
for(auto it : device_list)
delete it;
}
void stop(int ret)
{
exit(ret);
}
void inthandler(int dummy)
{
stop(0);
}
int main(int argc, char* argv[])
{
signal(SIGINT, inthandler);
signal(SIGCHLD, SIG_IGN); //not expecting returns from child processes
signal(SIGCHLD, SIG_IGN); //signal that we aren't expecting returns from child processes
OptionSet options;
options.addOption(Option('f',"file",true));
options.addOption(Option('h',"help", false, "Display this help message"));
options.addOption(Option("file-format", false, "Display file format help"));
options.addOption(Option("command-tags", false, "Display for command tag help"));
options.addOption(Option("shell-format", false, "Display for shell format help"));
options.addOption(Option('l',"list", false, "List detected devices"));
options.addOption(Option('L',"full-list", false, "Print whole device list details"));
options.addOption(Option('p',"port", true, "Connect to device and output to console", "device"));
// options.addOption(Option('i',"interactive", false, "Start in interactive mode"));
auto argvec = argVector(argc, argv);
auto t = options.getOptions(argvec);
std::vector<std::string> arg=t.first;
if( !t.second )
{
fprintf(stderr, "Unexpected error\n");
if( !t.second ) //invalid option
return 1;
Option* op=nullptr;
op = options.findOption('h');
if( op->activated )
{
help();
return 0;
}
op = options.findOption("file-format");
if( op->activated )
{
printf("%s\n", FILE_FORMAT);
return 0;
}
op = options.findOption("command-tags");
if( op->activated )
{
printf("%s\n", COMMAND_TAGS);
return 0;
}
op = options.findOption("shell-format");
if( op->activated )
{
printf("%s\n", SHELL_FORMAT);
return 0;
}
op = options.findOption('h');
if( op->activated )
{
help();
return 0;
}
op = options.findOption('L');
if( op->activated )
{
sh("aseqdump -l");
return 0;
}
op = options.findOption('l');
if( op->activated )
{
sh(LIST_COMMAND);
return 0;
}
op = options.findOption('p');
if( op->activated )
{
option_p(op->argument);
return 0;
}
if (arg.size() <= 0 || arg[0] == "")
{
fprintf(stderr, "No config file specified\n");
return 2;
help();
return 0;
}
Filedat file(arg[0]);
@ -40,11 +138,12 @@ int main(int argc, char* argv[])
return 10;
}
printf("Loading config file '%s'\n", arg[0].c_str());
printf("Loading map file '%s'\n", arg[0].c_str());
try
{
file.importFile();
file.import_file();
//create commands
for(int i=0 ; i<file.chunk().listSize() ; i++)
{
Device *newDevice = new Device;
@ -53,30 +152,22 @@ int main(int argc, char* argv[])
printf("Loaded %d commands for device '%s'\n", newDevice->nb_command, newDevice->name.c_str());
}
//main loop
printf("Starting scan for devices\n");
announce_loop();
for(auto it : device_list)
{
delete it;
}
}
catch (file_format_error& e)
catch (format_error& e)
{
printErrorIndex(e.data(), e.where(), e.what(), e.origin());
return 11;
}
catch (chunk_format_error& e)
{
std::cerr << "Chunk Error: " << e.what() << std::endl ;
return 11;
printFormatException(e);
cleanup();
stop(11);
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
return 11;
stop(-1);
}
cleanup();
return 0;
}

View file

@ -24,16 +24,18 @@ Option::~Option()
{
}
Option::Option(char c, bool arg)
Option::Option(char c, bool arg, std::string helptext, std::string argname)
{
shortDef=true;
longDef=false;
takesArgument=arg;
activated=false;
charName=c;
arg_name=argname;
help_text=helptext;
}
Option::Option(std::string const& str, bool arg)
Option::Option(std::string const& str, bool arg, std::string helptext, std::string argname)
{
shortDef=false;
longDef=true;
@ -41,8 +43,10 @@ Option::Option(std::string const& str, bool arg)
activated=false;
charName=0;
strName=str;
arg_name=argname;
help_text=helptext;
}
Option::Option(char c, std::string const& str, bool arg)
Option::Option(char c, std::string const& str, bool arg, std::string helptext, std::string argname)
{
shortDef=true;
longDef=true;
@ -50,6 +54,47 @@ Option::Option(char c, std::string const& str, bool arg)
activated=false;
charName=c;
strName=str;
arg_name=argname;
help_text=helptext;
}
void Option::printHelp(int leftpad, int rightpad)
{
//prepadding
printf("%*s", -1*leftpad, "");
//short def
if(this->shortDef)
{
printf("-%c ", this->charName);
rightpad -= 3;
}
//longdef
if(this->longDef)
{
printf("--%s ", this->strName.c_str());
rightpad -= 3 + this->strName.size();
}
//argument
if(this->takesArgument)
{
printf(" <%s>", arg_name.c_str());
rightpad -= arg_name.size() + 3;
}
printf("%*s%s", -1*rightpad, "", help_text.c_str());
printf("\n");
}
void OptionSet::printHelp(int leftpad, int rightpad)
{
for(auto it : this->m_options)
{
it.printHelp(leftpad,rightpad);
}
}
OptionSet::OptionSet()
@ -101,9 +146,9 @@ std::pair<std::vector<std::string>, bool> OptionSet::getOptions(std::vector<std:
}
if(popt->takesArgument)
{
if( ++it == input.end() ) //se termine ici
if( ++it == input.end() ) //finishes here
{
(*errStream) << "No argument given to option " << popt->strName << std::endl;
(*errStream) << "No argument given to option --" << popt->strName << std::endl;
return std::make_pair(out, false);
}
popt->activated = true;
@ -138,17 +183,17 @@ std::pair<std::vector<std::string>, bool> OptionSet::getOptions(std::vector<std:
while( !tstop && it!=input.end() && (*it).size()>i )
{
popt=this->findOption((*it)[i]);
if(popt==nullptr) //non trouvé: erreur
if(popt==nullptr) //not found: error
{
(*errStream) << "Unknown option: " << (*it)[i] << std::endl;
(*errStream) << "Unknown option: -" << (*it)[i] << std::endl;
return std::make_pair(out, false);
}
if(popt->takesArgument) //prends un argument
if(popt->takesArgument) //no argument
{
i++;
if((*it).size()<=i) //se termine ici
if((*it).size()<=i) //finishes here
{
if( ++it == input.end() ) //se termine ici
if( ++it == input.end() )
{
(*errStream) << "No argument given to option -" << popt->charName << std::endl;
return std::make_pair(out, false);
@ -170,7 +215,7 @@ std::pair<std::vector<std::string>, bool> OptionSet::getOptions(std::vector<std:
tstop=true;
}
}
else // ne prends pas d'argument
else //no argument
{
popt->activated = true;
}

View file

@ -9,28 +9,50 @@
#include <string>
#include <vector>
#define ANNOUNCE_COMMAND "aseqdump -p System:1"
#define LIST_COMMAND "aseqdump -l | tail -n +2 | cut -c10- | tr -s ' '"
void device_check()
{
char* buff = NULL;
size_t buff_size = 0;
FILE *stream = popen(LIST_COMMAND, "r");
FILE *stream = popen(LIST_EXTENDED_COMMAND, "r");
std::string str;
std::vector<std::pair<int,std::string>> ls_device;
getline(&buff, &buff_size, stream); //discard the first line
int i=0,j=0;
int t;
while ( getline(&buff, &buff_size, stream) > 0 ) //retrieve device lines
{
str += buff;
//port id get
i=0;
while(buff[i] == ' ')
i++;
j=i;
while(buff[i] != ':')
i++;
t=stoi( std::string(buff+j, i-j) );
//name get
j=9;
i=10;
while(buff[i+1] != '\n')
i++;
while(buff[i-1] == ' ')
i--;
ls_device.push_back(std::make_pair(t, std::string(buff+j, i-j)));
}
for ( auto it : device_list ) // iterate devices
for ( auto dev : device_list ) // iterate devices
{
if( !it->busy && str.find(it->name) != std::string::npos ) //device detected
for ( auto ls = ls_device.begin() ; ls != ls_device.end() ; ls++ )
{
printf("Device '%s' found\n", it->name.c_str());
it->start_loop();
if( !dev->busy && dev->name == ls->second && ls->first > 0) //device detected
{
dev->client_id = ls->first;
printf("Device '%s' found\n", dev->name.c_str());
dev->start_loop();
}
if( dev->busy && dev->client_id == ls->first )
ls->first = -1;
}
}