Clock API
The clock object provides beat-synced timing and scheduling for JavaScript objects. It reads the global Transport in the main thread and workers.
Supported Objects
Use clock in js, worker, p5, and canvas. It is also available in three, textmode, hydra, and DOM variants.
Clock Properties
| Property | Type | Description |
|---|---|---|
clock.time |
number | Current time in seconds |
clock.ticks |
number | Current time in ticks (192 PPQ) |
clock.beat |
number | Current beat in measure (0 to beatsPerBar-1) |
clock.phase |
number | Position within current beat (0.0 to 1.0) |
clock.bpm |
number | Current tempo in BPM |
clock.isPlaying |
boolean | Whether the global transport is currently playing |
clock.bar |
number | Current bar (0-indexed) |
clock.beatsPerBar |
number | Beats per bar (default: 4) |
clock.timeSignature |
[number, number] | Time signature: [6, 8] is 6/8 |
Basic Usage
// Use clock.time for animations
const x = Math.sin(clock.time) * 100;
const y = Math.cos(clock.time * 0.5) * 50;
circle(width/2 + x, height/2 + y, 20);
// Use clock.phase for beat-synced pulsing
const pulse = 1 + clock.phase * 0.5;
circle(width/2, height/2, 50 * pulse);
// Use clock.beat to change on each beat
const colors = ['red', 'blue', 'green', 'yellow'];
fill(colors[clock.beat]);
// Gate animation while the transport is stopped
if (clock.isPlaying) {
circle(width / 2, height / 2, 40 + clock.phase * 20);
}
Control Methods
Use these methods to control the transport from your code:
| Method | Description |
|---|---|
clock.play() |
Start transport |
clock.pause() |
Pause transport |
clock.stop() |
Stop and reset to 0 |
clock.setBpm(bpm) |
Set tempo |
clock.setTimeSignature(n, d) |
Set time signature (e.g., 6, 8 for 6/8) |
clock.seek(seconds) |
Seek to time in seconds |
Play State Events
Use clock.onPlayStateChange() to respond to play, pause, or stop events. This avoids checking clock.isPlaying in each frame.
const id = clock.onPlayStateChange((state, time) => {
if (state === 'playing') {
send({ type: 'started', time });
}
if (state === 'paused') {
send({ type: 'paused', time });
}
if (state === 'stopped') {
send({ type: 'reset' });
}
});
// Remove the listener later if you no longer need it
clock.cancel(id);
Transport Control Example
// React to messages
recv((m) => {
if (m === 'go') {
clock.setBpm(140);
clock.play();
}
if (m === 'drop') {
clock.setBpm(70); // half-time feel
}
});
// Auto-start on load
clock.play();
Time Signature Example
// Set 3/4 time (3 quarter-note beats per bar)
clock.setTimeSignature(3, 4);
// Set 6/8 time (6 eighth-note beats per bar)
clock.setTimeSignature(6, 8);
// Now clock.beat cycles 0, 1, 2, 0, 1, 2...
clock.onBeat(0, () => kick()); // downbeat of each bar
clock.onBeat(2, () => snare()); // beat 3 of each bar
Subdivision Methods
Patchies computes subdivisions per node. Different nodes can use different subdivisions at the same time, such as triplets and quintuplets.
| Method | Return | Description |
|---|---|---|
clock.subdiv(n) |
number | Current subdivision index (0 to n-1) within the beat |
clock.subdivPhase(n) |
number | Progress within current subdivision (0.0 to 1.0) |
Quintuplets Example
// Each node picks its own subdivision count — no global state
clock.subdiv(5); // → 0, 1, 2, 3, 4 within each beat
clock.subdiv(3); // → 0, 1, 2 (triplets, can run simultaneously)
clock.subdiv(4); // → 0, 1, 2, 3 (sixteenths)
Polyrhythmic Patterns
// Node A uses triplets, Node B uses quintuplets — at the same time
const triAngle = clock.subdiv(3) / 3 * TAU;
const quintAngle = clock.subdiv(5) / 5 * TAU;
Animate on Subdivision Phase
// Pulse that breathes once per sixteenth note
const t = clock.subdivPhase(4);
const radius = 50 + 20 * Math.sin(t * Math.PI);
circle(width/2, height/2, radius);
Rhythmic Color Changes
const colors = ['red', 'orange', 'yellow', 'green', 'blue'];
fill(colors[clock.subdiv(5)]);
Scheduling Methods
Use these scheduling methods instead of tracking beat changes yourself. Each scheduling callback receives a time argument with the precise event transport time.
By default, callbacks fire after the event. This works well for visuals. Pass { audio: true } as the last argument for lookahead scheduling.
Lookahead callbacks fire about 100 ms early. Use their precise time for Web Audio API scheduling. Audio onBeat and every callbacks can also receive eventClock as a second argument.
eventClock contains the scheduled event time, beat, and phase. It does not contain values from the current poll.
onBeat
Use onBeat to respond when a specified beat occurs.
// Fire on specific beat (0 to beatsPerBar-1)
clock.onBeat(0, () => kick()); // downbeat
clock.onBeat(2, () => snare()); // beat 3
// Fire on multiple beats
clock.onBeat([0, 2], () => snare()); // beats 1 and 3
// Fire on every beat
clock.onBeat('*', () => hihat());
// Audio-precise scheduling — fires early with precise time
clock.onBeat(0, (time, eventClock) => {
oscillator.start(time);
// eventClock.beat and eventClock.phase describe the future beat
send({ type: 'downbeat', beat: eventClock.beat, time: eventClock.time });
}, { audio: true });
schedule
Use schedule to run one callback at a specific time.
The bar:beat:sixteenth notation uses zero-based indexes, like Tone.js. '0:0:0' is the start. '1:0:0' is one bar from the start.
DAWs such as Ableton use 1.1.1 for the start.
// Absolute time in seconds
clock.schedule(clock.time + 2, () => drop());
// Bar:beat:sixteenth notation (zero-indexed)
clock.schedule('4:0:0', () => breakdown()); // 4 bars from start
clock.schedule('8:2:0', () => buildUp()); // 8 bars + 2 beats from start
// Audio-precise — fires early with precise time
clock.schedule('4:0:0', (time) => {
send({ type: 'set', value: 880, time });
}, { audio: true });
every
Use every to run a callback at a musical interval.
// Bar:beat:sixteenth interval
clock.every('1:0:0', () => flash()); // every bar
clock.every('0:1:0', () => pulse()); // every beat
clock.every('0:0:1', () => tick()); // every sixteenth
// Audio-precise repeating — fires early with grid-aligned time
clock.every('0:1:0', (time, eventClock) => {
send({
type: 'trigger',
values: { peak: 1, sustain: 0.7 },
attack: 0.01,
decay: 0.1,
beat: eventClock.beat,
time
});
}, { audio: true });
setTimelineStyle
Patchies shows all onBeat, schedule, and every callbacks in the Timeline Viewer when it is open. Each node gets a unique color.
The viewer shows markers for upcoming events. It also flashes when an event occurs.
Use setTimelineStyle to change how this node appears in the Timeline Viewer.
// Set a custom color for this node in the timeline
clock.setTimelineStyle({ color: '#ff6b6b' });
// Hide this node from the timeline entirely
clock.setTimelineStyle({ visible: false });
// Show again with a specific color
clock.setTimelineStyle({ color: '#4af', visible: true });
| Option | Type | Description |
|---|---|---|
color |
string | CSS color for this node's markers and label |
visible |
boolean | Whether this node appears in the timeline (default: true) |
cancel
Use cancel to remove scheduled callbacks.
// Cancel specific callback
const id = clock.onBeat(0, () => flash());
clock.cancel(id);
// Cancel all callbacks (automatic on code change)
clock.cancelAll();
Examples
Beat Visualization
// Flash background on downbeat
clock.onBeat(0, () => {
background(255);
setTimeout(() => background(0), 100);
});
// Pulse circle on every beat
function draw() {
const size = 100 + clock.phase * 50;
circle(width / 2, height / 2, size);
}
Scheduled Transitions
// Build-up and drop
clock.schedule('4:0:0', () => setMode('intense'));
clock.schedule('8:0:0', () => setMode('drop'));
Repeating Patterns
// Color cycle every 4 bars
let colorIndex = 0;
const colors = ['red', 'blue', 'green', 'yellow'];
clock.every('4:0:0', () => {
setColor(colors[colorIndex++ % colors.length]);
});
Manual Beat Detection
Use manual beat detection when you need direct control:
let lastBeat = -1;
function draw() {
if (clock.beat !== lastBeat) {
// Beat changed!
if (clock.beat === 0) flash();
lastBeat = clock.beat;
}
}
Seek to Bar
// Jump to bar 8
const secondsPerBar = (60 / clock.bpm) * clock.beatsPerBar;
clock.seek(8 * secondsPerBar);
Audio-Rate Beat Sync
For continuous beat-synced signals in the audio graph, use beat~. clock.schedule fires discrete callbacks. beat~ outputs a sample-by-sample 0→1 sawtooth ramp synchronized to the transport BPM.
beat~ → ramp 0→1 once per beat
beat~ 4 → ramp 0→1 four times per beat (16th notes)
beat~ 0.25 → ramp 0→1 once per bar (in 4/4)
The multiply parameter sets the beat frequency. 1 is once per beat by default. 2 is eighth notes, 4 is sixteenth notes, and 0.25 is once per bar.
The parameter is an AudioParam. Other signals can modulate it.
The output follows transport play, pause, and stop. It freezes on pause and resets on stop.
Tone.js Scheduling
When you use Tone.js, you can schedule events with Tone.js Transport. Use this with Tone.js synths and effects that connect directly to Tone.getTransport().
// In a tone~ object — Tone.js Transport is synced to the global transport
const synth = new Tone.Synth().connect(outputNode);
Tone.getTransport().scheduleRepeat((time) => {
synth.triggerAttackRelease('C4', '8n', time);
}, '4n');
Tone.getTransport().schedule((time) => {
synth.triggerAttackRelease('E4', '2n', time);
}, '4:0:0');
Always use the time callback argument (not Tone.now()) for sample-accurate timing.
Choose a Scheduling Method
| Approach | Best for |
|---|---|
clock.onBeat / every / schedule |
Visual sync — fires after event (~25ms precision) |
Any method + { audio: true } |
Audio scheduling — fires early with precise time arg |
beat~ |
Continuous audio-rate modulation (tremolo, FM, waveshaping) |
tone~ + Tone.Transport |
Scheduling with Tone.js synths/effects |
Scheduling Audio Parameters
Pass { audio: true } to use lookahead scheduling with parameter automation messages. Then pass the callback time argument to set, trigger, and release messages.
Patchies uses this value as an absolute time by default. It lets you automate audio parameters, such as gain~, osc~, and filters, with beat-synced sample-accurate timing:
// Trigger an envelope on every downbeat
clock.onBeat(0, (time) => {
send({
type: 'trigger',
values: { peak: 1, sustain: 0.7 },
attack: 0.01,
decay: 0.1,
time
});
}, { audio: true });
// Schedule a filter sweep at bar 4
clock.schedule('4:0:0', (time) => {
send({ type: 'set', value: 2000, time });
}, { audio: true });
// Repeating audio-precise scheduling every beat
clock.every('0:1:0', (time) => {
send({
type: 'trigger',
values: { peak: 1, sustain: 0.7 },
attack: 0.01,
decay: 0.1,
time
});
}, { audio: true });
Precision
All scheduling methods (onBeat, schedule, every) poll every ~25ms.
Default (visual): Callbacks fire after the event. They are accurate to about 25 ms, which suits visual sync.
{ audio: true }: Callbacks fire before the event in a 100 ms lookahead window. The time argument contains the precise event transport time.
In worker environments, such as worker and canvas, all scheduling uses frame-based polling. At 60 fps, polling occurs about every 16 ms.
For continuous audio-rate signals, use beat~. For Tone.js integration, use tone~.
Hydra
In Hydra, you can use either clock.time or the bare time variable:
// Both work in Hydra
osc(10, 0.1, () => clock.time)
osc(10, 0.1, () => time) // shorthand
See Also
- beat — Outputs current beat as a message on each beat change
- Parameter Automation — Automate audio parameters with sample-accurate timing
- Transport Control — Play/pause, BPM, time display
- JavaScript Runner — Full JSRunner documentation
- Audio Reactivity — Using FFT data in visuals