const DrumVoice = ({
  wavetable,
  trigger,
  gate,
  velocity,
  output,
  basePitch,
  modPitch,
  wtTime,
  pitchTime,
  gainTime,
  filterTime,
}) => {
  const exit = enterChildScope();

  const gainEnvelope = useEnvelope(Curve.Log2, false);

  const gain = gainEnvelope(
    trigger,
    gate,
    useConstantBuffer(1 * MS_TO_SAMPLES),
    gainTime,
    useConstantBuffer(0),
    gainTime
  );

  if (allEqual(gain, 0)) {
    exit();
    return;
  }

  const filterEnvelope = useEnvelope(Curve.Log2, false);
  const pitchEnvelope = useEnvelope(Curve.Log2, false);
  const wtPosEnvelope = useEnvelope(Curve.Linear, false);

  const oscillator = useWavetableReader(wavetable, WavetableWarpMode.Off);

  const wtPos = wtPosEnvelope(
    trigger,
    gate,
    useConstantBuffer(0),
    wtTime,
    useConstantBuffer(0),
    wtTime
  );

  subtract(useConstantBuffer(1), wtPos, wtPos);

  const pitch = pitchEnvelope(
    trigger,
    gate,
    useConstantBuffer(0),
    pitchTime,
    useConstantBuffer(0),
    pitchTime
  );

  const frequency = useMonoSignal();
  for (let i = 0; i < pitch.length; i++) {
    frequency[i] = pitchToHz(pitch[i] * modPitch[i] + basePitch[i]);
  }

  multiply(gain, velocity, gain);

  const oscillatorOutput = oscillator(
    frequency,
    wtPos,
    useConstantBuffer(0),
    useConstantBuffer(0),
    trigger
  );

  const filterFrequency = filterEnvelope(
    trigger,
    gate,
    useConstantBuffer(0),
    filterTime,
    useConstantBuffer(0),
    filterTime
  );

  for (let i = 0; i < filterFrequency.length; i++) {
    filterFrequency[i] = zeroOneToHz(filterFrequency[i] * 0.75 + 0.25);
  }

  const voiceOutput = useMonoSignal();

  const filter = useBiQuadFilter();

  const filterOutput = filter(
    oscillatorOutput,
    BiQuadFilterMode.LowPass,
    filterFrequency,
    useConstantBuffer(0.2),
    useConstantBuffer(0)
  );

  multiply(filterOutput, gain, voiceOutput);

  add(output[0], voiceOutput, output[0]);
  add(output[1], voiceOutput, output[1]);

  exit();
};

const RattleVoice = (
  trigger,
  gate,
  velocity,
  output,
  attackTime,
  decayTime
) => {
  const exit = enterChildScope();

  const gainEnvelope = useEnvelope(Curve.Log2, false);

  const gain = gainEnvelope(
    trigger,
    gate,
    attackTime,
    decayTime,
    useConstantBuffer(0),
    decayTime
  );

  if (allEqual(gain, 0)) {
    exit();
    return;
  }

  multiply(gain, gain, gain);
  multiply(gain, useConstantBuffer(0.35), gain);
  multiply(gain, velocity, gain);

  const noise = useOscillator(Waveform.Noise);
  const voiceOutput = useMonoSignal();
  const filter = useBiQuadFilter();
  const shaper = useBiQuadFilter();
  const filtered = filter(
    noise(useConstantBuffer(8000)),
    BiQuadFilterMode.BandPass,
    useConstantBuffer(3000),
    useConstantBuffer(0.25),
    useConstantBuffer(0)
  );
  const shaped = shaper(
    filtered,
    BiQuadFilterMode.EQBand,
    useConstantBuffer(4000),
    useConstantBuffer(0.5),
    useConstantBuffer(1.5)
  );
  multiply(shaped, gain, voiceOutput);

  add(output[0], voiceOutput, output[0]);
  add(output[1], voiceOutput, output[1]);

  exit();
};

const hatToneFrequencies = [953, 950, 1023, 1135, 1258].map((x) => x * 1.3);

const CymbalVoice = (settings) => {
  const exit = enterChildScope();

  const { wavetable, trigger, gate, velocity, output, attackTime, decayTime } =
    settings;

  const gainEnvelope = useEnvelope(Curve.Log2, false);

  const gain = gainEnvelope(
    trigger,
    gate,
    attackTime,
    decayTime,
    useConstantBuffer(0),
    decayTime
  );

  if (allEqual(gain, 0)) {
    exit();
    return;
  }

  const zero = useConstantBuffer(0);
  const carrierWtPosEnvelope = useEnvelope(Curve.Linear, false);
  const modulatorWtPosEnvelope = useEnvelope(Curve.Linear, false);

  multiply(gain, gain, gain);
  multiply(gain, useConstantBuffer(0.4), gain);
  multiply(gain, velocity, gain);

  const carrierWtPos = carrierWtPosEnvelope(
    trigger,
    gate,
    useConstantBuffer(0),
    useConstantBuffer(decayTime),
    useConstantBuffer(0),
    useConstantBuffer(decayTime)
  );
  subtract(useConstantBuffer(1), carrierWtPos, carrierWtPos);

  const modulatorWtPos = modulatorWtPosEnvelope(
    trigger,
    gate,
    useConstantBuffer(0),
    useConstantBuffer(decayTime),
    useConstantBuffer(0),
    useConstantBuffer(decayTime)
  );
  subtract(useConstantBuffer(1), modulatorWtPos, modulatorWtPos);

  const summedTones = useMonoSignal();
  summedTones.fill(0);

  const osc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);
  const osc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);
  const osc3 = useWavetableReader(wavetable, WavetableWarpMode.Off);
  const osc4 = useWavetableReader(wavetable, WavetableWarpMode.Off);
  const osc5 = useWavetableReader(wavetable, WavetableWarpMode.Off);

  const osc1Freq = settings.carrierRoot;

  const osc2Freq = useMonoSignal();
  const scaledDelta1 = useMonoSignal();
  multiply(settings.carrier1Delta, settings.carrierScale, scaledDelta1);
  add(osc1Freq, scaledDelta1, osc2Freq);

  const osc3Freq = useMonoSignal();
  const scaledDelta2 = useMonoSignal();
  multiply(settings.carrier2Delta, settings.carrierScale, scaledDelta2);
  add(osc2Freq, scaledDelta2, osc3Freq);

  const osc4Freq = useMonoSignal();
  const scaledDelta3 = useMonoSignal();
  multiply(settings.carrier3Delta, settings.carrierScale, scaledDelta3);
  add(osc3Freq, scaledDelta3, osc4Freq);

  const osc5Freq = useMonoSignal();
  const scaledDelta4 = useMonoSignal();
  multiply(settings.carrier4Delta, settings.carrierScale, scaledDelta4);
  add(osc4Freq, scaledDelta4, osc5Freq);

  add(
    summedTones,
    osc1(osc1Freq, carrierWtPos, zero, zero, trigger),
    summedTones
  );
  add(
    summedTones,
    osc2(osc2Freq, carrierWtPos, zero, zero, trigger),
    summedTones
  );
  add(
    summedTones,
    osc3(osc3Freq, carrierWtPos, zero, zero, trigger),
    summedTones
  );
  add(
    summedTones,
    osc4(osc4Freq, carrierWtPos, zero, zero, trigger),
    summedTones
  );
  add(
    summedTones,
    osc5(osc5Freq, carrierWtPos, zero, zero, trigger),
    summedTones
  );

  const ringOsc1 = useWavetableReader(wavetable, WavetableWarpMode.Off);
  const ringOsc2 = useWavetableReader(wavetable, WavetableWarpMode.Off);
  const ring1 = ringOsc1(
    settings.modulator1,
    modulatorWtPos,
    useConstantBuffer(0),
    useConstantBuffer(0),
    trigger
  );
  const ring2 = ringOsc2(
    settings.modulator2,
    modulatorWtPos,
    useConstantBuffer(0),
    useConstantBuffer(0),
    trigger
  );

  const bandpass1 = useBiQuadFilter();
  const bandpass2 = useBiQuadFilter();
  const highpass = useBiQuadFilter();

  const bandpassed1 = bandpass1(
    summedTones,
    BiQuadFilterMode.BandPass,
    settings.band1Freq,
    settings.band1Res,
    useConstantBuffer(0)
  );
  const bandpassed2 = bandpass2(
    summedTones,
    BiQuadFilterMode.BandPass,
    settings.band2Freq,
    settings.band2Res,
    useConstantBuffer(0)
  );

  multiply(ring1, bandpassed1, ring1);
  multiply(ring2, bandpassed2, ring2);
  add(ring1, ring2, summedTones);

  highpassed = highpass(
    summedTones,
    BiQuadFilterMode.HighPass,
    useConstantBuffer(1000),
    useConstantBuffer(1),
    useConstantBuffer(0)
  );
  multiply(highpassed, gain, highpassed);
  add(output[0], highpassed, output[0]);
  add(output[1], highpassed, output[1]);

  exit();
};

const hatPitchTiming = {
  54: {
    cymbalDecay: 200 * MS_TO_SAMPLES,
    cymbalAttack: 0,
    noiseDecay: 100 * MS_TO_SAMPLES,
    noiseAttack: 0 * MS_TO_SAMPLES,
    noiseGain: 1,
    cymbalGain: 1,
  },
  56: {
    cymbalDecay: 100 * MS_TO_SAMPLES,
    cymbalAttack: 4 * MS_TO_SAMPLES,
    noiseDecay: 25 * MS_TO_SAMPLES,
    noiseAttack: 4 * MS_TO_SAMPLES,
    noiseGain: 0.25,
    cymbalGain: 0.5,
  },
  58: {
    cymbalDecay: 1600 * MS_TO_SAMPLES,
    cymbalAttack: 0,
    noiseDecay: 150 * MS_TO_SAMPLES,
    noiseAttack: 0 * MS_TO_SAMPLES,
    noiseGain: 1,
    cymbalGain: 1,
  },
};

const voiceActivitySamples = {
  kick: 1024,
  snare: 1024,
  hat: 1024,
  hiTom: 1024,
  lowTom: 1024,
  midTom: 1024,
  flrTom: 1024,
};

const DigitalDrums = (settings) => {
  const {
    input,
    output,
    kick,
    snare,
    hat,
    hiTom,
    lowTom,
    midTom,
    flrTom,
    kickWavetable,
    snareWavetable,
    hatWavetable,
    tomWavetable,
  } = settings;
  output[0].fill(0);
  output[1].fill(0);

  hat[0].fill(0);
  hat[1].fill(0);

  hiTom[0].fill(0);
  hiTom[1].fill(0);

  lowTom[0].fill(0);
  lowTom[1].fill(0);

  midTom[0].fill(0);
  midTom[1].fill(0);

  flrTom[0].fill(0);
  flrTom[1].fill(0);

  // KICK
  const kickVoice = useSingleVoice(input, [47,48], 2);
  voiceActivitySamples.kick = allEqual(kickVoice.trigger, 0) ? voiceActivitySamples.kick : 1024;
  let exit = enterChildScope();
  if (voiceActivitySamples.kick > 0) {
    kick[0].fill(0);
    kick[1].fill(0);

    const kickModPitch = useMonoSignal();
    subtract(
      settings.kickAttackPitch,
      settings.kickResonantPitch,
      kickModPitch
    );

    const kickPitchTime = useMonoSignal();
    multiply(
      kickVoice.velocity,
      useConstantBuffer(50 * MS_TO_SAMPLES),
      kickPitchTime
    );
    add(kickPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), kickPitchTime);

    DrumVoice({
      ...kickVoice,
      wavetable: kickWavetable,
      output: kick,
      basePitch: settings.kickResonantPitch,
      modPitch: kickModPitch,
      wtTime: settings.kickWavetableScanTime,
      pitchTime: settings.kickPitchDropTime,
      gainTime: settings.kickAmplitudeDecayTime,
      filterTime: settings.kickFilterDropTime,
    });

    add(output[0], kick[0], output[0]);
    add(output[1], kick[1], output[1]);

    if (allEqual(kick[0], 0) && allEqual(kick[1], 0)) {
      voiceActivitySamples.kick -= bufferSize;
    }
  }
  exit();

  // SNARE
  const snareVoice = useSingleVoice(input, [50, 51, 52], 2);
  voiceActivitySamples.snare = allEqual(snareVoice.trigger, 0) ? voiceActivitySamples.snare : 1024;
  exit = enterChildScope();

  if (voiceActivitySamples.snare > 0) {
    snare[0].fill(0);
    snare[1].fill(0);

    const snareSkin1 = useStereoSignal();
    snareSkin1[0].fill(0);
    snareSkin1[1].fill(0);

    DrumVoice({
      ...snareVoice,
      wavetable: snareWavetable,
      output: snareSkin1,
      basePitch: settings.snareSkin1Pitch,
      modPitch: useConstantBuffer(0.6),
      wtTime: settings.snareSkin1WavetableScanTime,
      pitchTime: useConstantBuffer(300 * MS_TO_SAMPLES),
      gainTime: settings.snareSkin1Decay,
      filterTime: settings.snareSkin1Decay,
    });

    multiply(snareSkin1[0], settings.snareSkin1Level, snareSkin1[0]);
    multiply(snareSkin1[1], settings.snareSkin1Level, snareSkin1[1]);
    add(snare[0], snareSkin1[0], snare[0]);
    add(snare[1], snareSkin1[1], snare[1]);

    const snareSkin2 = useStereoSignal();

    const ringPitch = useMonoSignal();
    add(settings.snareSkin2Offset, settings.snareSkin1Pitch, ringPitch);
    snareSkin2[0].fill(0);
    snareSkin2[1].fill(0);

    DrumVoice({
      ...snareVoice,
      wavetable: snareWavetable,
      output: snareSkin2,
      basePitch: ringPitch,
      modPitch: useConstantBuffer(0.75),
      wtTime: settings.snareSkin2WavetableScanTime,
      pitchTime: useConstantBuffer(400 * MS_TO_SAMPLES),
      gainTime: settings.snareSkin2Decay,
      filterTime: settings.snareSkin2Decay,
    });

    multiply(snareSkin2[0], settings.snareSkin2Level, snareSkin2[0]);
    multiply(snareSkin2[1], settings.snareSkin2Level, snareSkin2[1]);

    add(snare[0], snareSkin2[0], snare[0]);
    add(snare[1], snareSkin2[1], snare[1]);

    const snareSnares = useStereoSignal();
    snareSnares[0].fill(0);
    snareSnares[1].fill(0);
    RattleVoice(
      snareVoice.trigger,
      snareVoice.gate,
      snareVoice.velocity,
      snareSnares,
      settings.snareSnaresAttack,
      settings.snareSnaresDecay
    );

    multiply(snareSnares[0], settings.snareSnaresLevel, snareSnares[0]);
    multiply(snareSnares[1], settings.snareSnaresLevel, snareSnares[1]);

    add(snare[0], snareSnares[0], snare[0]);
    add(snare[1], snareSnares[1], snare[1]);

    add(output[0], snare[0], output[0]);
    add(output[1], snare[1], output[1]);

    if (allEqual(snare[0], 0) && allEqual(snare[1], 0)) {
      voiceActivitySamples.snare -= bufferSize;
    }
  }
  exit();

  // HAT

  const hatVoice = useSingleVoice(input, [54, 56, 58], 2);
  voiceActivitySamples.hat = allEqual(hatVoice.trigger, 0) ? voiceActivitySamples.hat : 1024;
  exit = enterChildScope();

  if (voiceActivitySamples.hat > 0) {
    const hatCymbalDecay = useMonoSignal();
    const hatNoiseDecay = useMonoSignal();
    const hatNoiseAttack = useMonoSignal();
    const hatCymbalAttack = useMonoSignal();
    for (let i = 0; i < hatCymbalDecay.length; i++) {
      const timingSpec = hatPitchTiming[hatVoice.pitch[i]];
      hatCymbalDecay[i] = timingSpec?.cymbalDecay || 0;
      hatNoiseDecay[i] = timingSpec?.noiseDecay || 0;
      hatNoiseAttack[i] = timingSpec?.noiseAttack || 0;
      hatCymbalAttack[i] = timingSpec?.cymbalAttack || 0;
    }
    const hatCymbal = useStereoSignal();

    hatCymbal[0].fill(0);
    hatCymbal[1].fill(0);

    CymbalVoice({
      ...hatVoice,
      wavetable: hatWavetable,
      output: hatCymbal,
      attackTime: hatCymbalAttack,
      decayTime: hatCymbalDecay,
      carrierRoot: settings.hatCarrierRoot,
      carrierScale: settings.hatCarrierScale,
      carrier1Delta: settings.hatCarrierDelta1,
      carrier2Delta: settings.hatCarrierDelta2,
      carrier3Delta: settings.hatCarrierDelta3,
      carrier4Delta: settings.hatCarrierDelta4,
      modulator1: settings.hatModulator1Freq,
      modulator2: settings.hatModulator2Freq,
      band1Freq: settings.hatCarrierBandpass1Freq,
      band2Freq: settings.hatCarrierBandpass2Freq,
      band1Res: settings.hatCarrierBandpass1Res,
      band2Res: settings.hatCarrierBandpass2Res,
    });

    const hatNoise = useStereoSignal();
    hatNoise[0].fill(0);
    hatNoise[1].fill(0);
    RattleVoice(
      hatVoice.trigger,
      hatVoice.gate,
      hatVoice.velocity,
      hatNoise,
      hatNoiseAttack,
      hatNoiseDecay
    );
    for (let i = 0; i < hatNoise[0].length; i++) {
      const timingSpec = hatPitchTiming[hatVoice.pitch[i]];
      const noiseGain = timingSpec?.noiseGain || 0;
      const cymbalGain = timingSpec?.cymbalGain || 0;
      hatNoise[0][i] *= noiseGain;
      hatNoise[1][i] *= noiseGain;
      hatCymbal[0][i] *= cymbalGain;
      hatCymbal[1][i] *= cymbalGain;
    }
    add(hat[0], hatNoise[0], hat[0]);
    add(hat[1], hatNoise[1], hat[1]);
    add(hat[0], hatCymbal[0], hat[0]);
    add(hat[1], hatCymbal[1], hat[1]);

    add(output[0], hat[0], output[0]);
    add(output[1], hat[1], output[1]);

    if (allEqual(hat[0], 0) && allEqual(hat[1], 0)) {
      voiceActivitySamples.hat -= bufferSize;
    }
  }
  exit();

  const hiTomVoice = useSingleVoice(input, [59], 2);
  const midTomVoice = useSingleVoice(input, [57], 2);
  const lowTomVoice = useSingleVoice(input, [55], 2);
  const flrTomVoice = useSingleVoice(input, [53], 2);

  const tomPitchTime = useMonoSignal();
  multiply(
    hiTomVoice.velocity,
    useConstantBuffer(50 * MS_TO_SAMPLES),
    tomPitchTime
  );
  add(tomPitchTime, useConstantBuffer(100 * MS_TO_SAMPLES), tomPitchTime);

  for (let i = 0; i < 4; i++) {
    let voice, currentOutput, resonantPitchBuffer, activityKey;
    if (i === 0) {
      activityKey = "hiTom";
      voice = hiTomVoice;
      currentOutput = hiTom;
      resonantPitchBuffer = settings.hiTomResonantPitch;
    } else if (i === 1) {
      activityKey = "midTom";
      voice = midTomVoice;
      currentOutput = midTom;
      resonantPitchBuffer = settings.midTomResonantPitch;
    } else if (i === 2) {
      activityKey = "lowTom";
      voice = lowTomVoice;
      currentOutput = lowTom;
      resonantPitchBuffer = settings.lowTomResonantPitch;
    } else if (i === 3) {
      activityKey = "flrTom";
      voice = flrTomVoice;
      currentOutput = flrTom;
      resonantPitchBuffer = settings.flrTomResonantPitch;
    }

    voiceActivitySamples[activityKey] = allEqual(voice.trigger, 0) ? 
      voiceActivitySamples[activityKey] : 1024;

    exit = enterChildScope();
    if (voiceActivitySamples[activityKey] > 0) {
      DrumVoice({
        ...voice,
        wavetable: tomWavetable,
        output: currentOutput,
        basePitch: resonantPitchBuffer,
        modPitch: settings.tomModPitch,
        wtTime: settings.tomWavetableScanTime,
        pitchTime: settings.tomPitchDropTime,
        gainTime: settings.tomAmplitudeDecayTime,
        filterTime: settings.tomFilterDropTime,
      });

      add(output[0], currentOutput[0], output[0]);
      add(output[1], currentOutput[1], output[1]);

      if (allEqual(currentOutput[0], 0) && allEqual(currentOutput[1], 0)) {
        voiceActivitySamples[activityKey] -= bufferSize;
      }
    }
    exit();
  }
};

const WAVETABLES = {
  Kick: "[encoded data, 11456 characters]",
  Snare:
    "[encoded data, 5380 characters]",
  Crash:
    "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",

  Tom: "[encoded data, 5528 characters]",
};

this.constructDevice = () =>
  constructPerBufferDevice({
    noteInputHint: {
      interface: NoteInterfaceType.Pads,
      usedPitches: {
        48: "Kick",
        50: "Snare L",
        51: "Snare Ghost",
        52: "Snare R",
        54: "Hat Closed",
        56: "Hat Pedal",
        58: "Hat Open",
      },
    },
    implementation: DigitalDrums,
    constants: {
      kickWavetable: wavetableConstant("Kick Wavetable", WAVETABLES.Kick),
      snareWavetable: wavetableConstant("Snare Wavetable", WAVETABLES.Snare),
      hatWavetable: wavetableConstant("Hat Wavetable", WAVETABLES.Crash),
      tomWavetable: wavetableConstant("Tom Wavetable", WAVETABLES.Tom),
    },
    inputs: {
      input: midi("In"),

      kickResonantPitch: numberSetting("Kick Resonant Pitch", {
        min: 10,
        max: 30,
        defaultValue: 22,
      }),
      kickAttackPitch: numberSetting("Kick Attack Pitch", {
        min: 40,
        max: 55,
        defaultValue: 43.5,
      }),
      kickWavetableScanTime: adsrTimeSetting("Kick Wavetable Scan Time", 220),
      kickPitchDropTime: adsrTimeSetting("Kick Pitch Drop Time", 150),
      kickAmplitudeDecayTime: adsrTimeSetting(
        "Kick Amplitude Decay Time",
        1400
      ),
      kickFilterDropTime: adsrTimeSetting("Kick Filter Drop Time", 92),

      snareSkin1Pitch: numberSetting("Snare Skin 1 Pitch", {
        min: 30,
        max: 60,
        defaultValue: 42.8,
      }),
      snareSkin1Level: dbSetting("Snare Skin 1 Level", {
        defaultValue: -4,
        max: 0,
        min: -24,
      }),
      snareSkin1WavetableScanTime: adsrTimeSetting(
        "Snare Skin 1 Wavetable Scan Time",
        30
      ),
      snareSkin1Decay: adsrTimeSetting("Snare Skin 1 Decay", 230),

      snareSkin2Offset: numberSetting("Snare Skin 2 Offset", {
        min: 5,
        max: 9,
        defaultValue: 7.48,
      }),
      snareSkin2Level: dbSetting("Snare Skin 2 Level", {
        defaultValue: -15,
        max: -3,
        min: -30,
      }),
      snareSkin2WavetableScanTime: adsrTimeSetting(
        "Snare Skin 2 Wavetable Scan Time",
        30
      ),
      snareSkin2Decay: adsrTimeSetting("Snare Skin 2 Decay", 230),

      snareSnaresLevel: dbSetting("Snare Snares Level", { defaultValue: 0 }),
      snareSnaresAttack: adsrTimeSetting("Snare Snares Attack", 17),
      snareSnaresDecay: adsrTimeSetting("Snare Snares Decay", 190),

      hatCarrierDelta1: numberSetting("Hat Carrier Delta 1", {
        min: 0,
        max: 100,
        defaultValue: 1.6,
      }),
      hatCarrierDelta2: numberSetting("Hat Carrier Delta 2", {
        min: 0,
        max: 100,
        defaultValue: 15.8,
      }),
      hatCarrierDelta3: numberSetting("Hat Carrier Delta 3", {
        min: 0,
        max: 100,
        defaultValue: 31.8,
      }),
      hatCarrierDelta4: numberSetting("Hat Carrier Delta 4", {
        min: 0,
        max: 100,
        defaultValue: 51.0,
      }),
      hatCarrierRoot: numberSetting("Hat Carrier Root", {
        min: 900,
        max: 1100,
        defaultValue: 949,
      }),
      hatCarrierScale: numberSetting("Hat Carrier Scale", {
        min: 1,
        max: 5,
        defaultValue: 3,
      }),
      hatModulator1Freq: numberSetting("Hat Modulator 1 Freq", {
        min: 2000,
        max: 8000,
        defaultValue: 4000,
      }),
      hatModulator2Freq: numberSetting("Hat Modulator 2 Freq", {
        min: 2000,
        max: 8000,
        defaultValue: 5000,
      }),
      hatCarrierBandpass1Freq: numberSetting("Hat Carrier BPF 1 Freq", {
        min: 2000,
        max: 8000,
        defaultValue: 5000,
      }),
      hatCarrierBandpass2Freq: numberSetting("Hat Carrier BPF 2 Freq", {
        min: 2000,
        max: 8000,
        defaultValue: 6500,
      }),
      hatCarrierBandpass1Res: numberSetting("Hat Carrier BPF 1 Res", {
        min: 0.1,
        max: 5,
        defaultValue: 1,
      }),
      hatCarrierBandpass2Res: numberSetting("Hat Carrier BPF 2 Res", {
        min: 0.1,
        max: 5,
        defaultValue: 2,
      }),

      hiTomResonantPitch: numberSetting("High Tom Resonant Pitch", {
        min: 25,
        max: 60,
        defaultValue: 50,
      }),
      midTomResonantPitch: numberSetting("Mid Tom Resonant Pitch", {
        min: 25,
        max: 60,
        defaultValue: 46,
      }),
      lowTomResonantPitch: numberSetting("Low Tom Resonant Pitch", {
        min: 25,
        max: 60,
        defaultValue: 40,
      }),
      flrTomResonantPitch: numberSetting("Floor Tom Resonant Pitch", {
        min: 25,
        max: 60,
        defaultValue: 34,
      }),

      tomModPitch: numberSetting("Tom Modulated Pitch", {
        min: 0,
        max: 12,
        defaultValue: 6,
      }),
      tomWavetableScanTime: adsrTimeSetting("Tom Wavetable Scan Time", 670),
      tomPitchDropTime: adsrTimeSetting("Tom Pitch Drop Time", 220),
      tomAmplitudeDecayTime: adsrTimeSetting("Tom Amplitude Decay Time", 338),
      tomFilterDropTime: adsrTimeSetting("Tom Filter Drop Time", 110),
    },
    outputs: {
      output: stereo("Out"),
      kick: collapse(stereo("Kick")),
      snare: collapse(stereo("Snare")),
      hat: collapse(stereo("Hat")),
      hiTom: collapse(stereo("High Tom")),
      midTom: collapse(stereo("Mid Tom")),
      lowTom: collapse(stereo("Low Tom")),
      flrTom: collapse(stereo("Floor Tom")),
    },
    layout: row([
      col([
        box(
          col([
            spacer(5),
            label("Hat"),
            spacer(5),
            row([
              col([
                row([spacer(40), label("Carrier")]),
                spacer(10),
                row([
                  box(label("Root"), 40),
                  box(linear("hatCarrierRoot"), 132, 20, gray300, 4),
                ]),
                spacer(4),
                row([
                  box(label("Delta"), 40),
                  col([
                    row([
                      knob("hatCarrierDelta1", { size: 30 }),
                      spacer(4),
                      knob("hatCarrierDelta2", { size: 30 }),
                      spacer(4),
                      knob("hatCarrierDelta3", { size: 30 }),
                      spacer(4),
                      knob("hatCarrierDelta4", { size: 30 }),
                    ]),
                  ]),
                ]),
                spacer(4),
                row([
                  box(label("Mult"), 40),
                  box(linear("hatCarrierScale"), 132, 20, gray300, 4),
                ]),
              ]),
              spacer(4),
              col([
                row([label("BPF")]),
                spacer(4),
                knob("hatCarrierBandpass1Freq", { size: 40 }),
                spacer(4),
                knob("hatCarrierBandpass2Freq", { size: 40 }),
              ]),
              spacer(4),
              col([
                row([label("Mod")]),
                spacer(4),
                knob("hatModulator1Freq", { size: 40 }),
                spacer(4),
                knob("hatModulator2Freq", { size: 40 }),
              ]),
              spacer(4),
              col([
                row([label("Pres.")]),
                spacer(4),
                knob("hatCarrierBandpass1Res", { size: 40 }),
                spacer(4),
                knob("hatCarrierBandpass2Res", { size: 40 }),
              ]),
              spacer(3),
            ]),
            spacer(5),
          ]),
          310,
          "auto",
          gray200,
          8
        ),
        spacer(1),
        box(
          col([
            spacer(5),
            label("Snare"),
            row([
              spacer(5),
              box(label("Skin 1"), 40, "auto"),
              spacer(2),
              titled.knob("snareSkin1Level", "Level", 30),
              box(titled.knob("snareSkin1Pitch", "Pitch", 30), 60, "auto"),
              col([
                row([
                  box(label("Timbre"), 50, 20),
                  box(
                    linear("snareSkin1WavetableScanTime"),
                    90,
                    20,
                    gray300,
                    4
                  ),
                ]),
                spacer(2),
                row([
                  box(label("Mute"), 50, 20),
                  box(linear("snareSkin1Decay"), 90, 20, gray300, 4),
                ]),
                spacer(2),
              ]),
              spacer(4),
            ]),
            row([
              spacer(5),
              box(label("Skin 2"), 40, "auto"),
              spacer(2),
              titled.knob("snareSkin2Level", "Level", 30),
              box(titled.knob("snareSkin2Offset", "Shift", 30), 60, "auto"),
              col([
                row([
                  box(label("Timbre"), 50, 20),
                  box(
                    linear("snareSkin2WavetableScanTime"),
                    90,
                    20,
                    gray300,
                    4
                  ),
                ]),
                spacer(2),
                row([
                  box(label("Mute"), 50, 20),
                  box(linear("snareSkin2Decay"), 90, 20, gray300, 4),
                ]),
                spacer(2),
              ]),
              spacer(4),
            ]),
            row([
              spacer(5),
              box(label("Snares"), 40, "auto"),
              spacer(2),
              titled.knob("snareSnaresLevel", "Level", 30),
              box(spacer(0), 60, "auto"),
              col([
                row([
                  box(label("Attack"), 50, 20),
                  box(linear("snareSnaresAttack"), 90, 20, gray300, 4),
                ]),
                spacer(2),
                row([
                  box(label("Decay"), 50, 20),
                  box(linear("snareSnaresDecay"), 90, 20, gray300, 4),
                ]),
                spacer(2),
              ]),
              spacer(4),
            ]),
          ]),
          310,
          "auto",
          gray200,
          8
        ),
        spacer(1),
        box(
          col([
            spacer(5),
            label("Kick"),
            row([
              titled.knob("kickResonantPitch", "Res. Pitch", 60),
              titled.knob("kickAttackPitch", "Atk. Pitch", 60),
              col([
                row([
                  box(label("Pitch"), 50, 20),
                  box(linear("kickPitchDropTime"), 90, 20, gray300, 4),
                ]),
                spacer(2),
                row([
                  box(label("Timbre"), 50, 20),
                  box(linear("kickWavetableScanTime"), 90, 20, gray300, 4),
                ]),
                spacer(2),
                row([
                  box(label("Clarity"), 50, 20),
                  box(linear("kickFilterDropTime"), 90, 20, gray300, 4),
                ]),
                spacer(2),
                row([
                  box(label("Mute"), 50, 20),
                  box(linear("kickAmplitudeDecayTime"), 90, 20, gray300, 4),
                ]),
              ]),
              spacer(4),
            ]),
          ]),
          310,
          "auto",
          gray200,
          8
        ),
      ]),
      col([
        box(
          col([
            spacer(5),
            label("Toms"),
            row([
              col([
                row([
                  knob("hiTomResonantPitch", { size: 30 }),
                  spacer(4),
                  knob("midTomResonantPitch", { size: 30 }),
                  spacer(4),
                  knob("lowTomResonantPitch", { size: 30 }),
                  spacer(4),
                  knob("flrTomResonantPitch", { size: 30 }),
                ]),
                spacer(4),
                box(label("Resonant Pitches"), 120, 20, gray100, 4),
              ]),
              titled.knob("tomModPitch", "Mod. Pitch", 30),
              col([
                row([
                  box(label("Pitch"), 50, 20),
                  box(linear("tomPitchDropTime"), 90, 20, gray300, 4),
                ]),
                spacer(2),
                row([
                  box(label("Timbre"), 50, 20),
                  box(linear("tomWavetableScanTime"), 90, 20, gray300, 4),
                ]),
              ]),
              col([
                row([
                  box(label("Clarity"), 50, 20),
                  box(linear("tomFilterDropTime"), 90, 20, gray300, 4),
                ]),
                spacer(2),
                row([
                  box(label("Mute"), 50, 20),
                  box(linear("tomAmplitudeDecayTime"), 90, 20, gray300, 4),
                ]),
              ]),
              spacer(4),
            ]),
          ]),
          310,
          "auto",
          gray200,
          8
        ),
      ]),
    ]),
    presetTags: {
      Genre: ["Funk", "Pop", "Metal", "Drum & Bass"],
    },
  });
