/**
 * Track class - Improved version
 * 
 * Replace this entire class in TrackManager.pde
 */
class Track {
  int orbit;
  color trackColor;
  boolean active;
  float activity;
  float lastTriggerTime;
  String lastSound;
  ArrayList<Float> historyGain;
  
  // Visual properties
  float baseHeight;
  float targetHeight;
  float currentHeight;
  
  Track(int orbit) {
    this.orbit = orbit;
    this.trackColor = orbitColors[orbit];
    this.active = false;
    this.activity = 0;
    this.lastTriggerTime = -1000;
    this.lastSound = "";
    
    this.historyGain = new ArrayList<Float>();
    
    // Visual initialization
    this.baseHeight = height / 20.0;  // Changed from 24.0
    this.targetHeight = baseHeight;
    this.currentHeight = baseHeight;
  }
  
  void update() {
    // Faster decay for better responsiveness to new events
    activity *= 0.95;
    
    // Update height with smooth animation
    currentHeight = lerp(currentHeight, targetHeight, 0.15);
    
    // Reset target height if activity is low
    if (activity < 0.02) {
      targetHeight = baseHeight;
      active = false;
    }
  }
  
  void display() {
    // Only display if activity is above threshold
    if (activity < 0.02) return;
    
    // Better vertical distribution
    float yPos = map(orbit, 0, 15, height * 0.05, height * 0.95);
    float trackWidth = width * 0.9;
    float xOffset = width * 0.05;
    
    // Draw minimal track background - VERY subtle
    noStroke();
    fill(red(trackColor), green(trackColor), blue(trackColor), activity * 40);  // Very low alpha
    rect(xOffset, yPos - currentHeight/2, trackWidth, currentHeight, 5);
    
    // Draw minimal edge - just one subtle stroke
    stroke(trackColor, activity * 80);  // Low alpha
    strokeWeight(1);  // Thin line
    noFill();
    rect(xOffset, yPos - currentHeight/2, trackWidth, currentHeight, 5);
    
    // Draw activity meter - the most visible part
    float meterWidth = map(activity, 0, 1, 0, trackWidth * 0.8);  // 80% of track width
    noStroke();
    fill(trackColor, activity * 180);  // Higher alpha for visibility
    rect(xOffset + (trackWidth * 0.1), yPos - currentHeight/4, meterWidth, currentHeight/4, 3);
    
    // Add orbit label for active tracks
    if (activity > 0.2) {  // Lower threshold for visibility
      fill(255, activity * 220);
      textAlign(LEFT, CENTER);
      textSize(12);
      text("d" + (orbit + 1), xOffset + 10, yPos);
    }
  }
  
  void onSound(String sound, float gain) {
    // Update track state
    active = true;
    lastTriggerTime = millis();
    lastSound = sound;
    activity = 1.0;
    
    // Store gain history (for visual patterns)
    historyGain.add(gain);
    if (historyGain.size() > 16) {
      historyGain.remove(0);
    }
    
    // Update visual properties - more dramatic height change
    targetHeight = baseHeight + (gain * baseHeight * 3);
  }
  
  boolean isActive() {
    return active;
  }
  
  void reset() {
    activity = 0;
    historyGain.clear();
    currentHeight = baseHeight;
    targetHeight = baseHeight;
    active = false;
  }
}

/**
 * Track class - Improved version
 * 
 * Represents a single TidalCycles track (d1-d16)
 * Enhanced visibility and layout
 */
class Track {
  int orbit;
  color trackColor;
  boolean active;
  float activity;
  float lastTriggerTime;
  String lastSound;
  ArrayList<Float> historyGain;
  
  // Visual properties
  float baseHeight;
  float targetHeight;
  float currentHeight;
  
  Track(int orbit) {
    this.orbit = orbit;
    this.trackColor = orbitColors[orbit];
    this.active = false;
    this.activity = 0;
    this.lastTriggerTime = -1000;
    this.lastSound = "";
    
    this.historyGain = new ArrayList<Float>();
    
    // Visual initialization - INCREASED heights for better visibility
    this.baseHeight = height / 24.0;  // Changed from 32.0
    this.targetHeight = baseHeight;
    this.currentHeight = baseHeight;
  }
  
  void update() {
    // Slower decay for better visibility
    activity *= 0.97;  // Changed from 0.95
    
    // Update height with smooth animation
    currentHeight = lerp(currentHeight, targetHeight, 0.2);
    
    // Reset target height if activity is low
    if (activity < 0.05) {  // Changed from 0.1
      targetHeight = baseHeight;
      active = false;
    }
  }
  
    
  void display() {
    if (activity < 0.02) return; // Lower threshold for visibility
    
    // Better vertical distribution 
    float yPos = map(orbit, 0, 15, height * 0.05, height * 0.95);
    float trackWidth = width * 0.9;
    float xOffset = width * 0.05;
    
    // Draw track background with trail effect - MUCH subtler
    noStroke();
    fill(red(trackColor), green(trackColor), blue(trackColor), activity * 60);  // Reduced from 180
    rect(xOffset, yPos - currentHeight/2, trackWidth, currentHeight, 5);
    
    // Draw glowing edge - subtler
    stroke(trackColor, activity * 160);  // Reduced from 255
    strokeWeight(1 + activity);  // Reduced from 2+activity
    noFill();
    rect(xOffset, yPos - currentHeight/2, trackWidth, currentHeight, 5);
    
    // Draw activity meter - MORE prominent than track
    float meterWidth = map(activity, 0, 1, 0, trackWidth);
    noStroke();
    fill(trackColor, activity * 200);  // Actually brighter than track background
    rect(xOffset, yPos - currentHeight/3, meterWidth, currentHeight/3, 5);
    
    // Add orbit label for active tracks
    if (activity > 0.3) {  // Lower threshold from 0.5
      fill(255, activity * 255);
      textAlign(LEFT, CENTER);
      textSize(12);
      text("d" + (orbit + 1), xOffset + 10, yPos);
    }
  }
  
  void drawGlow(float x, float y, float w, float h) {
    // More subtle glow effect
    for (int i = 0; i < 3; i++) {  // Reduced from 5 layers
      float alpha = map(i, 0, 2, activity * 70, 0);  // Reduced from 150
      stroke(red(trackColor), green(trackColor), blue(trackColor), alpha);
      strokeWeight(i * 2 + 2);  // Reduced from i*2+3
      noFill();
      rect(x, y - currentHeight/2, w, currentHeight, 5);
    }
  }
  
  void onSound(String sound, float gain) {
    // Update track state
    active = true;
    lastTriggerTime = millis();
    lastSound = sound;
    activity = 1.0;
    
    // Store gain history (for visual patterns)
    historyGain.add(gain);
    if (historyGain.size() > 16) {
      historyGain.remove(0);
    }
    
    // Update visual properties - MORE DRAMATIC HEIGHT CHANGE
    targetHeight = baseHeight + (gain * baseHeight * 4);  // Changed from 2
  }
  
  boolean isActive() {
    return active;
  }
  
  void reset() {
    activity = 0;
    historyGain.clear();
    currentHeight = baseHeight;
    targetHeight = baseHeight;
    active = false;
  }
}

/**
 * SoundEvent class - Enhanced version
 * 
 * Replace this in TrackManager.pde to make events much more visible
 */
class SoundEvent {
  int orbit;
  String sound;
  float gain;
  float pan;
  float delta;
  float birthTime;
  float lifespan;
  color eventColor;
  
  // Visual properties
  float size;
  float alpha;
  PVector position;
  PVector velocity;  // Add movement to events
  
  SoundEvent(int orbit, String sound, float gain, float pan, float delta) {
    this.orbit = orbit;
    this.sound = sound;
    this.gain = gain;
    this.pan = pan;
    this.delta = delta;
    this.birthTime = millis();
    this.lifespan = 1200 + (gain * 800);  // Longer lifespan
    
    // Initialize visuals with enhanced properties
    this.eventColor = orbitColors[orbit];
    this.size = 40 + (gain * 100);  // Much larger size
    this.alpha = 255;
    
    // Better spread across the screen
    float xPos = map(pan, 0, 1, width * 0.15, width * 0.85);
    float yPos = map(orbit, 0, 15, height * 0.1, height * 0.9);
    this.position = new PVector(xPos, yPos);
    
    // Add some gentle movement to the events
    float angle = random(TWO_PI);
    float speed = random(0.1, 0.5);  // Slow drift
    this.velocity = new PVector(cos(angle) * speed, sin(angle) * speed);
  }
  
  void update() {
    // Calculate age
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Update position with velocity for gentle movement
    position.add(velocity);
    
    // Slow down over time
    velocity.mult(0.98);
    
    // Bounce off screen edges
    if (position.x < width * 0.1 || position.x > width * 0.9) {
      velocity.x *= -0.9;
    }
    if (position.y < height * 0.05 || position.y > height * 0.95) {
      velocity.y *= -0.9;
    }
    
    // Slower fade out
    alpha = map(age, 0, lifespan, 255, 0);
    
    // Grow size slightly over time
    size = (40 + (gain * 100)) * (1 + (progress * 0.3));
  }
  
  void display() {
    if (alpha <= 0) return;
    
    // Draw the event with enhanced glow
    noStroke();
    
    // Draw outer glow layers
    for (int i = 5; i > 0; i--) {
      float glowSize = size * (1 + i * 0.15);
      float glowAlpha = alpha * (0.1 + (1.0 - i/5.0) * 0.2);
      fill(red(eventColor), green(eventColor), blue(eventColor), glowAlpha);
      ellipse(position.x, position.y, glowSize, glowSize);
    }
    
    // Draw core with full brightness
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.8);
    ellipse(position.x, position.y, size, size);
    
    // Add bright center
    fill(255, alpha * 0.6);
    ellipse(position.x, position.y, size * 0.3, size * 0.3);
  }
  
  boolean isDone() {
    return (millis() - birthTime) > lifespan;
  }
}
/**
 * Specialized event classes with enhanced visuals
 * 
 * Add these to TrackManager.pde
 */

/**
 * KickEvent - Enhanced for strong impact
 */
class KickEvent extends SoundEvent {
  KickEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 800 + (gain * 400);  // Longer for kicks
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw multiple shock wave rings
    for (int i = 0; i < 3; i++) {
      float waveProgress = constrain(progress - (i * 0.1), 0, 1);
      if (waveProgress > 0) {
        noFill();
        stroke(red(eventColor), green(eventColor), blue(eventColor), 
               alpha * (1 - waveProgress) * 0.7);
        strokeWeight(3 + (gain * 3) * (1 - waveProgress));
        ellipse(position.x, position.y, 
                size * (1 + waveProgress * 3), 
                size * (1 + waveProgress * 3));
      }
    }
    
    // Draw solid core
    noStroke();
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    ellipse(position.x, position.y, size * (1 - progress * 0.5), size * (1 - progress * 0.5));
    
    // Add bright center
    fill(255, alpha * 0.7);
    ellipse(position.x, position.y, size * 0.3 * (1 - progress * 0.5), size * 0.3 * (1 - progress * 0.5));
  }
}

/**
 * SnareEvent - Enhanced with particles
 */
class SnareEvent extends SoundEvent {
  ArrayList<PVector> particles;
  ArrayList<Float> particleSizes;
  
  SnareEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 1000 + (gain * 500);  // Longer for snares
    
    // Create particles for snare effect
    particles = new ArrayList<PVector>();
    particleSizes = new ArrayList<Float>();
    int particleCount = int(15 + (gain * 25));  // More particles
    
    for (int i = 0; i < particleCount; i++) {
      float angle = random(TWO_PI);
      float speed = random(1, 6);  // Faster particles
      particles.add(new PVector(cos(angle) * speed, sin(angle) * speed));
      particleSizes.add(random(3, 8 + (gain * 6)));  // Varied sizes
    }
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw particles
    noStroke();
    for (int i = 0; i < particles.size(); i++) {
      PVector p = particles.get(i);
      float size = particleSizes.get(i);
      
      float x = position.x + (p.x * age * 0.15);  // Faster movement
      float y = position.y + (p.y * age * 0.15);
      float particleSize = size * (1 - progress * 0.8);
      
      // Draw particle with glow
      fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.2);
      ellipse(x, y, particleSize * 2, particleSize * 2);
      
      fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.7);
      ellipse(x, y, particleSize, particleSize);
    }
    
    // Draw core
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.8);
    ellipse(position.x, position.y, size * 0.5 * (1 - progress * 0.7), size * 0.5 * (1 - progress * 0.7));
    
    // Add bright center
    fill(255, alpha * 0.6);
    ellipse(position.x, position.y, size * 0.2 * (1 - progress * 0.7), size * 0.2 * (1 - progress * 0.7));
  }
}

/**
 * HihatEvent - Enhanced with star pattern
 */
class HihatEvent extends SoundEvent {
  float rotation;
  
  HihatEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 600 + (gain * 300);  // Longer for hihats
    rotation = random(TWO_PI);
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw star-like shape with rotation
    pushMatrix();
    translate(position.x, position.y);
    rotate(rotation + (progress * PI * 2));  // Spin as it fades
    
    // Draw rays
    noStroke();
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    
    int points = 8;  // 8-point star
    for (int i = 0; i < points; i++) {
      float angle = i * TWO_PI / points;
      float innerRadius = size * 0.2 * (1 - progress * 0.6);
      float outerRadius = size * 0.5 * (1 - progress * 0.3);
      
      beginShape();
      // Outer point
      vertex(cos(angle) * outerRadius, sin(angle) * outerRadius);
      // Inner point 1
      vertex(cos(angle + TWO_PI/(points*2)) * innerRadius, 
             sin(angle + TWO_PI/(points*2)) * innerRadius);
      // Next outer point
      vertex(cos(angle + TWO_PI/points) * outerRadius, 
             sin(angle + TWO_PI/points) * outerRadius);
      // Inner point 2
      vertex(cos(angle + TWO_PI/(points*2) + TWO_PI/points) * innerRadius, 
             sin(angle + TWO_PI/(points*2) + TWO_PI/points) * innerRadius);
      endShape(CLOSE);
    }
    
    // Draw center
    fill(255, alpha * 0.7);
    ellipse(0, 0, size * 0.2 * (1 - progress * 0.5), size * 0.2 * (1 - progress * 0.5));
    
    popMatrix();
  }
}

/**
 * BassEvent - Enhanced with ripple effect
 */
class BassEvent extends SoundEvent {
  BassEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 1200 + (gain * 800);  // Much longer for bass
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw multiple ripple effects
    for (int i = 0; i < 4; i++) {  // More ripples
      float rippleProgress = (progress + (i * 0.15)) % 1.0;
      float rippleSize = size * (0.6 + rippleProgress * 2.5);  // Larger ripples
      float rippleAlpha = alpha * (1 - rippleProgress) * 0.5;
      
      // Only draw visible ripples
      if (rippleAlpha > 5) {
        noFill();
        stroke(red(eventColor), green(eventColor), blue(eventColor), rippleAlpha);
        strokeWeight(2 + (4 * (1 - rippleProgress)));  // Thicker lines
        ellipse(position.x, position.y, rippleSize, rippleSize * 0.5);  // Oval for bass
      }
    }
    
    // Draw core with pulsing effect
    float pulseScale = 0.7 + sin(age * 0.01) * 0.2;
    noStroke();
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.8);
    ellipse(position.x, position.y, 
            size * 0.6 * pulseScale, 
            size * 0.3 * pulseScale);
            
    // Add bright center
    fill(255, alpha * 0.6);
    ellipse(position.x, position.y, 
            size * 0.3 * pulseScale, 
            size * 0.15 * pulseScale);
  }
}
