/** * 🌿 Ivy's Creative Studio * Tab 8: p5.js Audio Visualizer * * Dedicated audio visualization with p5.js and p5.sound * 10 styles, 8 palettes, bass boost, glow, particles! 🎤🌿 */ class P5AudioRenderer { constructor() { this.p5Instance = null; this.container = null; this.isActive = false; this.audioStarted = false; // Parameters this.params = { style: "rings", sensitivity: 1.5, smoothing: 0.8, palette: "neon", bassBoost: 1.0, mirror: true, glow: false, particles: false }; } init(container) { this.container = container; } start() { this.isActive = true; this.container.classList.remove("hidden"); this.container.innerHTML = ""; this.createSketch(); } stop() { this.isActive = false; this.container.classList.add("hidden"); if (this.p5Instance) { this.p5Instance.remove(); this.p5Instance = null; } } reset() { if (this.p5Instance) { this.p5Instance.remove(); } this.createSketch(); } setStyle(style) { this.params.style = style; } setSensitivity(value) { this.params.sensitivity = value; } setSmoothing(value) { this.params.smoothing = value; } setPalette(palette) { this.params.palette = palette; } setBassBoost(value) { this.params.bassBoost = value; } setMirror(enabled) { this.params.mirror = enabled; } setGlow(enabled) { this.params.glow = enabled; } setParticles(enabled) { this.params.particles = enabled; } async startAudio() { if (this.audioStarted) return true; try { if (typeof p5 !== "undefined" && p5.prototype.getAudioContext) { const audioContext = p5.prototype.getAudioContext(); if (audioContext.state !== "running") { await audioContext.resume(); } } this.audioStarted = true; this.reset(); return true; } catch (err) { console.error("Failed to start audio:", err); return false; } } getPalette() { const palettes = { ivy: ["#22c55e", "#16a34a", "#4ade80", "#86efac", "#166534"], neon: ["#ff00ff", "#00ffff", "#ff00aa", "#00ff88", "#ffff00"], fire: ["#ff0000", "#ff4400", "#ff8800", "#ffcc00", "#ffff00"], ocean: ["#001133", "#003366", "#0066cc", "#00aaff", "#66ddff"], rainbow: ["#ff0000", "#ff8800", "#ffff00", "#00ff00", "#0088ff", "#8800ff"], synthwave: ["#ff006e", "#8338ec", "#3a86ff", "#fb5607", "#ffbe0b"], cosmic: ["#240046", "#5a189a", "#9d4edd", "#c77dff", "#e0aaff"], candy: ["#ff70a6", "#ff9770", "#ffd670", "#e9ff70", "#70d6ff"] }; return palettes[this.params.palette] || palettes.neon; } createSketch() { const self = this; const params = this.params; const sketch = p => { let fft, amplitude, mic; let spectrum = []; let waveform = []; let level = 0; let history = []; const historyLength = 60; p.setup = function () { const canvas = p.createCanvas(self.container.clientWidth, self.container.clientHeight); canvas.parent(self.container); p.colorMode(p.HSB, 360, 100, 100, 100); p.angleMode(p.DEGREES); // Initialize audio if started if (self.audioStarted) { setupAudio(); } // Initialize history for (let i = 0; i < historyLength; i++) { history.push(new Array(64).fill(0)); } }; function setupAudio() { fft = new p5.FFT(params.smoothing, 256); amplitude = new p5.Amplitude(); mic = new p5.AudioIn(); mic.start(); fft.setInput(mic); } p.draw = function () { const palette = self.getPalette(); const sensitivity = params.sensitivity; // Dark background p.background(0, 0, 5); if (!self.audioStarted || !fft) { // Show message p.push(); p.colorMode(p.RGB); p.fill(255); p.textAlign(p.CENTER, p.CENTER); p.textSize(24); p.text('🎵 Click "Start Audio" to begin', p.width / 2, p.height / 2); p.textSize(16); p.fill(150); p.text("The visualizer will react to your microphone.", p.width / 2, p.height / 2 + 40); p.pop(); return; } // Get audio data spectrum = fft.analyze(); waveform = fft.waveform(); level = amplitude.getLevel() * sensitivity; // Store history for 3D effects const currentSpectrum = []; for (let i = 0; i < 64; i++) { currentSpectrum.push((spectrum[i * 2] / 255) * sensitivity); } history.unshift(currentSpectrum); if (history.length > historyLength) history.pop(); // Draw based on style switch (params.style) { case "rings": drawRings(palette); break; case "bars3d": drawBars3D(palette); break; case "particles": drawParticles(palette); break; case "waveform": drawWaveform(palette); break; case "spiral": drawSpiral(palette); break; case "terrain": drawTerrain(palette); break; case "ivy": drawIvy(palette); break; case "galaxy": drawGalaxy(palette); break; case "fireworks": drawFireworks(palette); break; case "kaleidoscope": drawKaleidoscope(palette); break; } // Background particles effect if (params.particles) { drawBackgroundParticles(palette); } // Glow effect if (params.glow) { applyGlowEffect(); } }; // Background particles let bgParticles = []; function drawBackgroundParticles(palette) { // Initialize if needed if (bgParticles.length === 0) { for (let i = 0; i < 50; i++) { bgParticles.push({ x: p.random(p.width), y: p.random(p.height), size: p.random(2, 6), speed: p.random(0.5, 2), color: palette[p.floor(p.random(palette.length))] }); } } for (let particle of bgParticles) { particle.y -= particle.speed * (1 + level * 3); if (particle.y < 0) { particle.y = p.height; particle.x = p.random(p.width); } p.push(); p.colorMode(p.RGB); const c = p.color(particle.color); c.setAlpha(100 + level * 100); p.fill(c); p.noStroke(); p.ellipse(particle.x, particle.y, particle.size + level * 5); p.pop(); } } function applyGlowEffect() { p.push(); p.blendMode(p.ADD); p.filter(p.BLUR, 1); p.blendMode(p.BLEND); p.pop(); } function drawRings(palette) { p.translate(p.width / 2, p.height / 2); const numRings = 8; const maxRadius = Math.min(p.width, p.height) * 0.45; for (let ring = 0; ring < numRings; ring++) { const freqStart = ring * 8; let avgAmp = 0; for (let j = 0; j < 8; j++) { avgAmp += spectrum[freqStart + j] / 255; } avgAmp = (avgAmp / 8) * params.sensitivity; const baseRadius = ((ring + 1) / numRings) * maxRadius * 0.5; const radius = baseRadius + avgAmp * maxRadius * 0.5; p.push(); p.colorMode(p.RGB); const c = p.color(palette[ring % palette.length]); p.noFill(); p.strokeWeight(3 + avgAmp * 10); c.setAlpha(150 + avgAmp * 100); p.stroke(c); // Pulsing ring with distortion p.beginShape(); for (let a = 0; a < 360; a += 5) { const freqIndex = Math.floor((a / 360) * 64); const amp = (spectrum[freqIndex * 2] / 255) * params.sensitivity; const r = radius + amp * 50; const x = p.cos(a) * r; const y = p.sin(a) * r; p.vertex(x, y); } p.endShape(p.CLOSE); p.pop(); } // Center glow const centerSize = 30 + level * 100; p.push(); p.colorMode(p.RGB); for (let i = 3; i >= 0; i--) { const c = p.color(palette[0]); c.setAlpha(50 - i * 10); p.fill(c); p.noStroke(); p.ellipse(0, 0, centerSize + i * 20); } p.pop(); } function drawBars3D(palette) { const numBars = 64; const barWidth = p.width / numBars; const maxHeight = p.height * 0.7; for (let i = 0; i < numBars; i++) { const amp = (spectrum[i * 2] / 255) * params.sensitivity; const h = amp * maxHeight; const colorIndex = Math.floor(amp * palette.length); p.push(); p.colorMode(p.RGB); const c = p.color(palette[colorIndex % palette.length]); // Main bar p.fill(c); p.noStroke(); const x = i * barWidth; const y = p.height - h; p.rect(x, y, barWidth - 2, h); // Reflection if (params.mirror) { c.setAlpha(80); p.fill(c); p.rect(x, p.height, barWidth - 2, h * 0.3); } // Top glow for (let g = 0; g < 3; g++) { c.setAlpha(50 - g * 15); p.fill(c); p.rect(x - g * 2, y - g * 2, barWidth - 2 + g * 4, 4); } p.pop(); } } function drawParticles(palette) { p.translate(p.width / 2, p.height / 2); const numParticles = 128; for (let i = 0; i < numParticles; i++) { const freqIndex = i * 2; const amp = (spectrum[freqIndex] / 255) * params.sensitivity; if (amp > 0.1) { const angle = (i / numParticles) * 360 + p.frameCount * 0.5; const radius = 50 + amp * 200; const x = p.cos(angle) * radius; const y = p.sin(angle) * radius; const size = 5 + amp * 30; p.push(); p.colorMode(p.RGB); const c = p.color(palette[i % palette.length]); c.setAlpha(amp * 255); p.fill(c); p.noStroke(); p.ellipse(x, y, size); // Trail if (params.mirror) { c.setAlpha(amp * 100); p.fill(c); const trailX = p.cos(angle - 10) * (radius - 20); const trailY = p.sin(angle - 10) * (radius - 20); p.ellipse(trailX, trailY, size * 0.6); } p.pop(); } } // Bass circle in center const bassAmp = ((spectrum[0] + spectrum[1] + spectrum[2]) / 3 / 255) * params.sensitivity; p.push(); p.colorMode(p.RGB); const c = p.color(palette[0]); c.setAlpha(bassAmp * 200); p.fill(c); p.noStroke(); p.ellipse(0, 0, 50 + bassAmp * 100); p.pop(); } function drawWaveform(palette) { const centerY = p.height / 2; const amplitude = p.height * 0.35 * params.sensitivity; // Draw multiple layers for (let layer = 0; layer < 3; layer++) { p.push(); p.colorMode(p.RGB); const c = p.color(palette[layer % palette.length]); c.setAlpha(200 - layer * 50); p.stroke(c); p.strokeWeight(4 - layer); p.noFill(); p.beginShape(); for (let i = 0; i < waveform.length; i++) { const x = p.map(i, 0, waveform.length, 0, p.width); const y = centerY + waveform[i] * amplitude * (1 - layer * 0.2); p.vertex(x, y); } p.endShape(); p.pop(); } // Mirror reflection if (params.mirror) { p.push(); p.colorMode(p.RGB); const c = p.color(palette[0]); c.setAlpha(50); p.stroke(c); p.strokeWeight(2); p.noFill(); p.beginShape(); for (let i = 0; i < waveform.length; i++) { const x = p.map(i, 0, waveform.length, 0, p.width); const y = centerY - waveform[i] * amplitude * 0.5; p.vertex(x, y); } p.endShape(); p.pop(); } // Add frequency bars at bottom const barHeight = 50; for (let i = 0; i < 32; i++) { const amp = (spectrum[i * 4] / 255) * params.sensitivity; const x = i * (p.width / 32); const h = amp * barHeight; p.push(); p.colorMode(p.RGB); const c = p.color(palette[i % palette.length]); c.setAlpha(150); p.fill(c); p.noStroke(); p.rect(x, p.height - h, p.width / 32 - 2, h); p.pop(); } } function drawSpiral(palette) { p.translate(p.width / 2, p.height / 2); p.rotate(p.frameCount * 0.2); const numPoints = 128; const maxRadius = Math.min(p.width, p.height) * 0.4; p.push(); p.colorMode(p.RGB); p.noFill(); for (let spiral = 0; spiral < 3; spiral++) { const c = p.color(palette[spiral % palette.length]); c.setAlpha(200); p.stroke(c); p.strokeWeight(3); p.beginShape(); for (let i = 0; i < numPoints; i++) { const angle = (i / numPoints) * 360 * 3 + spiral * 120; const baseRadius = (i / numPoints) * maxRadius; const freqIndex = i * 2; const amp = (spectrum[freqIndex] / 255) * params.sensitivity; const radius = baseRadius + amp * 50; const x = p.cos(angle) * radius; const y = p.sin(angle) * radius; p.vertex(x, y); } p.endShape(); } p.pop(); // Center pulse p.push(); p.colorMode(p.RGB); const bassAmp = ((spectrum[0] + spectrum[1]) / 2 / 255) * params.sensitivity; const c = p.color(palette[0]); c.setAlpha(bassAmp * 255); p.fill(c); p.noStroke(); p.ellipse(0, 0, 40 + bassAmp * 60); p.pop(); } function drawTerrain(palette) { const cols = 64; const rows = historyLength; const cellWidth = p.width / cols; const cellHeight = p.height / rows; for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { const amp = history[y][x]; if (amp > 0.05) { const screenX = x * cellWidth; const screenY = y * cellHeight; // Perspective effect const scale = 1 - (y / rows) * 0.5; const offsetX = (p.width / 2 - screenX) * (1 - scale); p.push(); p.colorMode(p.RGB); const colorIndex = Math.floor(amp * palette.length); const c = p.color(palette[colorIndex % palette.length]); const alpha = (1 - y / rows) * 200 * amp; c.setAlpha(alpha); p.fill(c); p.noStroke(); const w = cellWidth * scale; const h = amp * 50 * scale; p.rect(screenX + offsetX, screenY, w - 1, h); p.pop(); } } } // Add horizontal lines for depth for (let y = 0; y < rows; y += 5) { p.push(); p.colorMode(p.RGB); const c = p.color(palette[0]); c.setAlpha(30); p.stroke(c); p.strokeWeight(1); p.line(0, y * cellHeight, p.width, y * cellHeight); p.pop(); } } // 🌿 IVY CUTE - Version p5.js kawaii! 🎤 function drawIvy(palette) { p.push(); p.colorMode(p.RGB); const centerX = p.width / 2; const centerY = p.height / 2; const time = p.frameCount * 0.02; // Get audio levels const bass = ((spectrum[0] + spectrum[1] + spectrum[2] + spectrum[3]) / 4 / 255) * params.sensitivity; const mid = ((spectrum[20] + spectrum[25] + spectrum[30] + spectrum[35]) / 4 / 255) * params.sensitivity; const high = ((spectrum[60] + spectrum[70] + spectrum[80] + spectrum[90]) / 4 / 255) * params.sensitivity; const faceSize = Math.min(p.width, p.height) * 0.28; // Colors const ivyGreen = p.color(34, 197, 94); const skinTone = p.color(255, 220, 195); const hairBrown = p.color(120, 80, 50); const pinkBlush = p.color(255, 180, 190); // === SOFT BACKGROUND GLOW === for (let i = 4; i > 0; i--) { p.noStroke(); p.fill(34, 197, 94, 8 + bass * 15); p.ellipse(centerX, centerY, faceSize * 3 + i * 50 + bass * 80); } // === FLOATING MUSIC NOTES (smaller, cuter) === for (let n = 0; n < 6; n++) { const noteAngle = (n / 6) * p.TWO_PI + time * 0.3; const noteRadius = faceSize * 1.8 + p.sin(time * 1.5 + n) * 20; const noteX = centerX + p.cos(noteAngle) * noteRadius; const noteY = centerY + p.sin(noteAngle) * noteRadius; const noteSize = 10 + (spectrum[n * 20] / 255) * 15 * params.sensitivity; const noteColor = p.color(palette[n % palette.length]); noteColor.setAlpha(120 + (spectrum[n * 20] / 255) * 80); p.fill(noteColor); p.noStroke(); p.ellipse(noteX, noteY, noteSize); } // === HAIR (behind face) - Soft brown waves === p.noStroke(); for (let i = 0; i < 12; i++) { const hairAngle = (i / 12) * p.PI + p.PI * 0.1; const freq = (spectrum[i * 8] / 255) * params.sensitivity; const sway = p.sin(time * 2 + i * 0.4) * 8; const hairX = centerX + p.cos(hairAngle) * (faceSize * 0.85 + sway); const hairY = centerY + p.sin(hairAngle) * (faceSize * 0.9); const hairSize = 35 + freq * 20; p.fill(hairBrown); p.ellipse(hairX, hairY, hairSize, hairSize * 1.3); } // Hair top volume p.fill(hairBrown); p.ellipse(centerX, centerY - faceSize * 0.7, faceSize * 1.6, faceSize * 0.8); // === FACE - Soft oval === p.fill(skinTone); p.noStroke(); p.ellipse(centerX, centerY, faceSize * 1.5, faceSize * 1.7); // Subtle face shading p.fill(255, 200, 170, 50); p.ellipse(centerX - faceSize * 0.3, centerY, faceSize * 0.4, faceSize * 0.8); p.ellipse(centerX + faceSize * 0.3, centerY, faceSize * 0.4, faceSize * 0.8); // === EYES - Anime style, proportionate === const eyeSpacing = faceSize * 0.3; const eyeY = centerY - faceSize * 0.1; const eyeWidth = faceSize * 0.25 + high * 8; const eyeHeight = faceSize * 0.3 + high * 10; // Eye whites p.fill(255); p.ellipse(centerX - eyeSpacing, eyeY, eyeWidth, eyeHeight); p.ellipse(centerX + eyeSpacing, eyeY, eyeWidth, eyeHeight); // Eye outline p.noFill(); p.stroke(80, 60, 50); p.strokeWeight(2); p.ellipse(centerX - eyeSpacing, eyeY, eyeWidth, eyeHeight); p.ellipse(centerX + eyeSpacing, eyeY, eyeWidth, eyeHeight); // Irises - Green! const irisSize = eyeWidth * 0.65; const lookX = p.sin(time * 0.4) * 3; const lookY = p.cos(time * 0.3) * 2; p.noStroke(); p.fill(34, 160, 80); p.ellipse(centerX - eyeSpacing + lookX, eyeY + lookY, irisSize, irisSize); p.ellipse(centerX + eyeSpacing + lookX, eyeY + lookY, irisSize, irisSize); // Pupils const pupilSize = irisSize * 0.45 + bass * 5; p.fill(20, 40, 20); p.ellipse(centerX - eyeSpacing + lookX, eyeY + lookY, pupilSize, pupilSize); p.ellipse(centerX + eyeSpacing + lookX, eyeY + lookY, pupilSize, pupilSize); // Eye sparkles ✨ p.fill(255, 255, 255, 220 + high * 35); p.ellipse(centerX - eyeSpacing - eyeWidth * 0.15, eyeY - eyeHeight * 0.15, 6, 6); p.ellipse(centerX + eyeSpacing - eyeWidth * 0.15, eyeY - eyeHeight * 0.15, 6, 6); // Secondary sparkle p.fill(255, 255, 255, 150); p.ellipse(centerX - eyeSpacing + eyeWidth * 0.1, eyeY + eyeHeight * 0.05, 3, 3); p.ellipse(centerX + eyeSpacing + eyeWidth * 0.1, eyeY + eyeHeight * 0.05, 3, 3); // === EYEBROWS === p.stroke(hairBrown); p.strokeWeight(3); p.noFill(); const browRaise = mid * 8; p.arc(centerX - eyeSpacing, eyeY - eyeHeight * 0.6 - browRaise, eyeWidth * 0.7, 12, p.PI + 0.4, p.TWO_PI - 0.4); p.arc(centerX + eyeSpacing, eyeY - eyeHeight * 0.6 - browRaise, eyeWidth * 0.7, 12, p.PI + 0.4, p.TWO_PI - 0.4); // === BLUSH - Cute rosy cheeks === p.noStroke(); const blushAlpha = 60 + high * 100; p.fill(255, 150, 160, blushAlpha); p.ellipse(centerX - eyeSpacing - eyeWidth * 0.4, eyeY + eyeHeight * 0.7, 25, 15); p.ellipse(centerX + eyeSpacing + eyeWidth * 0.4, eyeY + eyeHeight * 0.7, 25, 15); // === NOSE - Simple cute dot === p.fill(240, 180, 160); p.ellipse(centerX, centerY + faceSize * 0.1, 8, 6); // === MOUTH - Cute smile that opens gently === const mouthY = centerY + faceSize * 0.4; const mouthWidth = faceSize * 0.25 + mid * 15; const mouthOpen = 5 + bass * faceSize * 0.2; // Smile shape p.fill(180, 80, 90); p.noStroke(); if (mouthOpen > 15) { // Open mouth (singing) p.ellipse(centerX, mouthY, mouthWidth, mouthOpen); // Teeth p.fill(255); p.rect(centerX - mouthWidth * 0.35, mouthY - mouthOpen * 0.4, mouthWidth * 0.7, 6, 2); // Tongue hint if (mouthOpen > 25) { p.fill(220, 120, 130); p.ellipse(centerX, mouthY + mouthOpen * 0.2, mouthWidth * 0.5, mouthOpen * 0.3); } } else { // Closed smile p.noFill(); p.stroke(180, 80, 90); p.strokeWeight(3); p.arc(centerX, mouthY - 5, mouthWidth, 15, 0.2, p.PI - 0.2); } // Lip gloss highlight p.noStroke(); p.fill(255, 200, 210, 100); p.ellipse(centerX, mouthY - mouthOpen * 0.3, mouthWidth * 0.3, 4); // === HAIR DECORATIONS - Ivy leaves! 🌿 === // Left leaf p.push(); p.translate(centerX - faceSize * 0.9, centerY - faceSize * 0.7); p.rotate(-0.4 + p.sin(time * 1.5) * 0.1); p.fill(ivyGreen); p.noStroke(); p.ellipse(0, 0, 40 + bass * 15, 18); p.stroke(34, 150, 70); p.strokeWeight(2); p.line(-15, 0, 15, 0); // Leaf veins p.line(-8, -4, 0, 0); p.line(-8, 4, 0, 0); p.line(8, -4, 0, 0); p.line(8, 4, 0, 0); p.pop(); // Right leaf p.push(); p.translate(centerX + faceSize * 0.9, centerY - faceSize * 0.7); p.rotate(0.4 - p.sin(time * 1.5) * 0.1); p.fill(ivyGreen); p.noStroke(); p.ellipse(0, 0, 40 + bass * 15, 18); p.stroke(34, 150, 70); p.strokeWeight(2); p.line(-15, 0, 15, 0); p.line(-8, -4, 0, 0); p.line(-8, 4, 0, 0); p.line(8, -4, 0, 0); p.line(8, 4, 0, 0); p.pop(); // === SOUND WAVES (subtle) === if (bass > 0.2) { for (let w = 0; w < 3; w++) { const waveProgress = (time * 1.5 + w * 0.33) % 1; const waveRadius = 20 + waveProgress * 60; const waveAlpha = (1 - waveProgress) * 100 * bass; p.noFill(); p.stroke(255, 180, 200, waveAlpha); p.strokeWeight(2); p.arc(centerX, mouthY, waveRadius * 2, waveRadius, 0.3, p.PI - 0.3); } } // === NAME TAG === p.noStroke(); p.fill(34, 197, 94); p.textSize(16); p.textAlign(p.CENTER, p.CENTER); p.text("🌿 Ivy", centerX, centerY + faceSize * 1.5); p.pop(); } // === NEW STYLES === let galaxyStars = []; function drawGalaxy(palette) { p.translate(p.width / 2, p.height / 2); // Initialize stars if (galaxyStars.length === 0) { for (let i = 0; i < 200; i++) { const angle = p.random(p.TWO_PI); const radius = p.random(50, Math.min(p.width, p.height) * 0.45); galaxyStars.push({ angle: angle, radius: radius, speed: p.random(0.001, 0.005), size: p.random(1, 4), color: palette[p.floor(p.random(palette.length))] }); } } const bassBoost = params.bassBoost; let bassLevel = 0; for (let i = 0; i < 10; i++) { bassLevel += spectrum[i] / 255; } bassLevel = (bassLevel / 10) * params.sensitivity * bassBoost; // Rotate and draw stars for (let star of galaxyStars) { star.angle += star.speed * (1 + level * 2); const spiralOffset = star.radius * 0.02; const x = p.cos(star.angle + spiralOffset) * (star.radius + bassLevel * 30); const y = p.sin(star.angle + spiralOffset) * (star.radius + bassLevel * 30) * 0.6; p.push(); p.colorMode(p.RGB); const c = p.color(star.color); c.setAlpha(150 + level * 100); p.fill(c); p.noStroke(); p.ellipse(x, y, star.size + bassLevel * 3); p.pop(); } // Center glow for (let i = 5; i >= 0; i--) { p.push(); p.colorMode(p.RGB); const c = p.color(palette[0]); c.setAlpha(30 - i * 5); p.fill(c); p.noStroke(); p.ellipse(0, 0, 50 + i * 30 + bassLevel * 50); p.pop(); } } let fireworksParticles = []; function drawFireworks(palette) { const bassBoost = params.bassBoost; // Launch new firework on bass hit let bassLevel = 0; for (let i = 0; i < 10; i++) { bassLevel += spectrum[i] / 255; } bassLevel = (bassLevel / 10) * params.sensitivity * bassBoost; if (bassLevel > 0.5 && p.random() > 0.7) { const x = p.random(p.width * 0.2, p.width * 0.8); const y = p.random(p.height * 0.2, p.height * 0.5); const color = palette[p.floor(p.random(palette.length))]; for (let i = 0; i < 30; i++) { const angle = p.random(p.TWO_PI); const speed = p.random(2, 8); fireworksParticles.push({ x: x, y: y, vx: p.cos(angle) * speed, vy: p.sin(angle) * speed, life: 1, color: color, size: p.random(3, 8) }); } } // Update and draw particles for (let i = fireworksParticles.length - 1; i >= 0; i--) { const particle = fireworksParticles[i]; particle.x += particle.vx; particle.y += particle.vy; particle.vy += 0.1; // Gravity particle.life -= 0.015; if (particle.life <= 0) { fireworksParticles.splice(i, 1); continue; } p.push(); p.colorMode(p.RGB); const c = p.color(particle.color); c.setAlpha(particle.life * 255); p.fill(c); p.noStroke(); p.ellipse(particle.x, particle.y, particle.size * particle.life); p.pop(); // Trail if (particle.life > 0.5) { p.push(); p.colorMode(p.RGB); const tc = p.color(particle.color); tc.setAlpha(particle.life * 100); p.stroke(tc); p.strokeWeight(1); p.line(particle.x, particle.y, particle.x - particle.vx * 2, particle.y - particle.vy * 2); p.pop(); } } } function drawKaleidoscope(palette) { p.translate(p.width / 2, p.height / 2); const segments = 12; const bassBoost = params.bassBoost; for (let seg = 0; seg < segments; seg++) { p.push(); p.rotate((seg / segments) * p.TWO_PI); if (seg % 2 === 1) { p.scale(-1, 1); } // Draw frequency bars in each segment const barsPerSegment = 16; for (let i = 0; i < barsPerSegment; i++) { const freqIndex = i * 4; let amp = (spectrum[freqIndex] / 255) * params.sensitivity; // Apply bass boost to low frequencies if (i < 4) amp *= bassBoost; const barWidth = 10; const barHeight = amp * 150; const x = 50 + i * 12; p.push(); p.colorMode(p.RGB); const c = p.color(palette[(seg + i) % palette.length]); c.setAlpha(180); p.fill(c); p.noStroke(); // Triangular shape p.beginShape(); p.vertex(x, 0); p.vertex(x + barWidth, 0); p.vertex(x + barWidth / 2, -barHeight); p.endShape(p.CLOSE); p.pop(); } p.pop(); } // Center mandala const centerSize = 40 + level * 30; for (let i = 3; i >= 0; i--) { p.push(); p.colorMode(p.RGB); const c = p.color(palette[i % palette.length]); c.setAlpha(150 - i * 30); p.fill(c); p.noStroke(); p.ellipse(0, 0, centerSize + i * 15); p.pop(); } } p.windowResized = function () { p.resizeCanvas(self.container.clientWidth, self.container.clientHeight); }; }; this.p5Instance = new p5(sketch); } dispose() { this.stop(); } } // Export window.P5AudioRenderer = P5AudioRenderer;