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

enum PDWaveform {
  Sine,
  SawUp,
  SawDown,
  Pulse,
  DoubleSine,
  ResonantSaw,
}

enum PDAlgorithm {
  Single,    // Single oscillator with PD
  Dual,      // Two oscillators, different waveforms
  Detune,    // Two slightly detuned oscillators
  Octave,    // Oscillator + octave up
  Fifth,     // Base + fifth up (1.5x frequency)
  Sub,       // Base + octave down
  Stack,     // Base + fifth + octave
  Wide,      // Heavily detuned oscillators
}

enum LFOWaveform {
  Sine,
  Triangle,
  Square,
  Saw,
  SampleHold,
  RandomWalk,
}

interface PDOscillatorParams {
  waveform: PDWaveform;
  level: number;
  detune: number;  // in cents
  dcw: number;     // Digitally Controlled Waveshaping (0-1, controls brightness)
  pulseWidth: number;  // 0-1, controls pulse wave width
  attack: number;
  decay: number;
  sustain: number;
  release: number;
  dcwAttack: number;
  dcwDecay: number;
  dcwSustain: number;
  dcwRelease: number;
}

interface LFOParams {
  rate: number;
  depth: number;
  waveform: LFOWaveform;
  target: 'pitch' | 'dcw';
}

export interface PhaseDistortionFMParams {
  baseFreq: number;
  algorithm: PDAlgorithm;
  oscillators: [PDOscillatorParams, PDOscillatorParams];
  lfo: LFOParams;
  stereoWidth: number;
}

export class PhaseDistortionFM implements SynthEngine<PhaseDistortionFMParams> {
  private static workletLoaded = false;
  private static workletURL: string | null = null;

  getName(): string {
    return 'Phase Dist';
  }

  getDescription(): string {
    return 'Casio CZ-style Phase Distortion synthesis with DCW envelopes';
  }

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

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

  generate(params: PhaseDistortionFMParams, sampleRate: number, duration: number): [Float32Array, Float32Array] {
    const numSamples = Math.floor(sampleRate * duration);
    const leftBuffer = new Float32Array(numSamples);
    const rightBuffer = new Float32Array(numSamples);
    const TAU = Math.PI * 2;

    const detune = 1 + (params.stereoWidth * 0.001);
    const leftFreq = params.baseFreq / detune;
    const rightFreq = params.baseFreq * detune;

    let osc1PhaseL = 0;
    let osc1PhaseR = 0;
    let osc2PhaseL = 0;
    let osc2PhaseR = 0;

    let lfoPhaseL = 0;
    let lfoPhaseR = Math.PI * params.stereoWidth * 0.3;

    let lfoSampleHoldValue = Math.random() * 2 - 1;
    let lfoSampleHoldCounter = 0;
    const lfoSampleHoldInterval = Math.max(1, Math.floor(sampleRate / (params.lfo.rate * 4)));
    let lfoRandomWalkCurrent = Math.random() * 2 - 1;
    let lfoRandomWalkTarget = Math.random() * 2 - 1;
    let lfoRandomWalkCounter = 0;
    const lfoRandomWalkInterval = Math.max(1, Math.floor(sampleRate / (params.lfo.rate * 2)));

    let dcBlockerPrevL = 0;
    let dcBlockerAccL = 0;
    let dcBlockerPrevR = 0;
    let dcBlockerAccR = 0;
    const dcBlockerCutoff = 0.995;

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

      const env1 = this.calculateEnvelope(t, duration, params.oscillators[0]);
      const env2 = this.calculateEnvelope(t, duration, params.oscillators[1]);

      const dcw1 = this.calculateDCWEnvelope(t, duration, params.oscillators[0]);
      const dcw2 = this.calculateDCWEnvelope(t, duration, params.oscillators[1]);

      // Update LFO states
      lfoSampleHoldCounter++;
      if (lfoSampleHoldCounter >= lfoSampleHoldInterval) {
        lfoSampleHoldValue = Math.random() * 2 - 1;
        lfoSampleHoldCounter = 0;
      }

      lfoRandomWalkCounter++;
      if (lfoRandomWalkCounter >= lfoRandomWalkInterval) {
        lfoRandomWalkTarget = Math.random() * 2 - 1;
        lfoRandomWalkCounter = 0;
      }
      const walkSpeed = 0.01;
      lfoRandomWalkCurrent += (lfoRandomWalkTarget - lfoRandomWalkCurrent) * walkSpeed;

      const lfoL = this.generateLFO(lfoPhaseL, params.lfo.waveform, lfoSampleHoldValue, lfoRandomWalkCurrent);
      const lfoR = this.generateLFO(lfoPhaseR, params.lfo.waveform, lfoSampleHoldValue, lfoRandomWalkCurrent);

      const lfoModL = lfoL * params.lfo.depth;
      const lfoModR = lfoR * params.lfo.depth;

      let pitchModL = 0, pitchModR = 0;
      let dcwMod1 = 0, dcwMod2 = 0;

      if (params.lfo.target === 'pitch') {
        pitchModL = lfoModL * 0.02;
        pitchModR = lfoModR * 0.02;
      } else {
        dcwMod1 = lfoModL * 0.3;
        dcwMod2 = lfoModL * 0.3;
      }

      const [sampleL, sampleR] = this.processAlgorithm(
        params.algorithm,
        params.oscillators,
        [osc1PhaseL, osc2PhaseL],
        [osc1PhaseR, osc2PhaseR],
        [env1, env2],
        [Math.max(0, Math.min(1, dcw1 + dcwMod1)), Math.max(0, Math.min(1, dcw2 + dcwMod2))]
      );

      let outL = sampleL;
      let outR = sampleR;

      outL = this.softClip(outL);
      outR = this.softClip(outR);

      // DC blocker
      const dcFilteredL = outL - dcBlockerPrevL + dcBlockerCutoff * dcBlockerAccL;
      dcBlockerPrevL = outL;
      dcBlockerAccL = dcFilteredL;

      const dcFilteredR = outR - dcBlockerPrevR + dcBlockerCutoff * dcBlockerAccR;
      dcBlockerPrevR = outR;
      dcBlockerAccR = dcFilteredR;

      leftBuffer[i] = dcFilteredL;
      rightBuffer[i] = dcFilteredR;

      // Advance oscillator phases
      const detune1 = Math.pow(2, params.oscillators[0].detune / 1200);
      const detune2 = Math.pow(2, params.oscillators[1].detune / 1200);

      const osc1FreqL = leftFreq * detune1 * (1 + pitchModL);
      const osc1FreqR = rightFreq * detune1 * (1 + pitchModR);
      const osc2FreqL = leftFreq * detune2 * (1 + pitchModL);
      const osc2FreqR = rightFreq * detune2 * (1 + pitchModR);

      osc1PhaseL += (TAU * osc1FreqL) / sampleRate;
      osc1PhaseR += (TAU * osc1FreqR) / sampleRate;
      osc2PhaseL += (TAU * osc2FreqL) / sampleRate;
      osc2PhaseR += (TAU * osc2FreqR) / sampleRate;

      while (osc1PhaseL >= TAU) osc1PhaseL -= TAU;
      while (osc1PhaseR >= TAU) osc1PhaseR -= TAU;
      while (osc2PhaseL >= TAU) osc2PhaseL -= TAU;
      while (osc2PhaseR >= TAU) osc2PhaseR -= TAU;

      lfoPhaseL += (TAU * params.lfo.rate) / sampleRate;
      lfoPhaseR += (TAU * params.lfo.rate) / sampleRate;
      while (lfoPhaseL >= TAU) lfoPhaseL -= TAU;
      while (lfoPhaseR >= TAU) lfoPhaseR -= TAU;
    }

    // Normalize
    let peakL = 0;
    let peakR = 0;
    for (let i = 0; i < numSamples; i++) {
      peakL = Math.max(peakL, Math.abs(leftBuffer[i]));
      peakR = Math.max(peakR, Math.abs(rightBuffer[i]));
    }
    const peak = Math.max(peakL, peakR);

    if (peak > 0.001) {
      const normalizeGain = 0.85 / peak;
      for (let i = 0; i < numSamples; i++) {
        leftBuffer[i] *= normalizeGain;
        rightBuffer[i] *= normalizeGain;
      }
    }

    return [leftBuffer, rightBuffer];
  }

  private processAlgorithm(
    algorithm: PDAlgorithm,
    oscillators: [PDOscillatorParams, PDOscillatorParams],
    phasesL: number[],
    phasesR: number[],
    envelopes: number[],
    dcws: number[]
  ): [number, number] {
    switch (algorithm) {
      case PDAlgorithm.Single: {
        // Single oscillator
        const outL = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const outR = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        return [outL, outR];
      }

      case PDAlgorithm.Dual: {
        // Two oscillators with different waveforms
        const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc2L = this.generatePDWaveform(phasesL[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        const osc2R = this.generatePDWaveform(phasesR[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707];
      }

      case PDAlgorithm.Detune: {
        // Two slightly detuned oscillators (chorus effect)
        const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc2L = this.generatePDWaveform(phasesL[1], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[1].level;
        const osc2R = this.generatePDWaveform(phasesR[1], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[1].level;
        return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707];
      }

      case PDAlgorithm.Octave: {
        // Oscillator + octave up
        const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc2L = this.generatePDWaveform(phasesL[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        const osc2R = this.generatePDWaveform(phasesR[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707];
      }

      case PDAlgorithm.Fifth: {
        // Base + fifth up (1.5x frequency)
        const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc2L = this.generatePDWaveform(phasesL[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        const osc2R = this.generatePDWaveform(phasesR[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707];
      }

      case PDAlgorithm.Sub: {
        // Base + octave down
        const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc2L = this.generatePDWaveform(phasesL[1] * 0.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        const osc2R = this.generatePDWaveform(phasesR[1] * 0.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707];
      }

      case PDAlgorithm.Stack: {
        // Base + fifth + octave - three oscillators!
        const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc2L = this.generatePDWaveform(phasesL[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        const osc2R = this.generatePDWaveform(phasesR[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        const osc3L = this.generatePDWaveform(phasesL[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level * 0.7;
        const osc3R = this.generatePDWaveform(phasesR[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level * 0.7;
        return [(osc1L + osc2L + osc3L) * 0.577, (osc1R + osc2R + osc3R) * 0.577];
      }

      case PDAlgorithm.Wide: {
        // Heavily detuned oscillators for super wide chorus
        const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth)
          * envelopes[0] * oscillators[0].level;
        const osc2L = this.generatePDWaveform(phasesL[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        const osc2R = this.generatePDWaveform(phasesR[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth)
          * envelopes[1] * oscillators[1].level;
        return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707];
      }

      default:
        return [0, 0];
    }
  }

  private generatePDWaveform(phase: number, waveform: PDWaveform, dcw: number, pulseWidth: number): number {
    const TAU = Math.PI * 2;
    let normalizedPhase = phase / TAU;
    normalizedPhase = normalizedPhase - Math.floor(normalizedPhase);

    // Apply phase distortion based on DCW
    const distortedPhase = this.applyPhaseDistortion(normalizedPhase, dcw);

    switch (waveform) {
      case PDWaveform.Sine:
        return Math.sin(distortedPhase * TAU);

      case PDWaveform.SawUp:
        return distortedPhase * 2 - 1;

      case PDWaveform.SawDown:
        return 1 - distortedPhase * 2;

      case PDWaveform.Pulse:
        return distortedPhase < pulseWidth ? 1 : -1;

      case PDWaveform.DoubleSine:
        return Math.sin(distortedPhase * TAU * 2);

      case PDWaveform.ResonantSaw:
        return this.resonantSaw(distortedPhase, dcw);

      default:
        return Math.sin(distortedPhase * TAU);
    }
  }

  private applyPhaseDistortion(phase: number, dcw: number): number {
    // Casio CZ-style phase distortion
    // DCW = 0: no distortion (linear phase)
    // DCW = 1: maximum distortion (transforms waveform)

    if (dcw < 0.01) return phase;

    // Add a triangle wave to the phase - this is the classic CZ method
    // The triangle modulates how fast we read through the waveform
    const trianglePhase = phase < 0.5 ? phase * 2 : 2 - phase * 2;

    // Mix between linear and distorted phase
    return phase + (trianglePhase - 0.5) * dcw;
  }

  private resonantSaw(phase: number, brightness: number): number {
    // Band-limited sawtooth with fewer harmonics for cleaner sound
    const maxHarmonics = Math.min(4, Math.floor(1 + brightness * 3));
    let sum = 0;
    let normalization = 0;
    for (let n = 1; n <= maxHarmonics; n++) {
      sum += Math.sin(Math.PI * 2 * phase * n) / n;
      normalization += 1 / n;
    }
    return normalization > 0 ? (sum / normalization) * 0.7 : 0;
  }

  private calculateEnvelope(t: number, duration: number, osc: PDOscillatorParams): number {
    const attackTime = Math.max(0.0001, osc.attack * duration);
    const decayTime = Math.max(0.0001, osc.decay * duration);
    const releaseTime = Math.max(0.0001, osc.release * duration);
    const sustainStart = attackTime + decayTime;
    const releaseStart = duration - releaseTime;

    if (t < attackTime) {
      return t / attackTime;
    } else if (t < sustainStart) {
      const progress = (t - attackTime) / decayTime;
      return 1 - progress * (1 - osc.sustain);
    } else if (t < releaseStart) {
      return osc.sustain;
    } else {
      const progress = (t - releaseStart) / releaseTime;
      return osc.sustain * (1 - progress);
    }
  }

  private calculateDCWEnvelope(t: number, duration: number, osc: PDOscillatorParams): number {
    const attackTime = Math.max(0.0001, osc.dcwAttack * duration);
    const decayTime = Math.max(0.0001, osc.dcwDecay * duration);
    const releaseTime = Math.max(0.0001, osc.dcwRelease * duration);
    const sustainStart = attackTime + decayTime;
    const releaseStart = duration - releaseTime;

    if (t < attackTime) {
      return (t / attackTime) * osc.dcw;
    } else if (t < sustainStart) {
      const progress = (t - attackTime) / decayTime;
      return osc.dcw * (1 - progress * (1 - osc.dcwSustain));
    } else if (t < releaseStart) {
      return osc.dcw * osc.dcwSustain;
    } else {
      const progress = (t - releaseStart) / releaseTime;
      return osc.dcw * osc.dcwSustain * (1 - progress);
    }
  }

  private generateLFO(phase: number, waveform: LFOWaveform, sampleHoldValue: number, randomWalkValue: number): number {
    const TAU = Math.PI * 2;
    let normalizedPhase = phase / TAU;
    normalizedPhase = normalizedPhase - Math.floor(normalizedPhase);

    switch (waveform) {
      case LFOWaveform.Sine:
        return Math.sin(phase);

      case LFOWaveform.Triangle:
        return normalizedPhase < 0.5
          ? normalizedPhase * 4 - 1
          : 3 - normalizedPhase * 4;

      case LFOWaveform.Square:
        return normalizedPhase < 0.5 ? 1 : -1;

      case LFOWaveform.Saw:
        return normalizedPhase * 2 - 1;

      case LFOWaveform.SampleHold:
        return sampleHoldValue;

      case LFOWaveform.RandomWalk:
        return randomWalkValue;

      default:
        return 0;
    }
  }

  private softClip(x: number): number {
    const absX = Math.abs(x);
    if (absX < 0.7) return x;
    if (absX > 3) return Math.sign(x) * 0.98;
    const x2 = x * x;
    return x * (27 + x2) / (27 + 9 * x2);
  }

  randomParams(pitchLock?: PitchLock): PhaseDistortionFMParams {
    const algorithm = this.randomInt(0, 7) as PDAlgorithm;

    let baseFreq: number;
    if (pitchLock?.enabled) {
      baseFreq = pitchLock.frequency;
    } else {
      const baseFreqChoices = [55, 82.4, 110, 146.8, 220, 293.7, 440, 587.3, 880];
      baseFreq = this.randomChoice(baseFreqChoices);
    }

    return {
      baseFreq,
      algorithm,
      oscillators: [
        this.randomOscillator(algorithm),
        this.randomOscillator(algorithm),
      ],
      lfo: {
        rate: this.randomRange(0.5, 8),
        depth: this.randomRange(0, 0.4),
        waveform: this.randomInt(0, 5) as LFOWaveform,
        target: this.randomChoice(['pitch', 'dcw'] as const),
      },
      stereoWidth: this.randomRange(0.1, 0.5),
    };
  }

  private randomOscillator(algorithm: PDAlgorithm): PDOscillatorParams {
    const waveform = this.randomInt(0, 5) as PDWaveform;

    let detune = 0;
    if (algorithm === PDAlgorithm.Detune) {
      detune = this.randomRange(-10, 10);
    } else if (algorithm === PDAlgorithm.Wide) {
      detune = this.randomRange(-30, 30);
    }

    return {
      waveform,
      level: this.randomRange(0.5, 0.9),
      detune,
      dcw: this.randomRange(0.2, 0.8),
      pulseWidth: this.randomRange(0.2, 0.8),
      attack: this.randomRange(0.001, 0.1),
      decay: this.randomRange(0.02, 0.2),
      sustain: this.randomRange(0.3, 0.8),
      release: this.randomRange(0.05, 0.3),
      dcwAttack: this.randomRange(0.001, 0.08),
      dcwDecay: this.randomRange(0.02, 0.15),
      dcwSustain: this.randomRange(0.2, 0.7),
      dcwRelease: this.randomRange(0.05, 0.25),
    };
  }

  mutateParams(params: PhaseDistortionFMParams, mutationAmount: number = 0.15, pitchLock?: PitchLock): PhaseDistortionFMParams {
    const baseFreq = pitchLock?.enabled ? pitchLock.frequency : params.baseFreq;

    return {
      baseFreq,
      algorithm: Math.random() < 0.1 ? this.randomInt(0, 7) as PDAlgorithm : params.algorithm,
      oscillators: params.oscillators.map(osc =>
        this.mutateOscillator(osc, mutationAmount)
      ) as [PDOscillatorParams, PDOscillatorParams],
      lfo: {
        rate: this.mutateValue(params.lfo.rate, mutationAmount, 0.1, 15),
        depth: this.mutateValue(params.lfo.depth, mutationAmount, 0, 0.6),
        waveform: Math.random() < 0.08 ? this.randomInt(0, 5) as LFOWaveform : params.lfo.waveform,
        target: Math.random() < 0.08 ? this.randomChoice(['pitch', 'dcw'] as const) : params.lfo.target,
      },
      stereoWidth: this.mutateValue(params.stereoWidth, mutationAmount, 0, 0.7),
    };
  }

  private mutateOscillator(osc: PDOscillatorParams, amount: number): PDOscillatorParams {
    return {
      waveform: Math.random() < 0.1 ? this.randomInt(0, 5) as PDWaveform : osc.waveform,
      level: this.mutateValue(osc.level, amount, 0.3, 1.0),
      detune: this.mutateValue(osc.detune, amount, -20, 20),
      dcw: this.mutateValue(osc.dcw, amount, 0, 1),
      pulseWidth: this.mutateValue(osc.pulseWidth, amount, 0.1, 0.9),
      attack: this.mutateValue(osc.attack, amount, 0.001, 0.2),
      decay: this.mutateValue(osc.decay, amount, 0.01, 0.3),
      sustain: this.mutateValue(osc.sustain, amount, 0.1, 0.95),
      release: this.mutateValue(osc.release, amount, 0.02, 0.5),
      dcwAttack: this.mutateValue(osc.dcwAttack, amount, 0.001, 0.15),
      dcwDecay: this.mutateValue(osc.dcwDecay, amount, 0.01, 0.25),
      dcwSustain: this.mutateValue(osc.dcwSustain, amount, 0.1, 0.9),
      dcwRelease: this.mutateValue(osc.dcwRelease, amount, 0.02, 0.4),
    };
  }

  private randomRange(min: number, max: number): number {
    return min + Math.random() * (max - min);
  }

  private randomInt(min: number, max: number): number {
    return Math.floor(this.randomRange(min, max + 1));
  }

  private randomChoice<T>(choices: readonly T[]): T {
    return choices[Math.floor(Math.random() * choices.length)];
  }

  private mutateValue(value: number, amount: number, min: number, max: number): number {
    const variation = value * amount * (Math.random() * 2 - 1);
    return Math.max(min, Math.min(max, value + variation));
  }
}
