setup()
setup() runs once when the sketch starts. Here it does double duty: preparing the visual grid AND wiring up the entire Tone.js audio graph (synths -> reverb -> speakers) before any sound can play.
🔬 This nested loop builds the grid. What happens visually and sonically if you change `let note = random(scale);` to always use `scale[0]` instead of a random pick?
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
let x = i * cellSpacing + cellSpacing / 2;
let y = j * cellSpacing + cellSpacing / 2;
let note = random(scale);
cells.push(new Cell(x, y, note));
}
}
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(HSB, 360, 100, 100, 100); // Use HSB for easier color manipulation
noStroke(); // No borders for the circles
// --- Tone.js Setup ---
// Create a PolySynth with a sine wave oscillator for cell notes
synth = new Tone.PolySynth(Tone.Synth, {
oscillator: { type: "sine" },
envelope: { attack: 0.05, decay: 0.1, sustain: 0.5, release: 1 }
}).toDestination();
// Create a Reverb effect for ambience, connected to master output
reverb = new Tone.Reverb(2).toDestination();
// Connect the cell synth to the reverb
synth.connect(reverb);
// Initialize ambient drone synth
ambientDrone = new Tone.Synth({
oscillator: { type: "sawtooth" }, // Sawtooth wave for a richer, more complex drone sound
envelope: { attack: 4, decay: 2, sustain: 0.8, release: 8 } // Long attack/release for a smooth drone
}).connect(reverb); // Connect drone to reverb as well
// Start Tone.Transport for scheduling autonomous events
// Note: Tone.Transport will start, but audio won't play until Tone.context.resume() is called
Tone.Transport.start(); // https://tonejs.github.io/docs/14.7.77/Transport#start
// Initialize idle sequence for autonomous cell triggering
idleSequence = new Tone.Sequence((time, value) => {
if (value === 1 && mode === 'idle') {
triggerRandomCell();
}
}, [1, null, 1, null, null, 1, null, 1, null, null, 1, null, 1, null, null, 1], "8n");
idleSequence.loop = true;
idleSequence.loopEnd = "2m"; // Loop length of 2 measures
// --- Visuals Setup ---
cellSpacing = min(width, height) / gridSize;
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
let x = i * cellSpacing + cellSpacing / 2;
let y = j * cellSpacing + cellSpacing / 2;
let note = random(scale);
cells.push(new Cell(x, y, note));
}
}
// We no longer call switchMode('idle') here.
// The first user interaction will handle starting the sketch.
lastInteractionTime = millis(); // Initialize interaction time
}
Line-by-line explanation (14 lines)
🔧 Subcomponents:
synth = new Tone.PolySynth(Tone.Synth, { ... }).toDestination();
Creates the synth that plays a note whenever a cell finishes expanding, and routes it through a reverb effect
idleSequence = new Tone.Sequence((time, value) => { ... }, [1, null, 1, ...], "8n");
Defines a repeating rhythmic pattern of 1s and nulls that decides which 8th-notes trigger a random cell during idle mode
for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) { ... } }
Nested loop that walks through every row and column, calculates each cell's pixel position, assigns it a random note from the scale, and creates a Cell object
createCanvas(windowWidth, windowHeight);- Makes the canvas fill the entire browser window.
colorMode(HSB, 360, 100, 100, 100);- Switches p5's color system to Hue/Saturation/Brightness with ranges 0-360, 0-100, 0-100, 0-100 - much easier for cycling colors smoothly than RGB.
noStroke();- Turns off outlines so every circle drawn later is a clean filled shape with no border.
synth = new Tone.PolySynth(Tone.Synth, {...}).toDestination();- Creates a synth capable of playing several notes at once (poly = many voices) with a sine wave tone, and sends its sound straight to your speakers.
reverb = new Tone.Reverb(2).toDestination();- Creates a reverb effect with a 2-second decay tail and connects it to the speakers, giving sounds a spacious, echoey quality.
synth.connect(reverb);- Routes the cell synth's sound into the reverb before it reaches the speakers, so every note gets that ambient wash.
ambientDrone = new Tone.Synth({...}).connect(reverb);- Creates a second synth with a sawtooth wave and very long attack/release envelope, used only for the sustained background drone in idle mode.
Tone.Transport.start();- Starts Tone.js's internal clock, which is required for scheduled events like the idle Sequence to actually run.
idleSequence = new Tone.Sequence((time, value) => {...}, [...], "8n");- Builds a repeating 16-step pattern (playing every 8th note) where a '1' in the array triggers a random cell, as long as the sketch is currently in idle mode.
cellSpacing = min(width, height) / gridSize;- Divides the smaller of the canvas's width or height by gridSize to figure out how many pixels each grid cell should occupy.
for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) {...} }- Loops over every row (i) and column (j) to place one Cell at each grid position.
let note = random(scale);- Picks a random note from the pentatonic scale array for this specific cell to play whenever it's triggered.
cells.push(new Cell(x, y, note));- Creates a new Cell object at position (x, y) with its assigned note and adds it to the global cells array.
lastInteractionTime = millis();- Records the current time so the idle-timeout check in draw() has a valid starting point before the user has clicked anything.