stream byter - binary logic math - midi messages - midi designer pro - dk70
The Stream Byter plugin for Midi Designer can be used to reroute and alter MIDI messages before the MIDI stream hits the app, or before the MIDI is sent out. Stream Byter was licensed from Audeonic's MidiFire app. If you run on an older iOS device, like an iPad Mini 2 with iOS 12.5, you cannot upgrade to the latest version of Midi Designer Pro 2, and you have to work with an earlier version of the StreamByter plugin. Some newer StreamByter commands result in an ERROR in the MidiDesigner version.
The lack of the newer "Rules" (read "Commands") in MidiDesigner's implementation of the StreamByter plugin will not get in the way of your programming needs at all. The only difference is, that they would have made life easier. Still it's a lot of fun to use. This page intents to show the limitations for the Streambyter plugin in the Midi Designer Pro 2 app, for versions earlier than v2.200. You can check your version of Midi Designer Pro 2 by pressing the "More" button, press "Config", press the "About" tab. If you see version v2.200 or higher, you can use the newer Streambyter commands. Else this page will be handy.
Manual
For pre-v2.200 versions you can stop studying the Audeonic University Stream Byter manual roughly at "Defines, Subroutines and Includes". These functions are not yet implemented in the Streambyter plugin. For a primer, start with this MIDI Bridge Stream Byter manual (MIDI Bridge is an older product from Audeonic).
From Audeonic: StreamByter is a plugin for creating custom MIDI effects on iOS & MacOS. StreamByter is ported from our MidiFire (=routing app) MIDI processing environment. An iOS 11 device and a suitable AU host app such as AUM, apeMatrix, Cubasis or Sequencism is required to use the AU variant on an iDevice.
From Midi Designer Pro 2: The Stream Byter version in the pre-v2.200 Midi Designer Pro app, is the "Stream Byter II" version that exists in Audeonic’s MidiFire app (iOS/macOS). Stream Byter input and output rules are saved and shared with your layout. Any MIDI Designer Pro 2 user may open a layout with rules someone else has authored free of charge. There's an in-app purchase to author or edit Stream Byter rules.
MidiDesigner StreamByter version
Download the UltraEdit Wordfile for Streambyter code (for MDpro2 pre-v2.200). Copy/Paste the code to the WORDFILE.TXT, maybe change L10 to another number.
Here are the limitations for pre-2.200 versions of Midi Designer. The table contains a list of "rules" from the Audeonic manual, and the implementation in earlier MidiDesignerPro 2 versions.
It also means, that if you develop with the limitations for earlier versions in mind, your code will work in all Streambyter versions of MidiDesigner.
Streambyter rules:
MidiDesigner v2.200 and above | MidiDesigner before v2.200 limitations | |||
Rules | explanation | Rules | explanation | |
# | comment | # | ||
ALIAS | custom names for variables | -- | n/i | |
ASSIGN | assign a value to a variable | ASS | max 16 conseq. Array elements in allocation | |
BLOCK | block an event | -- | n/i | |
DEFINE | simple code macro | -- | n/i | |
EXIT | finish processing immediately | -- | n/i | |
IF/ELSE END |
conditional logic | IF END |
ELSE not implemented (operators, see below) |
|
KEY | send keystroke | -- | n/i | |
LOG | log a message to the event monitor | -- | n/i * | |
MATH | perform mathematical calculations | MAT | (math functions, see below) | |
CALC | MATH synonym | n/i | ||
SEND | send a message | SND | max 16 values (see quick manual). split SysEx into multi line with +F flag. --FLAGS-- +I (inject) do not Send this to SB MIDI Out, but send it to SB MIDI IN + F (force) no autocheck for message validity (used for multiline split SysEx messages) |
|
SET | set system vars | -- | n/i | |
SUBROUTINE | create a re-usable subroutine | -- | n/i | |
WHILE | loops | IF <..> +L END |
loops max 128 times for safety |
* not implemented, though All MIDI communication is automatically printed to the MidiDesigner LOG screen.
To continue with the limitations for earlier versions
SET not implemented (see Nil Velocity Rewrite), so use '9X XX 00 = 8X' for Note Off rewrite,
because the Note On check 'if MT == 90' could be Note off, when Velocity == 00.
Decimal values not implemented. MIDI Array index "MC" can be accessed, because the reserved variable MC does not exist.
To summarize:
- Use only HEX and Note values, decimal values are not implemented
- The reserved MIDI Variables "MC" and "MT" are not implemented
SYNTAX
INPUT RULES | = | OUTPUT RULES | FLAGS |
[Midi input stream rules match] | [process this Midi input stream] | ||
The Input Rules process MIDI after it comes into MIDI Designer, before it is parsed and affects your controls. | For the MIDI IN The Output Rules process MIDI before it hits MIDI Designer For the MIDI OUT The Output Rules process MIDI before it hits your Wi-Fi, bluetooth, virtual and hardware MIDI destinations. |
+C +B +D |
|
all X's on the LEFT side are wildcards | all X's on the RIGHT side are referers to the corresponding incoming bytes on the LEFT side |
FLAGS
LEFT | RIGHT | ||
no flag | : | match the input stream string. trash the incoming message after processing. |
reuse incoming values to rebuild the message. send the new message to the app. |
+C | : | match the input stream string. let the incoming message pass, this results in 2 messages. |
same as above. |
+B | : | match the input stream string. trash the incoming message. |
type 'XX' and do nothing |
+D | : | delay an event by nn milliseconds |
Rules are evaluated top to bottom and the results of each rule are fed into the next (unless the clone flag is set).
SYNTAX (verbose)
vertical table
LEFT SIDE (INCOMING): IF incoming matches THIS normally 3 bytes exceptions: Cx, Dx, F3-FE: 1 or 2 bytes; F0, F1: > 3 bytes) |
= | RIGHT SIDE (OUTGOING) SND THIS rewritten message | |||
1st byte | 1st nibble | type | var N: match Note Off (8) or On (9) var X: match "all event types" range 8-F: range of types to match |
var X: preserve corresponding incoming nibble | |
2nd nibble | channel | var X: match "any channel" range 0-F: range of channels to match |
var X: preserve corresponding incoming nibble | ||
2nd byte | name | var XX: match "any name" | var XX: copy byte 2 of incoming var X3: replace byte 2 with byte 3 flag +C: copy byte (2 and) 3 of incoming (opt) |
||
3rd byte | value | var XX: match "any value" | var XX: copy byte 3 of incoming var X2: replace byte 3 with byte 2 flag +C: copy byte 2 and 3 of incoming |
||
LEFT: all X's are wildcards | RIGHT: all X's are referers |
IF (LEFT SIDE matches string) {
SND RIGHT SIDE composed string
}
SYNTAX (verbose)
same, but horizontal table
LEFT SIDE (INCOMING): IF incoming matches THIS normally 3 bytes exceptions: Cx, Dx, F3-FE: 1 or 2 bytes; F0, F1: > 3 bytes) |
RIGHT SIDE (OUTGOING) SND THIS rewritten message | |||||||
1st byte | 2nd byte | 3rd byte | 1st byte | 2nd byte | 3rd byte | |||
Type and Channel nibble | Name byte | Value byte | = | Type and Channel nibble | Name byte | Value byte | ||
#: type number | #: channel number | ##: name nr | ##: value nr | #: type number | #: channel number | ##: name nr | ##: value nr | |
X: all types | X: all channels | XX: any name | XX: any value | X: copy 1st nibble from incoming |
X: copy 2nd nibble from incoming |
XX: copy 2nd byte from incoming |
XX: copy 3rd byte from incoming |
|
8-F: type range | 0-F: channel range | 00-7F: range | 00-7F: range | X3: copy 3rd byte from incoming |
X2: copy 2nd byte from incoming |
|||
N: Note On or Off | ||||||||
LEFT: all X's are wildcards | RIGHT: all X's are referers |
Range example '8, A, C' is not implemented for any Streambyter versions.
Examples - Match CC $7 from every channel and add CC $8 to the MID stream with the same value - Match all CC from Ch 1 and copy Name/Value to Ch 2 - Match all CC from Ch 1 and move the Name/Value to Ch 2 - Match all CC $8 from Ch 1 and move the Name/Value to CC $7 on Ch 2 - Rewrite all Note On/Off Msg from Ch 1 to Ch 2 - Merge all Note On/Off from all Channels to Ch 1 - Block all Mod Wheel msg |
PERMITTED VARIABLE NAMES (ALPHABETICAL)
name | purpose |
0-9, A-F | hexadecimal numbers. reserved |
^A-^G | note names |
BP | current tempo of the MidiFire Clock |
G | global array |
I, J, K, L | local array |
M | midi message array |
MC | midi channel number |
ML | midi message length |
MT | midi type (1st nibble of 1st byte) second nibble is always 0 |
N | LEFT: note off or on wildcard (8 or 9) RIGHT: not available |
P | precision array |
PO | current MidiFire clock position |
Q | MidiFire GUI control |
R | returns random number |
T | timer |
W | wide array |
X | LEFT: wildcards RIGHT: referers |
Z | arguments array in SUB |
ZN | nr of arguments in SUB |
ARRAY VARIABLES
indices start from 0.
0 is hex and $0 is decimal
name | array type | indices | values | example |
I, J, K, L | local | 256 [0-255] [00-FF] |
2-bytes values (unsigned 16 bits int) | L00-LFF (hex) L$0-L$255 (dec) |
G | global | 256 | 2-bytes values (unsigned 16 bits int) | G00-GFF |
W | wide | 2048 [0-7FF] | 2-bytes values (unsigned 16 bits int) | W0-W7FF W$0-W$2047 |
P | precision | 256 | 4-bytes values (signed 32 bits int) | P00-PFF |
T | timer | 8 | timing calculations in milliseconds (16-bit, max 65535 seconds) |
T00-T07 |
T: Each time you refer to a timer variable, the value returned will be the number of milliseconds elapsed since that timer variable was last referenced. The first time you refer to a timer variable after a scene load, it will return 0 milliseconds. The values of variables are not reset when you press the 'Install Rules' button, but they are reset during a scene load.
R/O variables that contains a copy of the current midi message
M | midi message array | 65536 (M0-MFFFF) | unsigned 8 bit | M0A - 11th message byte (hex) M$10 - 11th msg byte (dec) |
ML | --- | length of midi message | ||
MC | --- | midi channel number | 0-F | returns F0 if the message is not a channel message (eg. the F-range) alert! C (hex) = 13 (dec) to get the 13th element of a midi message use M$12 - TQ5 v1.0, MIDI OUT rules: "MT and MC doesn't seem to work in MD" - which is quite fortunate, because $12 doesn't seem to work either. nearly made me decide to trash streambyter development. |
MT | --- | midi type/status | [8-F] | first nibble of the first byte (channel removed) - TQ5 v1.0, MIDI OUT rules: "MT and MC doesn't seem to work in MD" |
other variables
R | returns random number | 0-nn | nn = hex number or variable name |
BP | current tempo of the MidiFire Dynamic Clock module * 100 | BPM 127.32 = value 12732 | |
PO | current MidiFire clock position in milliseconds | 32 bit signed value |
indirect array addressing
GL0 | global array G, index is that of the value stored in variable L0 |
MG03 | MIDI message array, index is that of the value stored in the variable G03 |
VALUES
Values come in different flavours
Values can be:
- a literal hex value: 0A (hex)
- a literal decimal value: $10 (dec) DOES NOT WORK IN STREAM BYTER
- a literal negative decimal value: $-10 (dec) DOES NOT WORK IN STREAM BYTER
- a note literal: ^C-2 (from C-2 to G8), ex. ^C3 ^G#6 ^Bb0 ^Bb-1 ^D-2 ^G2
- the contents of another variable
ASSIGN
As in: the LET command in BASIC
ASS L0 = 5
ASS L1 = 70 23 2 A7 $51 L02 4 3 ^C1 # Fill L01-09 with hex hex hex hex dec var hex hex note
MATH
Mathematical functions. like ASS but with operators.
MAT I0 = I0 + 1 # Add
MAT I0 = I0 - 1 # Subtract
MAT I0 = I0 * 3 # Multiply
MAT I0 = I0 / 2 # Divide
MAT I0 = I0 % 2 # Mod
And Bit operators
MAT I0 = I0 & 0F # And
MAT I0 = I0 | 0F # Or
MAT I0 = I0 ^ FF # Xor
See bit operations on the page Binary Logic Math.
SEND
Send a MIDI stream of max. 16 bytes.
Split SysEx messages into separate Send-commands
SND B0 01 L01 # Send CC $2 to Channel 2, get the value from L01
SND 90 C2 7F # Send Note C2 On to Channel 1, Velocity 127
PROGRAMMING STUFF
CONDITIONAL BLOCK
can be nested
IF M0 == C0
# do something
# ELSE is not implemented, use a variable or XOR
END
OPERATORS
==, !=, <, <=, >, >=
note that = means assign (ASS), and == means compare (IF, WHILE)
IF-CLAUSE STRUCTURE
<value> <OPERATOR> <value> [<value> [<value>] [<value>]] [+L[OOP]]
If more than one value is specified after the operator (max 4), then the left hand value should be an Array and each right hand variable corresponds to an incremental index of the Array.
IF M12 == 64 32 G01
# do something
END
# Same as:
# IF (M12==64) AND (M13==32) AND (M14==G01) THEN
Another fun example: get the index for K from variable I
ASS K0 = 1 2 3 4 5 6 7 # indices K0-K6 are filled
ASS I0 = 3
IF KI0 == 4 5 6
# this will be true
END
# the conditional above works out to be the same as
IF K3 == 4
IF K4 == 5
IF K5 == 6
END
END
END
ASS I0 = 0
IF I0 < 10 +L
MAT I0 = I0 + 1
END
HEADER OF CODE
LOAD command, is executed before any MIDI has arrived, so the M variable is still empty.
Commands in RED do not function for earlier Midi Designer Pro 2 versions.
IF LOAD ALIAS ... DEFINE ... SUB ... SEND C0 01 +D2000 END |
Contribution from Don Sabourin on the MDP2 Facebook topic, thank you Don. See also the topic "When does IF LOAD actuate in SB?" on the MDP2 Q/A website. IF LOAD will be triggered: - on rule install - on layout load - on first start layout load - on connection change from 0 to 1 or more connection - on quitting MDP (so it is not running in the background) - on forced shutdown of the iPad The first 4 can be observed in the log and/or using a SND in the load. The last 2 can only be observed when a message is output that uses the initial condition(s) in the Load. My test code is executed by any output midi and monitoring that the SND result is 0 after a Load event, otherwise it is a 1. IF LOAD ASS I0 = 0 END SND B3 I0 $127 ASS I0 = 1 All 6 stated Load conditions pass this test. |
Behaviors that apply to any Output and Input Rules
Don Sabourin also observed the following behaviors while testing v2.200.
The BLOCK can be inserted anywhere in the Code (beginning, middle, or end). The location of the BLOCK affects the ability to use parameters of the Output Rules (triggering) message being blocked (such as M1, M2, etc.). Parameters can be used prior to the BLOCK, but do not exist after BLOCK even though the code still runs.
The outgoing message, if not blocked, will be sent immediately. Any messages sent within the code will follow the outgoing message with minimal delay.
Delays occur after the outgoing message is received. If the outgoing message is not blocked, it will be sent immediately followed by any delay messages after their delay.
For multiple SNDs with delays, timing of the sends are independent strictly based on their delay value. A delay with D1000 will be sent before a delay of 2000, no matter their location in the code (e.g., if D2000 is entered in the code prior to D1000, D1000 will occur one second before D2000).