Yesterday I made a multihead sequencer, inspired by the Fugue Machine, with my buddy Ollie who is a professional games programmer. I learnt a lot about writing code from him and we used some pretty cool techniques. The base code for the tapeheads is only a few lines and can run up to 63 tape heads if you want! We brought that down to 4 for a nicer user interface. I’ve tried to explain every line of code here. For those of you who are new to coding (like me), there are some really neat tricks in here. In particular we made good use of chaining Loop ( L ) commands and passing variables through them to other scripts. I’ve tried to make it clear how each script is linked.
On the main page you have the sequencer. Botton left button enters edit page, 2nd bottom button is a reset button. The 4 buttons at the bottom right are wrap/bounce buttons which decide on tape head behaviour when they reach the end of the sequence.
Edit mode hides the step sequencer and brings up 4 buttons on the left which act as mute buttons for each playhead. The 4 faders act as clock dividers for each track.
It sends 4 pairs of Gate/CV
Here’s a quick demo of it in action:
Things I’d like to add: (hopefully possible with the soon to be added group Ops, which should free up some space!)
Exclusive notes/step (currently it just reads the lowest, which isn’t very elegant)
Pitch shift/octave shift per tapehead
Global Scale Editor
Global Pause button or maybe per tapehead?
Trigger length dependent on clock division
Fugetta
//M
//Line 1: The metro starts by setting all sequencer buttons (group 0) to brightness level 2.
//Line 2: It then starts a loop which applies the local variable I to global variable X, and triggering script 1. This essentially triggers script 1 4 times with X at 1,2,3 and 4. (You can in theory change the Loop range to be higher to have more play heads, but it wouldn’t work with the user interface…)
//Line 3: This detects if button 117, the reset button, is on. It then sets the wrap/bounce setting for each track to ‘wrap’. (As metro is triggered by this button it will engage as soon as pressed.)
//Line 4: Metro will be disabled as long as the reset button is held.
//Line 5: This detects if the reset button is on and subsequently sets all sequencer button (group 0) brightness to 2 (normal)
//Line 6: Sets PARAM knob to control BPM
#M
G.GBTN.L 0 2 2
L 1 4: X I; SCRIPT 1
IF G.BTN.V 117: G.GBTN.V 1 0
M.ACT - 1 G.BTN.V 117
IF G.BTN.V 117: G.GBTN.L 0 2 2
M SCALE 0 16383 300 20 PARAM
// Initialisation script sets up 2 groups of buttons: Group 0 are the main sequencer buttons, group 1 are the wrap/bounce buttons at the bottom right. We then run out space and so defer to script 7 for more initialisation but not before setting the active group to 2. (see #7 for continuation
//Line 1: Sets button group to 0
//Line 2: Creates step sequencer buttons
//Line 3: Sets button group to 1
//Line 4: Creates wrap/bounce buttons
//Line 5: Sets group to 2. Then triggers script 7 (which acts as an extra initialisation script)
#I
G.GRP 0
G.BTX 0 0 0 1 1 1 2 0 16 7
G.GRP 1
G.BTX 112 12 7 1 1 1 4 0 4 1
G.GRP 2; SCRIPT 7
//#1 Is triggered at every metro beat. It runs 4 times, once for each X = 1,2,3 & 4 (see #M Line 2)
//Line 1: Sets variable T to the current playhead (X)’s clock divider counter (PN 3) and adds 1. We are using T as a local variable. Then sets variable B to the reset buttons value
//Line 2: Sets the clock divider (PN 3) counter to T, therefore incrementing by 1. This is wrapped between 1 and the clock division value (PN 2)
//Line 3: If the clock divider counter (PN 3) is at 1, Script 2 will be run (which will move the active playhead to the next step)
//Line 4: This is looped 7 times, applying local variable I to Y which and triggering Script 8, taking the value of Y with it (Script 8 will light up the playhead position)
//Line 5: If the reset button is being held (B - see line 1), sets the clock divider counter (PN 3) to 0 and the playhead position (PN 1) to -1
//Line 6: If the reset button is being held, sets playhead direction to forward and sets variables D and A to 0 (Reviewing this, I’m not certain why setting these variables here was necessary)
#1
T + PN 3 X 1; B G.BTN.V 117
PN 3 X WRAP T 1 PN 2 X
IF EQ 1 PN 3 X: SCRIPT 2
L 0 6: Y I; SCRIPT 8
IF B: PN 3 X 0; PN 1 X -1
IF B: PN 0 X 1; D 0; A 0
//#2 This is the script to move the playhead 1 step in it’s direction. It is triggered by Script 1, Line 3.
//Line 1: Sets local variable to the current playheads direction (PN 0 X). Then it sets variable C to the sum of the playhead direction (-1 or +1) and the playhead position.
//Line 2: If the corresponding wrap/bounce button is on (set to bounce) go to script 3 (the bounce rules)
//Line 3: Otherwise set C to wrap between 0 and 15
//Line 4: The playhead position (PN 1) is then updated to C
//Line 5: This is run 7 times, applying local variable I to A (I was previously set to PN 0 X (direction)) and taking it through to script 4.
#2
I PN 0 X; C + I PN 1 X
IF G.BTN.V + 111 X: SCRIPT 3
ELSE: C WRAP C 0 15
PN 1 X C
L 0 6: A I; SCRIPT 4
#3 This is for bouncing playheads. It takes variable C from script 2. It takes 2 steps to reverse a play head, resulting in steps 0/15 being triggered twice, for the most musical effect.
//Line 1: If the playhead is at position 15, it sets the direction variable (PN 0) to -1 it’s current value
//Line 2: If the playhead is at position 0, it sets the direction variable to +1 it’s current value
#3
IF EQ C 15: PN 0 X - PN 0 X 1
IF EZ C: PN 0 X + PN 0 X 1
#4 This is for triggering pulse and CV outputs. The script is run once per single sequencer button. It is run in response to Script 2, Line 5.
//Line 1: Sets local variable D to be the the sequencer button identity, informed by playhead position (PN 1) and direction (A)
//Line 2: If the reset button (117) is on, stops the script
//Line 3: If the sequencer button is on, a trigger is made at the corresponding gate
//Line 4: Sets local variable I to the new playhead position (PN 0 + A) added to 9. A will give the look up position for note from the scale which is saved in PN 0, Index 9 - 15. I will then be set to that note (N) number
//Line 5: If the sequencer button is on, set the corresponding CV out to the note value of I
#4
D + PN 1 X * 16 A
IF EZ G.BTN.V + 117 X: BREAK
IF G.BTN.V D: TR.P X
I PN 0 + A 9
IF G.BTN.V D: CV X N I
//#5 Triggered by the Clock divider faders in edit mode - sets clock division
//Line 1: Loops 4 times - sets each clock division memory (PN 2) to the fader value
#5
L 1 4: PN 2 I G.FDR.V I
//#6 This is triggered when the edit button (116) is pressed. It creates the clock divider control faders
//Line 1: Sets active group to 3
//Line 2: Creates 4 horizontal faders
//Line 3: Disables and hides the sequencer trigger buttons when in edit mode. It will re-enable this group on leaving as the script is triggered again.
//Line 4: Sets the range of the faders to 1-8
//Line 5: Sets the mute buttons (Group 5) to be enabled when in edit mode and disable when leaving.
#6
G.GRP 3
G.FDX 1 2 1 8 1 0 4 5 1 4
G.GRP.EN 3 G.BTNV
G.GRP.EN 0 - 1 G.BTNV
G.GFDR.RN 3 1 8
G.GRP.EN 5 G.BTNV
//#7 is triggered on initialisation, the current group is 2. We then create 2 new buttons.
//Line 1: Button 116 is a latch button to enter edit mode by triggering script.
//Line 2: Button 117 is a reset button which triggers the metro script.
//Line 3: Set group to 5
//Line 4: We create a new group of buttons in group 5 which will be the active/mute buttons.
//Line 5: These are then de-activated and set to off. Therefore all tracks are muted on start up.
#7
G.BTN 116 0 7 1 1 1 4 6
G.BTN 117 2 7 1 1 0 4 9
G.GRP 5
G.BTX 118 0 1 1 1 1 5 0 1 4
G.GRP.EN 5 0; G.GBTN.V 5 0
//#8 Lights up the active playhead positions on the grid. It is triggered by Script #1 7 times (each row) for each play head. Therefore 27 times.
//Line 1: Local variable D is set to the sequencer button ID number - This is calculated from the playhead position (PN 1 X) and the row number (Y - sent through from script 1)
//Line 2: Lights up the button - play heads 1-4 have slightly different levels to try and distinguish them
#8
D + PN 1 X * 16 Y
G.BTN.L D + 4 * 2 X
//#P
// P0 1-4: Direction of play ; P0 9-15: Scale
// P1 1-4: Position of playhead
// P2 1-4: Clock division
// P3 1-4: Clock divider counter (will increment on each metro)
#P
5 5 6 0
1 1 1 1
0 0 0 0
63 63 63 63
0 0 0 0
1 2 4 3
1 8 4 2
1 5 4 1
1 7 4 4
0 13 5 4
0 0 0 0
0 0 0 0
0 0 0 0
12 0 0 0
11 0 0 0
9 0 0 0
7 0 0 0
5 0 0 0
4 0 0 0
2 0 0 0
0 0 0 0