import type { PitchLock, SynthEngine } from './base/SynthEngine';

interface BassDrumParams {
  // Core frequency (base pitch of the kick)
  baseFreq: number;

  // Pitch envelope (how much frequency sweeps down)
  pitchMod: number;

  // Pitch envelope speed
  pitchDecay: number;

  // Amplitude decay time
  decay: number;

  // Click/snap amount (high freq transient)
  click: number;

  // Attack noise burst (sharp transient)
  attackNoise: number;
  attackNoiseFreq: number; // Filter frequency for attack noise

  // Body resonance
  bodyResonance: number;
  bodyFreq: number;

  // Body tone variation
  bodyDecay: number;

  // Noise amount
  noise: number;

  // Noise decay
  noiseDecay: number;

  // Harmonic content
  harmonics: number;

  // Wave shape (0 = sine, 0.5 = triangle, 1 = square)
  waveShape: number;

  // Phase distortion amount
  phaseDistortion: number;

  // Distortion amount (0 = clean, 1 = heavy)
  distortion: number;

  // Tuning offset
  tuning: number;

  // Envelope curve parameters
  attackTime: number; // Attack duration (0 = 0.5ms, 1 = 50ms)
  attackCurve: number; // Attack curve shape (0 = soft/slow, 1 = sharp/fast)
  ampDecayCurve: number; // Amplitude decay curve (0 = loose/boomy, 1 = tight/punchy)
  pitchDecayCurve: number; // Pitch envelope curve (0 = loose, 1 = tight)
}

export class BassDrum implements SynthEngine {
  getName(): string {
    return 'Dark Kick';
  }

  getDescription(): string {
    return 'Versatile kick drum synthesizer with varied styles from sub to tom';
  }

  getType() {
    return 'generative' as const;
  }

  getCategory() {
    return 'Percussion' as const;
  }

  randomParams(pitchLock?: PitchLock): BassDrumParams {
    // Choose a kick character/style
    const styleRoll = Math.random();

    let baseFreq: number, pitchMod: number, pitchDecay: number, decay: number;
    let click: number, attackNoise: number, attackNoiseFreq: number;
    let bodyResonance: number, bodyFreq: number, bodyDecay: number;
    let noise: number, noiseDecay: number, harmonics: number;
    let waveShape: number, phaseDistortion: number, distortion: number;
    let attackTime: number, attackCurve: number, ampDecayCurve: number, pitchDecayCurve: number;

    if (styleRoll < 0.08) {
      // Tom/bongo style (starts HIGH, decays VERY fast - rare!)
      baseFreq = 0.6 + Math.random() * 0.4; // 83-115 Hz (very high!)
      pitchMod = 0.8 + Math.random() * 0.2; // Extreme pitch sweep
      pitchDecay = 0.01 + Math.random() * 0.08; // Ultra fast pitch decay (10-90ms)
      decay = 0.05 + Math.random() * 0.25; // Very short decay
      click = 0.3 + Math.random() * 0.5; // Good click
      attackNoise = 0.4 + Math.random() * 0.6; // Strong attack
      attackNoiseFreq = 0.7 + Math.random() * 0.3; // High filtered
      bodyResonance = 0.1 + Math.random() * 0.3;
      bodyFreq = 0.5 + Math.random() * 0.5;
      bodyDecay = 0.05 + Math.random() * 0.25; // Short body decay
      noise = Math.random() * 0.08;
      noiseDecay = 0.02 + Math.random() * 0.15; // Fast noise decay
      harmonics = 0.2 + Math.random() * 0.5; // Lots of harmonics
      waveShape = 0.3 + Math.random() * 0.4; // Triangle-ish
      phaseDistortion = 0.1 + Math.random() * 0.4; // Some phase distortion
      distortion = 0; // Clean
      attackTime = 0.0 + Math.random() * 0.2; // Very fast attack (0.5-10ms)
      attackCurve = 0.6 + Math.random() * 0.3; // Sharp attack (0.6-0.9)
      ampDecayCurve = 0.7 + Math.random() * 0.3; // Very tight decay (0.7-1.0)
      pitchDecayCurve = 0.6 + Math.random() * 0.3; // Fast pitch sweep (0.6-0.9)
    } else if (styleRoll < 0.25) {
      // Tight, punchy 808-style kick (short, lots of pitch, CLEAN)
      baseFreq = 0.2 + Math.random() * 0.4; // 56-88 Hz (mid-high kicks)
      pitchMod = 0.7 + Math.random() * 0.3; // High pitch sweep
      pitchDecay = 0.05 + Math.random() * 0.4; // Fast to medium pitch decay
      decay = 0.1 + Math.random() * 0.35; // Short to medium decay
      click = 0.2 + Math.random() * 0.4; // Good click
      attackNoise = 0.3 + Math.random() * 0.5; // Sharp attack
      attackNoiseFreq = 0.5 + Math.random() * 0.5; // Mid-high filtered
      bodyResonance = 0.05 + Math.random() * 0.2;
      bodyFreq = 0.3 + Math.random() * 0.5;
      bodyDecay = 0.1 + Math.random() * 0.5; // Varied body decay
      noise = Math.random() * 0.05; // Very minimal
      noiseDecay = 0.05 + Math.random() * 0.4; // Varied noise decay
      harmonics = 0.1 + Math.random() * 0.3;
      waveShape = 0.2 + Math.random() * 0.5; // Sine to triangle
      phaseDistortion = Math.random() * 0.3; // Light phase distortion
      distortion = 0; // Clean
      attackTime = 0.0 + Math.random() * 0.3; // Fast attack (0.5-15ms)
      attackCurve = 0.3 + Math.random() * 0.3; // Medium-sharp attack (0.3-0.6)
      ampDecayCurve = 0.5 + Math.random() * 0.3; // Medium-tight decay (0.5-0.8)
      pitchDecayCurve = 0.4 + Math.random() * 0.3; // Medium pitch sweep (0.4-0.7)
    } else if (styleRoll < 0.43) {
      // Deep, smooth kick (long, clean, lots of body)
      baseFreq = 0.0 + Math.random() * 0.35; // 40-68 Hz (low to mid kicks)
      pitchMod = 0.3 + Math.random() * 0.4; // Moderate pitch sweep
      pitchDecay = 0.2 + Math.random() * 0.6; // Medium to slow pitch decay
      decay = 0.4 + Math.random() * 0.6; // Long decay
      click = Math.random() * 0.2; // Minimal click
      attackNoise = 0.1 + Math.random() * 0.4; // Some attack
      attackNoiseFreq = 0.3 + Math.random() * 0.4; // Mid filtered
      bodyResonance = 0.2 + Math.random() * 0.5; // Lots of body
      bodyFreq = 0.1 + Math.random() * 0.4;
      bodyDecay = 0.3 + Math.random() * 0.7; // Long body decay
      noise = Math.random() * 0.08;
      noiseDecay = 0.2 + Math.random() * 0.6; // Varied noise decay
      harmonics = Math.random() * 0.2;
      waveShape = Math.random() * 0.4; // Sine to triangle
      phaseDistortion = Math.random() * 0.25; // Subtle phase distortion
      distortion = 0; // Clean
      attackTime = 0.1 + Math.random() * 0.4; // Slower attack (5-25ms)
      attackCurve = 0.2 + Math.random() * 0.3; // Soft attack (0.2-0.5)
      ampDecayCurve = 0.2 + Math.random() * 0.3; // Loose, boomy decay (0.2-0.5)
      pitchDecayCurve = 0.2 + Math.random() * 0.3; // Slow pitch sweep (0.2-0.5)
    } else if (styleRoll < 0.63) {
      // Sub/electronic kick (very low, pure sine-like)
      baseFreq = 0.0 + Math.random() * 0.25; // 40-60 Hz (sub bass kicks)
      pitchMod = 0.1 + Math.random() * 0.4; // Low to moderate pitch sweep
      pitchDecay = 0.15 + Math.random() * 0.5; // Varied pitch decay
      decay = 0.4 + Math.random() * 0.6; // Long decay
      click = Math.random() * 0.15; // Almost no click
      attackNoise = Math.random() * 0.3; // Variable attack
      attackNoiseFreq = 0.2 + Math.random() * 0.3; // Low filtered
      bodyResonance = 0.3 + Math.random() * 0.6; // Strong body
      bodyFreq = 0.05 + Math.random() * 0.3;
      bodyDecay = 0.4 + Math.random() * 0.6; // Long body decay
      noise = Math.random() * 0.03; // Almost no noise
      noiseDecay = 0.1 + Math.random() * 0.4; // Varied noise decay
      harmonics = Math.random() * 0.15;
      waveShape = Math.random() * 0.3; // Mostly sine
      phaseDistortion = Math.random() * 0.2; // Very subtle
      distortion = 0; // Clean
      attackTime = 0.2 + Math.random() * 0.4; // Medium attack (10-30ms)
      attackCurve = 0.3 + Math.random() * 0.3; // Medium attack (0.3-0.6)
      ampDecayCurve = 0.1 + Math.random() * 0.3; // Very loose, sustaining (0.1-0.4)
      pitchDecayCurve = 0.1 + Math.random() * 0.3; // Gentle pitch sweep (0.1-0.4)
    } else if (styleRoll < 0.78) {
      // Snappy, clicky kick (fast attack, short)
      baseFreq = 0.3 + Math.random() * 0.5; // 64-100 Hz (high kicks)
      pitchMod = 0.5 + Math.random() * 0.5; // High pitch sweep
      pitchDecay = 0.02 + Math.random() * 0.25; // Very fast pitch
      decay = 0.08 + Math.random() * 0.35; // Very short to short
      click = 0.4 + Math.random() * 0.6; // Lots of click
      attackNoise = 0.5 + Math.random() * 0.5; // Strong attack burst
      attackNoiseFreq = 0.6 + Math.random() * 0.4; // High filtered
      bodyResonance = Math.random() * 0.2; // Minimal body
      bodyFreq = 0.4 + Math.random() * 0.6;
      bodyDecay = 0.05 + Math.random() * 0.3; // Short body decay
      noise = Math.random() * 0.1;
      noiseDecay = 0.02 + Math.random() * 0.2; // Fast noise decay
      harmonics = 0.2 + Math.random() * 0.5;
      waveShape = 0.4 + Math.random() * 0.6; // Triangle to square
      phaseDistortion = 0.2 + Math.random() * 0.5; // More phase distortion
      distortion = 0; // Clean
      attackTime = 0.0 + Math.random() * 0.15; // Instant attack (0.5-8ms)
      attackCurve = 0.7 + Math.random() * 0.3; // Very sharp attack (0.7-1.0)
      ampDecayCurve = 0.7 + Math.random() * 0.3; // Very tight (0.7-1.0)
      pitchDecayCurve = 0.7 + Math.random() * 0.3; // Fast pitch sweep (0.7-1.0)
    } else if (styleRoll < 0.88) {
      // Weird/experimental kick
      baseFreq = Math.random() * 0.7; // Full range 35-96 Hz
      pitchMod = Math.random();
      pitchDecay = 0.05 + Math.random() * 0.8; // Any decay
      decay = 0.1 + Math.random() * 0.8; // Any decay
      click = Math.random() * 0.5;
      attackNoise = Math.random() * 0.7;
      attackNoiseFreq = Math.random();
      bodyResonance = Math.random() * 0.7;
      bodyFreq = Math.random();
      bodyDecay = 0.1 + Math.random() * 0.9; // Any body decay
      noise = Math.random() * 0.15;
      noiseDecay = 0.05 + Math.random() * 0.8; // Any noise decay
      harmonics = Math.random() * 0.6;
      waveShape = Math.random(); // Any shape
      phaseDistortion = Math.random() * 0.7; // Any amount
      distortion = 0; // Clean
      attackTime = Math.random(); // Full range (0.5-50ms)
      attackCurve = Math.random(); // Full range (0.0-1.0)
      ampDecayCurve = Math.random(); // Full range (0.0-1.0)
      pitchDecayCurve = Math.random(); // Full range (0.0-1.0)
    } else {
      // Dirty/distorted kick (only 15% of the time!)
      baseFreq = 0.1 + Math.random() * 0.5; // 44-84 Hz (low to mid)
      pitchMod = 0.4 + Math.random() * 0.5;
      pitchDecay = 0.1 + Math.random() * 0.6; // Varied pitch decay
      decay = 0.2 + Math.random() * 0.6; // Varied decay
      click = 0.2 + Math.random() * 0.5;
      attackNoise = 0.3 + Math.random() * 0.5;
      attackNoiseFreq = 0.4 + Math.random() * 0.5;
      bodyResonance = 0.1 + Math.random() * 0.5;
      bodyFreq = 0.2 + Math.random() * 0.6;
      bodyDecay = 0.15 + Math.random() * 0.7; // Varied body decay
      noise = 0.05 + Math.random() * 0.2;
      noiseDecay = 0.15 + Math.random() * 0.6; // Varied noise decay
      harmonics = 0.1 + Math.random() * 0.4;
      waveShape = 0.3 + Math.random() * 0.5; // Varied shapes
      phaseDistortion = 0.2 + Math.random() * 0.6; // Good amount
      distortion = 0.3 + Math.random() * 0.5; // Only this style has distortion
      attackTime = 0.1 + Math.random() * 0.4; // Medium attack (5-25ms)
      attackCurve = 0.4 + Math.random() * 0.4; // Medium-sharp attack (0.4-0.8)
      ampDecayCurve = 0.3 + Math.random() * 0.4; // Medium varied (0.3-0.7)
      pitchDecayCurve = 0.3 + Math.random() * 0.4; // Medium varied (0.3-0.7)
    }

    return {
      baseFreq: pitchLock ? this.freqToParam(pitchLock.frequency) : baseFreq,
      pitchMod,
      pitchDecay,
      decay,
      click,
      attackNoise,
      attackNoiseFreq,
      bodyResonance,
      bodyFreq,
      bodyDecay,
      noise,
      noiseDecay,
      harmonics,
      waveShape,
      phaseDistortion,
      distortion,
      tuning: pitchLock ? 0.5 : 0.4 + Math.random() * 0.2,
      attackTime,
      attackCurve,
      ampDecayCurve,
      pitchDecayCurve,
    };
  }

  mutateParams(params: BassDrumParams, mutationAmount: number = 0.15, pitchLock?: PitchLock): BassDrumParams {
    const mutate = (value: number, amount: number = mutationAmount): number => {
      return Math.max(0, Math.min(1, value + (Math.random() - 0.5) * amount));
    };

    return {
      baseFreq: pitchLock ? params.baseFreq : mutate(params.baseFreq, 0.2),
      pitchMod: mutate(params.pitchMod, 0.2),
      pitchDecay: mutate(params.pitchDecay, 0.2),
      decay: mutate(params.decay, 0.25),
      click: mutate(params.click, 0.2),
      attackNoise: mutate(params.attackNoise, 0.25),
      attackNoiseFreq: mutate(params.attackNoiseFreq, 0.25),
      bodyResonance: mutate(params.bodyResonance, 0.2),
      bodyFreq: mutate(params.bodyFreq, 0.25),
      bodyDecay: mutate(params.bodyDecay, 0.2),
      noise: mutate(params.noise, 0.15),
      noiseDecay: mutate(params.noiseDecay, 0.2),
      harmonics: mutate(params.harmonics, 0.2),
      waveShape: mutate(params.waveShape, 0.25),
      phaseDistortion: mutate(params.phaseDistortion, 0.25),
      distortion: mutate(params.distortion, 0.2),
      tuning: pitchLock ? params.tuning : mutate(params.tuning, 0.15),
      attackTime: mutate(params.attackTime, 0.15),
      attackCurve: mutate(params.attackCurve, 0.2),
      ampDecayCurve: mutate(params.ampDecayCurve, 0.2),
      pitchDecayCurve: mutate(params.pitchDecayCurve, 0.2),
    };
  }

  generate(
    params: BassDrumParams,
    sampleRate: number,
    duration: number,
    pitchLock?: PitchLock
  ): [Float32Array, Float32Array] {
    const numSamples = Math.floor(sampleRate * duration);
    const left = new Float32Array(numSamples);
    const right = new Float32Array(numSamples);

    // Base frequency: 35Hz to 115Hz (full kick range from sub to high)
    const baseFreq = pitchLock ? pitchLock.frequency : 35 + params.baseFreq * 80;

    // Tuning offset: -12% to +12%
    const tuningFactor = 0.88 + params.tuning * 0.24;
    const tunedFreq = baseFreq * tuningFactor;

    // Pitch modulation: when locked, starts at locked freq and decays down
    // when not locked, starts higher and decays to base
    const pitchMultiplier = 1 + params.pitchMod * 1.5; // 1x to 2.5x
    const pitchDecayRatio = pitchLock ? 1 / pitchMultiplier : pitchMultiplier; // Invert for pitch lock

    // Pitch decay time scaled by duration (50ms to 200ms)
    const pitchDecayTime = (0.05 + params.pitchDecay * 0.15) * duration;

    // Amplitude decay time scaled by duration
    const ampDecayTime = (0.2 + params.decay * 1.8) * duration;

    // Noise decay time
    const noiseDecayTime = (0.02 + params.noiseDecay * 0.15) * duration; // 20ms to 170ms

    // Body resonance frequency (40Hz to 200Hz for low body resonances)
    const bodyFreq = 40 + params.bodyFreq * 160;
    const bodyDecayTime = (0.1 + params.bodyDecay * 0.4) * duration; // 100ms to 500ms

    // Attack time (0.5ms to 50ms, scaled by duration)
    const attackTime = (0.0005 + params.attackTime * 0.0495) * duration;

    // Click decay (very fast for transient)
    const clickDecayTime = 0.003 + params.click * 0.007; // 3ms to 10ms

    // Attack noise burst decay (super fast: 1-5ms)
    const attackNoiseDecayTime = 0.001 + params.attackNoise * 0.004; // 1ms to 5ms

    // Attack noise filter frequency (500Hz to 8000Hz)
    const attackNoiseFilterFreq = 500 + params.attackNoiseFreq * 7500;

    // Low-pass filter at 3000Hz
    const filterFreq = 3000;

    for (let channel = 0; channel < 2; channel++) {
      const output = channel === 0 ? left : right;

      // Triangle oscillator phase
      let phase = 0;

      // Low-pass filter state
      let filterState = 0;

      // Body resonance filter state
      let bodyState1 = 0;
      let bodyState2 = 0;

      // Attack noise filter state (highpass for snap)
      let attackNoiseState1 = 0;
      let attackNoiseState2 = 0;

      // Stereo variation
      const stereoDetune = channel === 0 ? 0.999 : 1.001;
      const stereoPhaseOffset = channel === 0 ? 0 : 0.05;

      for (let i = 0; i < numSamples; i++) {
        const t = i / sampleRate;

        // Pitch envelope: curved decay
        const pitchEnv = this.applyDecayCurve(t, pitchDecayTime, params.pitchDecayCurve);

        // When pitch locked: start at locked freq, decay down
        // When not locked: start higher, decay to base
        const currentFreq = pitchLock
          ? tunedFreq * stereoDetune * (pitchDecayRatio + (1 - pitchDecayRatio) * pitchEnv)
          : tunedFreq * stereoDetune * (1 + (pitchMultiplier - 1) * pitchEnv);

        // Generate oscillator with wave shape morphing and phase distortion
        phase += (2 * Math.PI * currentFreq) / sampleRate;
        if (phase > 2 * Math.PI) phase -= 2 * Math.PI;

        // Apply phase distortion (modulate the phase itself)
        let distortedPhase = phase;
        if (params.phaseDistortion > 0.01) {
          // Phase distortion creates harmonic richness
          const phaseMod = Math.sin(phase * 2) * params.phaseDistortion * 0.5;
          distortedPhase = phase + phaseMod;
          if (distortedPhase < 0) distortedPhase += 2 * Math.PI;
          if (distortedPhase > 2 * Math.PI) distortedPhase -= 2 * Math.PI;
        }

        // Wave shape morphing: 0=sine, 0.5=triangle, 1=square
        let waveform: number;

        // Generate sine wave
        const sine = Math.sin(distortedPhase);

        // Generate triangle wave
        const triangle = distortedPhase < Math.PI
          ? -1 + (2 * distortedPhase) / Math.PI
          : 3 - (2 * distortedPhase) / Math.PI;

        // Generate square wave
        const square = distortedPhase < Math.PI ? 1 : -1;

        // Morph between shapes
        if (params.waveShape < 0.5) {
          // Morph between sine and triangle
          const mix = params.waveShape * 2; // 0 to 1
          waveform = sine * (1 - mix) + triangle * mix;
        } else {
          // Morph between triangle and square
          const mix = (params.waveShape - 0.5) * 2; // 0 to 1
          waveform = triangle * (1 - mix) + square * mix;
        }

        // Attack envelope (curved)
        const attackEnv = t < attackTime
          ? this.applyAttackCurve(t, attackTime, params.attackCurve)
          : 1.0;

        // Amplitude envelope: curved decay with attack
        const ampEnv = this.applyDecayCurve(t, ampDecayTime, params.ampDecayCurve) * attackEnv;

        // 1. Morphed oscillator (main tone)
        let signal = waveform * ampEnv * 0.6;

        // 1b. Add harmonics (2nd and 3rd) for more character
        if (params.harmonics > 0.01) {
          const harmonic2 = Math.sin(2 * distortedPhase) * params.harmonics * 0.3;
          const harmonic3 = Math.sin(3 * distortedPhase) * params.harmonics * 0.15;
          signal += (harmonic2 + harmonic3) * ampEnv;
        }

        // 2. Attack noise burst (super sharp transient, 1-5ms)
        if (params.attackNoise > 0.01) {
          const attackEnv = Math.exp(-t / attackNoiseDecayTime);
          const attackNoise = Math.random() * 2 - 1;

          // Highpass filter the attack noise for snap
          const attackNoiseFiltered = this.highpassFilter(
            attackNoise,
            attackNoiseFilterFreq,
            2.0,
            sampleRate,
            attackNoiseState1,
            attackNoiseState2
          );
          attackNoiseState1 = attackNoiseFiltered.state1;
          attackNoiseState2 = attackNoiseFiltered.state2;

          signal += attackNoiseFiltered.output * params.attackNoise * attackEnv * 0.4;
        }

        // 3. Click/snap transient (fast decaying high-pitched sine)
        if (params.click > 0.01) {
          const clickEnv = Math.exp(-t / clickDecayTime);
          const clickFreq = tunedFreq * 4; // High frequency for click
          const clickOsc = Math.sin(2 * Math.PI * clickFreq * t);
          signal += clickOsc * params.click * clickEnv * 0.15;
        }

        // 4. Body resonance (bandpass filtered feedback with decay)
        if (params.bodyResonance > 0.05) {
          const bodyEnv = Math.exp(-t / bodyDecayTime);
          const bodyFiltered = this.bandpassFilter(
            signal,
            bodyFreq,
            5 + params.bodyResonance * 10,
            sampleRate,
            bodyState1,
            bodyState2
          );
          bodyState1 = bodyFiltered.state1;
          bodyState2 = bodyFiltered.state2;
          signal += bodyFiltered.output * params.bodyResonance * bodyEnv * 0.2;
        }

        // 5. Add sustaining noise
        if (params.noise > 0.01) {
          const noise = Math.random() * 2 - 1;
          const noiseEnv = Math.exp(-t / noiseDecayTime);
          signal += noise * params.noise * noiseEnv * 0.1;
        }

        // 6. Optional distortion/saturation (only if distortion > 0.2)
        if (params.distortion > 0.2) {
          const distAmount = 1 + params.distortion * 3;
          signal = this.waveshaper(signal * distAmount, distAmount) / distAmount;
        }

        // 7. Low-pass filter at 3000Hz
        const normalizedFreq = Math.min(filterFreq / sampleRate, 0.48);
        const a = 2 * Math.PI * normalizedFreq;
        filterState += a * (signal - filterState);
        signal = filterState;

        output[i] = signal;
      }
    }

    // Normalize output to consistent level
    let peak = 0;
    for (let i = 0; i < numSamples; i++) {
      peak = Math.max(peak, Math.abs(left[i]), Math.abs(right[i]));
    }

    if (peak > 0.001) {
      const normGain = 0.5 / peak; // Normalize to 0.5 peak for more headroom
      for (let i = 0; i < numSamples; i++) {
        left[i] *= normGain;
        right[i] *= normGain;
      }
    }

    return [left, right];
  }

  private applyDecayCurve(t: number, decayTime: number, curveParam: number): number {
    const normalized = Math.min(t / decayTime, 5);
    const baseEnv = Math.exp(-normalized);
    const exponent = 0.4 + curveParam * 2.1;
    return Math.pow(baseEnv, exponent);
  }

  private applyAttackCurve(t: number, attackTime: number, curveParam: number): number {
    const normalized = Math.min(t / attackTime, 1);
    const exponent = 0.4 + curveParam * 2.1;
    return Math.pow(normalized, exponent);
  }

  private bandpassFilter(
    input: number,
    freq: number,
    q: number,
    sampleRate: number,
    state1: number,
    state2: number
  ): { output: number; state1: number; state2: number } {
    // State variable filter in bandpass mode
    const normalizedFreq = Math.min(freq / sampleRate, 0.48);
    const f = 2 * Math.sin(Math.PI * normalizedFreq);
    const qRecip = 1 / Math.max(q, 0.5);

    const lowpass = state2 + f * state1;
    const highpass = input - lowpass - qRecip * state1;
    const bandpass = f * highpass + state1;

    // Update states with clamping
    const newState1 = Math.max(-2, Math.min(2, bandpass));
    const newState2 = Math.max(-2, Math.min(2, lowpass));

    return {
      output: bandpass,
      state1: newState1,
      state2: newState2,
    };
  }

  private highpassFilter(
    input: number,
    freq: number,
    q: number,
    sampleRate: number,
    state1: number,
    state2: number
  ): { output: number; state1: number; state2: number } {
    // State variable filter in highpass mode
    const normalizedFreq = Math.min(freq / sampleRate, 0.48);
    const f = 2 * Math.sin(Math.PI * normalizedFreq);
    const qRecip = 1 / Math.max(q, 0.5);

    const lowpass = state2 + f * state1;
    const highpass = input - lowpass - qRecip * state1;
    const bandpass = f * highpass + state1;

    // Update states with clamping
    const newState1 = Math.max(-2, Math.min(2, bandpass));
    const newState2 = Math.max(-2, Math.min(2, lowpass));

    return {
      output: highpass,
      state1: newState1,
      state2: newState2,
    };
  }

  private waveshaper(x: number, amount: number): number {
    const PI = Math.PI;
    if (Math.abs(x) < 0.001) return x;
    return ((PI + amount) * x) / (PI + amount * Math.abs(x));
  }

  private softClip(x: number): number {
    if (x > 1) return 1;
    if (x < -1) return -1;
    if (x > 0.66) return (3 - (2 - 3 * x) ** 2) / 3;
    if (x < -0.66) return -(3 - (2 - 3 * -x) ** 2) / 3;
    return x;
  }

  private freqToParam(freq: number): number {
    // Map frequency to 0-1 range (35-115 Hz)
    return Math.max(0, Math.min(1, (freq - 35) / 80));
  }
}
