function drawRibbon(bass, mid) {
    let spacing = 15;
    let numLines = 8;

    for (let i = 0; i < numLines; i++) {
        let offset = (i - numLines / 2) * spacing;

        beginShape();
        for (let x = -width / 2; x < width / 2; x += 5) {
            // Additive synthesis: fundamental + harmonics
            let y = 0;
            let baseFreq = 0.01;
            let baseAmp = map(bass, 0, 255, 30, 100);

            // Fundamental frequency
            y += sin(x * baseFreq + time + i * 0.3) * baseAmp;

            // 2nd harmonic (octave)
            y += sin(x * baseFreq * 2 + time + i * 0.3) * (baseAmp * 0.5);

            // 3rd harmonic
            y += sin(x * baseFreq * 3 + time * 1.2 + i * 0.3) * (baseAmp * 0.33);

            // 4th harmonic (with mid frequency)
            y += sin(x * baseFreq * 4 - time * 0.7) * map(mid, 0, 255, 10, 30);

            // 5th harmonic (subtle)
            y += sin(x * baseFreq * 5 + time * 1.5) * (baseAmp * 0.2);

            y += offset;

            vertex(x, y);
        }
        endShape();
    }
}

											

												
function draw() {

    // Get audio analysis
    let bass = fft.getEnergy("bass");
    let mid = fft.getEnergy("mid");
    let treble = fft.getEnergy("treble");

    // Map audio to visual parameters
    let bassNorm = map(bass, 0, 255, 0, 1);
    let midNorm = map(mid, 0, 255, 0, 1);
    let trebleNorm = map(treble, 0, 255, 0, 1);

    // Keep colors between blue (240°) and pink (320°)
    let hueRange = 80; // 320 - 240 = 80 degrees
    let baseHue = 240; // Starting from blue
    let hue = baseHue + (frameCount * 0.2 + bassNorm * 20) % hueRange;

    colorMode(HSB, 310, 180, 100);

    // Dark base background - use specific color
    colorMode(RGB, 255);
    background("#03272eff");
    colorMode(HSB, 310, 180, 100);

    push();

    // Draw concentric circles pattern (Kusama's infinity nets style)
    noFill();

    for (let tile of tiles) {
        let tileHue = (hue + tile.x * 0.05 + tile.y * 0.05) % 360;

        push();
        translate(tile.x + tileSize / 2, tile.y + tileSize / 2);

        // Stroke weight responds to mid frequencies
        strokeWeight(2 + midNorm * 3);

        // Truchet quarter circles creating organic flow
        stroke(tileHue, 90, 70, 0.3 + trebleNorm * 0.4);

        // Arc size pulses with bass
        let arcScale = 1.5 + bassNorm * 0.3;

        switch (tile.type) {
            case 0:
                // Quarter circle top-left
                arc(-tileSize / 2, -tileSize / 2, tileSize * arcScale, tileSize * arcScale, 0, 90);
                break;
            case 1:
                // Quarter circle top-right
                arc(tileSize / 2, -tileSize / 2, tileSize * arcScale, tileSize * arcScale, 90, 180);
                break;
            case 2:
                // Quarter circle bottom-right
                arc(tileSize / 2, tileSize / 2, tileSize * arcScale, tileSize * arcScale, 180, 270);
                break;
            case 3:
                // Quarter circle bottom-left
                arc(-tileSize / 2, tileSize / 2, tileSize * arcScale, tileSize * arcScale, 270, 360);
                break;
        }

        // Polka dots in each tile - size responds to treble
        randomSeed(tile.x * 1000 + tile.y);
        let numDots = 2;
        for (let i = 0; i < numDots; i++) {
            let dotX = random(-tileSize / 2, tileSize / 2);
            let dotY = random(-tileSize / 2, tileSize / 2);
            let dotSize = random(8, 15);

            // Pulse with music
            dotSize += bassNorm * 5 + trebleNorm * 3;

            noStroke();
            // Keep dot hue within blue-to-pink range (240° to 320°)
            let dotHue = baseHue + ((tileHue - baseHue + 60) % hueRange);
            fill(dotHue, 100, 90, 0.7 + midNorm * 0.2);
            circle(dotX, dotY, dotSize);
        }
        randomSeed();

        pop();
    }

    pop();
}
											

												
function draw() {
  noFill()

  // Only bass influences the bezier control points
  if (bass >= limit) {
    x1 = noise(offset + 5) * width;
    y1 = noise(offset + 10) * height;
    x2 = noise(offset + 15) * width;
    y2 = noise(offset + 20) * height;
    x3 = noise(offset + 25) * width;
    y3 = noise(offset + 30) * height;
    x4 = noise(offset + 35) * width;
    y4 = noise(offset + 40) * height;
    bezier(x1, y1, x2, y2, x3, y3, x4, y4);
    offset += 0.01; // Slower, fixed animation speed
  }

}
											

												
function draw() {
    const kickDrum = fft.getEnergy(80,100)
    const kickEnergy = map(kickDrum, 0, 255, 0, 100);

    flying -= 0.01;
    var yoff = flying;

    let terrainHeight = map(kickEnergy, 0, 100, 80, 200);

    for (var y = 0; y < rowsTerrain; y++) {
        var xoff = 0;
        for (var x = 0; x < colsTerrain; x++) {
            terrain[x][y] = map(noise(xoff, yoff), 0, 1, -terrainHeight, terrainHeight);
            xoff += 0.2;
        }
        yoff += 0.2;
    }

    push()
    stroke(255);
    strokeWeight(0.5);
    fill(200, 200, 200, 150);
    rotateX(PI / 3);

    for (var y = 0; y < rowsTerrain - 1; y++) {
        beginShape(QUAD_STRIP);
        const colorA = 160 //teal
        const colorB = 240 //blue
        let hue = map(y, 0, rowsTerrain - 1, colorA, colorB);

        for (var x = 0; x < colsTerrain; x++) {
            let sat = 100;
            let brightness = 220;
            fill(hue, sat, brightness, 150);
            vertex(x * scl, y * scl, terrain[x][y]);
            vertex(x * scl, (y + 1) * scl, terrain[x][y + 1]);
        }
        endShape();
    }
    pop()
}
											

												
function draw() {
    const bass = fft.getEnergy("bass");
    const treble = fft.getEnergy(80, 180);
    const mid = fft.getEnergy("mid");
    const lowMid = fft.getEnergy("highMid");

    const mapBass = map(bass, 0, 255, 20, 170);
    const mapTreble = map(treble, 0, 255, 10, 190);
    const mapMid = map(mid, 0, 255, 20, 210);
    const mapLowMid = map(lowMid, 0, 255, 90, 150);

    // Draw all blobs with irregular positions
    for (let blob of blobs) {
        // Select audio value based on blob's type
        let audioValue;
        switch (blob.audioType) {
            case 0: audioValue = mapBass; break;
            case 1: audioValue = mapTreble; break;
            case 2: audioValue = mapMid; break;
            case 3: audioValue = mapLowMid; break;
        }

        drawBlob(blob, audioValue);
    }
}

function drawBlob(blob, audioInfluence) {
    noFill();

    // Map audio influence to stroke weight (thickness)
    const strokeThickness = map(audioInfluence, 0, 255, 0.2, 5);
    strokeWeight(strokeThickness);

    beginShape();

    for (let i = 0; i < blob.numPoints; i++) {
        const angle = map(i, 0, blob.numPoints, 0, TWO_PI) + frameCount * blob.rotationSpeed;

        // Add MORE irregularity with noise for asymmetric shapes
        const noiseVal = noise(
            blob.noiseOffsetX + cos(angle) * 0.5,
            blob.noiseOffsetY + sin(angle) * 0.5,
            frameCount * 0.008
        );
        const irregularity = map(noiseVal, 0, 1, 0.5, 1.5); // Much more variation

        // Radius influenced by audio
        const audioMod = map(audioInfluence, 0, 255, 0.7, 1.4);
        const r = blob.baseSize * irregularity * audioMod;

        const px = blob.x + cos(angle) * r;
        const py = blob.y + sin(angle) * r;

        vertex(px, py);
    }

    endShape(CLOSE);
}
											

												
function draw() {
  const bass = fft.getEnergy("bass");
  const treble = fft.getEnergy(100, 150);
  const mid = fft.getEnergy("mid");

  const mapBass = map(bass, 0, 255, 20, 50);
  const mapTreble = map(treble, 0, 255, 10, 70);
  const mapMid = map(mid, 0, 255, 20, 80);

  blendMode(BLEND)
  background("#002b36");
  blendMode(SCREEN)

  drawWaveWithBackground(waves[0], colorPalette[1], 0, mapBass);
  drawWaveWithBackground(waves[3], colorPalette[3], 0, mapTreble);
  drawWaveDotted(waves[1], colorPalette[2], 1, mapTreble);
  drawWaveStripes(waves[2], colorPalette[0], 5, mapMid);

  stroke('white');
  strokeWeight(2);
  line(0, height / 2, width, height / 2)
  noStroke();
}

function drawWaveStripes(waves, color, size, amp) {
  stroke(color)

  for (let x = 0; x < width; x += 5) {
    let y = 0;
    for (let wave of waves) {
      y += wave.evaluate(x, amp);
    }

    ellipse(x, y + height / 2, 2);
    line(x, height / 2, x, y + height / 2)
  }

  for (let wave of waves) {
    wave.update();
  }
}

function drawWaveDotted(waves, color, size, amp) {
  stroke(color)

  for (let x = 0; x < width; x += 8) {
    let y = 0;
    for (let wave of waves) {
      y += wave.evaluate(x, amp);
    }

    ellipse(x, y + height / 2, 2);
  }

  for (let wave of waves) {
    wave.update();
  }
}

function drawWaveWithBackground(waves, color, size, amp) {
  fill(color);
  beginShape();
  vertex(0, yAxis);
  let previousY = 0;

  for (let x = 0; x < width; x += 1) {
    let y = 0;
    for (let wave of waves) {
      y += wave.evaluate(x, amp);
    }
    y += height / 2;

    // crossed y-axis, ending and beginning a new shape
    if ((y > yAxis && previousY < yAxis) || (y < yAxis && previousY > yAxis)) {
      vertex(x, yAxis);
      endShape();
      beginShape();
      vertex(x, yAxis);
    }
    // continuing the shape
    else {
      vertex(x, y);
    }
    previousY = y;
  }
  vertex(width, yAxis);
  endShape();

  for (let wave of waves) {
    wave.update();
  }
}
											

												
function draw() {
    const bass = fft.getEnergy("bass");
    const treble = fft.getEnergy(80, 180);
    const mid = fft.getEnergy("mid");
    const lowMid = fft.getEnergy("highMid");

    const mapBass = map(bass, 0, 255, 20, 170);
    const mapTreble = map(treble, 0, 255, 10, 190);
    const mapMid = map(mid, 0, 255, 20, 210);
    const mapLowMid = map(lowMid, 0, 255, 40, 180);

    // Draw center reference circle
    noFill();
    ellipse(centerX, centerY, baseRadius * 2, baseRadius * 2);

    // Position each wave group at different locations
    drawCircle({
        color: colorPalette[0],
        size: 5,
        lineLength: 190,
        baseRadius: 50,
        randomVal: 0,
        amp: mapMid,
        x: width * 0.5,
        y: height * 0.55,
        shouldRotate: true
    });

    drawCircle({
        color: colorPalette[2],
        size: 7,
        lineLength: 35,
        baseRadius: 35,
        randomVal: 0,
        amp: mapLowMid,
        x: width * 0.38,
        y: height * 0.21,
        shouldRotate: false 
    });

    drawCircle({
        color: colorPalette[1],
        size: 3,
        lineLength: 30,
        baseRadius: 30,
        randomVal: 3,
        amp: mapTreble,
        x: width / 2 + sin(oscillationAngle) * 15, // Oscillate horizontally
        y: height * 0.18 + cos(oscillationAngle) * 10, // Oscillate vertically
        shouldRotate: false
    });

    drawCircle({
        color: color[3],
        size: 2,
        lineLength: 32,
        baseRadius: 20,
        randomVal: 2,
        amp: mapBass,
        x: width * 0.30,
        y: height * 0.7,
        shouldRotate: false 
    });

    // Increment rotation angle for next frame
    rotationAngle += 0.01;

    // Increment oscillation angle for smooth movement
    oscillationAngle += 0.02;

}

function drawCircle(config) {
    const {
        color: colorStr,
        size,
        lineLength,
        baseRadius,
        randomVal,
        amp,
        x: posX,
        y: posY,
        shouldRotate = false
    } = config;

    strokeWeight(size / 4);

    // Draw center circle with radial gradient
    noStroke();
    push(); // Save current color mode
    
    fill(col);
    ellipse(posX, posY, r * 2, r * 2);

    // Draw lines with fixed length and same alpha based on amp
    // Map amp to alpha (0-255) - all lines will have this same alpha
    let alpha = map(amp, 10, 80, 10, 255);

    push();

    colorMode(RGB);
    let col = color(colorStr);
    col.setAlpha(alpha);
    stroke(col);

    let noiseOffset = 0;
    // Add rotation offset if shouldRotate is true
    let angleOffset = shouldRotate ? rotationAngle : 0;

    for (let angle = 0; angle < TWO_PI; angle += 0.05) {
        // Apply rotation offset to the angle
        let rotatedAngle = angle + angleOffset;
        let x = posX + cos(rotatedAngle) * baseRadius;
        let y = posY + sin(rotatedAngle) * baseRadius;

        line(posX + cos(rotatedAngle) * baseRadius, posY + sin(rotatedAngle) * baseRadius, x, y);
    }
    pop();
}
											

												
function draw() {
    var treble = fft.getEnergy("treble");

    // Add gentle time-based variation
    time += 0.01;
    var trebleFreq = treble + sin(time * 30) * 0.5;

    // Use hue based on treble, but in the coral/red range like #D86072
    let hue = map(smoothedTreble, 0, 255, 340, 20); // Range around the coral color hue
    stroke(hue, 80, 88);

    SphericalLissajous(trebleFreq);
}

function SphericalLissajous(freq) {
    let step = 1.0;

    beginShape();
    noFill();

    for (let theta = 0; theta < 720; theta += step) { 
        let x = r * cos(theta * freq);
        let y = r * sin(theta * freq) * sin(theta * 2);
        let z = r * sin(theta * freq) * cos(theta * 2);

        vertex(x, y, z);
    }
    endShape();
}