Post by nic on Jul 20, 2018 11:56:05 GMT
I was recently asked about writing a 'live' quantiser in Stream Byter code. Never being able to resist a challenge, I have had a go and the code is produced below that can be copied/pasted. This will only work with MidiFire or StreamByter AU. Unfortunately, not the Midi Designer version since the code uses some more advanced features that were added to the Stream Byter since Dan integrated it. I have been a bit more verbose with the comments as there are quite a few tricks to this.
In the code, you tell it the bpm you wish to use (whole numbers only), the divisions (1 = bar, 2=half, 4=quarter and so on) and whether you wish the lengths of the notes to be quantised also. These are set as constants at the top of the ruleset. I guess it would be possible to add code that allows you to set these values via MIDI message and change them dynamically. That's an exercise for someone!
Since it is not possible to turn back time and 'fix' late notes, the quantiser has a fixed lookahead latency that depends upon the interval calculated from the bpm and divisions (set at top of code). This latency (in milliseconds) is shown on the right hand block label so you can adjust other event streams to match by delaying them this same amount. The configured bpm is shown in the left hand block label.
In addition, this code only quantises note events; all other events are left untouched, which means they will be ahead by the latency value if they pass through this code. If using CC's pitchbend and the like, then split those events into a separate stream and add a delay (use the value in the right block label) and they will be in time with the grid.
Note, that the very first note that is seen sets the start of the quantise grid; all subsequent notes are thus aligned in the grid relative to the first note. If you need to reset this you can press the 'Install Rules' button (or again could be remotely controlled via MIDI message) so that the grid alignment restarts on the next received note.
I think that's enough to start with. If anyone tries it out, please post here with results/suggestions and (ahem) complaints.
Regards, Nic
In the code, you tell it the bpm you wish to use (whole numbers only), the divisions (1 = bar, 2=half, 4=quarter and so on) and whether you wish the lengths of the notes to be quantised also. These are set as constants at the top of the ruleset. I guess it would be possible to add code that allows you to set these values via MIDI message and change them dynamically. That's an exercise for someone!
Since it is not possible to turn back time and 'fix' late notes, the quantiser has a fixed lookahead latency that depends upon the interval calculated from the bpm and divisions (set at top of code). This latency (in milliseconds) is shown on the right hand block label so you can adjust other event streams to match by delaying them this same amount. The configured bpm is shown in the left hand block label.
In addition, this code only quantises note events; all other events are left untouched, which means they will be ahead by the latency value if they pass through this code. If using CC's pitchbend and the like, then split those events into a separate stream and add a delay (use the value in the right block label) and they will be in time with the grid.
Note, that the very first note that is seen sets the start of the quantise grid; all subsequent notes are thus aligned in the grid relative to the first note. If you need to reset this you can press the 'Install Rules' button (or again could be remotely controlled via MIDI message) so that the grid alignment restarts on the next received note.
I think that's enough to start with. If anyone tries it out, please post here with results/suggestions and (ahem) complaints.
Regards, Nic
# Quantiser by audeonic
# requires MidiFire 1.13/StreamByterAU 1.0
IF LOAD
# customise these constants
# K: 0-bpm, 1-divisions, 2-quantise length (0/1)
ASS K0 = 78 8 1
SET LB0 K0 +D
# calculate division interval (ms) into K3
# (or you could just set your own division in ms)
MAT K3 = EA60 / K0 # ms/beat
MAT K3 = K3 * 4 # ms/bar
MAT K3 = K3 / K1 # ms/division
# calculate/show latency into K4
MAT K4 = K3 / 2
SET LB1 K4 +D
# internal variables used
# L: 00-7F-lookup, 80-elapsed, 81-rtz flag
ASS L80 = 0 1
END
# force note on + velocity 0 to note off
# to make life easier
9X XX 00 = 8X
# quantise note event logic
IF MT < A0
# first, increase elapsed (L80) since last note
MAT L80 = L80 + T0
# prevent integer overflow
IF L80 > 7D00
MAT L80 = L80 % K3
END
# handle first note logic
# (toggles flag, throws away T0)
IF L81 == 1
ASS L80 = 0 0
END
# calculate time until next division into i0
MAT I0 = L80 % K3
# is event bang on time?
# (1st note will always be)
IF I0 == 0
ASS I1 = K4
END
# event is early or late
IF I0 != 0
# is event late?
IF I0 < K4
MAT I1 = K4 - I0
END
# is event early?
IF I0 >= K4
MAT I1 = K3 - I0
MAT I1 = I1 + K4
END
END
# if not quantising lengths, shift note off
# with same delay we shifted the note on by
# from lookup table
IF MT == 80
IF K2 == 0
ASS I1 = LM1
END
END
# send note event with calculated delay (I1)
SND M0 M1 M2 +DI1
# maintain last delay used in lookup table
# (only note on delays are actually used)
ASS LM1 = I1
# block original note
# (since we sent it delayed)
XX = XX +B
END