uhh very cool game for cdww but tiny diffrnet

This sketch renders a shimmering chrome sphere that ripples and distorts in real-time using WebGL ray marching. Move your mouse to inject energy into the metallic surface, intensifying the ripples while a generative techno beat responds to your mouse velocity.

🧪 Try This!

Experiment with the code by making these changes:

  1. Slow down the ripples — The ripple frequency determines how tightly packed the waves are—lower values create larger, slower undulations across the sphere.
  2. Make motion control the music louder — The map() call for volume uses a 0–30 velocity range. Shrinking it to 0–15 means even slow movement triggers loud sound.
  3. Speed up the beat — Increasing BPM makes the kick, bass, and hi-hat rhythm fire faster—try 160 for an energetic techno tempo.
  4. Increase ripple intensity from mouse movement — The 0.015 multiplier scales mouse velocity into ripple amplitude—raising it makes every twitch of your mouse create bigger waves.
  5. Zoom the camera closer to the sphere — The z component of ro (ray origin) controls how close the camera is—lower values zoom in and show more detail.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a mesmerizing chrome sphere that floats in digital space, its surface rippling like liquid mercury with deep blues and purples that shift and dance continuously. The magic comes from three core p5.js and Web Audio techniques: WebGL shaders that perform ray marching to render a 3D sphere with procedurally generated ripples, smooth mouse velocity tracking that drives both visual and audio reactivity, and Tone.js synthesis that generates a pulsing techno beat whose filter and volume respond to how fast you move your mouse.

The code is organized into a shader pair (vertex and fragment) that renders the sphere, a setup() that initializes the canvas and UI, and a draw() loop that tracks mouse motion, passes it to the shader as uniforms, and modulates audio synthesis in real-time. By studying it you will learn how to write a raymarching shader, pass dynamic data from JavaScript into GLSL, smooth noisy input with lerp(), and connect visual interactivity to audio synthesis using Tone.js nodes and ramps.

⚙️ How It Works

  1. When you open the sketch, a fullscreen dark canvas appears with a centered 'ENTER EXPERIENCE' button. Clicking it triggers the Tone.js audio context, initializes a 130 BPM generative techno synth with kick, bass, and hi-hat voices, and fades in the UI text.
  2. Every frame, the draw() loop calculates how fast your mouse moved (dx and dy), then smooths that velocity with lerp() so it doesn't jitter, and dampens it toward zero so motion trails off naturally.
  3. If audio has started, the smooth velocity magnitude is mapped to two audio parameters: a lowpass filter cutoff frequency (quiet and muffled when still, bright and open when moving fast) and a master volume level (silent when still, loud when moving). These ramp smoothly over 100ms to avoid clicks.
  4. The fragment shader receives the current time, mouse position, and smoothed velocity as uniforms. It performs ray marching—shooting rays from the camera through each pixel and stepping along them until they hit the sphere.
  5. When a ray hits, the shader calculates surface normals via finite differences, then samples a procedural ripple pattern whose amplitude is driven by your mouse velocity. The ripples combine high-frequency sine and cosine waves with a very subtle constant wobble.
  6. The final color is computed using chromatic reflections: the reflection vector is sampled for dynamic blue-to-purple-to-silver shifts, and a fresnel effect brightens edges. The result is a liquid metallic surface that moves with your mouse and sounds better when you move faster.

🎓 Concepts You'll Learn

WebGL ray marchingFragment shadersMouse tracking and smoothingTone.js audio synthesisReal-time audio-visual reactivitySigned distance functionsSurface normals and lighting

📝 Code Breakdown

setup()

setup() runs once at sketch start. Here we initialize the WebGL context, compile shaders, and create UI elements. The audio context is not started here because browsers require a user gesture (like a click) to begin audio playback.

function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  
  // Create UI overlay to trigger audio context
  startBtn = createButton('ENTER EXPERIENCE');
  startBtn.id('start-btn');
  startBtn.mousePressed(initAudio);
  
  // Create instructions text (hidden initially)
  instructions = createDiv('MOVE MOUSE / TOUCH TO SHAPE THE METAL & MODULATE THE BEAT');
  instructions.id('instructions');
  
  // Create creator credit text
  creditText = createDiv('MADE BY CORBUN');
  creditText.id('credit');
  
  liquidShader = createShader(vertShader, fragShader);
  noStroke();
}
Line-by-line explanation (7 lines)
createCanvas(windowWidth, windowHeight, WEBGL);
Creates a fullscreen WebGL canvas—the WEBGL parameter enables shader rendering instead of the default 2D canvas.
startBtn = createButton('ENTER EXPERIENCE');
Creates an interactive button element that will be centered on screen via CSS styling.
startBtn.mousePressed(initAudio);
Attaches the initAudio() function as a callback—when clicked, it starts the Tone.js audio context and synth.
instructions = createDiv('MOVE MOUSE / TOUCH TO SHAPE THE METAL & MODULATE THE BEAT');
Creates a hidden instructional text element that fades in after audio starts.
creditText = createDiv('MADE BY CORBUN');
Creates a small credit text positioned via CSS in the top-right corner.
liquidShader = createShader(vertShader, fragShader);
Compiles the vertex and fragment shader strings into a GPU program that will render the sphere each frame.
noStroke();
Disables stroke outlines on shapes, so the rectangle drawn in draw() appears as a solid filled quad.

draw()

draw() executes 60 times per second (or whatever frameRate is set). Here we track smooth mouse motion, modulate audio in real-time based on velocity, and pass dynamic data to the shader. This is the heart of the interactivity: every millisecond, JavaScript calculates fresh parameters and sends them to the GPU, which immediately uses them in ray marching calculations.

🔬 This lerp blends 10% of the way toward raw motion each frame. What happens if you change 0.1 to 0.5 (more responsive) or to 0.01 (more laggy)?

  // Smooth the velocity
  smoothVelX = lerp(smoothVelX, dx, 0.1);
  smoothVelY = lerp(smoothVelY, dy, 0.1);

🔬 The map() function remaps velocity 0–40 to frequencies 250–8000. Try changing the input range from 0–40 to 0–100 to make the filter less sensitive—slow motion won't open it as much.

  if (audioStarted) {
    // Calculate the magnitude of our velocity
    let velMag = sqrt(smoothVelX * smoothVelX + smoothVelY * smoothVelY);
    
    // Map velocity to filter cutoff (low/muffled when still -> high/bright when moving fast)
    let targetFreq = map(velMag, 0, 40, 250, 8000, true);
    synthFilter.frequency.rampTo(targetFreq, 0.1);
function draw() {
  // Calculate instantaneous velocity
  let dx = mouseX - pmouseX;
  let dy = mouseY - pmouseY;

  // Smooth the velocity
  smoothVelX = lerp(smoothVelX, dx, 0.1);
  smoothVelY = lerp(smoothVelY, dy, 0.1);

  // Dampen velocity down to zero
  smoothVelX *= 0.96;
  smoothVelY *= 0.96;
  
  // --- AUDIO REACTIVITY ---
  if (audioStarted) {
    // Calculate the magnitude of our velocity
    let velMag = sqrt(smoothVelX * smoothVelX + smoothVelY * smoothVelY);
    
    // Map velocity to filter cutoff (low/muffled when still -> high/bright when moving fast)
    let targetFreq = map(velMag, 0, 40, 250, 8000, true);
    synthFilter.frequency.rampTo(targetFreq, 0.1);
    
    // Map velocity to volume (-20dB when still -> 0dB when moving)
    let targetVol = map(velMag, 0, 30, -20, 0, true);
    masterVolume.volume.rampTo(targetVol, 0.1);
  }

  // Activate shader
  shader(liquidShader);

  // Pass uniforms
  liquidShader.setUniform('u_resolution', [width, height]);
  liquidShader.setUniform('u_time', millis() / 1000.0);
  liquidShader.setUniform('u_mouse', [mouseX, mouseY]);
  liquidShader.setUniform('u_mouse_vel', [smoothVelX, smoothVelY]);

  // Draw fullscreen quad
  rect(-width / 2, -height / 2, width, height);
}
Line-by-line explanation (17 lines)

🔧 Subcomponents:

calculation Velocity Calculation let dx = mouseX - pmouseX; let dy = mouseY - pmouseY;

Captures raw instantaneous mouse movement by subtracting previous frame position from current position

calculation Velocity Smoothing with Lerp smoothVelX = lerp(smoothVelX, dx, 0.1); smoothVelY = lerp(smoothVelY, dy, 0.1);

Smooths jittery velocity by blending current velocity 90% toward the raw delta—creates fluid motion trails

calculation Velocity Damping smoothVelX *= 0.96; smoothVelY *= 0.96;

Multiplies velocity by 0.96 each frame so motion gradually decays when you stop moving, avoiding snappy resets

conditional Audio Reactivity Block if (audioStarted) { let velMag = sqrt(smoothVelX * smoothVelX + smoothVelY * smoothVelY); let targetFreq = map(velMag, 0, 40, 250, 8000, true); synthFilter.frequency.rampTo(targetFreq, 0.1); let targetVol = map(velMag, 0, 30, -20, 0, true); masterVolume.volume.rampTo(targetVol, 0.1); }

Maps mouse velocity to audio parameters: filter cutoff (dull to bright) and volume (silent to loud) using linear mapping with clamping

calculation Shader Uniform Passing liquidShader.setUniform('u_resolution', [width, height]); liquidShader.setUniform('u_time', millis() / 1000.0); liquidShader.setUniform('u_mouse', [mouseX, mouseY]); liquidShader.setUniform('u_mouse_vel', [smoothVelX, smoothVelY]);

Sends JavaScript variables to the GPU shader as uniforms so the fragment shader can use time, mouse position, and velocity in its calculations

let dx = mouseX - pmouseX;
Calculates horizontal movement: current mouse X minus previous frame's X position.
let dy = mouseY - pmouseY;
Calculates vertical movement: current mouse Y minus previous frame's Y position.
smoothVelX = lerp(smoothVelX, dx, 0.1);
Smooths velocity by blending: 10% of the way from current smoothVelX toward raw dx. This prevents jerky ripples from tiny frame-to-frame jitter.
smoothVelY = lerp(smoothVelY, dy, 0.1);
Same smoothing applied to vertical velocity.
smoothVelX *= 0.96;
Dampens horizontal velocity: multiplying by 0.96 means it loses 4% of its magnitude each frame, so it coasts to zero instead of stopping instantly.
smoothVelY *= 0.96;
Dampens vertical velocity the same way.
let velMag = sqrt(smoothVelX * smoothVelX + smoothVelY * smoothVelY);
Calculates the overall speed: the length of the velocity vector (Pythagorean theorem).
let targetFreq = map(velMag, 0, 40, 250, 8000, true);
Maps velocity magnitude (0–40 range) to filter frequency (250–8000 Hz): fast motion = bright, slow = dull. The true parameter clamps values outside the range.
synthFilter.frequency.rampTo(targetFreq, 0.1);
Smoothly transitions the lowpass filter cutoff frequency to targetFreq over 100 milliseconds, avoiding audio clicks.
let targetVol = map(velMag, 0, 30, -20, 0, true);
Maps velocity (0–30) to volume in decibels (-20 dB to 0 dB): static = silent, moving = loud.
masterVolume.volume.rampTo(targetVol, 0.1);
Smoothly transitions the master volume to targetVol over 100 milliseconds.
shader(liquidShader);
Activates the compiled shader program so subsequent drawing operations use it instead of the default 2D renderer.
liquidShader.setUniform('u_resolution', [width, height]);
Passes canvas dimensions to the shader so it can compute normalized screen coordinates correctly.
liquidShader.setUniform('u_time', millis() / 1000.0);
Passes current time in seconds to the shader for animating ripples and lighting shifts.
liquidShader.setUniform('u_mouse', [mouseX, mouseY]);
Passes current mouse position to the shader (though this sketch doesn't actively use it in the current fragment shader logic).
liquidShader.setUniform('u_mouse_vel', [smoothVelX, smoothVelY]);
Passes smoothed velocity to the shader so ripples only appear when you move the mouse.
rect(-width / 2, -height / 2, width, height);
Draws a fullscreen rectangle in WEBGL mode. Since a shader is active, each pixel is shaded by the fragment shader instead of the default color.

initAudio()

initAudio() is called once by the start button, and it sets up the entire synthesizer and rhythm. This is where Tone.js nodes (synths, filters, volumes) are created and connected into a signal chain. The scheduleRepeat pattern is a core Tone.js idiom for building step sequencers: you define logic for each step and let the transport clock drive it. All synths are routed through the filter, so mouse velocity in draw() modulates all of them at once.

🔬 The kick plays when step % 4 === 0 (steps 0, 4, 8, 12). What happens if you change === 0 to === 1 or === 2? The kick will shift to the offbeat.

  // Sequencer loop (16th notes)
  let step = 0;
  Tone.Transport.scheduleRepeat((time) => {
    // 4/4 Kick on the downbeat (0, 4, 8, 12)
    if (step % 4 === 0) {
      kick.triggerAttackRelease("C1", "8n", time);
    }

🔬 The bass plays through this 8-note array, cycling with step % 8. What happens if you simplify bassNotes to just ["C2"], so the bass plays the same note every time instead of a melody?

  const bassNotes = ["C2", "C2", "Eb2", "C2", "F2", "C2", "Bb1", "C2"];
  
  // Sequencer loop (16th notes)
  let step = 0;
  Tone.Transport.scheduleRepeat((time) => {
    // 4/4 Kick on the downbeat (0, 4, 8, 12)
    if (step % 4 === 0) {
      kick.triggerAttackRelease("C1", "8n", time);
    }
    
    // Offbeat hi-hat (2, 6, 10, 14)
    if (step % 4 === 2) {
      hihat.triggerAttackRelease("16n", time, 0.3);
    }
    
    // Driving 16th note bassline (skipping the kick beats)
    if (step % 4 !== 0) {
      bass.triggerAttackRelease(bassNotes[step % 8], "16n", time, 0.5);
async function initAudio() {
  // Required: Start Tone context on user gesture
  await Tone.start();
  
  // Create master effects nodes controlled by the mouse movement
  masterVolume = new Tone.Volume(-20).toDestination();
  synthFilter = new Tone.Filter(250, "lowpass").connect(masterVolume);
  
  // 1. Kick Drum
  const kick = new Tone.MembraneSynth({
    pitchDecay: 0.05,
    octaves: 4,
    oscillator: { type: "sine" },
    envelope: { attack: 0.001, decay: 0.4, sustain: 0.01, release: 1.4 }
  }).connect(synthFilter);
  
  // 2. Gritty FM Bassline
  const bass = new Tone.FMSynth({
    harmonicity: 1,
    modulationIndex: 2,
    oscillator: { type: "square" },
    envelope: { attack: 0.01, decay: 0.2, sustain: 0.1, release: 0.5 }
  }).connect(synthFilter);
  
  // 3. Hi-Hat
  const hihat = new Tone.NoiseSynth({
    noise: { type: "white" },
    envelope: { attack: 0.001, decay: 0.1, sustain: 0, release: 0.1 }
  }).connect(synthFilter);
  
  // Bassline notes pattern
  const bassNotes = ["C2", "C2", "Eb2", "C2", "F2", "C2", "Bb1", "C2"];
  
  // Sequencer loop (16th notes)
  let step = 0;
  Tone.Transport.scheduleRepeat((time) => {
    // 4/4 Kick on the downbeat (0, 4, 8, 12)
    if (step % 4 === 0) {
      kick.triggerAttackRelease("C1", "8n", time);
    }
    
    // Offbeat hi-hat (2, 6, 10, 14)
    if (step % 4 === 2) {
      hihat.triggerAttackRelease("16n", time, 0.3);
    }
    
    // Driving 16th note bassline (skipping the kick beats)
    if (step % 4 !== 0) {
      bass.triggerAttackRelease(bassNotes[step % 8], "16n", time, 0.5);
    }
    
    step = (step + 1) % 16;
  }, "16n");

  // Set BPM and start transport
  Tone.Transport.bpm.value = 130;
  Tone.Transport.start();
  
  // Clean up UI and update state
  audioStarted = true;
  startBtn.remove();
  
  // Fade in texts
  instructions.style('opacity', '1');
  creditText.style('opacity', '1');
}
Line-by-line explanation (28 lines)

🔧 Subcomponents:

calculation Audio Context Initialization await Tone.start();

Starts the Web Audio API context—required by modern browsers before sound can play

calculation Master Effects Chain masterVolume = new Tone.Volume(-20).toDestination(); synthFilter = new Tone.Filter(250, "lowpass").connect(masterVolume);

Creates a volume control and lowpass filter chained to the speakers, both of which will be modulated by mouse velocity in the draw loop

calculation Kick Drum Synthesizer const kick = new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 4, oscillator: { type: "sine" }, envelope: { attack: 0.001, decay: 0.4, sustain: 0.01, release: 1.4 } }).connect(synthFilter);

Creates a punchy kick drum that pitches down over 50ms and decays over 400ms; routed through the filter

calculation FM Bass Synthesizer const bass = new Tone.FMSynth({ harmonicity: 1, modulationIndex: 2, oscillator: { type: "square" }, envelope: { attack: 0.01, decay: 0.2, sustain: 0.1, release: 0.5 } }).connect(synthFilter);

Creates a gritty square-wave bass using frequency modulation (one oscillator modulates another's pitch) for harmonic richness

calculation Hi-Hat Synthesizer const hihat = new Tone.NoiseSynth({ noise: { type: "white" }, envelope: { attack: 0.001, decay: 0.1, sustain: 0, release: 0.1 } }).connect(synthFilter);

Creates a crisp hi-hat by filtering white noise through a fast attack and decay envelope

for-loop 16-Step Sequencer Tone.Transport.scheduleRepeat((time) => { if (step % 4 === 0) { kick.triggerAttackRelease("C1", "8n", time); } if (step % 4 === 2) { hihat.triggerAttackRelease("16n", time, 0.3); } if (step % 4 !== 0) { bass.triggerAttackRelease(bassNotes[step % 8], "16n", time, 0.5); } step = (step + 1) % 16; }, "16n");

Schedules notes to play on a 16th-note grid: kick on beats 0/4/8/12, hi-hat on 2/6/10/14, bass on all other steps cycling through the 8-note pattern

await Tone.start();
Starts the Web Audio API context. The 'await' keyword pauses execution until the audio system is ready—required by modern browsers after a user gesture.
masterVolume = new Tone.Volume(-20).toDestination();
Creates a Volume node starting at -20 dB (very quiet) and connects it to the speakers (toDestination). This node's level will be controlled by mouse velocity in draw().
synthFilter = new Tone.Filter(250, "lowpass").connect(masterVolume);
Creates a lowpass filter with initial cutoff 250 Hz (dull, muffled sound) and chains it to the volume control. All synths route through this filter.
const kick = new Tone.MembraneSynth({
Creates a MembraneSynth, which is a drum-like synth that pitches down over time, perfect for kick drums.
pitchDecay: 0.05,
The pitch falls for 50 milliseconds—how fast the kick 'slides' down in pitch.
octaves: 4,
The pitch falls across 4 octaves—the range of the pitch slide.
envelope: { attack: 0.001, decay: 0.4, sustain: 0.01, release: 1.4 }
ADSR envelope: attacks in 1ms, decays to sustain over 400ms, holds sustain at 1%, releases over 1.4s after the note ends.
const bass = new Tone.FMSynth({
Creates an FM (frequency modulation) synth where one oscillator modulates the frequency of another, creating rich harmonic complexity.
harmonicity: 1,
The modulator frequency is 1× the carrier frequency—creates complex but predictable tones.
modulationIndex: 2,
Controls how deeply the modulator affects the carrier pitch—higher values = more chaotic/metallic sound.
oscillator: { type: "square" },
Uses a square wave as the carrier, which is already harmonically rich before FM is applied.
const hihat = new Tone.NoiseSynth({
Creates a noise synthesizer—white noise filtered by an envelope to create percussive hi-hat sounds.
const bassNotes = ["C2", "C2", "Eb2", "C2", "F2", "C2", "Bb1", "C2"];
An array of 8 musical notes (MIDI note names) that cycles—the bass plays through this pattern repeatedly.
Tone.Transport.scheduleRepeat((time) => {
Schedules a repeating callback function that fires on every 16th note ("16n" at the end of the line).
if (step % 4 === 0) {
On steps 0, 4, 8, 12 (every 4th sixteenth note = every beat).
kick.triggerAttackRelease("C1", "8n", time);
Plays the kick drum at pitch C1 for an eighth note duration (16th note subdivisions).
if (step % 4 === 2) {
On steps 2, 6, 10, 14 (the offbeats between the main beats).
hihat.triggerAttackRelease("16n", time, 0.3);
Triggers the hi-hat noise for a 16th note duration with velocity 0.3 (30% amplitude).
if (step % 4 !== 0) {
On all steps except 0, 4, 8, 12 (i.e., skip the kick beats).
bass.triggerAttackRelease(bassNotes[step % 8], "16n", time, 0.5);
Plays a bass note from the array (cycling every 8 steps) for a 16th note at 50% velocity.
step = (step + 1) % 16;
Increments step and wraps it back to 0 after 15, creating a repeating 16-step pattern.
}, "16n");
Closes the callback and specifies "16n" (sixteenth note) as the repeat interval, so it fires every 16th of a quarter note.
Tone.Transport.bpm.value = 130;
Sets the transport's tempo to 130 beats per minute.
Tone.Transport.start();
Starts the transport clock, so all scheduled notes begin playing.
audioStarted = true;
Sets the global flag so draw() knows to apply audio reactivity.
startBtn.remove();
Deletes the 'ENTER EXPERIENCE' button from the DOM now that audio has started.
instructions.style('opacity', '1');
Fades in the instruction text by setting its CSS opacity from 0 to 1 (CSS transition handles the animation).
creditText.style('opacity', '1');
Fades in the creator credit text the same way.

windowResized()

windowResized() is a built-in p5.js callback that fires whenever the window is resized. Calling resizeCanvas() inside it ensures your sketch stays fullscreen and responsive. Without this, the canvas would stay its original size.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Automatically resizes the p5.js canvas when the browser window is resized, keeping the sketch fullscreen.

📦 Key Variables

vertShader string

GLSL vertex shader code that passes vertex positions through to the fragment shader without modification

const vertShader = `precision highp float; ...`;
fragShader string

GLSL fragment shader code that performs ray marching and calculates per-pixel color based on sphere distortion, normals, and reflection

const fragShader = `precision highp float; ...`;
liquidShader object

Compiled p5.Shader object created from vertShader and fragShader, activated in draw() via shader()

let liquidShader;
smoothVelX number

Stores the smoothed horizontal mouse velocity, updated via lerp() and damping to create fluid motion trails

let smoothVelX = 0;
smoothVelY number

Stores the smoothed vertical mouse velocity, used the same way as smoothVelX

let smoothVelY = 0;
audioStarted boolean

Flag that tracks whether audio has been initialized; used to gate audio reactivity in draw()

let audioStarted = false;
startBtn object

Reference to the p5.Renderer button element that triggers initAudio() when clicked

let startBtn;
instructions object

Reference to the p5.Renderer div element that displays instructions text and fades in after audio starts

let instructions;
creditText object

Reference to the p5.Renderer div element that displays creator credit and fades in after audio starts

let creditText;
masterVolume object

Tone.js Volume node that controls overall output level, modulated by mouse velocity

let masterVolume;
synthFilter object

Tone.js lowpass Filter node that colors all synths, with cutoff frequency modulated by mouse velocity

let synthFilter;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

PERFORMANCE fragment shader, ray marching loop

The loop runs up to 80 iterations per pixel at 60 FPS on a fullscreen canvas—on high-res displays this is heavy. No adaptive step size or early exit optimization.

💡 Implement adaptive stepping: start with larger step sizes and reduce them near the surface. Or use binary search (bisection) instead of linear stepping to converge faster on the surface.

BUG draw(), audio reactivity

If audioStarted is false, velMag and targetFreq/targetVol are calculated inside the conditional but the values are never used. Minor: velocity damping still happens even when audio is off.

💡 Move velocity smoothing into the audio block so it only runs when audio is active, or calculate it outside and reuse it. This prevents unnecessary computation when the sketch is idle.

FEATURE initAudio()

The bass pattern bassNotes[step % 8] is hardcoded. There's no way to dynamically change the melody or scale without editing code.

💡 Extract bassNotes and the BPM to global variables so users (or a future UI) can switch between patterns. Or generate bassNotes algorithmically from a scale and a randomized sequence.

STYLE fragmentShader map() function

The ripple combination (sin * cos * sin) is intuitive but produces symmetrical patterns. The constant wobble (0.03) is small and barely visible.

💡 Experiment with perlin noise (if available) or Simplex noise instead of sine waves for more organic, irregular rippling. Or vary the wobble amplitude over time based on u_time to create breathing effects.

BUG initAudio(), sequencer

The sequencer does not account for the sustained notes overlapping. If step % 4 !== 0 fires frequently and the release time is 0.5s, bass notes can pile up and create buildup.

💡 Add a check to only trigger bass if no other bass note is still sustaining, or reduce the release time to 0.2s to ensure notes end crisply between triggers.

PERFORMANCE shader uniforms, setUniform() calls

Every frame, setUniform() is called 4 times (resolution, time, mouse, velocity) even if some values don't change (resolution). Redundant uniform uploads can add overhead.

💡 Cache resolution and only set it once in setup() or when windowResized() fires. Or batch all uniforms into a single object. This is micro-optimization on modern GPUs but good practice.

🔄 Code Flow

Code flow showing setup, draw, initaudio, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> velocity-calculation[Velocity Calculation] draw --> velocity-smoothing[Velocity Smoothing] draw --> velocity-damping[Velocity Damping] draw --> audio-reactivity[Audio Reactivity Block] draw --> shader-uniforms[Shader Uniform Passing] click setup href "#fn-setup" click draw href "#fn-draw" click velocity-calculation href "#sub-velocity-calculation" click velocity-smoothing href "#sub-velocity-smoothing" click velocity-damping href "#sub-velocity-damping" click audio-reactivity href "#sub-audio-reactivity" click shader-uniforms href "#sub-shader-uniforms" setup --> initaudio[initaudio] initaudio --> tone-context-start[Tone Context Initialization] initaudio --> master-chain[Master Effects Chain] initaudio --> kick-synth[Kick Drum Synthesizer] initaudio --> bass-synth[Bass Synthesizer] initaudio --> hihat-synth[Hi-Hat Synthesizer] initaudio --> sequencer-loop[16-Step Sequencer] click initaudio href "#fn-initaudio" click tone-context-start href "#sub-tone-context-start" click master-chain href "#sub-master-chain" click kick-synth href "#sub-kick-synth" click bass-synth href "#sub-bass-synth" click hihat-synth href "#sub-hihat-synth" click sequencer-loop href "#sub-sequencer-loop" draw --> windowresized[windowresized] windowresized --> resizeCanvas[Resize Canvas] click windowresized href "#fn-windowresized" click resizeCanvas href "#sub-resize-canvas"

❓ Frequently Asked Questions

What visual effects does the p5.js sketch create?

The sketch produces a shimmering chrome sphere that ripples like liquid mercury, displaying deep blues and purples that reflect and distort dynamically over time.

How can users interact with the chrome sphere in this sketch?

Users can move their mouse to inject energy into the sphere's surface, which intensifies the metallic waves and enhances the visual rippling effect.

What creative coding techniques are showcased in this p5.js project?

The sketch demonstrates raymarching and shader programming to create a 3D liquid metal effect that responds to user input.

Preview

uhh very cool game for cdww but tiny diffrnet - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of uhh very cool game for cdww but tiny diffrnet - Code flow showing setup, draw, initaudio, windowresized
Code Flow Diagram