Controls for music
The shape of a note as a graph, a grid of steps, pads to hit, wheels, editors for a synth's trance gate and its arpeggio pattern, and a whole drum sequencer in one line.
envelope, stepGrid and pads
const lead = makeSynth("lead"), kick = makeSynth("kick")
envelope("shape", lead) // lead's attack, decay, sustain and release
const grid = stepGrid("drums", 16, ["kick", "snare", "hat"])
const kit = pads("kit", ["kick", "snare", "hat", "clap"], kick)
let last = -1
function update(dt) {
const step = Math.floor(beat() * 4) % 16 // sixteen steps to four beats
if (step === last) return
last = grid.step = step // light the column that is playing
if (grid.get("kick", step)) kick.play("C2")
}envelope
envelope(label, target) is how a note begins, settles, holds and dies away, as a graph whose corners you drag. The first corner sets the attack, the second the decay (across) and the sustain (up and down), and the last the release. Two taps put it back.
It finds the target's attack, decay, sustain and release by those names, or takes the four names: envelope("shape", lead, "a", "d", "s", "r"). With no target, shape.attack and the rest are numbers to use. onChange is called with the attack, decay, sustain and release (on_change).
stepGrid
stepGrid(label, steps, rows) is a grid of steps, each on or off, with a row for each sound (step_grid in Python). A tap turns a step over, and a finger dragged along turns on (or off) every step it crosses. On a narrow screen a long row is put on two lines.
| It has | What it is |
|---|---|
grid.value | The rows, each a list of 0 and 1 |
grid.get("kick", 0) | Reads one step |
grid.put("kick", 0, true) | Writes one step. In Python grid.put("kick", 0) turns it on |
grid.row("kick") | One row |
grid.clear() | Turns every step off |
grid.step = 3 | Lights that column as the one playing |
Options: value (the rows to start with), velocity (true: drag up or down on a step for how hard, 0 to 1) and onChange(row, step, on).
pads
pads(label, names, target) is pads to hit, several at once. Each plays a note of the target for as long as it is held, harder the nearer its middle it is hit. Pads named for drums play those drums' notes (a kick is 36, a snare 38, a hat 42); others count up from 36, or are told with notes.
| JavaScript | Python | What it is |
|---|---|---|
kit.value | kit.held | The pads held now |
kit.flash("kick") | kit.flash("kick") | Lights one from your code |
kit.hit("kick"), kit.lift("kick") | the same | Play one |
Options: notes, columns, size, onNoteOn(note, velocity, name) and onNoteOff(note, name) (on_note_on, on_note_off).
Wheels
const bend = pitchWheel("bend") // from -1 to 1, and back to 0 when you let go
const lead = makeSynth("lead")
modWheel("vibrato", lead, "vibrato") // stays where it is put| JavaScript | Python | What it is |
|---|---|---|
pitchWheel(label, target, "setting") | pitch_wheel(…) | A wheel that is rolled, and rolls back to where it started when you let go. Alone it runs from -1 to 1 |
modWheel(…), wheel(…) | mod_wheel(…), wheel(…) | The same wheel, which stays where it is put. spring: true makes it roll back (spring=True). Both are sliders in every other way |
Patterns: a trance gate, an arpeggio and a lane of steps
A synth can have a trance gate, which chops its sound in a pattern of steps, and an arpeggio pattern of its own. These two controls draw those patterns, to change by hand as the music plays:
setTempo(138)
const pad = makeSynth("pad"), lead = makeSynth("lead")
pad.tranceGate = "trance" // the synth's setting: a pattern by name, or "x.x.xx.x"
tranceGate("gate", pad) // the editor for it, with its depth, rate and the rest beside it
arpPattern("arp", lead) // draws lead.arpPattern
function onMouseDown() {
pad.start(["A3", "C4", "E4"]) // a held chord, chopped by the gate
lead.startArp("Am7", { step: "sixteenth" }) // an arpeggio that follows the pattern
}
function onKeyDown() {
stopSound()
}Two things have the same name. In JavaScript pad.tranceGate = "x.x" is the synth's setting, and tranceGate("gate", pad) is the editor for it. In Python they are pad.trance_gate and trance_gate("gate", pad).
The step that is sounding is lit as the music plays.
A pattern can also be one of your own, tied to nothing, to read in your program:
const chop = tranceGate("chop", "x.x.x5x.x.3.xx..") // chop.value, chop.text
const run = arpPattern("arp", "1 - 3 2 _ 1^ ~3 5':4") // an arpeggio's pattern
const accents = steps("accent", 16) // a lane of steps, for anything| JavaScript | Python | What it is |
|---|---|---|
tranceGate(label, target) | trance_gate(…) | The steps that open and shut a sound in time, each a bar as tall as the gate is open. Tap a bar on and off, drag up and down on it for its level, drag across to draw a line of them. The − and + at its corner set how many steps (up to 32). gate.preset("pump") writes a built-in pattern: eighths, offbeat, gallop, trance, stutter, pump, triplets, dotted |
arpPattern(label, target) | arp_pattern(…) | An arpeggio's pattern: steps across, and the notes of the chord up the side. 1 is its lowest, and past its top note the count goes on an octave up, so on a chord of four notes row 5 is the lowest again, an octave higher. Tap a cell and that step plays that note; tap it again for a rest; drag sideways from it and the note is held across the next steps. arp.preset("trance") writes a built-in pattern: bounce, octaves, root-fifth, thumb, converge, diverge, offbeat, gallop, trance, acid. Options: notes (how many rows of notes to start with, 8) and steps |
steps(label, 16) | steps(label, 16) | The lane of steps that the two above, and a row of a grid, are made of, to use for anything: a row of accents, a setting that changes each step. Tap and drag across. levels: true makes each a bar, and accents: true gives an accent to a step you drag up on. steps(label, target, "setting") ties it to a setting |
Under an arpeggio's grid is a lane for each other thing a step has:
| Lane | What a tap or a drag does | Its mark in the text |
|---|---|---|
| Accent | Tap for an accent | ^ |
| Slide | Tap for a slide into the note | ~ |
| Octave | Tap to go round the octaves | ' |
| Length | Drag up and down for the step's own length | : |
Each control has value, text (the same as text), length and step. value and text may be set, to a list, to text or to a built-in pattern's name, and onChange(value) hears a hand (a frame later in Python).
value is a list. For a gate or a lane it is levels from 0 to 1. For an arpeggio it is a step each, with a note, an octave, a tie, an accent, a slide and a length; note 0 is a rest, and length 0 is the arpeggio's own.
A pattern as text
As text, a pattern is exactly what the sound engine reads. So it can be saved, pasted into your code and handed to a synth.
| Pattern | How it is written |
|---|---|
| A gate | One mark a step: . off, x on, 1 to 8 on at that many ninths (0 is off and 9 is on). Spaces are for the eye: "x...x... x.5.x...". A gate has no accents |
| An arpeggio | Steps with a space between: 1 2 3 that note of the chord, - (or .) a rest, _ the note before held on. After a note ' is an octave up (, down), ^ an accent, and :1 to :9 its own length in tenths of a step. Before a note ~ slides into it: "1 - 3 2 _ 1^ ~3 5':4" |
| A drum's row, a lane, a row of a grid | The gate's marks and also X, on with an accent, and | for the eye as well as a space |
A drum's row is not a gate pattern, though it looks like one. It is the controls' own, for a drum sequencer and a lane. Do not hand it to a synth as its gate, nor a gate's text to a drum sequencer as a row with accents.
Tied to a synth
Tied to a synth, a pattern is one setting of it: tranceGate for a gate and arpPattern for an arpeggio. It is read and written as the synth keeps it, which is as text.
- The step that is sounding is lit, as the synth says which.
- A select of the built-in patterns is put on the same plate.
- A gate's settings go beside it: a small dial each for
tranceGateDepth,tranceGateRate,tranceGateSmoothandtranceGateSwing, and a switch each fortranceGateSendsandtranceGateStereo.beside: falseleaves them out (beside=False).
Tied to a target of your own, the setting is found by one of those names (trance_gate, arp_pattern and plain pattern are looked for too), or you name another. A target that has a step() or a position() lights the step that is sounding.
A drum sequencer
const kit = { kick: makeSynth("kick"), snare: makeSynth("snare"), hat: makeSynth("hat") }
const drums = drumSeq("beat", ["kick", "snare", "hat"], { targets: kit, notes: [36, 52, 60], clock: soundTime, pattern: "backbeat", tempo: 110 })drumSeq is a whole drum machine in one control (drum_seq in Python). Tap a step on and off, drag across to paint, and drag up on a step for an accent. Tap a drum's name to hear it, and the dot beside it to mute its row.
Its own strip has play and stop, the tempo (− and +, or tap the number in time), swing, starter patterns (four on the floor, a backbeat, a breakbeat) and clear. On a phone it shows as many steps as fit a finger, a page at a time, and turns the page as it plays.
It plays whatever it is given: one target for every drum, as in drumSeq("beat", ["kick", "snare"], makeSynth("kick")), or targets, a synth for each drum by its name. A target is anything a keyboard can play, and each row plays its drum's note: a kick is 36, a snare 38, a hat 42 and a clap 39.
| Option | What it does |
|---|---|
pattern | The pattern to start with: "backbeat", "breakbeat" |
tempo | Beats a minute |
notes | The note each drum plays, where it is not the usual one |
targets | A list of targets, or one for each drum by its name, in place of one for all |
clock | What keeps the time. In JavaScript give it the sound's own clock, clock: soundTime |
onStep (on_step) | A function called with the drum, the step and how hard, for every step that sounds. With no target at all, your own code makes the sound |
From your code:
| JavaScript | Python | What it does |
|---|---|---|
drums.play(), drums.stop() | the same | Starts and stops it |
drums.tempo, drums.swing | the same | The tempo and the swing |
drums.pattern = "breakbeat" | drums.pattern("breakbeat") | Chooses a starter pattern |
drums.mute("hat") | the same | Mutes a row |
drums.get("kick", 0), drums.put("kick", 0, true) | drums.get("kick", 0), drums.put("kick", 0) | Reads and writes one step |
drums.value, drums.text | the same | The rows, and a line of marks for each drum |
Keeping time
In JavaScript, give the sequencer the sound's own clock, { clock: soundTime }. Each step is then handed to its synth a little before it is due, to play at exactly that time: the beat stays tight however busy the page is. With no such clock a step is played on the frame it falls due, and the beat will wobble when the page is busy.
In Python the page plays the beat, not Python. So a synth is in time however busy Python is, and a Python object given as a target is played a frame after its step.