excellent!! funny enough, i just finished my version. i prefer your solution, it’s more elegant, but i’ll post mine just as an example of another way to implement it.
IF G.BTNV: SCRIPT 2; BREAK
L 0 - PN.L 0 1: Z I; SCRIPT 5
IF EQ PN.L 0 D: PN.RM 1 0
IF EQ PN.L 0 D: PN.RM 0 0
L 1 D: Z I; SCRIPT 3
PN.PUSH 0 G.BTNI; PN.PUSH 1 A
B G.BTNI; SCRIPT 4
IF EZ PN.L 0: BREAK
T - PN.L 0 1
L 0 T: C || C EQ Z PN 1 I
IF EZ C: A Z
Y - 7 G.BTN.Y B
X N + * Y 5 G.BTN.X B
JF.VOX A X V 8
IF NE PN 0 Z G.BTNI: BREAK
PN.RM 0 Z
A PN.RM 1 Z
JF.VOX A 0 0
G.BTX 0 1 0 1 1 0 3 1 15 8
JF.SHIFT V -2
PN.L 0 0
PN.L 1 0
pattern bank 0 stores pressed button ids (so its length reflects how many buttons are currently pressed). pattern 1 stores the corresponding JF voice.
when a button is pressed script 2 gets executed (via script 1). if the length of pattern bank 0 is 6 this means all 6 notes are used, so we remove the top one (which would be the oldest one - we append new notes at the end). script 3 checks if a JF voice passed in
Z variable is available, and if it is we store it in
A. we then append the new note using
PN.PUSH. finally, script 4 triggers the JF voice (
A is the voice number and
B is the button id). one thing i like about extracting this into its own script is that you can easily change the keyboard layout here, or do some additional stuff, like sending a pulse to trigger an envelope for a filter.
script 5 will be called upon a button release - it checks if this button was used for any of the active notes and removes the corresponding note.
edit: modified the scene so it can be used for any number of voices - set
D to the desired number in
re: using math on button id vs using
G.BTN.Y - both are good options, but using
Y has a couple of advantages: if you change your button ids it will still work, and if you decide to change the number of rows / columns you don’t have to change your formula.