Live sets
Rise
A live build — evaluate the numbered blocks top to bottom and it rises from a pad intro into driving industrial techno. Shows the tempo-locked rgate + fbdelay, ebass with dist2, a compkick, and pbuild with per-bar layer gates. Boot + load the kit first.
Clock.bpm = 126
Root.default = "E"
Scale.default = "minor"
# 1 — pads + a sine motif
p1 >> pads([0, 3, (0,3,7), 5], oct=4, dur=8, attack=2, release=5, reverb=0.6, room=0.9, lpf=linvar([500, 2200], [16]), amp=0.5)
p2 >> sine([7, 5, 3, 0], oct=5, dur=4, amp=0.25, mverb=0.6)
# 2 — the motif picks up a feedback delay
p2 >> sine([0, 3, 5, 7], oct=5, dur=2, amp=0.3, lpf=sinvar([800, 5000], [8]), fbdelay=0.5, fbtime=0.25, fbfeed=0.5, fbcutoff=3000, fbspread=0.02)
# 3 — gated saw + ebass come in
p3 >> saw([0, 0, 7, 0], oct=3, dur=0.25, lpf=sinvar([400, 4000], [8]), rgate=0.7, rgaterate=4, amp=0.3, fbdelay=0.5, fbtime=0.25, fbfeed=0.5, fbcutoff=3000, fbspread=0.02)
p4 >> ebass([0, 0, 7, 0], oct=4, dist2=0.6, dist2shape=1, dur=0.25, lpf=sinvar([400, 4000], [8]), rgate=0.7, rgaterate=4, amp=0.3, fbdelay=0.5, fbtime=0.25, fbfeed=0.5, fbcutoff=3000, fbspread=0.02)
# 4 — lift the pads an octave
p1 >> pads([0, 3, (0,3,7), 5], oct=6, dur=8, attack=2, release=5, reverb=0.6, room=0.9, lpf=linvar([500, 2200], [16]), amp=0.5)
# 5 — the drop: compkick + industrial drums (snare/hat gated out)
~p2 >> compkick([0], oct=3, punch=4, comp=80, click=40, crunch=120, sub=4, body=0.6, tone=4)
v1 >> play(pbuild("indus", evolve=8, fill=4, density=1, kick=1, snare=0, hat=0, perc=1), dur=1/2, fbdelay=0.5, fbtime=0.25, fbfeed=0.5, fbcutoff=3000, fbspread=0.02)
v2 >> play("X ", amp=1)
p4 >> ebass([0, 0, 7, 4], oct=4, dist2=0.6, dist2shape=1, dur=0.25, lpf=sinvar([400, 4000], [8]), rgate=0.7, rgaterate=4, amp=0.3, fbdelay=0.5, fbtime=0.25, fbfeed=0.5, fbcutoff=3000, fbspread=0.02)
# 6 — open the saw up an octave
p3 >> saw([0, 0, 7, 0], oct=5, dur=0.25, lpf=sinvar([400, 4000], [8]), rgate=0.7, rgaterate=4, amp=0.3, fbdelay=0.5, fbtime=0.25, fbfeed=0.5, fbcutoff=3000, fbspread=0.02)
Full composition
A complete live set wired as a #@ arrangement. Run #@intro and let it auto-advance. The drop is split into layered parts (dropA/B/C) joined by #@goto routers: #@goto(dropA, 0.5) is a zero-length node that, when reached, has a 50% chance to jump back to dropA and 50% to fall through to the next section — so the drop loops a random number of times and the set never plays the same way twice. It also uses chords & groups, FX chains, linvar/sinvar, P-patterns, probability, accents and ~reset. Boot audio first. (Keep part names unique — jumps resolve to the first match.)
#@#@ showcase_set
#@intro(16)
# pads fade in on an opening filter + sparse kick, then advance to build
p1 >> pads([0, (0,4,7), 5, (2,5,9)], oct=4, dur=4, attack=0.5, lpf=linvar([400, 4500], [16]), reverb=0.5, room=0.85, amp=0.5)
b1 >> play(x...x...x...x..., amp=0.6)
#@build(16)
# add driven sub-bass + ghosted hats; the kick sometimes stutters
p1 >> dbass([0, -3, 0, 4], oct=4, mverb=0.3, tanh=0.4, drive=3, amp=0.8)
b1 >> play(x.x.x.x., amp=0.7).sometimes("stutter", 2)
h1 >> play(-.-.-.-., hpf=5000, amp=Pacc("ghost"))
#@dropA(16)
# drop layer 1: arpeggio on a moving filter, four-on-the-floor kick
p2 >> saw([0, (0,4,7), 4, (2,5,9)], oct=4, dur=0.5, chorus=0.6, chorus_rate=0.5, lpf=sinvar([900, 6000], [8]), amp=0.4).every(8, "reverse")
b1 >> play(X.x.X.x., amp=0.9)
#@dropB(16)
# drop layer 2: add a high blip lead + euclid-accented kick
p3 >> blip(PRange(0, 7), oct=6, dur=0.25, echo=0.4, echo_time=0.375, amp=0.25).sometimes("stutter", 4)
b1 >> play(X.[xx]X.x., amplify=PFDur((3,8),(5,8)), amp=0.9)
h1 >> play(<-.><-o>, hpf=6000, amp=Pacc("offbeat"))
#@goto(dropA, 0.5) # 50% loop back to dropA (re-vary the drop), else go on
#@dropC(16)
# drop layer 3: chord stabs an octave up, reversing every 8 beats
p2 >> prophet([0, (0,4,7), 4, (2,5,9)], oct=6, dur=0.5, chorus=0.7, lpf=sinvar([1200, 7000], [4]), amp=0.35).every(8, "reverse")
#@goto(dropB, 0.4) # 40% drop back to dropB, else continue to the break
#@break(16)
# strip back to a single frozen-reverb chord stab
# p3 >>
# h1 >>
p2 >> prophet((0,4,7), oct=4, dur=2, mverb=0.85, mverbfreeze=1, amp=0.4)
b1 >> play(x..., amp=0.6)
#@goto(dropA, 0.5) # 50% back into the drop, else resolve to the outro
#@outro(16)
# ~p1 resets that slot to a fresh bell; everything fades on a closing filter
~p1 >> bell([0, 4, 7, 11], oct=5, dur=1, reverb=0.6, room=0.9, lpf=linvar([5000, 600], [16]), amp=linvar([0.5, 0], [16]))
# p2 >>
# b1 >>
#@end(8)
Basics
Introduction — boot, load the kit, run #@intro
Hiya! 1. Click boot (top-left). 2. Put the cursor on a loadpack line below and press Ctrl+Enter to load the sound kit. 3. Put the cursor on #@intro(16) and press Ctrl+Enter — the set plays and auto-advances (it branches with #@goto, so it never plays the same way twice). Ctrl+; stops everything.
Best in Chromium / Brave / Edge — Firefox has audio + sample issues. Keys: Ctrl+Enter run line · Ctrl+Alt+Enter run block · Alt+X stop · Alt+↑/↓ nudge a number live. In a ?session= room, say hi in the chat (right panel) — leave a comment if you connect!
# load the kit — evaluate ONE of these (cursor on it, Ctrl+Enter)
loadpack("https://raw.githubusercontent.com/CrashServer/webfoxdot-kit/v1/pack.json")
# CDN mirror (faster, but sometimes cold — hard-refresh & try the other if silent):
# loadpack("https://cdn.jsdelivr.net/gh/CrashServer/webfoxdot-kit@v1/pack.json")
#@#@ welcome_set
#@intro(16)
p1 >> pads([0, (0,4,7), 5, (2,5,9)], oct=4, dur=4, attack=0.5, lpf=linvar([400, 4500], [16]), reverb=0.5, room=0.85, amp=0.5)
b1 >> play(x..., amp=0.6)
#@build(16)
p1 >> dbass([0, -3, 0, 4], oct=4, mverb=0.3, tanh=0.4, drive=3, amp=0.8).unison(2)
b1 >> play(x.x.x.x., amp=0.8).sometimes("stutter", 2)
h1 >> play(-.-.-.-., hpf=5000, amp=Pacc("ghost"))
p2 >> saw([0, (0,4,7), 4, (2,5,9)], oct=4, dur=0.5, chorus=0.6, lpf=sinvar([900, 6000], [8]), amp=0.4).every(8, "reverse")
#@dropA(16)
p2 >> saw([0, (0,4,7), 4, (2,5,9)], oct=4, dur=0.5, chorus=0.6, lpf=sinvar([900, 6000], [8]), amp=0.4).every(8, "reverse")
b1 >> play(X.x.X.x., amp=0.9)
#@dropB(16)
p3 >> blip([0,4,7,5,7,4], oct=6, dur=0.25, echo=0.4, echo_time=0.375, amp=0.25).sometimes("stutter", 4)
b1 >> play(X.[xx]X.x., amplify=PFDur((3,8),(5,8)), amp=0.9)
h1 >> play(<-.><-o>, hpf=6000, amp=Pacc("offbeat"))
#@goto(dropA, 0.5) # 50% loop the drop, else go on
#@dropC(16)
p2 >> prophet([0, (0,4,7), 4, (2,5,9)], oct=6, dur=0.5, chorus=0.7, lpf=sinvar([1200, 7000], [4]), amp=0.35).every(8, "reverse")
#@goto(dropB, 0.4) # 40% back to dropB, else continue
#@break(16)
# p3 >>
# h1 >>
p2 >> prophet((0,4,7), oct=4, dur=2, mverb=0.85, mverbfreeze=1, amp=0.4)
b1 >> play(x..., amp=0.6)
#@goto(dropA, 0.5) # 50% back into the drop, else resolve
#@outro(16)
~p1 >> bell([0, 4, 7, 11], oct=5, dur=1, reverb=0.6, room=0.9, lpf=linvar([5000, 600], [16]), amp=linvar([0.5, 0], [16]))
#@end(8)
Start here
Boot audio first (the boot button). Put the cursor on a line and press Ctrl+Enter to run it; Ctrl+Alt+Enter runs the whole block. Edit and re-run live. Ctrl+; stops everything (or Ctrl/Cmd+. from anywhere). Click any code box below to copy it.
Ctrl+Space autocompletes (synths, params, FX, patterns). Alt+I shows info on the symbol under the cursor — and for a pattern it evaluates and shows the values it makes.
Clock.bpm = 120
Scale.default = "minor"
Root.default = 0
Drums — play()
Chars map to samples. . or space = rest. Brackets: (Xo) together · [Xo] subdivide · {Xo} random · <Xo> alternate.
b1 >> play(x.o., amp=0.9) # kick / snare
b2 >> play(x-o-, amp=0.9) # - = closed hihat
b3 >> play(x.[oo]x.<o->, amp=0.8) # subdivide + alternate
b4 >> play((x*)..{o-}.., amp=0.8) # together + random
b5 >> play(<x.><[--]><x.>, amp=0.8) # brackets nest
b6 >> play(x-o-, lpf=1500, reverb=0.3) # FX work on drums
b7 >> play(x-o-).sometimes("stutter", 2) # probabilistic
pbuild(genre) generates a genre drum pattern as a play() string. Genres: techno · ebm · dnb · house · breaks · halftime · industrial · reggae · afro. evolve = bars before it loops (each a mutation, so the groove drifts) · fill every N bars · density 0–1 thins hits · mute a layer · per-layer access via pkit().
b1 >> play(pbuild("techno"), dur=0.25)
b1 >> play(pbuild("house", evolve=8, fill=4, density=0.8), dur=0.25)
b1 >> play(pbuild(0, 16), dur=0.25) # genre by index + evolve=16
b1 >> play(pbuild("dnb", snare=PBin(4), hat=<1,0>), dur=0.25) # gate layers per bar
b1 >> play(pbuild("house", snare=0, fill={4,2}), dur=0.25) # cut snare; random fills
kit = pkit("breaks") # per-layer access
b1 >> play(kit.kick, dur=0.25)
h1 >> play(kit.hat, dur=0.25)
.drummer() turns a play() player into a self-evolving rock drummer (FoxDot/CrashServer port): it picks a random groove + fill, drops the fill in at the end of each loop, then re-randomises every durloop beats (default 16). Step dur defaults to 0.5.
b1 >> play("x").drummer() # auto rock drummer
b1 >> play("x").drummer(8, 0.25) # 8-beat loop, 1/16 steps
b1 >> play("x", sample=2).drummer().solo() # chains with solo/stop
All synths
Degree arrays are scale steps. Each synth's extra params are shown filled in with their defaults. Newest: plaits.
A playing player inherits its params on re-run — p1 >> saw([0,4], dur=4) then p1 >> saw([0,4], oct=6) keeps dur=4. Prefix ~ to reset to defaults: ~p1 >> saw([0,4]).
p1 >> dbass([0, 4, 7, 4], oct=5, amp=0.6, cutoff=2000, rq=0.5, phase=0.9)
p2 >> a_gesa([0, 4, 7, 4], oct=5, amp=0.6, distortion=8, cutoff=800, resonance=0.7)
p3 >> a_daft([0, 4, 7, 4], oct=5, amp=0.6, cutoff=300, resonance=0.8, punch=1.2)
p4 >> a_hhat([0, 4, 7, 4], oct=5, amp=0.6, tone=8000, decay=0.1, metallic=1, distortion=2, open=0)
p5 >> pumpbass([0, 4, 7, 4], oct=5, amp=0.6, cutoff=800, res=0.4, sub=0.3, body=4, growl=0.2, fuzz=0, fuzzgain=1.5, noiz=0, noizr=1, noizt=0.5, hpr=0, pump=1)
p6 >> saw([0, 4, 7, 4], oct=5, amp=0.6, cutoff=8000, rq=0.8, rate=0.5)
p7 >> sine([0, 4, 7, 4], oct=5, amp=0.6, cutoff=2800, rq=0.8, rate=0.1)
p8 >> rsin([0, 4, 7, 4], oct=5, amp=0.6, cutoff=2000, rq=0.1, feedback=0)
p9 >> donk([0, 4, 7, 4], oct=5, amp=0.6)
p10 >> pluck([0, 4, 7, 4], oct=5, amp=0.6, cutoff=8000, rq=0.7)
p11 >> pulse([0, 4, 7, 4], oct=5, amp=0.6, cutoff=6000, rq=0.8, width=0.5)
p12 >> blip([0, 4, 7, 4], oct=5, amp=0.6, cutoff=12000, rq=0.6, rate=4)
p13 >> fm([0, 4, 7, 4], oct=5, amp=0.6, ratio=2, index=5, cutoff=8000, rq=0.8)
p14 >> bell([0, 4, 7, 4], oct=5, amp=0.6, rate=1)
p15 >> pads([0, 4, 7, 4], oct=5, amp=0.6, cutoff=1200, rq=0.6)
p16 >> bass([0, 4, 7, 4], oct=5, amp=0.6, cutoff=2000, rq=0.6)
p17 >> prophet([0, 4, 7, 4], oct=5, amp=0.6, cutoff=3000, rq=0.4)
p18 >> plaits([0, 4, 7, 4], oct=5, amp=0.6, engine=0, timbre=0.5, harm=0.5, morph=0.5, cutoff=6000, rq=0.6)
p19 >> tb303([0, 4, 7, 4], oct=5, amp=0.6, cutoff=500, rq=0.3, env=2, wave=0, dist=0)
p20 >> choir([0, 4, 7, 4], oct=5, amp=0.6, vox=0, cutoff=4000)
p21 >> brass([0, 4, 7, 4], oct=5, amp=0.6, cutoff=2000, rq=0.4, bright=0.5)
p22 >> organ([0, 4, 7, 4], oct=5, amp=0.6, cutoff=6000, perc=0)
p23 >> ssaw([0, 4, 7, 4], oct=5, amp=0.6, cutoff=4000, rq=0.6, detune=0.5)
p24 >> karp([0, 4, 7, 4], oct=5, amp=0.6, cutoff=6000)
p25 >> piano([0, 4, 7, 4], oct=5, amp=0.6, tone=0.5)
p26 >> ebass([0, 4, 7, 4], oct=5, amp=0.6, pick=0.414, rq=0.5, cutoff=250, decay=0.01)
p27 >> faim([0, 4, 7, 4], oct=5, amp=0.6, decay=0.01, beef=0, rate=0.01, level=0.8, peak=1)
p28 >> guit([0, 4, 7, 4], oct=5, amp=0.6, decay=0.01, detune=0.01, tone=0.7, beef=0.7, fdecay=1, mod=0.2)
p29 >> lapin([0, 4, 7, 4], oct=5, amp=0.6, decay=0.01, rate=1, level=0.8, peak=1)
p30 >> acidbass([0, 4, 7, 4], oct=5, amp=0.6, decay=0.4, rate=4, width=0.51, rq=0.4)
p31 >> hoover([0, 4, 7, 4], oct=5, amp=0.6, decay=0.2, level=0.8, peak=1, porta=1, portadur=0.125)
p32 >> cs80([0, 4, 7, 4], oct=5, amp=0.6, fatk=0.75, fdec=0.5, fsus=0.8, frel=1, cutoff=2200, detune=0.002, vibspeed=4, vibdepth=0.015)
p33 >> moogpluck([0, 4, 7, 4], oct=5, amp=0.6, pluck_filter=4, pluck_mix=0.8, rate=1)
p34 >> compkick([0, 4, 7, 4], oct=5, amp=0.6, punch=0.7, comp=8, click=0.4, crunch=1.5, sub=1, body=0.6, tone=0.3)
plaits — a multi-engine macro-oscillator (stock-UGen take on Mutable's Plaits). engine 0-7 picks the model: 0 virtual-analog · 1 FM · 2 wavefold · 3 harmonic · 4 wavetable · 5 noise · 6 string · 7 modal. timbre/harm/morph are the three voice controls. Morph the engine live with a var.
p1 >> plaits([0,4,7], oct=4, engine=0, timbre=0.6) # virtual analog
p1 >> plaits([0,4,7], oct=5, engine=1, harm=0.3, timbre=0.7) # FM
p1 >> plaits([0,4,7], oct=4, engine=var([0,1,4,6], 8), timbre=sinvar([0.2,0.9],[8])) # morph engines
tb303 — acid bass: a resonant filter swept by a per-note envelope (env = mod amount, rq = resonance, dist = drive). Pairs beautifully with PArp arpeggios, and tweak cutoff live with attribute assignment.
p1 >> tb303(PArp([0,3,7], 5), oct=3, cutoff=300, rq=0.15, env=4, dur=1/4, dist=0.3)
p1.cutoff = linvar([200, 3000], 16) # ride the filter live
p1.env = PStep(8, 6, 2)
choir — formant vowel pad (vowel 0/1/2 = ah/eh/oh). brass — filtered saw with a per-note "blat" (bright scales the sweep). Both love long chords + reverb; add vibrato for life.
ch >> choir([(0,3,5),(6,1,3),(5,0,3)], oct=4, dur=4, attack=2, vowel=0, amp=0.4, reverb=0.9, room=0.95)
br >> brass([0,3,5,7,5,3,7,5], oct=5, dur=1/2, bright=0.7, amp=0.4, reverb=0.5, room=0.6)
vc >> prophet([0,5,3,4], oct=4, dur=2, vibrato=0.6, vib_rate=5, vib_depth=0.01, reverb=0.8, room=0.9) # strings w/ vibrato
Live tweaking
Run this, then put the cursor ON the 2000 and press Alt+Up / Alt+Down — cutoff changes live (±1, or ±0.1 on decimals; Shift+Alt for ×10). The current line re-runs automatically so you hear it instantly.
p1 >> saw([0,4,7], oct=4, cutoff=2000, amp=0.5)
Alt+I on a name shows what it is — for a pattern it shows the values it makes. Try Alt+I on Pacc below. Ctrl+Space anywhere autocompletes (pick a synth = full call, pick an FX = all its params).
b1 >> play(x.x.x.x., amp=Pacc("ghost"))
Re-run a player and only change one thing — the rest is kept. Run the first line, then the second: the lpf stays.
p2 >> bass([0,-3], oct=3, lpf=900)
p2 >> bass([0,-3,5,4])
Or tweak one attribute of a running player without re-stating the line — player.attr = value (the bank's core live-coding move):
p2 >> tb303([0,3,5,7], oct=3, cutoff=400, rq=0.2, env=3).every(8, "reverse")
p2.cutoff = linvar([300, 4000], 16) # sweep the filter live
p2.dur = 1/4 # tighten the rhythm
p2.env = PArp([0,3,7], 5)
Master / global FX over the whole mix — for live drops & risers. Server.clearFx() resets it.
Server.addFx(lpf=600, mverb=0.4) # global filter + space
Master().lpf = 4000 # ride it back open
Server.clearFx()
Patterns & time
Sequences, chords & groups
[a,b,c] = a per-step sequence. (a,b,c) = a chord/group fired together — also works on any param (zipped across voices). . = rest.
p1 >> saw([0, (0,4,7), 4, (2,5,9)], oct=4) # chord on steps 2 & 4
p1 >> dbass((0,4,7), oct=4) # a held chord
p1 >> saw([0,4,7], pan=(-1,1), amp=(0.6,0.3)) # grouped params zip into voices
p1 >> sine([0, ., 4, .], oct=5) # . = rest
p1 >> saw(<0 4 7>, dur=<1 2>) # <..> alternates each time it's reached
Probability modifiers
Roll a chance each step and apply a player method. Aliases by likelihood: always(1) · almostAlways(.9) · often(.7) · sometimes(.5) · rarely(.25) · almostNever(.1) · never(0). A leading number overrides the chance. Trailing kwargs temporarily change params for that trigger. Chain several — each rolls on its own.
p1 >> saw([0,4,7,5], oct=4).sometimes("stutter", 4)
p1 >> dbass([0,-3], oct=4).often(0.8, "reverse")
b1 >> play(x-o-).rarely("stutter", 2, rate=2, amp=0.6) # kwargs override
b1 >> play(x.o.).often("stutter", 2).sometimes("stutter", 8) # chained
Time-varying values (var)
Evolve a parameter over beats. var steps; linvar/sinvar/expvar interpolate. Args: (values, durations-in-beats).
p1 >> dbass([0,-3,0,4], oct=4, cutoff=linvar([400, 4000], [8, 8]))
p1 >> saw([0,4,7], cutoff=sinvar([500, 5000], [4]))
p1 >> pulse([0,3], width=var([0.2, 0.5, 0.8], [2, 2, 4]))
p1 >> fm([0,7], index=expvar([1, 12], [16]))
Parameter envelopes (_)
A _ suffix runs an envelope per note. fi fade in, fo fade out, fb bounce/wobble. Signature: f(beats, from, to). FX-chain params only (lpf, hpf, reverb, echo, crush…).
p1 >> saw([0,4], oct=4, dur=1, lpf_=fi(0.5, 400, 5000)) # filter opens
p1 >> saw([0,3], oct=3, dur=1, lpf_=fo(1, 5000, 400)) # filter closes
p1 >> saw([0,3], oct=3, dur=1, lpf_=fb(0.25, 300, 3000)) # wobble
Patterns
Pattern objects produce a new value each step. P shorthands: P*[a,b,c] random pick · P[a,b,c] cyclic list · P(a,b,c) chord. Inline in a list: (a,b) = chord, {a,b} = random pick. TimeVars can hold patterns.
p1 >> saw([0,2,4,7], amp=PWhite(0.4, 0.9)) # random float
p1 >> pluck([0,4,7], oct=PRand(4, 6)) # random int
p1 >> sine(PRange(0, 7), dur=0.5) # 0..7 ramp
p1 >> saw([0, 3, 5, P*[7,10,5]], oct=4) # P*[...] = random pick
v1 >> dbass([0, 2, (4, 2), {2, 4}]) # (chord) + {random pick}
p1 >> saw([0,4,7], dur=var([P*[1,2], 1/4])) # pattern inside a var
p1 >> saw([0,4,7], oct=4) + 7 # transpose up
p1 >> dbass([0,3,5]) + (0,3,7) # + a group = chord
p1 >> saw([0,4,7], oct=4).unison(4, 0.4) # 4 detuned voices, spread
b1 >> play(x., amp=Pacc("ghost")) # accent pattern
b2 >> play(x-o-, amplify=PLife(0.5)) # cellular-automaton amp
Freeze a generator with a slice — [:N] samples N values once and loops them, so a random source becomes a stable N-step phrase that repeats (instead of a fresh value every step). Works on melody(), PWhite, any pattern.
d1 >> dbass(melody()[:8], dur=1/2) # an 8-note melody, looped
p1 >> saw([0,4,7], amp=PWhite(0.3, 1)[:8]) # 8 fixed random amps, repeating
p1 >> saw(PRange(0, 12)[:4], oct=5) # first 4 of a ramp
Grooves & accents
Accent / density patterns shape amp & feel. Pacc templates (ghost, tresillo, offbeat…), PSwing shuffle, PFDur Euclidean density, PLife generative. Drop them on amp or amplify (a per-step multiplier).
b1 >> play(x., amp=Pacc("tresillo")) # 3+3+2 accents
b2 >> play(-, amp=Pacc("ghost", 8)) # ghost-note hats
b3 >> play(x-o-, amplify=PSwing(0.3)) # swing feel
b4 >> play(x..x..x., amplify=PFDur((3,8),(5,8))) # layered density
b5 >> play(o., amplify=PLife(0.6)) # generative accents
Feel: delay nudges a note off the grid (in beats) — micro-timing without changing the pattern. .human(velocity, humanize, swing) does it for you: random velocity + timing jitter, with optional swing %.
b1 >> play(x-o-, delay=[0, 0.04]) # push the off-beats late
b2 >> play(x-o-).human(30, 8, 10) # humanise: vel 30, jitter 8%, swing 10%
p1 >> saw([0,4,7], dur=0.5).human(20, 6) # human feel on a synth too
b3 >> play(PEuclid2(3, 8, ".", "x")) # euclid rhythm as play chars → "..x..x.x"
Sync, timed solo/stop & chaos
Quantised start. A new player begins on the next beat that is a multiple of its dur: dur=4 waits for a bar, dur=1/4 starts almost at once — so everything stays in sync. Run the kick, then the pads a beat later: the pads snap onto the bar.
b1 >> play(x.x.x.x., amp=0.7)
p1 >> pads([0, (0,4,7), 5], oct=4, dur=4, lpf=1200, amp=0.5) # lands on the next bar
Timed solo / only / stop — chain them right on the player, or call as methods. The number is grid-aligned (the next multiple of N), like the dur grid.
p1 >> prophet([0,4,7], oct=5, dur=1/2).solo(8) # solo 8 beats, then restore
p2 >> blip([0,4,7,5], oct=6, dur=1/4).only(8) # at the next mult of 8, stop the others
b1 >> play(x-x-, amp=0.8).stop(16) # stop on the next mult of 16
p1.soloDrop(8) # solo-drop 8 beats (method form)
chaos(n) — generate n random player lines into g1, g2, … (kept apart from your own) and paste them into the editor as a block — it does not run them. Review/edit, then evaluate (Ctrl+Alt+↵). chaos(3, "drum") or chaos(2, "synth") forces one kind; re-run to add more.
chaos(4) # 4 random players (synth + drum mix)
chaos(2, "synth") # 2 random melodic/bass players
chaos(3, "drum") # 3 random drum patterns
Sound design
FX — append to any player
FX run on a persistent per-player chain. Combine freely — on synths AND on play() drums. Available: lpf hpf crush reverb mverb cheapverb resonbank rgate chorus tremolo tanh echo fbdelay shape dist2 chop multicrush vibrato ringmod flanger phaser formant. (And leg scales note length: leg>1 overlaps, leg<1 staccato.)
p1 >> saw([0,4,7], lpf=2000, lpf_rq=0.3) # low-pass
p1 >> saw([0,4,7], hpf=300, reverb=0.4, room=0.8) # high-pass + reverb
p1 >> saw([0,4,7], echo=0.4, echo_time=0.375) # delay
p1 >> dbass([0,-3], crush=0.6, bits=4, srate=6000) # bitcrush
p1 >> prophet([0,4,7], chorus=0.6, chorus_rate=0.5)# chorus
b1 >> play(x.o., resonbank=0.3, rbfreq=[47,50,62]) # resonator bank
b2 >> play(x-o-, rgate=0.8, rgaterate=8) # rhythmic gate
b3 >> play(x.o., mverb=0.6, mverbfreeze=1) # frozen reverb
b4 >> play(x-o-, tremolo=0.8, trem_rate=8) # tremolo
p1 >> blip([0,4,7], dur=1/2, fbdelay=0.5, fbtime=0.25, fbfeed=0.7, fbspread=0.3) # ping-pong feedback delay
p1 >> saw([0,4,7], oct=3, shape=0.6) # sine wavefolder
p1 >> dbass([0,-3], oct=4, dist2=0.7, dist2shape=0.4) # fold + tanh saturation
p1 >> dbass([0,-3], oct=4, multicrush=0.7, mchighdrive=5) # 3-band multiband drive
p1 >> saw([0,4,7], dur=1/2, chop=4) # rhythmic gate, 4 slices/beat
p1 >> pads([0,4,7], oct=4, dur=1, leg=4) # legato — notes overlap (pad)
p1 >> prophet([0,4,7], dur=2, vibrato=0.6, vib_rate=5, vib_depth=0.01) # pitch vibrato
p1 >> ssaw([0,4,7], oct=4, ringmod=0.5, ringmod_freq=180) # ring mod
p1 >> ssaw([0,4,7], oct=4, flanger=0.6, flanger_rate=0.3) # flanger
p1 >> ssaw([0,4,7], oct=4, phaser=0.7, phaser_rate=0.4) # phaser
p1 >> saw([0,4,7], oct=4, formant=0.8, formant_vowel=1) # vowel filter (eh)
Define synths live — defsynth()
Build a SynthDef in the browser (no SuperCollider needed) and play it like a built-in. The build fn gets the standard controls (out, note, amp, sus, pan, attack, release) + your extras, each a UGen. Convert pitch with note.midicps(); end with Out.ar(out, …); use doneAction:2 to free the voice. Run the defsynth block once, then play it.
defsynth("mylead", { cutoff: 2000, rq: 0.4 }, ({ out, note, amp, sus, pan, attack, release, cutoff, rq }) => {
const freq = note.midicps()
const env = EnvGen.ar(Env.linen(attack, sus, release), { doneAction: 2 })
const sig = RLPF.ar(Saw.ar(freq), cutoff, rq).mul(env).mul(amp)
Out.ar(out, Pan2.ar(sig, pan))
})
p1 >> mylead([0, 4, 7, 4], oct=4, cutoff=3000, dur=0.5)
UGens available: SinOsc Saw LFSaw Pulse VarSaw LFTri Blip Impulse, WhiteNoise PinkNoise LFNoise0/1/2, RLPF RHPF LPF HPF BPF, Line XLine, Pan2, Out, EnvGen + Env.perc/linen/triangle. Math: .mul .add .sub .div .midicps() .abs() .neg()
External samples — the webfoxdot-kit pack
Boot the default kit (the original FoxDot bank, hosted on our public webfoxdot-kit repo) with one loadpack. In multiplayer, put it at the top of the shared doc so every peer loads the same samples from the same URL.
⚠️ Firefox: sample loading can be unstable in Firefox (shared-memory WASM growth). Chrome / Chromium / Edge are recommended for sample-heavy or multiplayer sets.
# default kit via CDN (jsDelivr — fast, cached)
loadpack("https://cdn.jsdelivr.net/gh/CrashServer/webfoxdot-kit@v1/pack.json")
# …or straight from GitHub (raw) if the CDN is cold:
# loadpack("https://raw.githubusercontent.com/CrashServer/webfoxdot-kit/v1/pack.json")
b1 >> play(x-o-, amp=0.9)
b2 >> play(<X.><o.> [--], amp=0.7)
Or load a single WAV from any public URL and bind it to a play() char (use [urls] for sample-index slots):
loadsample("K", "https://raw.githubusercontent.com/USER/REPO/main/kick.wav")
b3 >> play(K.K.K.K.)
Audio loops ( loop )
loadloop(name, url) registers an audio loop under a multi-character name; b1 >> loop("name", dur=N) plays it time-stretched to lock to N beats (so it follows the tempo). opts: amp · pan · rate · sample · pos · stretch · looping. Loops share the sample buffer store, so the name just can't be a single play() character.
loadloop("break", "https://example.com/amen.wav") # register once
b1 >> loop("break", dur=8) # stretch to fit 8 beats (locks to tempo)
b1 >> loop("break", dur=8, rate=1.5) # faster playback (also raises pitch)
b1 >> loop("break", dur=4, pos=0.5) # start halfway in
b1 >> loop("break", dur=8, lpf=1200, mverb=0.3) # loops route through the FX chain
b1 >> loop("break", dur=8).every(8, "reverse") # player methods work too
Perform & MIDI
Section sequencer ( #@ )
Put the cursor on a #@ line and Ctrl+Enter. Sections auto-advance after their beat count. A commented player line (# p1 >>) stops that player on entry. #@#@ groups sections into a foldable track.
Branching: #@goto(target, prob) is a zero-length router — prob chance (default 0.5) to jump to target, else fall through to the next section. Chain them for a probabilistic set that never repeats. (#@loop(beats, a:2, b:1) still does a timed weighted-jump.) Keep part names unique — jumps resolve to the first match.
#@#@ my_set
#@intro(16)
p1 >> dbass([0,-3,0,4], oct=4)
b1 >> play(x.o.)
#@verse(32)
p1 >> dbass([0,-3,5,4], oct=4)
p2 >> pads([0,3,5], oct=4, dur=4, reverb=0.4)
# b1 >>
#@fill(4)
b1 >> play(<x.ox.> [xox] x.x., crush=0.5, bits=4)
#@goto(verse, 0.6) # 60% back to verse, else resolve
#@end(8)
MIDI — control in, notes out
Web MIDI (Chromium / Edge / Brave). Enables on the first midi() / mlearn() / midiout() call (the eval keypress is the required user gesture). Discover CC numbers and pick the output port in the MIDI panel (Alt+I sidebar).
Control in — a midi() value is sampled every step (like a TimeVar), so a knob sweeps any synth/FX param live. One CC can drive several params (a macro). Curves: lin, exp (cutoffs/freq), log, quad, cubic, sqrt, s (smoothstep).
p1 >> prophet([0, (0,4,7), 4], oct=5, dur=0.5)
p1.lpf = midi(74, 100, 8000, "exp") # CC74 knob → filter cutoff (exp)
p1.amp = midi(7, 0, 1) # CC7 fader → volume (linear)
p2 >> saw([0,4,7], room=midi(1, 0, 1, "s")) # mod-wheel → reverb size
p1.mverb = mlearn(0, 1) # MIDI learn: twist the NEXT control
Notes out — midiout() sends to an external/virtual MIDI port instead of making sound. Velocity follows amp, note length follows sus/leg, groups (0,4,7) make chords, and degree / +transpose / .every / .stutter / .sometimes all work like a synth. Notes are scheduled sub-beat-accurate, phase-locked to the internal synths. Route through a virtual port (IAC · loopMIDI · ALSA/JACK) to drive a DAW. Stop / Ctrl+. sends all-notes-off.
m1 >> midiout([0, 2, 4, 7], channel=1, oct=5, dur=0.5)
m2 >> midiout([0, (0,4,7), 5, (2,5,9)], channel=2, oct=4, dur=1, amp=0.9)
m1 >> midiout([0,2,4], channel=1, sus=2, leg=1.5) # long, overlapping notes
m1 >> midiout(P[0,3,5,7], channel=10).every(4, "reverse") # drum machine on ch10
# layer: hear it locally AND send it out
p1 >> prophet([0,4,7], oct=5, dur=0.5)
m1 >> midiout([0,4,7], channel=1, oct=5, dur=0.5)
Performance
p1.every(8, 'stutter', 4) # every 8 beats, stutter x4
p1.every(16, 'reverse') # reverse the degree array
p1.solo(8) # solo 8 beats then restore (beat-aligned)
p1.stop(16) # stop after 16 beats
soloRnd(8) # solo a random player for 8 beats
drop(14, 2) # bar-aligned drop, then restore
unsolo() # restore all
Shortcuts: Alt+S solo · Ctrl+Alt+S unsolo · Alt+O soloDrop(8) · Alt+X comment+stop the player at the cursor.