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 hasWhat it is
grid.valueThe 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 = 3Lights 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.

JavaScriptPythonWhat it is
kit.valuekit.heldThe pads held now
kit.flash("kick")kit.flash("kick")Lights one from your code
kit.hit("kick"), kit.lift("kick")the samePlay 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
JavaScriptPythonWhat 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
JavaScriptPythonWhat 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:

LaneWhat a tap or a drag doesIts mark in the text
AccentTap for an accent^
SlideTap for a slide into the note~
OctaveTap to go round the octaves'
LengthDrag 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.

PatternHow it is written
A gateOne 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 arpeggioSteps 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 gridThe 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, tranceGateSmooth and tranceGateSwing, and a switch each for tranceGateSends and tranceGateStereo. beside: false leaves 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.

OptionWhat it does
patternThe pattern to start with: "backbeat", "breakbeat"
tempoBeats a minute
notesThe note each drum plays, where it is not the usual one
targetsA list of targets, or one for each drum by its name, in place of one for all
clockWhat 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:

JavaScriptPythonWhat it does
drums.play(), drums.stop()the sameStarts and stops it
drums.tempo, drums.swingthe sameThe tempo and the swing
drums.pattern = "breakbeat"drums.pattern("breakbeat")Chooses a starter pattern
drums.mute("hat")the sameMutes 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.textthe sameThe 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.