-
Notifications
You must be signed in to change notification settings - Fork 0
JACK MIDI event scripting triggerable on events
License
paulguy/crustymidi
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
__ , __ ___ |_ ,' ` |' ` | | ,' ' | | | | | | | `--. | | | `.__. | `.__/| .___.' \_ `.__/| .__.' ____________ ___ ____________ ___ | `, | | | `, | | | ,--, ,--, | | | '----------, | | | | | | | | | | | ,---, | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | '------' | | | |__| |__| |__| |___| |____________.' |___| ############################################# #############################################:: ::::::::::::::::::::::::::::::::::::::::::::: MIDI event scripting for JACK _ / \ ---------------- |/| [-] CONTENTS [-] \_/ ---------------- 1. BUILDING 2. RUNNING 3. SCRIPT FILES 4. CRUSTYVM SCRIPT REFERENCE 5. CRUSTYVM VARIABLE REFERENCES 6. CRUSTYMIDI CALLBACKS /| ---------------- | [-] BUILDING [-] _|_ ---------------- Just run 'make' in the directory. It needs nothing but JACK headers and development libraries. _ ' | --------------- / [-] RUNNING [-] /__ --------------- ./crustymidi [-Dvariable=value] <script file> -D is a means of passing in substring replacements. Any string "variable" appearing within a word or quoted string will be replaced by "value". Mostly to be used for passing in optional parameters. If a filename begins with a -, you can end a line with -- then the next argument will be taken as a filename. _ ' | -------------------- -< [-] SCRIPT FILES [-] ._| -------------------- Scripts must contain an 'init' and 'event' procedure. 'init' is called once after the script is loaded. All services are available at this point and timers and events can be written at this point. 'event' is called once per MIDI input event. A script may consume or change its internal state based on the event and never emit another event or can emit theoretically any number of additional events or timers per input event. By default a script will have one input and one output port, named 'in' and 'out', respectively. Ports may be named and additional input and output ports may be defined by starting the script with the string ';crustymidi ' then following up with 'in:<name>' and 'out:<name>' statements, all on the same line. Both sets of input and output ports start numbered from 0 and increment with each statement. See example .cvm files. midi.inc can be included for some useful constants, but it's still very incomplete. /| --------------------------------- |_| [-] CRUSTYVM SCRIPT REFERENCE [-] | --------------------------------- Before anything is done, a pass is made to find all the tokens in the program. Quoted strings act as a single token. Comments are thrown out at this stage. Comments begin with a ; and include everything following. Quoted strings may contain ;s though. At this point, only one statement is meaningful: include <filename> Read in the file <filename> in the root directory of the script and start parsing tokens in from this file. Care is taken that files above the script directory should be inaccessible to be included in to a script for safety reasons, but don't rely on it for security. Make reasonably sure like any other program or script that you trust where it came from. Preprocesor Statements The first step to a program running (after it's tokenized) is to run through and interpret the preprocessor statements, which are responsible for defining preprocessor-time variables, and rewriting statements based on those. Any time a single argument is needed to have spaces, it must be enclosed in quotes, otherwise it'll be interpreted as multiple arguments. Quoted strings support the following escape sequences: \r carriage return \n new line \<carriage return> ignore a carriage return in the string. A following new line is also ignored \<new line> ignore a new line in the string. A following carriage return is also ignored \\ a literal \ (backslash) \xNN - a two digit hex number representing a byte literal \" a literal " (quote) macro <name> [arguments ...] Start a macro definition. From here, lines will be passed over until until and endmacro statement with the same name is reached, at which time normal interpretation will resume. The arguments are a list of symbols which when the macro is evaluated, will be replaced by whatever values were passed in when invoked. endmacro <name> End the named macro definition. Normal interpretation will resume. if <number> <name> [arguments ...] If <number> is non-zero, start copying the named macro with the listed arguments. <number> may also be an expression which is to be replaced by an expression or a macro argument, or it may also be a variable passed in on the command line with -D, in this case, it'll always evaluate to true, even if the variable is specified to be 0. expr <variable> <expression> Evaluate an expression down to a numerical value and assign it to <variable>. At that point, any time <variable> appears in the program, it'll be replaced by the value which the expression evaluated to. An expression can only accept integer values and all operations are done as integers and the result is an integer. Expressions are arithmetic statements and support the following operators: * multiplication / division % modulo + addition - subtraction << binary shift left >> binary shift right < logical less than <= logical less than or equal > logical greater than >= logical greater than or equal == logical equals != logical not equals & bitwise AND !& bitwise NAND | bitwise OR !| bitwise NOR ^ bitwise XOR !^ bitwise XNOR Parentheses are also supported for grouping, otherwise it follows the precedence followed is similar to that of C arithmetic parsing precedence. <macroname> <arguments ...> Start evaluaing (copying) from macro and continue until the matched endmacro is reached. Replacing any argument values with the arguments passed in. All arguments specified must be provided. Symbol Definition Statements Following the preprocessing stage, the resulting code is scanned to find symbol definitions: procedures, global (static) variables and procedure (local) variables. Before this begins though, any callback variables defined by the VM will be added in as globals. proc <name> [arguments ...] Defines the start of procedure <name> and specifies which arguments, if any, it accepts. Those arguments become local variables which reference the memory of the variables passed in, that is, any modifications to them in the procedure will persist when the procedure returns. ret Return from procedure. Marks the end of a procedure. static <name> [N | ints <N | "N ..."> | floats <N | "N ..."> | string "..."] Define a global (static) variable <name>. If a single number is provided, it will act as a single integer initializer. If a type is specified, a single value may be provided to create an array of that size, otherwise, multiple arguments may be specified in quotes, separated by spaces or tabs, to create an array of the size of values given and initialized with those values. The exception is string, which can only be initialized with a single argument, quoted or not. Values are initialized once, on VM start. These may be specified anywhere, procedure or not. local <name> [N | ints <N | "N ..."> | floats <N | "N ..."> | string "..."] Define a procedure (local) variable <name>. The initializer is specified in the same way as global variables. These values are initialized on each call to a procedure. These can only be defined inside procedures. Procedure variables must have unique names to global variables. stack Not a real instruction, just indicate to accumulate stack. label Define a label within a procedure to jump to. Label names are scoped locally to procedures. binclude <name> <chars | ints | floats> <filename> [start] [length] Read in <filename> to be the initializer for a global variable <name>. The type must be defined as chars (a string), ints (integer array) or floats (double array). A start byte and length byte may be specified to include only a particular range of the file. As many items of the size of type which fit within the file or provided length will be read in and used as the array initializer. Like include, some care is taken to prevent arbitrary files above the script's directory from being read in. The same warning applies. Program Instructions The final stage is parsing instructions and generating bytecode. The language supports a fairly limited set of instructions, but ones which vaguely represent the selection of instructions one may have available on a more primitive platform, just with some added feature and artistic license for the sake of simplciity and safety, but bugs can certainly still come up. move <destionation>[:<index>] <source>[:<index>] Simply move a value from source to destination. Source may be a variable, an integer immediate or a callback with the index passed to it, which will return a value. Values will be converted to the type destination is before being stored. If destination is a callback itself, it will be passed a reference to the source at the index provided, assuming it's not also a callback or an immediate, in which case only the single value ia passed in. All indexes are range checked to avoid hidden out of bounds accesses, in which case the program is terminated. A read or write callback indicating an error will also terminate execution. An index of 0 is implied if it's excluded. add <destination>[:<index>] <source>[:<index>] Add <destionation> at <index> and <source at <index> then store the value in <destination>. Source may be a callback in this case but destination cannot be, even if a callback may otherwise be readable and writeable. If either destination or source is a float value, the operation will be done as if they are floats, but will be converted to the type of the destination, truncated to the range/precision of the type. All other arithmetic operations follow similar rules. sub <destination>[:<index>] <source>[:<index>] Subtract. mul <destination>[:<index>] <source>[:<index>] Multiply. div <destination>[:<index>] <source>[:<index>] Divide. mod <destination>[:<index>] <source>[:<index>] Modulo (remainder). and <destination>[:<index>] <source>[:<index>] Bitwise AND. Bitwise instructions follow similar access rules as arithmetic expressions, but they are restricted to operating on integer or string types. A character of a string is converted to a 32 bit integer and padded out with 0s in its most significant bits. or <destination>[:<index>] <source>[:<index>] Bitwise OR. xor <destination>[:<index>] <source>[:<index>] Bitwise XOR. shr <destination>[:<index>] <source>[:<index>] Bitwise shift right. <source> may be a float, but the value will of course be converted to an integer. shl <destination>[:<index>] <source>[:<index>] Bitwise shift left. cmp <destination>[:<index>] <source>[:<index>] Compare (subtract) <destination> at <index> and <source> at <index>, but don't store it, simply hold on to the result for use with conditional jumps. Neither value necessarily needs to be writable, that is, either side or both sides can be an immediate or a read-only callback. In reality, any value which would be written or passed on to a destination is stored as the result from any of the above operations, for use on a following conditional jump, but it is only 1 value, and it is always replaced on one of these operations, regardless. jump <label> Jump to a label. jumpn <label> Jump to a label if result is not zero. From a cmp operation, this indicates that the two values were different. (cmp a b -> a != b) jumpz <label> Jump to a label if the result is zero. From a cmp operation, this indicates that the two values are equal. (cmp a b -> a == b) jumpl <label> jump to a label if the result is less than zero/negative. From a cmp operation, this indicates that the left value is less than the right value. (cmp a b -> a < b) jumpg <label> Jump to a label if the result is greater than zero/positive. From a cmp operation, this indicates that the left value is greater than the right value. (cmp a b -> a > b) call <procedure> <arguments ...> Call a procedure. All arguments indicated by the procedure must be provided and are passed in as reference to the procedure and may be changed once the procedure returns. __ | ------------------------------------ `-, [-] CRUSTYVM VARIABLE REFERENCES [-] ._/ ------------------------------------ In any place where a variable may be referenced or used, the format will always be the same: <name>[:[<index>]] Name must be provided, optionally followed by a colon ':', then optionally by an index. Just the colon will return the length of the array in variable <name>, where a colon followed by the index will fetch the value at the index, or in the case of a procedure call, pass in a reference starting from that index. Negative indexes aren't allowed anywhere. Out of range accesses will result in a compile or runtime error. __ / ---------------------------- |`\ [-] CRUSTYMIDI CALLBACKS [-] \_/ ---------------------------- Script procedure callbacks (all must be defined) init Called once on script initialization. event Called on every incoming MIDI event. Read callbacks length Get the length in bytes of the incoming event. data:n Read data from event at index n. Out of bounds access will return a callback error. port Defined port which the event came in on. time Sample on which the event came in on, depends on the JACK sample rate. JACK provides event times as 64 bit values; this just returns the low 32 bits, so this value may wrap as the script runs. rate Current JACK rate, may change from event to event. On rate change, any events in-flight will have automatically been scaled to occur at the intended time. Write callbacks length Set the event length which you'd like to write. Ignored when recommitting the input event. Initialized to 0. Data buffer growth is initialized to 0 on length changes. data:n Write data in to event at index n. n must be within bounds defined by the value written in to length. port Output port which event should be written to, initialized to 0. time Time in samples after input event time (or 0 on init) that the event should be output to JACK. A time of 0 means to emit the event at the same time as the event incoming. Initialized to 0. commit Commit an event. Multiple output events may be emitted per input event. A zero written to commit means to just re-emit the same event at the same time, however a different output port may be specified. timer Cause a timer event to be emitted by some samples in the future. The generated event will be of length 1 and and be command 0xF8, chosen for being time-related and JACK shouldn't be emitting these events.
About
JACK MIDI event scripting triggerable on events
Topics
Resources
License
Stars
Watchers
Forks
Packages 0
No packages published