MidiDesigner "Stream Byter" plugin

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.

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
conditional logic IF
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.

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


[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.
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  


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.


- Match CC $7 from every channel and add CC $8 to the MID stream with the same value
BX 06 XX = BX 07 XX +C
BX 06    = XX 07    +C # this is identical

- Match all CC from Ch 1 and copy Name/Value to Ch 2
B0 = B1 +C

- Match all CC from Ch 1 and move the Name/Value to Ch 2
B0 = X1

- Match all CC $8 from Ch 1 and move the Name/Value to CC $7 on Ch 2
B0 07 = B1 06
B0 07 XX = B1 06 XX # identical
B0 07 = X1 06 # identical, only type the name/vals that need to be changed

- Rewrite all Note On/Off Msg from Ch 1 to Ch 2
N0 = X1 # can not reuse N on the right side

- Merge all Note On/Off from all Channels to Ch 1
NX = X0

- Block all Mod Wheel msg
B0 01 = XX +B


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

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]
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
P precision 256 4-bytes values (signed 32 bits int) P00-PFF
T timer 8 timing calculations in milliseconds
(16-bit, max 65535 seconds)

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: &quot;MT and MC doesn't seem to work in MD&quot;

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

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

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



can be nested

IF M0 == C0
  # do something
  # ELSE is not implemented, use a variable or XOR


==, !=, <, <=, >, >=
note that = means assign (ASS), and == means compare (IF, WHILE)


<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

# 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

# the conditional above works out to be the same as
IF K3 == 4
  IF K4 == 5
    IF K5 == 6

use the +L flag in an IF

ASS I0 = 0
IF I0 < 10 +L
  MAT I0 = I0 + 1

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.

  ALIAS ...
  DEFINE ...
  SUB ...

  SEND C0 01 +D2000
  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.
ASS I0 = 0

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).

copyright: tinyloops.com - contact