let averagingAmt = 0.01;
let circles = [];
let maxCircles = 200;
let frameInterval = 300;
let currentStep = 0;
let mic;
let breathingRadiusOffset = 0;
let micStarted = false;
let startButton;

function setup() {
  createCanvas(600, 600).parent('header');

  // Create "Start" button for mic permission
  startButton = createButton('Click to allow microphone access and start the sound visualization');
  startButton.position((windowWidth / 2) - 200, windowHeight / 3, 'relative');
  startButton.size(400, 60);
  startButton.style('font-size', '20px')
             .style('color', '#988a6b')
             .style('background-color', '#f1f1f1')
             .style('text-align', 'center')
             .style('border', '2px #8e653c')
             .style('transition-duration', '0.4s');

  startButton.mouseOver(() => {
    startButton.style('background-color', "#ffffff");
    startButton.style('border', '4px #988a6b');
  });

  startButton.mouseOut(() => {
    startButton.style('background-color', "#f1f1f1");
    startButton.style('border', '2px #8e653c');
  });

  startButton.mousePressed(() => {
    getAudioContext().resume().then(() => {
      mic = new p5.AudioIn();
      mic.start();
      micStarted = true;
      startButton.hide();
    });
  });
}

function draw() {
  if (!micStarted) return; // Wait for user to start

  background(0, 10);

  // Define periodic loop
  let period = 3600;
  let t = frameCount % period;

  translate(width / 2, height / 2);
  rotate(-PI / 2); // Move the starting point to the 12 o'clock direction
  translate(-width / 2, -height / 2);

  // Small circle generation
  if (frameCount % frameInterval == 0 && circles.length < maxCircles) {
    let angle = radians(currentStep * 360);
    let radius = 0;
    let x = width / 2 + cos(-angle) * radius;
    let y = height / 2 + sin(-angle) * radius;
    circles.push(new Circle(x, y));
    currentStep = (currentStep + 1) % 200;
  }

  // Draw particles
  for (let i = circles.length - 1; i >= 0; i--) {
    let circle = circles[i];
    circle.update();
    circle.display();

    if (circle.opacity <= 0) {
      circles.splice(i, 1);
    }
  }

  // Sound-controlled amplitude
  let amplitude = mic.getLevel() * 1000;

  // Breathing contraction effect
  breathingRadiusOffset = map(sin(frameCount * 0.015), -1, 1, -180, 30);

  NoisyShape.draw(
    width / 2,
    height / 2,
    360,
    200 + breathingRadiusOffset,
    amplitude
  );
}

// ------------------ Class Definitions ------------------

class Circle {
    constructor(x, y) {
      this.x = x;
      this.y = y;
      this.r = random(10, 30); // Random particle size
      this.maxOpacity = 255; // Maximum opacity
      this.opacity = 0; // Initial opacity
      this.fadeSpeed = random(2, 5); // Opacity change speed
    }
  
    update() {
      // Dynamically adjust opacity (fade in and out effect)
      this.opacity += this.fadeSpeed;
      if (this.opacity > this.maxOpacity || this.opacity < 0) {
        this.fadeSpeed *= -1; // Reverse the fade direction when reaching max/min opacity
      }
    }
  
    display() {
      noStroke(); // Remove stroke
  
      // Add blur glow effect
      drawingContext.shadowBlur = 10; // Glow intensity
      drawingContext.shadowColor = color(255,0,0, this.opacity); // Glow color and opacity
      fill(255, this.opacity); // Particle color
      ellipse(this.x, this.y, this.r, this.r); // Draw particle
    }
  }

class NoisyShape {
    static draw(ox, oy, vertNum, radius, noiseAmplitude) {
      let angleStep = TWO_PI / vertNum;
      let noiseOffset = frameCount * 0.08;  // Control noise variation over time
  
      push();
      translate(ox, oy);
  
      noFill();
      stroke(255, 150); // Soft white ripple effect
      strokeWeight(1);
      beginShape();
  
      for (let vn = 0; vn <= vertNum; vn++) {
        let angle = vn * angleStep;
        let baseX = cos(angle) * radius; // Base circle x-coordinate
        let baseY = sin(angle) * radius; // Base circle y-coordinate
  
        // Add noise effect to generate irregular ripples
        let noiseValue = noise(vn * 0.2, noiseOffset); // Noise changes with position and time
        let offset = noiseValue * noiseAmplitude; // Noise-controlled offset
  
        // Generate irregular waves through noise
        let finalX = baseX + cos(angle) * offset;
        let finalY = baseY + sin(angle) * offset;
  
        curveVertex(finalX, finalY); // Generate ripple vertices
      }
  
      endShape(CLOSE); // Close shape
      pop();
    }
  }
