ivy-gpu-art-studio / js /p5audio-renderer.js
Elysia-Suite's picture
Upload 23 files
e5d943e verified
/**
* 🌿 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;