Sequencer¶
The sequencer class acts as an automatic looping grid, reading data from
arrays passed on to it and scheduling them in time according to a beats per
minute function, which tells the sequencer how much time each item in the
array takes up.
Timing¶
Before starting a sequencer, we need to set a BPM running:
Now we can define grids to set different subdivisions. The logic is as follows:
1: Each item is a quarter note (1 beat).
0.5: Each item is an eighth note (1/2 beat).
0.25: Each item is a sixteenth note (1/4 beat).
1/3: Each item is a triplet.
etc.
Considering this, let's define some different subdivisions:
const grid4 = 1; // Quarter notes
const grid8 = 0.5; // 8th notes
const grid16 = 0.25; // 16th notes
Patterns¶
At the core of the sequencer, we have patterns that will be repeated at the defined grid subdivision.
Given that, let's create them:
let backbeatPat = [kick, snare, kick, snare];
let bassPat = [[C3, 1, 0, 0.2], [C3, 1, 0, 0.2], [C4, 1, 0, 0.2], O, [Eb3, 1, 0, 0.2], O, [G3, 1, 0, 0.2], O];
let hatPat = [[closedHiHat, .2], [closedHiHat, 0.5], [openHiHat, .2], [closedHiHat, 0.5]];
let chordPat = [
[[C4, 1, 0, 3.75], [Eb4, 1, 0, 3.75], [G4, 1, 0, 3.75], [Bb4, 1, 0, 3.75]], // Cm7
O, O, O,
[[Ab3, 1, 0, 3.75], [C4, 1, 0, 3.75], [Eb4, 1, 0, 3.75], [G4, 1, 0, 3.75]], // Abmaj7
O, O, O,
];
Note
Notice that we are using the capital O for silent events in the bass.
Also notice that to get notes to play at same part in the chord pattern, we use nested brackets.
Local subdivisions¶
The special sub() method allows us to have subdivisions in a pattern without
having to rewrite it in for new grid. For example:
Will give an eighth note for the last two snares, occupying the last beat.
Any number of subdivisions are possible, as well as silences (O) inside a
subdivision.
Warning
Nested subdivisions (e.g. sub(snare, sub(snare, snare), snare)) are not
implemented yet and will return a silent event. For this case, we recommend to
go lower in the subdivision grid to have more control of each beat.
Tracks¶
The sequencer can have multiple tracks playing at the same time, each one of different lengths and playing at different subdivisions, but all syncronized to the same BPM setting.
The parameters for a new sequencer track is as follows:
Thus, we can go on and add our instruments:
let backbeatTrack = sequencer.add(drums, kckPat, 0.8, grid16);
let hatTrack = sequencer.add(drums, hatPat, 0.3, grid16);
let bassTrack = sequencer.add(synthBass1, bassPat, 0.6, grid8);
let padTrack = sequencer.add(pad1, chordPat, 0.4, grid4);
We can wrap everything into a function:
function synthWave() {
sequencer.clear();
setBpm(120);
const grid4 = 1;
const grid8 = 0.5;
const grid16 = 0.25;
let backbeatPat = [kick, snare, kick, snare];
let bassPat = [[C3, 1, 0, 0.2], [C3, 1, 0, 0.2], [C4, 1, 0, 0.2], O, [Eb3, 1, 0, 0.2], O, [G3, 1, 0, 0.2], O];
let hatPat = [[closedHiHat, .2], [closedHiHat, 0.5], [openHiHat, .2], [closedHiHat, 0.5]];
let chordPat = [
[[C4, 1, 0, 3.75], [Eb4, 1, 0, 3.75], [G4, 1, 0, 3.75], [Bb4, 1, 0, 3.75]], // Cm7
O, O, O,
[[Ab3, 1, 0, 3.75], [C4, 1, 0, 3.75], [Eb4, 1, 0, 3.75], [G4, 1, 0, 3.75]], // Abmaj7
O, O, O,
];
let backbeatTrack = sequencer.add(drums, backbeatPat, 0.8, grid4);
let hatTrack = sequencer.add(drums, hatPat, 0.3, grid16);
let bassTrack = sequencer.add(synthBass1, bassPat, 0.6, grid8);
let padTrack = sequencer.add(pad1, chordPat, 0.4, grid4);
sequencer.play();
}
synthWave();
Note
We use sequencer.clear() in the start of our function so we're able to
modify its patterns and instantly call it again!
Dynamic patterns¶
One problem with the example above is that it will always repeat in the same
way, without variations, which doesn't sound very musical...
To fix this, we can wrap a choose() function inside a pattern, making it
choose between some options each time.
let Cm7 = [[C4, 1, 0, 3.75], [Eb4, 1, 0, 3.75], [G4, 1, 0, 3.75], [Bb4, 1, 0, 3.75]];
let Abmaj7 = [[Ab3, 1, 0, 3.75], [C4, 1, 0, 3.75], [Eb4, 1, 0, 3.75], [G4, 1, 0, 3.75]];
let Ebmaj7 = [[Eb3, 1, 0, 3.75], [D4, 1, 0, 3.75], [G4, 1, 0, 3.75], [Bb4, 1, 0, 3.75]];
let Fm7 = [[F3, 1, 0, 3.75], [Eb3, 1, 0, 3.75], [C4, 1, 0, 3.75], [Ab4, 1, 0, 3.75]];
let chordPat = [
() => choose(Cm7, Ebmaj7), O, O, O,
() => choose(Abmaj7, Fm7), O, O, O
];