Oscilating Sound WebGL

This sketch creates a mesmerizing 3D WebGL visualization featuring an orbiting sphere and rotating torus, synchronized with a Tone.js synthesizer. The pitch of the sound follows the sphere's vertical position, while horizontal mouse movement controls the orbit size and colors.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the waveform from sine to square — A square wave sounds harsh and buzzy compared to the smooth sine wave—instantly changes the timbre of the entire piece
  2. Make the sphere trace a perfect circle instead of an ellipse — Setting both sin multipliers to the same value forces a circular Lissajous pattern—the pitch sweep becomes more regular and predictable
  3. Reverse the pitch mapping so high = low and low = high — The sphere pitch now inverts: touching the bottom makes high notes and the top makes low notes—counterintuitive but musically interesting
  4. Speed up the torus rotation — Multiplying the rotation angles by 2 makes the torus spin twice as fast—creates more visual energy without affecting sound
  5. Make the background brighter and more colorful — Increasing saturation from 60% to 100% and brightness from 15% to 35% makes the background glow more vividly as pitch changes
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch blends 3D graphics, real-time audio synthesis, and interactive controls into a single meditative experience. You drag to orbit a camera around a glowing sphere that traces a three-dimensional Lissajous curve, while a rotating torus sits at the center. As the sphere moves up and down, its vertical position is mapped directly to the pitch of a sine wave synthesizer—making the visual motion audible in real time. The colors shift as you move your mouse horizontally, changing both the hue palette and the sphere's orbital path.

The code is organized into five main functions: setup() initializes the WEBGL canvas and Tone.js audio engine, draw() runs every frame to update the 3D scene and audio frequency, drawHUD() renders text instructions overlaid on the 3D scene, and two event handlers (mousePressed and touchStarted) manage browser audio policies and muting. By studying it you will learn how to combine p5.js 3D transforms with real-time audio synthesis, map interactive inputs to multiple visual and sonic parameters, and manage the browser's strict autoplay requirements.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen WEBGL canvas, enables HSB color mode (which makes hue-based color shifts intuitive), and initializes variables for the oscillator and audio state.
  2. The draw() function runs 60 times per second. It uses millis() to calculate a smooth time variable t, which drives the sphere's position along a Lissajous curve with different frequencies in x, y, and z.
  3. The sphere's vertical (y) position is mapped to a frequency between 200 Hz and 800 Hz using the map() function. This frequency is sent to the Tone.js oscillator with a smooth 50 ms glide, making pitch changes feel musical rather than robotic.
  4. Horizontal mouse position (mouseX) controls both the orbit radius and the hue of the torus ring and sphere—moving right expands the orbit and shifts colors across the spectrum.
  5. The torus rotates continuously at its own speed, with its color linked to mouseX so it always complements the orbiting sphere's hue using a 60-degree offset.
  6. Text instructions are drawn as a 2D overlay using drawHUD(), which uses resetMatrix() to convert WEBGL's center-origin coordinate system back to top-left for readable UI placement.

🎓 Concepts You'll Learn

WEBGL 3D renderingTone.js audio synthesisLissajous curvesParameter mapping with map()Camera control with orbitControl()HSB color modeMatrix transformations (push/pop, translate, rotate)Browser autoplay policy

📝 Code Breakdown

setup()

setup() runs once when the sketch loads. It establishes the canvas size, color space, and default styles for all subsequent frames. WEBGL mode is required for 3D shapes like sphere() and torus().

function setup() {
  // WEBGL mode: https://p5js.org/reference/#/p5/createCanvas
  createCanvas(windowWidth, windowHeight, WEBGL);
  pixelDensity(1); // mobile-friendly: keeps GPU/CPU load saner

  colorMode(HSB, 360, 100, 100, 100);

  // Use browser default sans-serif font for HUD.
  // We do NOT call loadFont(), to avoid OpenType parsing errors.
  textFont("sans-serif");
  textAlign(CENTER, CENTER);
  textSize(16);

  noStroke();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

initialization WEBGL Canvas Setup createCanvas(windowWidth, windowHeight, WEBGL);

Creates a full-screen canvas in 3D WEBGL mode with dynamic window sizing

initialization HSB Color Mode colorMode(HSB, 360, 100, 100, 100);

Switches to Hue-Saturation-Brightness color space, making hue-based animations intuitive

createCanvas(windowWidth, windowHeight, WEBGL);
Creates a full-screen 3D canvas. The third argument WEBGL enables 3D rendering instead of 2D.
pixelDensity(1);
Sets pixel density to 1 for mobile performance—high-DPI devices won't render at 2x resolution, saving GPU power
colorMode(HSB, 360, 100, 100, 100);
Switches to HSB (Hue, Saturation, Brightness) where colors are defined by hue (0-360 degrees), saturation (0-100%), and brightness (0-100%). This makes rotating through colors feel more natural than RGB.
textFont("sans-serif");
Sets the font for text rendered in WEBGL to the browser's default sans-serif—avoiding font loading errors
textAlign(CENTER, CENTER);
Centers text both horizontally and vertically around its position—useful for overlay instructions
textSize(16);
Sets the default text size to 16 pixels
noStroke();
Disables outlines on all 3D shapes—only fills will be visible

draw()

draw() runs 60 times per second. It is the engine of animation: each frame recalculates positions, updates audio, and redraws everything. The key insight is that tiny position changes, repeated 60 times per second, create the illusion of smooth motion. This is how all animation works.

🔬 These three lines make the Lissajous curve. What if you change the multipliers (1.15 and 0.7)? Try setting them to 1.0 and 1.0—what shape does the sphere trace?

  const x = orbitRadius * cos(t);
  const y = orbitRadius * sin(t * 1.15);
  const z = zRadius * sin(t * 0.7);
function draw() {
  // Use current frequency to drive background color
  const bgHue = map(currentFreq, 200, 800, 220, 320, true);
  background(bgHue, 60, 15);

  // Basic lights for 3D
  ambientLight(40);
  directionalLight(255, 255, 255, 0.5, 1, -0.5);

  // Drag/touch to rotate the camera:
  // https://p5js.org/reference/#/p5/orbitControl
  orbitControl(1, 1, 0.1);

  const t = millis() * 0.001; // smoother than frameCount
  const baseRadius = min(width, height) * 0.2;

  // Horizontal position controls orbit size and color
  const mx = constrain(mouseX, 0, width);
  const orbitRadius =
    baseRadius + map(mx, 0, width, -baseRadius * 0.4, baseRadius * 0.4);
  const zRadius = baseRadius * 0.6;

  // 3D Lissajous-ish path for orb
  const x = orbitRadius * cos(t);
  const y = orbitRadius * sin(t * 1.15);
  const z = zRadius * sin(t * 0.7);

  // Audio: map orb height (y) to frequency
  if (audioStarted && osc) {
    // In WEBGL, origin is center; y positive is down.
    // Top (negative y) -> higher pitch; bottom (positive y) -> lower pitch.
    const maxY = orbitRadius;
    const minY = -orbitRadius;
    const freq = map(y, maxY, minY, 200, 800, true); // clamp to [200,800]
    currentFreq = freq;
    osc.frequency.rampTo(freq, 0.05); // smooth 50 ms glide
  }

  // Main ring in the center
  push();
  rotateY(t * 0.4);
  rotateX(t * 0.25);
  const ringHue = map(mx, 0, width, 160, 360);
  ambientMaterial(ringHue, 80, 80);
  torus(baseRadius * 0.7, baseRadius * 0.18, 32, 24);
  pop();

  // Orbiting orb
  push();
  translate(x, y, z);
  const orbHue = (ringHue + 60) % 360;
  ambientMaterial(orbHue, 90, 100);
  sphere(baseRadius * 0.18, 32, 24);
  pop();

  drawHUD();
}
Line-by-line explanation (24 lines)

🔧 Subcomponents:

calculation Background Color from Frequency const bgHue = map(currentFreq, 200, 800, 220, 320, true);

Maps the current audio frequency to a hue value, making background color respond to pitch

calculation Lissajous Curve Position const x = orbitRadius * cos(t); const y = orbitRadius * sin(t * 1.15); const z = zRadius * sin(t * 0.7);

Calculates the sphere's 3D position along a Lissajous curve using different frequencies in each axis

conditional Audio Frequency Mapping if (audioStarted && osc) { const maxY = orbitRadius; const minY = -orbitRadius; const freq = map(y, maxY, minY, 200, 800, true); currentFreq = freq; osc.frequency.rampTo(freq, 0.05); }

Maps the sphere's y position to audio frequency and smoothly slides the oscillator to that pitch

calculation Rotating Torus push(); rotateY(t * 0.4); rotateX(t * 0.25); const ringHue = map(mx, 0, width, 160, 360); ambientMaterial(ringHue, 80, 80); torus(baseRadius * 0.7, baseRadius * 0.18, 32, 24); pop();

Draws and rotates a torus at the center, with color linked to horizontal mouse position

calculation Orbiting Sphere push(); translate(x, y, z); const orbHue = (ringHue + 60) % 360; ambientMaterial(orbHue, 90, 100); sphere(baseRadius * 0.18, 32, 24); pop();

Draws the sphere at its calculated Lissajous position with a hue offset from the torus

const bgHue = map(currentFreq, 200, 800, 220, 320, true);
Maps the oscillator's frequency (200–800 Hz) to a hue range (220–320 degrees, blue to magenta). As pitch rises, background hue shifts.
background(bgHue, 60, 15);
Fills the entire background with the mapped hue at 60% saturation and 15% brightness (dark and desaturated). This clears the previous frame.
ambientLight(40);
Adds ambient light with brightness 40, so all 3D surfaces are visible even if not directly lit
directionalLight(255, 255, 255, 0.5, 1, -0.5);
Adds a white directional light (like the sun) pointing roughly down and forward, creating shadows and depth
orbitControl(1, 1, 0.1);
Enables interactive camera orbit control—drag to rotate the view around the 3D scene. The three numbers control sensitivity for pan, tilt, and zoom.
const t = millis() * 0.001;
Calculates elapsed time in seconds by converting milliseconds to seconds. Using millis() is smoother than frameCount because it's independent of frame rate.
const baseRadius = min(width, height) * 0.2;
Sets a base size for both the torus and orbit path, scaled to 20% of the smaller screen dimension so the scene fits on any device
const mx = constrain(mouseX, 0, width);
Clamps the mouse X position between 0 and width to ensure it stays valid for mapping calculations
const orbitRadius = baseRadius + map(mx, 0, width, -baseRadius * 0.4, baseRadius * 0.4);
Maps horizontal mouse position to the sphere's orbit radius. Left side makes a smaller orbit, right side makes a larger orbit.
const x = orbitRadius * cos(t);
Uses cosine of time to oscillate x position smoothly back and forth
const y = orbitRadius * sin(t * 1.15);
Uses sine of time (multiplied by 1.15) for y position. The different multiplier creates an ellipse instead of a circle.
const z = zRadius * sin(t * 0.7);
Uses sine of time (multiplied by 0.7, slower) to create depth variation. Together, x, y, z form a Lissajous curve.
if (audioStarted && osc) {
Only updates frequency if the audio context has started and the oscillator exists (safety check)
const freq = map(y, maxY, minY, 200, 800, true);
Maps the sphere's y position to frequency: top of orbit (negative y) = 800 Hz (high), bottom (positive y) = 200 Hz (low). The 'true' clamps the result to [200, 800].
osc.frequency.rampTo(freq, 0.05);
Smoothly slides the oscillator's frequency to the target value over 50 milliseconds (0.05 seconds), avoiding harsh pitch jumps
rotateY(t * 0.4);
Rotates the torus around the Y axis (vertical). The multiplier controls rotation speed.
rotateX(t * 0.25);
Rotates the torus around the X axis too, creating a tumbling effect
const ringHue = map(mx, 0, width, 160, 360);
Maps mouse X position (0 to width) to hue range (160–360 degrees, cyan to magenta). This torus color changes as you move the mouse left/right.
ambientMaterial(ringHue, 80, 80);
Sets the torus color in HSB mode: the ringHue varies, saturation is 80%, brightness is 80%
torus(baseRadius * 0.7, baseRadius * 0.18, 32, 24);
Draws a torus: major radius 70% of baseRadius, minor radius 18%, with 32 segments around the major circle and 24 around the minor
translate(x, y, z);
Moves the origin to the sphere's calculated position on the Lissajous curve
const orbHue = (ringHue + 60) % 360;
Creates the sphere's hue by rotating the torus hue by 60 degrees, ensuring they always complement each other
ambientMaterial(orbHue, 90, 100);
Sets the sphere to the offset hue with high saturation (90%) and full brightness (100%), making it glow
sphere(baseRadius * 0.18, 32, 24);
Draws a sphere with radius 18% of baseRadius, with 32 detail segments. Detail controls smoothness (higher = smoother but slower).

drawHUD()

drawHUD() is a helper function that renders the 2D text overlay on top of the 3D scene. Because WEBGL has a different coordinate system (origin at center, y-axis flipped), we must use resetMatrix() and translate() to make text render in familiar screen coordinates. This pattern is useful whenever you want to mix 3D graphics with 2D UI.

🔬 The text is positioned at topY which is 8% down the screen. What if you change 0.08 to 0.3? Where would the instructions move, and why might that be useful on a mobile phone?

  const topY = height * 0.08;

  if (!audioStarted) {
    text(
      "Tap / click anywhere to start the sound.\n" +
        "Drag to orbit the camera.\n" +
        "Move finger/mouse horizontally to change color & orbit size.",
      width / 2,
      topY
    );
function drawHUD() {
  // Draw 2D UI text overlay in WEBGL:
  // resetMatrix + translate to make (0,0) top-left again
  push();
  resetMatrix();
  translate(-width / 2, -height / 2, 0);

  noStroke();
  fill(0, 0, 100, 90); // bright white, slightly transparent

  const topY = height * 0.08;

  if (!audioStarted) {
    text(
      "Tap / click anywhere to start the sound.\n" +
        "Drag to orbit the camera.\n" +
        "Move finger/mouse horizontally to change color & orbit size.",
      width / 2,
      topY
    );
  } else {
    const status = isMuted ? "Muted (tap to unmute)" : "Tap / click to mute";
    text(
      "Drag to orbit the camera.\n" +
        "Move horizontally to change color & orbit size.\n" +
        "Pitch follows the orb's height.\n" +
        status,
      width / 2,
      topY
    );

    // Smaller line for current pitch
    textSize(14);
    text(
      "Current pitch: " + nf(currentFreq, 3, 0) + " Hz",
      width / 2,
      topY + 40
    );
    // Restore default size for next frame
    textSize(16);
  }

  pop();
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

calculation Matrix Reset for 2D Overlay resetMatrix(); translate(-width / 2, -height / 2, 0);

Converts WEBGL's center-origin coordinate system back to top-left, allowing text to be positioned like in 2D mode

conditional Audio State Display if (!audioStarted) { ... } else { ... }

Shows different instructions depending on whether audio has started or is muted

push();
Saves the current transformation matrix so the coordinate system changes don't affect the next frame
resetMatrix();
Clears all transformations (rotations, translations) back to default. Essential for 2D text to be readable.
translate(-width / 2, -height / 2, 0);
Shifts the origin from WEBGL's center to the top-left corner (0, 0). This lets text() use familiar screen coordinates.
noStroke();
Disables outlines for text rendering
fill(0, 0, 100, 90);
Sets text color to white (HSB: hue doesn't matter when saturation is 0, brightness is 100) with 90% opacity (slightly transparent)
const topY = height * 0.08;
Calculates a Y position 8% down from the top—safe space for text without overlap
if (!audioStarted) {
Checks if audio has not started yet. If true, show startup instructions.
text(..., width / 2, topY);
Draws multi-line text centered horizontally at x=width/2, vertically at topY. Newlines (\n) break into multiple lines.
const status = isMuted ? "Muted (tap to unmute)" : "Tap / click to mute";
Uses a ternary operator to choose the status message based on whether audio is muted
textSize(14);
Temporarily shrinks text size to 14 pixels for the frequency readout
text( "Current pitch: " + nf(currentFreq, 3, 0) + " Hz",
Displays the current oscillator frequency formatted as an integer (nf formats numbers: 3 digits, 0 decimal places)
textSize(16);
Restores the text size back to 16 for the next frame so instructions render at normal size
pop();
Restores the saved transformation matrix, undoing all the coordinate system changes

handleUserGesture()

handleUserGesture() is called by both mousePressed() and touchStarted(), ensuring the sketch works on desktop and mobile. The key insight is that web browsers require a user gesture (click or touch) to start audio. Without this, the sketch would silently fail on first load. The .then() callback ensures audio is ready before creating the oscillator.

// Shared handler for mouse + touch so mobile works nicely
function handleUserGesture() {
  if (!audioStarted) {
    // Must be called from a user gesture for browsers' autoplay policy
    Tone.start().then(() => {
      osc = new Tone.Oscillator(currentFreq, "sine").toDestination();
      osc.start();
      audioStarted = true;
      isMuted = false;
    });
  } else if (osc) {
    // After started, tap/click toggles mute
    isMuted = !isMuted;
    osc.mute = isMuted;
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Audio Context Initialization if (!audioStarted) { Tone.start().then(() => { osc = new Tone.Oscillator(currentFreq, "sine").toDestination(); osc.start(); audioStarted = true; isMuted = false; });

Starts the browser's audio context and creates a sine wave oscillator on first user gesture

conditional Mute Toggle } else if (osc) { isMuted = !isMuted; osc.mute = isMuted;

Toggles the mute state on subsequent taps/clicks after audio has started

if (!audioStarted) {
Checks if audio has not been started yet
Tone.start().then(() => {
Requests browser permission to use audio (must be called from user gesture). The .then() callback runs once audio is ready.
osc = new Tone.Oscillator(currentFreq, "sine").toDestination();
Creates a new sine wave oscillator at the current frequency and connects it to the speakers (toDestination())
osc.start();
Starts the oscillator playing sound
audioStarted = true;
Sets the flag so future clicks toggle mute instead of re-initializing
isMuted = false;
Ensures the oscillator is unmuted when first started
} else if (osc) {
If audio is already started and the oscillator exists, handle a subsequent click
isMuted = !isMuted;
Toggles the mute flag: if it was true, make it false; if false, make it true
osc.mute = isMuted;
Applies the mute state to the actual oscillator (Tone.js mute property)

mousePressed()

mousePressed() is a p5.js event that fires whenever the mouse is clicked. By delegating to handleUserGesture(), we reuse the same audio logic for both mouse and touch, keeping code DRY (Don't Repeat Yourself).

function mousePressed() {
  handleUserGesture();
}
Line-by-line explanation (1 lines)
handleUserGesture();
Delegates to the shared handler so desktop and mobile use the same audio startup logic

touchStarted()

touchStarted() is p5.js's touch event handler. Returning false prevents default browser behavior, which is critical for mobile—otherwise, the page would scroll while the user tries to drag and interact with the canvas.

function touchStarted() {
  handleUserGesture();
  // Prevent page scroll on mobile when touching canvas
  return false;
}
Line-by-line explanation (2 lines)
handleUserGesture();
Calls the shared handler so touch events trigger the same audio behavior as clicks
return false;
Prevents the browser's default touch behavior (page scroll), so swiping to rotate the camera doesn't also scroll the page

windowResized()

windowResized() fires whenever the window is resized (phone rotation, browser resize, etc.). Calling resizeCanvas() ensures the 3D scene stays responsive on any device.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Automatically resizes the canvas whenever the browser window is resized, keeping the sketch full-screen

📦 Key Variables

osc Tone.Oscillator

Stores the Tone.js oscillator object that generates the sine wave sound. Created on first user gesture and controlled throughout draw().

let osc;
audioStarted boolean

Tracks whether the browser's audio context has been initialized. Used to prevent double-starting audio and to show/hide UI instructions.

let audioStarted = false;
isMuted boolean

Stores whether the oscillator is currently muted. Toggled on each click/tap after audio has started.

let isMuted = false;
currentFreq number

Tracks the oscillator's current frequency in Hz. Updated every frame based on the sphere's y position and used to drive the background hue.

let currentFreq = 440;
t number

Time in seconds (calculated as millis() * 0.001). Drives the sphere's motion along the Lissajous curve.

const t = millis() * 0.001;
baseRadius number

Base size of the 3D scene, calculated as 20% of the smaller screen dimension. Used to scale the torus, orbit path, and sphere.

const baseRadius = min(width, height) * 0.2;
mx number

Constrained horizontal mouse position (0 to width). Controls orbit size and hue rotation of both torus and sphere.

const mx = constrain(mouseX, 0, width);
orbitRadius number

The radius of the sphere's circular path in the x-y plane. Varies based on horizontal mouse position.

const orbitRadius = baseRadius + map(mx, 0, width, -baseRadius * 0.4, baseRadius * 0.4);
x, y, z number

The sphere's 3D coordinates on the Lissajous curve. Updated every frame; y is mapped to audio frequency.

const x = orbitRadius * cos(t);
freq number

The calculated frequency (200–800 Hz) for the current frame, derived from the sphere's y position.

const freq = map(y, maxY, minY, 200, 800, true);
ringHue number

The hue (0–360 degrees) of the torus, mapped from horizontal mouse position.

const ringHue = map(mx, 0, width, 160, 360);
orbHue number

The hue of the orbiting sphere, calculated as ringHue + 60 degrees, ensuring color complementarity.

const orbHue = (ringHue + 60) % 360;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG draw() frequency mapping

If audioStarted is true but osc is null (unlikely but possible in edge cases), the code attempts to call methods on null and crashes

💡 The check `if (audioStarted && osc)` is already correct and prevents this. Good defensive programming!

PERFORMANCE draw() torus and sphere detail

torus(baseRadius * 0.7, baseRadius * 0.18, 32, 24) and sphere(baseRadius * 0.18, 32, 24) use 32 detail segments, which is high. On mobile this may cause frame drops.

💡 Reduce to 16 or 20 detail segments: `torus(baseRadius * 0.7, baseRadius * 0.18, 20, 16)` and `sphere(baseRadius * 0.18, 20, 16)` for better mobile performance while maintaining visual quality.

STYLE drawHUD() text formatting

The text size is changed mid-function (textSize(14), then textSize(16)) which can be confusing and error-prone if code is refactored

💡 Store the original size in a variable: `const originalSize = 16; textSize(14); ... textSize(originalSize);` or use push()/pop() to save/restore text properties.

FEATURE osc frequency range

The frequency range is hardcoded to 200–800 Hz. Users cannot easily explore higher or lower pitches without editing code.

💡 Make frequency bounds tunable by adding global variables `let minFreq = 200; let maxFreq = 800;` and using them in the map() call. Then add keyboard shortcuts or UI controls to adjust them.

FEATURE handleUserGesture() audio initialization

The waveform is hardcoded to 'sine'. No easy way for users to switch between sine, square, triangle, sawtooth without code edit.

💡 Add a global variable `let waveType = "sine";` and key press handlers to cycle through types: press 'W' for wave menu, numeric keys to select waveforms.

🔄 Code Flow

Code flow showing setup, draw, drawhud, handleusergesture, mousepressed, touchstarted, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> webgl-canvas[WEBGL Canvas Setup] setup --> color-mode-hsb[HSB Color Mode] setup --> draw[draw loop] click setup href "#fn-setup" click webgl-canvas href "#sub-webgl-canvas" click color-mode-hsb href "#sub-color-mode-hsb" draw --> drawhud[drawHUD] draw --> lissajous-calculation[Lissajous Curve Position] draw --> frequency-mapping[Audio Frequency Mapping] draw --> torus-drawing[Rotating Torus] draw --> sphere-drawing[Orbiting Sphere] draw --> background-hue-mapping[Background Color from Frequency] click draw href "#fn-draw" click drawhud href "#fn-drawhud" click lissajous-calculation href "#sub-lissajous-calculation" click frequency-mapping href "#sub-frequency-mapping" click torus-drawing href "#sub-torus-drawing" click sphere-drawing href "#sub-sphere-drawing" click background-hue-mapping href "#sub-background-hue-mapping" draw -->|loop| draw lissajous-calculation -->|calculation| frequency-mapping frequency-mapping -->|conditional| torus-drawing torus-drawing -->|calculation| sphere-drawing drawhud --> matrix-reset[Matrix Reset for 2D Overlay] drawhud --> conditional-hud[Audio State Display] click matrix-reset href "#sub-matrix-reset" click conditional-hud href "#sub-conditional-hud" handleusergesture --> audio-init[Audio Context Initialization] handleusergesture --> mute-toggle[Mute Toggle] click handleusergesture href "#fn-handleusergesture" click audio-init href "#sub-audio-init" click mute-toggle href "#sub-mute-toggle" mousepressed --> handleusergesture touchstarted --> handleusergesture windowresized --> resizeCanvas[Resize Canvas] click mousepressed href "#fn-mousepressed" click touchstarted href "#fn-touchstarted" click windowresized href "#fn-windowresized"

Preview

Oscilating Sound WebGL - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Oscilating Sound WebGL - Code flow showing setup, draw, drawhud, handleusergesture, mousepressed, touchstarted, windowresized
Code Flow Diagram