/*
controls.mjs - Registers audio controls for pattern manipulation and effects.
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/controls.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

import { logger } from './logger.mjs';
import { Pattern, pure, register, reify } from './pattern.mjs';

export function createParam(names) {
  let isMulti = Array.isArray(names);
  names = !isMulti ? [names] : names;
  const name = names[0];

  // todo: make this less confusing
  const withVal = (xs) => {
    let bag;
    // check if we have an object with an unnamed control (.value)
    if (typeof xs === 'object' && xs.value !== undefined) {
      bag = { ...xs }; // grab props that are already there
      xs = xs.value; // grab the unnamed control for this one
      delete bag.value;
    }
    if (isMulti && Array.isArray(xs)) {
      const result = bag || {};
      xs.forEach((x, i) => {
        if (i < names.length) {
          result[names[i]] = x;
        }
      });
      return result;
    } else if (bag) {
      bag[name] = xs;
      return bag;
    } else {
      return { [name]: xs };
    }
  };

  // todo: make this less confusing
  const func = function (value, pat) {
    if (!pat) {
      return reify(value).withValue(withVal);
    }
    if (typeof value === 'undefined') {
      return pat.fmap(withVal);
    }
    return pat.set(reify(value).withValue(withVal));
  };
  Pattern.prototype[name] = function (value) {
    return func(value, this);
  };
  return func;
}

// maps control alias names to the "main" control name
const controlAlias = new Map();

export function isControlName(name) {
  return controlAlias.has(name);
}

export function registerControl(names, ...aliases) {
  const name = Array.isArray(names) ? names[0] : names;
  let bag = {};
  bag[name] = createParam(names);
  controlAlias.set(name, name);
  aliases.forEach((alias) => {
    bag[alias] = bag[name];
    controlAlias.set(alias, name);
    Pattern.prototype[alias] = Pattern.prototype[name];
  });
  return bag;
}

export function registerMultiControl(names, maxControls, ...aliases) {
  names = Array.isArray(names) ? names : [names];
  let bag = {};
  for (let i = 1; i <= maxControls; i++) {
    let theseAliases = [...aliases];
    let theseNames = [...names];
    if (i === 1) {
      // adds e.g. fm1 as an alias for fm
      const aliases1 = theseAliases.map((a) => `${a}1`);
      const names1 = theseNames.map((n) => `${n}1`);
      theseAliases = theseAliases.concat(aliases1).concat(names1);
    } else {
      theseAliases = theseAliases.map((a) => `${a}${i}`);
      theseNames = theseNames.map((n) => `${n}${i}`);
    }
    const subBag = registerControl(theseNames, ...theseAliases);
    bag = { ...bag, ...subBag };
  }
  return bag;
}

/**
 * Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
 * separated by ':'.
 *
 * @name s
 * @tags superdough, samples
 * @param {string | Pattern} sound The sound / pattern of sounds to pick
 * @synonyms sound
 * @example
 * s("bd hh")
 * @example
 * s("bd:0 bd:1 bd:0:0.3 bd:1:1.4")
 *
 */
export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound');

/**
 * Position in the wavetable of the wavetable oscillator
 *
 * @name wt
 * @tags wavetable, superdough
 * @param {number | Pattern} position Position in the wavetable from 0 to 1
 * @synonyms wavetablePosition
 * @example
 * s("squelch").bank("wt_digital").seg(8).note("F1").wt("0 0.25 0.5 0.75 1")
 */
export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePosition');

/**
 * Amount of envelope applied wavetable oscillator's position envelope
 *
 * @name wtenv
 * @tags wavetable, envelope, superdough
 * @param {number | Pattern} amount between 0 and 1
 */
export const { wtenv } = registerControl('wtenv');
/**
 * Attack time of the wavetable oscillator's position envelope
 *
 * @name wtattack
 * @tags wavetable, envelope, superdough
 * @synonyms wtatt
 * @param {number | Pattern} time attack time in seconds
 */
export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt');

/**
 * Decay time of the wavetable oscillator's position envelope
 *
 * @name wtdecay
 * @tags wavetable, envelope, superdough
 * @synonyms wtdec
 * @param {number | Pattern} time decay time in seconds
 */
export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec');

/**
 * Sustain time of the wavetable oscillator's position envelope
 *
 * @name wtsustain
 * @tags wavetable, envelope, superdough
 * @synonyms wtsus
 * @param {number | Pattern} gain sustain level (0 to 1)
 */
export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus');

/**
 * Release time of the wavetable oscillator's position envelope
 *
 * @name wtrelease
 * @tags wavetable, envelope, superdough
 * @synonyms wtrel
 * @param {number | Pattern} time release time in seconds
 */
export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel');

/**
 * Rate of the LFO for the wavetable oscillator's position
 *
 * @name wtrate
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} rate rate in hertz
 */
export const { wtrate } = registerControl('wtrate');
/**
 * cycle synced rate of the LFO for the wavetable oscillator's position
 *
 * @name wtsync
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} rate rate in cycles
 */
export const { wtsync } = registerControl('wtsync');

/**
 * Depth of the LFO for the wavetable oscillator's position
 *
 * @name wtdepth
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} depth depth of modulation
 */
export const { wtdepth } = registerControl('wtdepth');

/**
 * Shape of the LFO for the wavetable oscillator's position
 *
 * @name wtshape
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
 */
export const { wtshape } = registerControl('wtshape');

/**
 * DC offset of the LFO for the wavetable oscillator's position
 *
 * @name wtdc
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
 */
export const { wtdc } = registerControl('wtdc');

/**
 * Skew of the LFO for the wavetable oscillator's position
 *
 * @name wtskew
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} skew How much to bend the LFO shape
 */
export const { wtskew } = registerControl('wtskew');

/**
 * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator
 *
 * @name warp
 * @tags wavetable, superdough
 * @param {number | Pattern} amount Warp of the wavetable from 0 to 1
 * @synonyms wavetableWarp
 * @example
 * s("basique").bank("wt_digital").seg(8).note("F1").warp("0 0.25 0.5 0.75 1")
 *   .warpmode("spin")
 */
export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp');

/**
 * Attack time of the wavetable oscillator's warp envelope
 *
 * @name warpattack
 * @tags wavetable, envelope, superdough
 * @synonyms warpatt
 * @param {number | Pattern} time attack time in seconds
 */
export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt');

/**
 * Decay time of the wavetable oscillator's warp envelope
 *
 * @name warpdecay
 * @tags wavetable, envelope, superdough
 * @synonyms warpdec
 * @param {number | Pattern} time decay time in seconds
 */
export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec');

/**
 * Sustain time of the wavetable oscillator's warp envelope
 *
 * @name warpsustain
 * @tags wavetable, envelope, superdough
 * @synonyms warpsus
 * @param {number | Pattern} gain sustain level (0 to 1)
 */
export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus');

/**
 * Release time of the wavetable oscillator's warp envelope
 *
 * @name warprelease
 * @tags wavetable, envelope, superdough
 * @synonyms warprel
 * @param {number | Pattern} time release time in seconds
 */
export const { warprelease, warprel } = registerControl('warprelease', 'warprel');

/**
 * Rate of the LFO for the wavetable oscillator's warp
 *
 * @name warprate
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} rate rate in hertz
 */
export const { warprate } = registerControl('warprate');

/**
 * Depth of the LFO for the wavetable oscillator's warp
 *
 * @name warpdepth
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} depth depth of modulation
 */
export const { warpdepth } = registerControl('warpdepth');

/**
 * Shape of the LFO for the wavetable oscillator's warp
 *
 * @name warpshape
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
 */
export const { warpshape } = registerControl('warpshape');

/**
 * DC offset of the LFO for the wavetable oscillator's warp
 *
 * @name warpdc
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
 */
export const { warpdc } = registerControl('warpdc');

/**
 * Skew of the LFO for the wavetable oscillator's warp
 *
 * @name warpskew
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} skew How much to bend the LFO shape
 */
export const { warpskew } = registerControl('warpskew');

/**
 * Type of warp (alteration of the waveform) to apply to the wavetable oscillator.
 *
 * The current options are: none, asym, bendp, bendm, bendmp, sync, quant, fold, pwm, orbit,
 * spin, chaos, primes, binary, brownian, reciprocal, wormhole, logistic, sigmoid, fractal, flip
 *
 * @name warpmode
 * @tags wavetable, superdough
 * @param {number | string | Pattern} mode Warp mode
 * @synonyms wavetableWarpMode
 * @example
 * s("morgana").bank("wt_digital").seg(8).note("F1").warp("0 0.25 0.5 0.75 1")
 *   .warpmode("<asym bendp spin logistic sync wormhole brownian>*2")
 *
 */
export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wavetableWarpMode');

/**
 * Amount of randomness of the initial phase of the wavetable oscillator.
 *
 * @name wtphaserand
 * @tags wavetable, superdough
 * @param {number | Pattern} amount Randomness of the initial phase. Between 0 (not random) and 1 (fully random)
 * @synonyms wavetablePhaseRand
 * @example
 * s("basique").bank("wt_digital").seg(16).wtphaserand("<0 1>")
 *
 */
export const { wtphaserand, wavetablePhaseRand } = registerControl('wtphaserand', 'wavetablePhaseRand');

/**
 * Amount of envelope applied wavetable oscillator's position envelope
 *
 * @name warpenv
 * @tags wavetable, envelope, superdough
 * @param {number | Pattern} amount between 0 and 1
 */
export const { warpenv } = registerControl('warpenv');

/**
 * cycle synced rate of the LFO for the wavetable warp position
 *
 * @name warpsync
 * @tags wavetable, lfo, superdough
 * @param {number | Pattern} rate rate in cycles
 */
export const { warpsync } = registerControl('warpsync');

/**
 * Define a custom webaudio node to use as a sound source.
 *
 * @name source
 * @tags external_io, superdough
 * @synonyms src
 * @param {function} getSource
 * @synonyms src
 *
 */
export const { source, src } = registerControl('source', 'src');
/**
 * Selects the given index:
 *  - for samples, it picks the sample by index, with wrap around
 *  - for scales, it picks the scale degree
 *  - for voicings, it picks the voice index
 *
 * @name n
 * @tags superdough, samples, tonal
 * @param {number | Pattern} value sample index starting from 0
 * @example
 * s("bd sd [~ bd] sd,hh*6").n("<0 1>")
 */
// also see https://codeberg.org/uzu/strudel/pulls/63
export const { n } = registerControl('n');

/**
 * Selects the given degree. Currently used in `xen` and `tune`:
 *
 * @name i
 * @tags tonal
 * @param {number | Pattern} value
 * @example
 * i("0 1 2 3 4 5 6 7").xen("<5edo 10edo 15edo hexany15>")
 */
export const { i } = registerControl('i');

/**
 * Plays the given note name or midi number. A note name consists of
 *
 * - a letter (a-g or A-G)
 * - optional accidentals (b or #)
 * - optional (possibly negative) octave number (0-9). Defaults to 3
 *
 * Examples of valid note names: `c`, `bb`, `Bb`, `f#`, `c3`, `A4`, `Eb2`, `c#5`
 *
 * You can also use midi numbers instead of note names, where 69 is mapped to A4 440Hz in 12EDO.
 *
 * @name note
 * @tags tonal
 * @example
 * note("c a f e")
 * @example
 * note("c4 a4 f4 e4")
 * @example
 * note("60 69 65 64")
 * @example
 * note("fbb1 a#0 cbbb-1 e##-2").sound("saw")
 */
export const { note } = registerControl(['note', 'n']);

/**
 * A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt.
 *
 * @name accelerate
 * @tags samples, superdirt
 * @param {number | Pattern} amount acceleration.
 * @superdirtOnly
 * @example
 * s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc()
 *
 */
export const { accelerate } = registerControl('accelerate');
/**
 * Sets the velocity from 0 to 1. Is multiplied together with gain.
 *
 * @name velocity
 * @tags amplitude, superdough, supradough
 * @synonyms vel
 * @example
 * s("hh*8")
 * .gain(".4!2 1 .4!2 1 .4 1")
 * .velocity(".4 1")
 */
export const { velocity, vel } = registerControl('velocity', 'vel');
/**
 * Controls the gain by an exponential amount.
 *
 * @name gain
 * @tags amplitude, superdough, supradough
 * @param {number | Pattern} amount gain.
 * @example
 * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2)
 *
 */
export const { gain } = registerControl('gain');
/**
 * Gain applied after all effects have been processed.
 *
 * @name postgain
 * @tags amplitude, superdough, supradough
 * @example
 * s("bd sd [~ bd] sd,hh*8")
 * .compressor("-20:20:10:.002:.02").postgain(1.5)
 *
 */
export const { postgain } = registerControl('postgain');
/**
 * Like `gain`, but linear.
 *
 * @name amp
 * @tags amplitude, superdirt
 * @param {number | Pattern} amount gain.
 * @superdirtOnly
 * @example
 * s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc()
 *
 */
export const { amp } = registerControl('amp');

/**
 * Sets the Frequency Modulation Harmonicity Ratio.
 * Controls the timbre of the sound.
 * Whole numbers and simple ratios sound more natural,
 * while decimal numbers and complex ratios sound metallic.
 *
 * A number may be added afterwards to control the harmonicity of
 * any of the 8 individual FMs (e.g. `fmh2`)
 *
 * @name fmh
 * @tags fm, superdough, supradough
 * @param {number | Pattern} harmonicity
 * @example
 * note("c e g b g e")
 * .fm(4)
 * .fmh("<1 2 1.5 1.61>")
 * ._scope()
 *
 */
export const { fmh, fmh1, fmh2, fmh3, fmh4, fmh5, fmh6, fmh7, fmh8 } = registerMultiControl(['fmh', 'fmi'], 8, 'fmh');

/**
 * Sets the Frequency Modulation of the synth.
 * Controls the modulation index, which defines the brightness of the sound.
 *
 * A number may be added afterwards to control the modulation index of
 * any of the 8 individual FMs (e.g. `fm3`). Also, FMs may be routed into
 * each other with matrix commands like `fm13`, which would send `fm1` back into
 * `fm3`
 *
 * @name fmi
 * @tags fm, superdough, supradough
 * @param {number | Pattern} brightness modulation index
 * @synonyms fm
 * @example
 * note("c e g b g e")
 * .fm("<0 1 2 8 32>")
 * ._scope()
 * @example
 * s("sine").note("F1").seg(8)
 *  .fm(4).fm2(rand.mul(4)).fm3(saw.mul(8).slow(8))
 *  .fmh(1.06).fmh2(10).fmh3(0.1)
 *
 */
export const { fmi, fmi1, fmi2, fmi3, fmi4, fmi5, fmi6, fmi7, fmi8, fm, fm1, fm2, fm3, fm4, fm5, fm6, fm7, fm8 } =
  registerMultiControl(['fmi', 'fmh'], 8, 'fm');

// fm envelope
/**
 * Ramp type of fm envelope. Exp might be a bit broken..
 *
 * A number may be added afterwards to control the envelope of
 * any of the 8 individual FMs (e.g. `fmenv4`)
 *
 * @name fmenv
 * @tags fm, envelope, superdough, supradough
 * @param {number | Pattern} type lin | exp
 * @synonyms fme
 * @example
 * note("c e g b g e")
 * .fm(4)
 * .fmdecay(.2)
 * .fmsustain(0)
 * .fmenv("<exp lin>")
 * ._scope()
 *
 */
export const { fmenv, fmenv1, fmenv2, fmenv3, fmenv4, fmenv5, fmenv6, fmenv7, fmenv8, fme } = registerMultiControl(
  'fmenv',
  8,
  'fme',
);

/**
 * Attack time for the FM envelope: time it takes to reach maximum modulation
 *
 * A number may be added afterwards to control the attack of the envelope of
 * any of the 8 individual FMs (e.g. `fmatt5`)
 *
 * @name fmattack
 * @tags fm, envelope, superdough, supradough
 * @synonyms fmatt
 * @param {number | Pattern} time attack time
 * @synonyms fmatt
 * @example
 * note("c e g b g e")
 * .fm(4)
 * .fmattack("<0 .05 .1 .2>")
 * ._scope()
 *
 */
export const {
  fmattack,
  fmattack1,
  fmattack2,
  fmattack3,
  fmattack4,
  fmattack5,
  fmattack6,
  fmattack7,
  fmattack8,
  fmatt,
  fmatt1,
  fmatt2,
  fmatt3,
  fmatt4,
  fmatt5,
  fmatt6,
  fmatt7,
  fmatt8,
} = registerMultiControl('fmattack', 8, 'fmatt');

/**
 * Waveform of the fm modulator
 *
 * A number may be added afterwards to control the waveform
 * any of the 8 individual FMs (e.g. `fmwave6`)
 *
 * @name fmwave
 * @tags fm, superdough, supradough
 * @param {number | Pattern} wave waveform
 * @example
 * n("0 1 2 3".fast(4)).scale("d:minor").s("sine").fmwave("<sine square sawtooth crackle>").fm(4).fmh(2.01)
 * @example
 * n("0 1 2 3".fast(4)).chord("<Dm Am F G>").voicing().s("sawtooth").fmwave("brown").fm(.6)
 *
 */
export const { fmwave, fmwave1, fmwave2, fmwave3, fmwave4, fmwave5, fmwave6, fmwave7, fmwave8 } = registerMultiControl(
  'fmwave',
  8,
);

/**
 * Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
 *
 * A number may be added afterwards to control the decay of the envelope of
 * any of the 8 individual FMs (e.g. `fmdec6`)
 *
 * @name fmdecay
 * @tags fm, envelope, superdough, supradough
 * @synonyms fmdec
 * @param {number | Pattern} time decay time
 * @synonyms fmdec
 * @example
 * note("c e g b g e")
 * .fm(4)
 * .fmdecay("<.01 .05 .1 .2>")
 * .fmsustain(.4)
 * ._scope()
 *
 */
export const {
  fmdecay,
  fmdecay1,
  fmdecay2,
  fmdecay3,
  fmdecay4,
  fmdecay5,
  fmdecay6,
  fmdecay7,
  fmdecay8,
  fmdec,
  fmdec1,
  fmdec2,
  fmdec3,
  fmdec4,
  fmdec5,
  fmdec6,
  fmdec7,
  fmdec8,
} = registerMultiControl('fmdecay', 8, 'fmdec');

/**
 * Sustain level for the FM envelope: how much modulation is applied after the decay phase
 *
 * A number may be added afterwards to control the sustain of the envelope of
 * any of the 8 individual FMs (e.g. `fmsus7`)
 *
 * @name fmsustain
 * @tags fm, envelope, superdough, supradough
 * @synonyms fmsus
 * @param {number | Pattern} level sustain level
 * @synonyms fmsus
 * @example
 * note("c e g b g e")
 * .fm(4)
 * .fmdecay(.1)
 * .fmsustain("<1 .75 .5 0>")
 * ._scope()
 *
 */
export const {
  fmsustain,
  fmsustain1,
  fmsustain2,
  fmsustain3,
  fmsustain4,
  fmsustain5,
  fmsustain6,
  fmsustain7,
  fmsustain8,
  fmsus,
  fmsus1,
  fmsus2,
  fmsus3,
  fmsus4,
  fmsus5,
  fmsus6,
  fmsus7,
  fmsus8,
} = registerMultiControl('fmsustain', 8, 'fmsus');

/**
 * Release time for the FM envelope: how much modulation is applied after the note is released
 *
 * A number may be added afterwards to control the release of the envelope of
 * any of the 8 individual FMs (e.g. `fmrel8`)
 *
 * @name fmrelease
 * @tags fm, envelope, superdough, supradough
 * @synonyms fmrel
 * @param {number | Pattern} time release time
 *
 */
export const {
  fmrelease,
  fmrelease1,
  fmrelease2,
  fmrelease3,
  fmrelease4,
  fmrelease5,
  fmrelease6,
  fmrelease7,
  fmrelease8,
  fmrel,
  fmrel1,
  fmrel2,
  fmrel3,
  fmrel4,
  fmrel5,
  fmrel6,
  fmrel7,
  fmrel8,
} = registerMultiControl('fmrelease', 8, 'fmrel');

// FM Matrix
// Note: we do not declare top-level exports here since it would add
// ~162 more explicit exports. This is likely fine as the most common use-case would be to at least
// declare one other FM prior to utilizing the matrix functionality, but if we ever decide we need it,
// TODO to add it explicitly / go with the globalThis approach
for (let i = 0; i <= 8; i++) {
  for (let j = 0; j <= 8; j++) {
    registerControl(`fmi${i}${j}`, `fm${i}${j}`);
  }
}

/**
 * Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
 *
 * @name bank
 * @tags samples, superdough
 * @param {string | Pattern} bank the name of the bank
 * @example
 * s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
 *
 */
export const { bank } = registerControl('bank');

/**
 * mix control for the chorus effect
 *
 * @name chorus
 * @tags pitch
 * @param {string | Pattern} chorus mix amount between 0 and 1
 * @example
 * note("d d a# a").s("sawtooth").chorus(.5)
 *
 */
export const { chorus } = registerControl('chorus');

// analyser node send amount 0 - 1 (used by scope)
export const { analyze } = registerControl('analyze');
// fftSize of analyser
export const { fft } = registerControl('fft');

/**
 * Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset.
 *
 * @name attack
 * @tags amplitude, envelope, superdough, supradough
 * @param {number | Pattern} attack time in seconds.
 * @synonyms att
 * @example
 * note("c3 e3 f3 g3").attack("<0 .1 .5>")
 *
 */
export const { attack, att } = registerControl('attack', 'att');

/**
 * Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level.
 * Note that the decay is only audible if the sustain value is lower than 1.
 *
 * @name decay
 * @tags amplitude, envelope, superdough, supradough
 * @param {number | Pattern} time decay time in seconds
 * @synonyms dec
 * @example
 * note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0)
 *
 */
export const { decay, dec } = registerControl('decay', 'dec');
/**
 * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
 *
 * @name sustain
 * @tags amplitude, envelope, superdough, supradough
 * @param {number | Pattern} gain sustain level between 0 and 1
 * @synonyms sus
 * @example
 * note("c3 e3 f3 g3").decay(.2).sustain("<0 .1 .4 .6 1>")
 *
 */
export const { sustain, sus } = registerControl('sustain', 'sus');
/**
 * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero.
 *
 * @name release
 * @tags amplitude, envelope, superdough, supradough
 * @param {number | Pattern} time release time in seconds
 * @synonyms rel
 * @example
 * note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2")
 *
 */
export const { release, rel } = registerControl('release', 'rel');
export const { hold } = registerControl('hold');
// TODO: in tidal, it seems to be normalized
/**
 * Sets the center frequency of the **b**and-**p**ass **f**ilter. When using mininotation, you
 * can also optionally supply the 'bpq' parameter separated by ':'.
 *
 * @name bpf
 * @tags filter, superdough, supradough
 * @param {number | Pattern} frequency center frequency
 * @synonyms bandf, bp
 * @example
 * s("bd sd [~ bd] sd,hh*6").bpf("<1000 2000 4000 8000>")
 *
 */
export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], 'bpf', 'bp');
// TODO: in tidal, it seems to be normalized
/**
 * Sets the **b**and-**p**ass **q**-factor (resonance).
 *
 * @name bpq
 * @tags filter, superdough, supradough
 * @param {number | Pattern} q q factor
 * @synonyms bandq
 * @example
 * s("bd sd [~ bd] sd").bpf(500).bpq("<0 1 2 3>")
 *
 */
// currently an alias of 'bandq' https://codeberg.org/uzu/strudel/issues/496
// ['bpq'],
export const { bandq, bpq } = registerControl('bandq', 'bpq');
/**
 * A pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
 *
 * @name begin
 * @tags samples
 * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
 * @example
 * samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples')
 * s("rave").begin("<0 .25 .5 .75>").fast(2)
 *
 */
export const { begin } = registerControl('begin');
/**
 * The same as .begin, but cuts off the end off each sample.
 *
 * @memberof Pattern
 * @name end
 * @tags samples
 * @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
 * @example
 * s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2)
 *
 */
export const { end } = registerControl('end');
/**
 * Loops the sample.
 * Note that the tempo of the loop is not synced with the cycle tempo.
 * To change the loop region, use loopBegin / loopEnd.
 *
 * @name loop
 * @tags samples
 * @param {number | Pattern} on If 1, the sample is looped
 * @example
 * s("casio").loop(1)
 *
 */
export const { loop } = registerControl('loop');
/**
 * Begin to loop at a specific point in the sample (inbetween `begin` and `end`).
 * Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`!
 * Note: Samples starting with wt_ will automatically loop! (wt = wavetable)
 *
 * @name loopBegin
 * @tags samples
 * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
 * @synonyms loopb
 * @example
 * s("space").loop(1)
 * .loopBegin("<0 .125 .25>")._scope()
 */
export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb');
/**
 *
 * End the looping section at a specific point in the sample (inbetween `begin` and `end`).
 * Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`!
 *
 * @name loopEnd
 * @tags samples
 * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
 * @synonyms loope
 * @example
 * s("space").loop(1)
 * .loopEnd("<1 .75 .5 .25>")._scope()
 */
export const { loopEnd, loope } = registerControl('loopEnd', 'loope');
/**
 * Bit crusher effect.
 *
 * @name crush
 * @tags superdough, supradough
 * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).
 * @example
 * s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
 *
 */
// ['clhatdecay'],
export const { crush } = registerControl('crush');
/**
 * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
 *
 * @name coarse
 * @tags superdough, supradough
 * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
 * @example
 * s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>")
 *
 */
export const { coarse } = registerControl('coarse');

/**
 * Modulate the amplitude of a sound with a continuous waveform
 *
 * @name tremolo
 * @tags amplitude, lfo, superdough
 * @synonyms trem
 * @param {number | Pattern} speed modulation speed in HZ
 * @example
 * note("d d d# d".fast(4)).s("supersaw").tremolo("<3 2 100> ").tremoloskew("<.5>")
 *
 */
export const { tremolo, trem } = registerControl(['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], 'trem');

/**
 * Modulate the amplitude of a sound with a continuous waveform
 *
 * @name tremolosync
 * @tags amplitude, lfo, superdough
 * @synonyms tremsync
 * @param {number | Pattern} cycles modulation speed in cycles
 * @example
 * note("d d d# d".fast(4)).s("supersaw").tremolosync("4").tremoloskew("<1 .5 0>")
 *
 */
export const { tremolosync } = registerControl(
  ['tremolosync', 'tremolodepth', 'tremoloskew', 'tremolophase'],
  'tremsync',
);

/**
 * Depth of amplitude modulation
 *
 * @name tremolodepth
 * @tags amplitude, lfo, superdough
 * @synonyms tremdepth
 * @param {number | Pattern} depth
 * @example
 * note("a1 a1 a#1 a1".fast(4)).s("pulse").tremsync(4).tremolodepth("<1 2 .7>")
 *
 */
export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth');
/**
 * Alter the shape of the modulation waveform
 *
 * @name tremoloskew
 * @tags amplitude, lfo, superdough
 * @synonyms tremskew
 * @param {number | Pattern} amount between 0 & 1, the shape of the waveform
 * @example
 * note("{f a c e}%16").s("sawtooth").tremsync(4).tremoloskew("<.5 0 1>")
 *
 */
export const { tremoloskew } = registerControl('tremoloskew', 'tremskew');

/**
 * Alter the phase of the modulation waveform
 *
 * @name tremolophase
 * @tags amplitude, lfo, superdough
 * @synonyms tremphase
 * @param {number | Pattern} offset the offset in cycles of the modulation
 * @example
 * note("{f a c e}%16").s("sawtooth").tremsync(4).tremolophase("<0 .25 .66>")
 *
 */
export const { tremolophase } = registerControl('tremolophase', 'tremphase');

/**
 * Shape of amplitude modulation
 *
 * @name tremoloshape
 * @tags amplitude, lfo, superdough
 * @synonyms tremshape
 * @param {number | Pattern} shape tri | square | sine | saw | ramp
 * @example
 * note("{f g c d}%16").tremsync(4).tremoloshape("<sine tri square>").s("sawtooth")
 *
 */
export const { tremoloshape } = registerControl('tremoloshape', 'tremshape');
/**
 * Filter overdrive for supported filter types
 *
 * @name drive
 * @tags filter, superdough
 * @param {number | Pattern} amount
 * @example
 * note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>")
 *
 */
export const { drive } = registerControl('drive');

/**
 * Modulate the amplitude of an orbit to create a "sidechain" like effect.
 *
 * Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")`
 *
 * @name duckorbit
 * @tags amplitude, orbit, superdough
 * @synonyms duck
 * @param {number | Pattern} orbit target orbit
 * @example
 * $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
 * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth(1)
 * @example
 * $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
 * $: s("hh*16").orbit(3)
 * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth(1)
 *
 */
export const { duck } = registerControl('duckorbit', 'duck');

/**
 * The amount of ducking applied to target orbit
 *
 * Can vary across orbits with the ':' mininotation, e.g. `duckdepth("0.3:0.1")`.
 * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`.
 *
 * @name duckdepth
 * @tags amplitude, orbit, superdough
 * @param {number | Pattern} depth depth of modulation from 0 to 1
 * @example
 * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>"))
 * @example
 * $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
 * $: s("hh*16").orbit(3)
 * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth("1:0.5")
 *
 */
export const { duckdepth } = registerControl('duckdepth');

/**
 * The time required for the ducked signal(s) to reach their lowest volume.
 * Can be used to prevent clicking or for creative rhythmic effects.
 *
 * Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`.
 * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`.
 *
 * @name duckonset
 * @tags amplitude, envelope, orbit, superdough
 * @synonyms duckons
 *
 * @param {number | Pattern} time The onset time in seconds
 * @example
 * // Clicks
 * sound: freq("63.2388").s("sine").orbit(2).gain(4)
 * duckerWithClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0).postgain(0)
 * @example
 * // No clicks
 * sound: freq("63.2388").s("sine").orbit(2).gain(4)
 * duckerWithoutClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0.01).postgain(0)
 * @example
 * // Rhythmic
 * noise: s("pink").distort("2:1").orbit(4) // used rhythmically with 0.3 onset below
 * hhat: s("hh*16").orbit(7)
 * ducker: s("bd*4").bank("tr909").duckorbit("4:7").duckonset("0.3:0.003").duckattack(0.25)
 *
 */
export const { duckonset } = registerControl('duckonset', 'duckons');

/**
 * The time required for the ducked signal(s) to return to their normal volume.
 *
 * Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`.
 * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`.
 *
 * @name duckattack
 * @tags amplitude, envelope, orbit, superdough
 * @synonyms duckatt, datt
 *
 * @param {number | Pattern} time The attack time in seconds
 * @example
 * sound: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2)
 * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack("<0.2 0 0.4>").duckdepth(1)
 * @example
 * moreduck: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2)
 * lessduck: s("hh*16").orbit(5)
 * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:5").duckattack("0.4:0.1")
 *
 */
export const { duckattack } = registerControl('duckattack', 'duckatt', 'datt');

/**
 * Create byte beats with custom expressions
 *
 * @name byteBeatExpression
 * @synonyms bbexpr, bb
 * @tags superdough
 *
 * @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat
 * @example
 * s("bytebeat").bbexpr('t*(t>>15^t>>66)')
 *
 */
export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr', 'bb');

/**
 * Create byte beats with custom expressions
 *
 * @name byteBeatStartTime
 * @synonyms bbst
 * @tags superdough
 *
 * @param {number | Pattern} byteBeatStartTime in samples (t)
 * @example
 * note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope()
 *
 */
export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst');

/**
 * Allows you to set the output channels on the interface
 *
 * @name channels
 * @tags external_io, superdough
 * @synonyms ch
 *
 * @param {number | Pattern} channels pattern the output channels
 * @example
 * note("e a d b g").channels("3:4")
 *
 */
export const { channels, ch } = registerControl('channels', 'ch');

/**
 * Controls the pulsewidth of the pulse oscillator
 *
 * @name pw
 * @tags superdough
 * @param {number | Pattern} pulsewidth
 * @example
 * note("{f a c e}%16").s("pulse").pw(".8:1:.2")
 * @example
 * n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1")
 */
export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);

/**
 * Controls the lfo rate for the pulsewidth of the pulse oscillator
 *
 * @name pwrate
 * @synonyms pwr
 * @tags superdough, lfo
 * @param {number | Pattern} rate
 * @example
 * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")

 *
 */
export const { pwrate } = registerControl('pwrate', 'pwr');

/**
 * Controls the lfo sweep for the pulsewidth of the pulse oscillator
 *
 * @name pwsweep
 * @synonyms pws
 * @tags superdough, lfo
 * @param {number | Pattern} sweep
 * @example
 * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
 *
 */
export const { pwsweep } = registerControl('pwsweep', 'pws');

/**
 * Phaser audio effect that approximates popular guitar pedals.
 *
 * @name phaser
 * @tags superdough
 * @synonyms ph
 * @param {number | Pattern} speed speed of modulation
 * @example
 * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
 * .phaser("<1 2 4 8>")
 *
 */
export const { phaserrate, ph, phaser } = registerControl(
  ['phaserrate', 'phaserdepth', 'phasercenter', 'phasersweep'],
  'ph',
  'phaser',
);

/**
 * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
 *
 * @name phasersweep
 * @tags superdough, lfo
 * @synonyms phs
 * @param {number | Pattern} phasersweep most useful values are between 0 and 4000
 * @example
 * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
 * .phaser(2).phasersweep("<800 2000 4000>")
 *
 */
export const { phasersweep, phs } = registerControl('phasersweep', 'phs');

/**
 * The center frequency of the phaser in HZ. Defaults to 1000
 *
 * @name phasercenter
 * @tags superdough
 * @synonyms phc
 * @param {number | Pattern} centerfrequency in HZ
 * @example
 * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
 * .phaser(2).phasercenter("<800 2000 4000>")
 *
 */

export const { phasercenter, phc } = registerControl('phasercenter', 'phc');

/**
 * The amount the signal is affected by the phaser effect. Defaults to 0.75
 *
 * @name phaserdepth
 * @tags superdough, superdirt
 * @synonyms phd, phasdp
 * @param {number | Pattern} depth number between 0 and 1
 * @example
 * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
 * .phaser(2).phaserdepth("<0 .5 .75 1>")
 *
 */
// also a superdirt control
export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd', 'phasdp');

/**
 * Choose the channel the pattern is sent to
 *
 * @name channel
 * @tags superdough
 * @param {number | Pattern} channel channel number
 *
 */
export const { channel } = registerControl('channel');
/**
 * In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.
 *
 * @name cut
 * @tags superdough
 * @param {number | Pattern} group cut group number
 * @example
 * s("[oh hh]*4").cut(1)
 *
 */
export const { cut } = registerControl('cut');
/**
 * Applies the cutoff frequency of the **l**ow-**p**ass **f**ilter.
 *
 * When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'.
 *
 * @name lpf
 * @tags filter, superdough, supradough
 * @param {number | Pattern} frequency audible between 0 and 20000
 * @synonyms cutoff, ctf, lp
 * @example
 * s("bd sd [~ bd] sd,hh*6").lpf("<4000 2000 1000 500 200 100>")
 * @example
 * s("bd*16").lpf("1000:0 1000:10 1000:20 1000:30")
 *
 */
export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp');

/**
 * Sets the lowpass filter envelope modulation depth.
 * @name lpenv
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_
 * @synonyms lpe
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .lpf(300)
 * .lpa(.5)
 * .lpenv("<4 2 1 0 -1 -2 -4>/4")
 */
export const { lpenv, lpe } = registerControl('lpenv', 'lpe');
/**
 * Sets the highpass filter envelope modulation depth.
 * @name hpenv
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_
 * @synonyms hpe
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .hpf(500)
 * .hpa(.5)
 * .hpenv("<4 2 1 0 -1 -2 -4>/4")
 */
export const { hpenv, hpe } = registerControl('hpenv', 'hpe');
/**
 * Sets the bandpass filter envelope modulation depth.
 * @name bpenv
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_
 * @synonyms bpe
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .bpf(500)
 * .bpa(.5)
 * .bpenv("<4 2 1 0 -1 -2 -4>/4")
 */
export const { bpenv, bpe } = registerControl('bpenv', 'bpe');
/**
 * Sets the attack duration for the lowpass filter envelope.
 * @name lpattack
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} attack time of the filter envelope
 * @synonyms lpa
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .lpf(300)
 * .lpa("<.5 .25 .1 .01>/4")
 * .lpenv(4)
 */
export const { lpattack, lpa } = registerControl('lpattack', 'lpa');
/**
 * Sets the attack duration for the highpass filter envelope.
 * @name hpattack
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} attack time of the highpass filter envelope
 * @synonyms hpa
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .hpf(500)
 * .hpa("<.5 .25 .1 .01>/4")
 * .hpenv(4)
 */
export const { hpattack, hpa } = registerControl('hpattack', 'hpa');
/**
 * Sets the attack duration for the bandpass filter envelope.
 * @name bpattack
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} attack time of the bandpass filter envelope
 * @synonyms bpa
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .bpf(500)
 * .bpa("<.5 .25 .1 .01>/4")
 * .bpenv(4)
 */
export const { bpattack, bpa } = registerControl('bpattack', 'bpa');
/**
 * Sets the decay duration for the lowpass filter envelope.
 * @name lpdecay
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} decay time of the filter envelope
 * @synonyms lpd
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .lpf(300)
 * .lpd("<.5 .25 .1 0>/4")
 * .lpenv(4)
 */
export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd');
/**
 * Sets the decay duration for the highpass filter envelope.
 * @name hpdecay
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} decay time of the highpass filter envelope
 * @synonyms hpd
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .hpf(500)
 * .hpd("<.5 .25 .1 0>/4")
 * .hps(0.2)
 * .hpenv(4)
 */
export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd');
/**
 * Sets the decay duration for the bandpass filter envelope.
 * @name bpdecay
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} decay time of the bandpass filter envelope
 * @synonyms bpd
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .bpf(500)
 * .bpd("<.5 .25 .1 0>/4")
 * .bps(0.2)
 * .bpenv(4)
 */
export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd');
/**
 * Sets the sustain amplitude for the lowpass filter envelope.
 * @name lpsustain
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} sustain amplitude of the lowpass filter envelope
 * @synonyms lps
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .lpf(300)
 * .lpd(.5)
 * .lps("<0 .25 .5 1>/4")
 * .lpenv(4)
 */
export const { lpsustain, lps } = registerControl('lpsustain', 'lps');
/**
 * Sets the sustain amplitude for the highpass filter envelope.
 * @name hpsustain
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} sustain amplitude of the highpass filter envelope
 * @synonyms hps
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .hpf(500)
 * .hpd(.5)
 * .hps("<0 .25 .5 1>/4")
 * .hpenv(4)
 */
export const { hpsustain, hps } = registerControl('hpsustain', 'hps');
/**
 * Sets the sustain amplitude for the bandpass filter envelope.
 * @name bpsustain
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} sustain amplitude of the bandpass filter envelope
 * @synonyms bps
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .bpf(500)
 * .bpd(.5)
 * .bps("<0 .25 .5 1>/4")
 * .bpenv(4)
 */
export const { bpsustain, bps } = registerControl('bpsustain', 'bps');
/**
 * Sets the release time for the lowpass filter envelope.
 * @name lprelease
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} release time of the filter envelope
 * @synonyms lpr
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .clip(.5)
 * .lpf(300)
 * .lpenv(4)
 * .lpr("<.5 .25 .1 0>/4")
 * .release(.5)
 */
export const { lprelease, lpr } = registerControl('lprelease', 'lpr');
/**
 * Sets the release time for the highpass filter envelope.
 * @name hprelease
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} release time of the highpass filter envelope
 * @synonyms hpr
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .clip(.5)
 * .hpf(500)
 * .hpenv(4)
 * .hpr("<.5 .25 .1 0>/4")
 * .release(.5)
 */
export const { hprelease, hpr } = registerControl('hprelease', 'hpr');
/**
 * Sets the release time for the bandpass filter envelope.
 * @name bprelease
 * @tags filter, envelope, superdough, supradough
 * @param {number | Pattern} release time of the bandpass filter envelope
 * @synonyms bpr
 * @example
 * note("c2 e2 f2 g2")
 * .sound('sawtooth')
 * .clip(.5)
 * .bpf(500)
 * .bpenv(4)
 * .bpr("<.5 .25 .1 0>/4")
 * .release(.5)
 */
export const { bprelease, bpr } = registerControl('bprelease', 'bpr');
/**
 * Sets the filter type. The ladder filter is more aggressive. More types might be added in the future.
 * @name ftype
 * @tags filter, superdough
 * @param {number | Pattern} type 12db (0), ladder (1), or 24db (2)
 * @example
 * note("{f g g c d a a#}%8").s("sawtooth").lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1)
 * @example
 * note("c f g g a c d4").fast(2)
 * .sound('sawtooth')
 * .lpf(200).fanchor(0)
 * .lpenv(3).lpq(1)
 * .ftype("<ladder 12db 24db>")
 */
export const { ftype } = registerControl('ftype');

/**
 * controls the center of the filter envelope. 0 is unipolar positive, .5 is bipolar, 1 is unipolar negative
 * @name fanchor
 * @tags filter, envelope, superdough
 * @param {number | Pattern} center 0 to 1
 * @example
 * note("{f g g c d a a#}%8").s("sawtooth").lpf("{1000}%2")
 * .lpenv(8).fanchor("<0 .5 1>")
 */
export const { fanchor } = registerControl('fanchor');
/**
 * Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter.
 *
 * When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'.
 *
 * @name hpf
 * @tags filter, superdough, supradough
 * @param {number | Pattern} frequency audible between 0 and 20000
 * @synonyms hp, hcutoff
 * @example
 * s("bd sd [~ bd] sd,hh*8").hpf("<4000 2000 1000 500 200 100>")
 * @example
 * s("bd sd [~ bd] sd,hh*8").hpf("<2000 2000:25>")
 *
 */
// currently an alias of 'hcutoff' https://codeberg.org/uzu/strudel/issues/496
// ['hpf'],

/**
 * Rate of the LFO for the lowpass filter
 *
 * @name lprate
 * @tags filter, lfo, superdough
 * @param {number | Pattern} rate rate in hertz
 * @example
 * note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lprate("<4 8 2 1>")
 */
export const { lprate } = registerControl('lprate');

/**
 * Cycle-synced rate of the LFO for the lowpass filter
 *
 * @name lpsync
 * @tags filter, lfo, superdough
 * @param {number | Pattern} rate rate in cycles
 * @example
 * note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpsync("<4 8 2 1>")
 */
export const { lpsync } = registerControl('lpsync');

/**
 * Depth of the LFO for the lowpass filter
 *
 * @name lpdepth
 * @tags filter, lfo, superdough
 * @param {number | Pattern} depth depth of modulation
 * @example
 * note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepth("<1 .5 1.8 0>")
 */

export const { lpdepth } = registerControl('lpdepth');
/**
 * Depth of the LFO for the lowpass filter, in HZ
 *
 * @name lpdepthfrequency
 * @tags filter, lfo, superdough
 * @synonyms lpdepthfreq
 * @param {number | Pattern} depth depth of modulation
 * @example
 * note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
 */

export const { lpdepthfrequency, lpdepthfreq } = registerControl('lpdepthfrequency', 'lpdepthfreq');

/**
 * Shape of the LFO for the lowpass filter
 *
 * @name lpshape
 * @tags filter, lfo, superdough
 * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
 */
export const { lpshape } = registerControl('lpshape');

/**
 * DC offset of the LFO for the lowpass filter
 *
 * @name lpdc
 * @tags filter, lfo, superdough
 * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
 */
export const { lpdc } = registerControl('lpdc');

/**
 * Skew of the LFO for the lowpass filter
 *
 * @name lpskew
 * @tags filter, lfo, superdough
 * @param {number | Pattern} skew How much to bend the LFO shape
 */
export const { lpskew } = registerControl('lpskew');

/**
 * Rate of the LFO for the bandpass filter
 *
 * @name bprate
 * @tags filter, lfo, superdough
 * @param {number | Pattern} rate rate in hertz
 */
export const { bprate } = registerControl('bprate');

/**
 * Cycle-synced rate of the LFO for the bandpass filter
 *
 * @name bpsync
 * @tags filter, lfo, superdough
 * @param {number | Pattern} rate rate in cycles
 */
export const { bpsync } = registerControl('bpsync');

/**
 * Depth of the LFO for the bandpass filter
 *
 * @name bpdepth
 * @tags filter, lfo, superdough
 * @param {number | Pattern} depth depth of modulation
 */
export const { bpdepth } = registerControl('bpdepth');

/**
 * Depth of the LFO for the bandpass filter, in HZ
 *
 * @name bpdepthfrequency
 * @tags filter, lfo, superdough
 * @synonyms bpdepthfreq
 * @param {number | Pattern} depth depth of modulation
 * @example
 * note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
 */

export const { bpdepthfrequency, bpdepthfreq } = registerControl('bpdepthfrequency', 'bpdepthfreq');

/**
 * Shape of the LFO for the bandpass filter
 *
 * @name bpshape
 * @tags filter, lfo, superdough
 * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
 */
export const { bpshape } = registerControl('bpshape');

/**
 * DC offset of the LFO for the bandpass filter
 *
 * @name bpdc
 * @tags filter, lfo, superdough
 * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
 */
export const { bpdc } = registerControl('bpdc');

/**
 * Skew of the LFO for the bandpass filter
 *
 * @name bpskew
 * @tags filter, lfo, superdough
 * @param {number | Pattern} skew How much to bend the LFO shape
 */
export const { bpskew } = registerControl('bpskew');

/**
 * Rate of the LFO for the highpass filter
 *
 * @name hprate
 * @tags filter, lfo, superdough
 * @param {number | Pattern} rate rate in hertz
 */
export const { hprate } = registerControl('hprate');

/**
 * Cycle-synced rate of the LFO for the highpass filter
 *
 * @name hpsync
 * @tags filter, lfo, superdough
 * @param {number | Pattern} rate rate in cycles
 */
export const { hpsync } = registerControl('hpsync');

/**
 * Depth of the LFO for the highpass filter
 *
 * @name hpdepth
 * @tags filter, lfo, superdough
 * @param {number | Pattern} depth depth of modulation
 */
export const { hpdepth } = registerControl('hpdepth');

/**
 * Depth of the LFO for the hipass filter, in hz
 *
 * @name hpdepthfrequency
 * @tags filter, lfo, superdough
 * @synonyms hpdepthfreq
 * @param {number | Pattern} depth depth of modulation
 * @example
 * note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
 */

export const { hpdepthfrequency, hpdepthfreq } = registerControl('hpdepthfrequency', 'hpdepthfreq');

/**
 * Shape of the LFO for the highpass filter
 *
 * @name hpshape
 * @tags filter, lfo, superdough
 * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
 */
export const { hpshape } = registerControl('hpshape');

/**
 * DC offset of the LFO for the highpass filter
 *
 * @name hpdc
 * @tags filter, lfo, superdough
 * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
 */
export const { hpdc } = registerControl('hpdc');

/**
 * Skew of the LFO for the highpass filter
 *
 * @name hpskew
 * @tags filter, lfo, superdough
 * @param {number | Pattern} skew How much to bend the LFO shape
 */
export const { hpskew } = registerControl('hpskew');

/**
 * Applies a vibrato to the frequency of the oscillator.
 *
 * @name vib
 * @tags pitch, lfo, superdough, supradough
 * @synonyms vibrato, v
 * @param {number | Pattern} frequency of the vibrato in hertz
 * @example
 * note("a e")
 * .vib("<.5 1 2 4 8 16>")
 * ._scope()
 * @example
 * // change the modulation depth with ":"
 * note("a e")
 * .vib("<.5 1 2 4 8 16>:12")
 * ._scope()
 */
export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', 'v');
/**
 * Adds pink noise to the mix
 *
 * @name noise
 * @tags generators, superdough, supradough
 * @param {number | Pattern} wet wet amount
 * @example
 * sound("<white pink brown>/2")
 */
export const { noise } = registerControl('noise');
/**
 * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set
 *
 * @name vibmod
 * @tags pitch, lfo, superdough, supradough
 * @synonyms vmod
 * @param {number | Pattern} depth of vibrato (in semitones)
 * @example
 * note("a e").vib(4)
 * .vibmod("<.25 .5 1 2 12>")
 * ._scope()
 * @example
 * // change the vibrato frequency with ":"
 * note("a e")
 * .vibmod("<.25 .5 1 2 12>:8")
 * ._scope()
 */
export const { vibmod, vmod } = registerControl(['vibmod', 'vib'], 'vmod');
export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp');
/**
 * Controls the **h**igh-**p**ass **q**-value.
 *
 * @name hpq
 * @tags filter, superdough, supradough
 * @param {number | Pattern} q resonance factor between 0 and 50
 * @synonyms hresonance
 * @example
 * s("bd sd [~ bd] sd,hh*8").hpf(2000).hpq("<0 10 20 30>")
 *
 */
export const { hresonance, hpq } = registerControl('hresonance', 'hpq');
/**
 * Controls the **l**ow-**p**ass **q**-value.
 *
 * @name lpq
 * @tags filter, superdough, supradough
 * @param {number | Pattern} q resonance factor between 0 and 50
 * @synonyms resonance
 * @example
 * s("bd sd [~ bd] sd,hh*8").lpf(2000).lpq("<0 10 20 30>")
 *
 */
// currently an alias of 'resonance' https://codeberg.org/uzu/strudel/issues/496
export const { resonance, lpq } = registerControl('resonance', 'lpq');
/**
 * DJ filter, below 0.5 is low pass filter, above is high pass filter.
 *
 * @name djf
 * @tags filter, superdough
 * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter
 * @example
 * n(irand(16).seg(8)).scale("d:phrygian").s("supersaw").djf("<.5 .3 .2 .75>")
 *
 */
export const { djf } = registerControl('djf');
// ['cutoffegint'],
// TODO: does not seem to work
/**
 * Sets the level of the delay signal.
 *
 * When using mininotation, you can also optionally add the 'delaytime' and 'delayfeedback' parameter,
 * separated by ':'.
 *
 *
 * @name delay
 * @tags orbit, superdough, supradough
 * @param {number | Pattern} level between 0 and 1
 * @example
 * s("bd bd").delay("<0 .25 .5 1>")
 * @example
 * s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7")
 *
 */
export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback']);
/**
 * Sets the level of the signal that is fed back into the delay.
 * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
 *
 * @name delayfeedback
 * @tags orbit, superdough, supradough
 * @param {number | Pattern} feedback between 0 and 1
 * @synonyms delayfb, dfb
 * @example
 * s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>")
 *
 */
export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', 'delayfb', 'dfb');

/**
 * Sets the time of the delay effect.
 *
 * @name delayspeed
 * @tags supradough
 * @param {number | Pattern} delayspeed controls the pitch of the delay feedback
 * @synonyms delayt, dt
 * @example
 * note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>")
 *
 */
export const { delayspeed } = registerControl('delayspeed');

/**
 * Sets the time of the delay effect in seconds.
 *
 * @name delaytime
 * @tags orbit, superdough, supradough
 * @param {number | Pattern} delay in seconds
 * @synonyms delayt, dt
 * @example
 * note("d d a# a".fast(2))
 * .s("sawtooth")
 * .delay(.8)
 * .delaytime(1/2)
 * .delayspeed("<2 .5 -1 -2>")
 */
export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt');

/**
 * Sets the time of the delay effect in cycles.
 *
 * @name delaysync
 * @tags orbit, superdough
 * @param {number | Pattern} cycles delay length in cycles
 * @synonyms delays, ds
 * @example
 * s("bd bd").delay(.25).delaysync("<1 2 3 5>".div(8))
 *
 */
export const { delaysync } = registerControl('delaysync', 'delays', 'ds');

/**
 * Specifies whether delaytime is calculated relative to cps.
 *
 * @name lock
 * @tags superdirt
 * @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle.
 * @superdirtOnly
 * @example
 * s("sd").delay().lock(1).osc()
 *
 *
 */

export const { lock } = registerControl('lock');
/**
 * Set detune for stacked voices of supported oscillators.
 *
 * @name detune
 * @tags pitch, superdough
 * @param {number | Pattern} amount
 * @synonyms det
 * @example
 * note("d f a a# a d3").fast(2).s("supersaw").detune("<.1 .2 .5 24.1>")
 *
 */
export const { detune, det } = registerControl('detune', 'det');
/**
 * Set number of stacked voices for supported oscillators.
 *
 * @name unison
 * @tags superdough
 * @param {number | Pattern} numvoices
 * @example
 * note("d f a a# a d3").fast(2).s("supersaw").unison("<1 2 7>")
 *
 */
export const { unison } = registerControl('unison');

/**
 * Set the stereo pan spread for supported oscillators
 *
 * @name spread
 * @tags superdough
 * @param {number | Pattern} spread between 0 and 1
 * @example
 * note("d f a a# a d3").fast(2).s("supersaw").spread("<0 .3 1>")
 *
 */
export const { spread } = registerControl('spread');
/**
 * Set dryness of reverb. See `room` and `size` for more information about reverb.
 *
 * @name dry
 * @tags superdirt
 * @param {number | Pattern} dry 0 = wet, 1 = dry
 * @example
 * n("[0,3,7](3,8)").s("superpiano").room(.7).dry("<0 .5 .75 1>").osc()
 * @superdirtOnly
 *
 */
export const { dry } = registerControl('dry');
/**
 * Used when using `begin`/`end` or `chop`/`striate` and friends, to change the fade out time of the 'grain' envelope.
 *
 * @name fadeTime
 * @tags superdirt
 * @synonyms fadeOutTime
 * @param {number | Pattern} time between 0 and 1
 * @example
 * s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
 *
 */
export const { fadeTime, fadeOutTime } = registerControl('fadeTime', 'fadeOutTime');
export const { fadeInTime } = registerControl('fadeInTime');
/**
 * Set frequency of sound.
 *
 * @name freq
 * @tags pitch, superdough
 * @param {number | Pattern} frequency in Hz. the audible range is between 20 and 20000 Hz
 * @example
 * freq("220 110 440 110").s("superzow").osc()
 * @example
 * freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc()
 *
 */
export const { freq } = registerControl('freq');
// pitch envelope
/**
 * Attack time of pitch envelope.
 *
 * @name pattack
 * @tags pitch, envelope, superdough, supradough
 * @synonyms patt
 * @param {number | Pattern} time time in seconds
 * @example
 * note("c eb g bb").pattack("0 .1 .25 .5").slow(2)
 *
 */
export const { pattack, patt } = registerControl('pattack', 'patt');
/**
 * Decay time of pitch envelope.
 *
 * @name pdecay
 * @tags pitch, envelope, superdough, supradough
 * @synonyms pdec
 * @param {number | Pattern} time time in seconds
 * @example
 * note("<c eb g bb>").pdecay("<0 .1 .25 .5>")
 *
 */
export const { pdecay, pdec } = registerControl('pdecay', 'pdec');
// TODO: how to use psustain?!
export const { psustain, psus } = registerControl('psustain', 'psus');
/**
 * Release time of pitch envelope
 *
 * @name prelease
 * @tags pitch, envelope, superdough, supradough
 * @synonyms prel
 * @param {number | Pattern} time time in seconds
 * @example
 * note("<c eb g bb> ~")
 * .release(.5) // to hear the pitch release
 * .prelease("<0 .1 .25 .5>")
 *
 */
export const { prelease, prel } = registerControl('prelease', 'prel');
/**
 * Amount of pitch envelope. Negative values will flip the envelope.
 * If you don't set other pitch envelope controls, `pattack:.2` will be the default.
 *
 * @name penv
 * @tags pitch, envelope, superdough, supradough
 * @param {number | Pattern} semitones change in semitones
 * @example
 * note("c")
 * .penv("<12 7 1 .5 0 -1 -7 -12>")
 *
 */
export const { penv } = registerControl('penv');
/**
 * Curve of envelope. Defaults to linear. exponential is good for kicks
 *
 * @name pcurve
 * @tags pitch, envelope, superdough
 * @param {number | Pattern} type 0 = linear, 1 = exponential
 * @example
 * note("g1*4")
 * .s("sine").pdec(.5)
 * .penv(32)
 * .pcurve("<0 1>")
 *
 */
export const { pcurve } = registerControl('pcurve');
/**
 * Sets the range anchor of the envelope:
 * - anchor 0: range = [note, note + penv]
 * - anchor 1: range = [note - penv, note]
 * If you don't set an anchor, the value will default to the psustain value.
 *
 * @name panchor
 * @tags pitch, envelope, superdough
 * @param {number | Pattern} anchor anchor offset
 * @example
 * note("c c4").penv(12).panchor("<0 .5 1 .5>")
 *
 */
export const { panchor } = registerControl('panchor');
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
export const { gate, gat } = registerControl('gate', 'gat');
// ['hatgrain'],
// ['lagogo'],
// ['lclap'],
// ['lclaves'],
// ['lclhat'],
// ['lcrash'],
// TODO:
// https://tidalcycles.org/docs/reference/audio_effects/#leslie-1
// https://tidalcycles.org/docs/reference/audio_effects/#leslie
/**
 * Emulation of a Leslie speaker: speakers rotating in a wooden amplified cabinet.
 *
 * @name leslie
 * @tags superdirt
 * @param {number | Pattern} wet between 0 and 1
 * @example
 * n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc()
 * @superdirtOnly
 *
 */
export const { leslie } = registerControl('leslie');
/**
 * Rate of modulation / rotation for leslie effect
 *
 * @name lrate
 * @tags superdirt
 * @param {number | Pattern} rate 6.7 for fast, 0.7 for slow
 * @example
 * n("0,4,7").s("supersquare").leslie(1).lrate("<1 2 4 8>").osc()
 * @superdirtOnly
 *
 */
// TODO: the rate seems to "lag" (in the example, 1 will be fast)
export const { lrate } = registerControl('lrate');
/**
 * Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble)
 *
 * @name lsize
 * @tags superdirt
 * @param {number | Pattern} meters somewhere between 0 and 1
 * @example
 * n("0,4,7").s("supersquare").leslie(1).lrate(2).lsize("<.1 .5 1>").osc()
 * @superdirtOnly
 *
 */
export const { lsize } = registerControl('lsize');
/**
 * Sets the displayed text for an event on the pianoroll
 *
 * @name label
 * @tags visualization
 * @param {string} label text to display
 */
export const { activeLabel } = registerControl('activeLabel');
export const { label } = registerControl(['label', 'activeLabel']);
// ['lfo'],
// ['lfocutoffint'],
// ['lfodelay'],
// ['lfoint'],
// ['lfopitchint'],
// ['lfoshape'],
// ['lfosync'],
// ['lhitom'],
// ['lkick'],
// ['llotom'],
// ['lophat'],
// ['lsnare'],
// TODO: what is this? not found in tidal doc
export const { degree } = registerControl('degree');
// TODO: what is this? not found in tidal doc
export const { mtranspose } = registerControl('mtranspose');
// TODO: what is this? not found in tidal doc
export const { ctranspose } = registerControl('ctranspose');
// TODO: what is this? not found in tidal doc
export const { harmonic } = registerControl('harmonic');
// TODO: what is this? not found in tidal doc
export const { stepsPerOctave } = registerControl('stepsPerOctave');
// TODO: what is this? not found in tidal doc
export const { octaveR } = registerControl('octaveR');
// TODO: why is this needed? what's the difference to late / early? Answer: it's in seconds, and delays the message at
// OSC time (so can't be negative, at least not beyond the latency value)
export const { nudge } = registerControl('nudge');
// TODO: the following doc is just a guess, it's not documented in tidal doc.
/**
 * Sets the default octave of a synth.
 *
 * @name octave
 * @tags superdirt
 * @synonyms oct
 * @param {number | Pattern} octave octave number
 * @example
 * n("0,4,7").scale("F:minor").s('supersaw').octave("<0 1 2 3>")
 */
export const { octave, oct } = registerControl('octave', 'oct');

// ['ophatdecay'],
// TODO: example
/**
 * An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
 *
 * @name orbit
 * @tags superdough
 * @synonyms o
 * @param {number | Pattern} number
 * @example
 * stack(
 *   s("hh*6").delay(.5).delaytime(.25).orbit(1),
 *   s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
 * )
 */
export const { orbit } = registerControl('orbit', 'o');

/**
 * A `bus` is a send which can be used for mixing patterns. It combines with..
 *   s("bus") to play that bus through another pattern (for, say, applying non-linear
 *   effects like distortion to multiple signals)
 *
 *   otherPat.bmod(..) (to modulate another pattern with the bus)
 *
 * @name bus
 * @tags superdirt
 * @param {number | Pattern} number
 */
export const { bus } = registerControl('bus');

/**
 * Postgain multiplier prior to sending the signal to the audio bus.
 *
 * @name busgain
 * @tags superdirt
 * @synonyms bgain
 * @param {number | Pattern} number
 */
export const { busgain, bgain } = registerControl('busgain', 'bgain');

// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
export const { overgain } = registerControl('overgain');
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
export const { overshape } = registerControl('overshape');
/**
 * Sets position in stereo.
 *
 * @name pan
 * @tags superdough, supradough
 * @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
 * @example
 * s("[bd hh]*2").pan("<.5 1 .5 0>")
 * @example
 * s("bd rim sd rim bd ~ cp rim").pan(sine.slow(2))
 *
 */
export const { pan } = registerControl('pan');
/**
 * Controls how much multichannel output is fanned out
 *
 * @name panspan
 * @tags superdirt
 * @param {number | Pattern} span between -inf and inf, negative is backwards ordering
 * @example
 * s("[bd hh]*2").pan("<.5 1 .5 0>").panspan("<0 .5 1>").osc()
 *
 */
export const { panspan } = registerControl('panspan');
/**
 * Controls how much multichannel output is spread
 *
 * @name pansplay
 * @tags superdirt
 * @param {number | Pattern} spread between 0 and 1
 * @example
 * s("[bd hh]*2").pan("<.5 1 .5 0>").pansplay("<0 .5 1>").osc()
 *
 */
export const { pansplay } = registerControl('pansplay');
export const { panwidth } = registerControl('panwidth');
export const { panorient } = registerControl('panorient');
// ['pitch1'],
// ['pitch2'],
// ['pitch3'],
// ['portamento'],

// TODO: slide param for certain synths
export const { slide } = registerControl('slide');
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
export const { semitone } = registerControl('semitone');

// TODO: synth param
export const { voice } = registerControl('voice');
// voicings // https://codeberg.org/uzu/strudel/issues/506
/**
 * The chord to voice
 * @name chord
 * @tags tonal
 * @param {string | Pattern} symbols chord symbols to voice e.g., C, Eb, Fm7, G7. The symbols can be defined via addVoicings
 * @example
 * chord("<Am C D F Am E Am E>").voicing()
 **/
export const { chord } = registerControl('chord');
/**
 * Which dictionary to use for the voicings. This falls back to the default dictionary if not provided
 *
 * @name dictionary
 * @tags tonal
 * @param {string} dictionaryName which dictionary (having been defined with `addVoicings`) to use
 * @example
 * addVoicings('house', {
'': ['7 12 16', '0 7 16', '4 7 12'],
'm': ['0 3 7']
})
chord("<Am C D F Am E Am E>")
.dict('house').anchor(66)
.voicing().room(.5)
 **/
export const { dictionary, dict } = registerControl('dictionary', 'dict');
/** The top note to align the voicing to. Defaults to c5
 *
 * @name anchor
 * @tags tonal
 * @param {string | Pattern} anchorNote the note to align the voicing or scale to
 * @example
 * anchor("<c4 g4 c5 g5>").chord("C").voicing()
 * @example
 * n("0 .. 7").anchor("<c4 g4 c5 g5>").scale("<C:major F:minor>")
 **/
export const { anchor } = registerControl('anchor');
/**
 * Sets how the voicing is offset from the anchored position
 *
 * @name offset
 * @tags tonal
 * @param {number | Pattern} shift the amount to shift the voicing up or down
 * @example
 * chord("<Am C D F Am E Am E>").offset("<0 1 2 3 4 5>") // alter the voicing each time
 **/
export const { offset } = registerControl('offset');
/**
 *  How many octaves are voicing steps spread apart, defaults to 1
 *
 *  @name octaves
 *  @tags tonal
 *  @param {number | Pattern} count the number of octaves
 *  @example
 *  chord("<Am C D F Am E Am E>").octaves("<2 4>").voicing()
 **/
export const { octaves } = registerControl('octaves');
/**
 * Remove anchor note from the voicing. Useful for melody harmonization
 *
 * @name mode
 * @tags tonal
 * @param {string | Pattern} modeName one of {below | above | duck | root}
 * @example
 * mode("<below above duck root>").chord("C").voicing()
 *
 **/
export const { mode } = registerControl(['mode', 'anchor']);

/**
 * Sets the level of reverb.
 *
 * When using mininotation, you can also optionally add the 'size' parameter, separated by ':'.
 *
 * @name room
 * @tags orbit, superdough
 * @param {number | Pattern} level between 0 and 1
 * @example
 * s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>")
 * @example
 * s("bd sd [~ bd] sd").room("<0.9:1 0.9:4>")
 *
 */
export const { room } = registerControl(['room', 'size']);
/**
 * Reverb lowpass starting frequency (in hertz).
 * When this property is changed, the reverb will be recaculated, so only change this sparsely..
 *
 * @name roomlp
 * @tags orbit, superdough
 * @synonyms rlp
 * @param {number} frequency between 0 and 20000hz
 * @example
 * s("bd sd [~ bd] sd").room(0.5).rlp(10000)
 * @example
 * s("bd sd [~ bd] sd").room(0.5).rlp(5000)
 */
export const { roomlp, rlp } = registerControl('roomlp', 'rlp');
/**
 * Reverb lowpass frequency at -60dB (in hertz).
 * When this property is changed, the reverb will be recaculated, so only change this sparsely..
 *
 * @name roomdim
 * @tags orbit, superdough
 * @synonyms rdim
 * @param {number} frequency between 0 and 20000hz
 * @example
 * s("bd sd [~ bd] sd").room(0.5).rlp(10000).rdim(8000)
 * @example
 * s("bd sd [~ bd] sd").room(0.5).rlp(5000).rdim(400)
 *
 */
export const { roomdim, rdim } = registerControl('roomdim', 'rdim');
/**
 * Reverb fade time (in seconds).
 * When this property is changed, the reverb will be recaculated, so only change this sparsely..
 *
 * @name roomfade
 * @tags orbit, superdough
 * @synonyms rfade
 * @param {number} seconds for the reverb to fade
 * @example
 * s("bd sd [~ bd] sd").room(0.5).rlp(10000).rfade(0.5)
 * @example
 * s("bd sd [~ bd] sd").room(0.5).rlp(5000).rfade(4)
 *
 */
export const { roomfade, rfade } = registerControl('roomfade', 'rfade');
/**
 * Sets the sample to use as an impulse response for the reverb.
 * @name iresponse
 * @tags orbit, superdough
 * @param {string | Pattern} sample to use as an impulse response
 * @synonyms ir
 * @example
 * s("bd sd [~ bd] sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
 *
 */
export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse');

/**
 * Sets speed of the sample for the impulse response.
 * @name irspeed
 * @tags orbit, superdough
 * @param {string | Pattern} speed
 * @example
 * samples('github:switchangel/pad')
 * $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.2).irspeed("<2 1 .5>/2").irbegin(.5).roomsize(.5)
 *
 */
export const { irspeed } = registerControl('irspeed');

/**
 * Sets the beginning of the IR response sample
 * @name irbegin
 * @tags orbit, superdough
 * @param {string | Pattern} begin between 0 and 1
 * @synonyms ir
 * @example
 * samples('github:switchangel/pad')
 * $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.65).irspeed("-2").irbegin("<0 .5 .75>/2").roomsize(.6)
 *
 */
export const { irbegin } = registerControl('irbegin');
/**
 * Sets the room size of the reverb, see `room`.
 * When this property is changed, the reverb will be recaculated, so only change this sparsely..
 *
 * @name roomsize
 * @tags orbit, superdough
 * @param {number | Pattern} size between 0 and 10
 * @synonyms rsize, sz, size
 * @example
 * s("bd sd [~ bd] sd").room(.8).rsize(1)
 * @example
 * s("bd sd [~ bd] sd").room(.8).rsize(4)
 *
 */
// TODO: find out why :
// s("bd sd [~ bd] sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc()
// .. does not work. Is it because room is only one effect?
export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', 'sz', 'rsize');
// ['sagogo'],
// ['sclap'],
// ['sclaves'],
// ['scrash'],
/**
 * (Deprecated) Wave shaping distortion. WARNING: can suddenly get unpredictably loud.
 * Please use distort instead, which has a more predictable response curve
 * second option in optional array syntax (ex: ".9:.5") applies a postgain to the output
 *
 *
 * @name shape
 * @tags distortion, superdough
 * @param {number | Pattern} distortion between 0 and 1
 * @example
 * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>")
 *
 */
export const { shape } = registerControl(['shape', 'shapevol']);
/**
 * Wave shaping distortion. CAUTION: it can get loud.
 * Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. Third option sets the waveshaping type.
 * Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;)
 *
 * @name distort
 * @tags distortion, superdough, supradough
 * @synonyms dist
 * @param {number | Pattern} distortion amount of distortion to apply
 * @param {number | Pattern} volume linear postgain of the distortion
 * @param {number | string | Pattern} type type of distortion to apply
 * @example
 * s("bd sd [~ bd] sd,hh*8").distort("<0 2 3 10:.5>")
 * @example
 * note("d1!8").s("sine").penv(36).pdecay(.12).decay(.23).distort("8:.4")
 * @example
 * s("bd:4*4").bank("tr808").distort("3:0.5:diode")
 *
 */
export const { distort, dist } = registerControl(['distort', 'distortvol', 'distorttype'], 'dist');

/**
 * Postgain for waveshaping distortion.
 *
 * @name distortvol
 * @synonyms distortion, distvol
 * @tags superdough, supradough
 * @param {number | Pattern} volume linear postgain of the distortion
 * @example
 * s("bd*4").bank("tr909").distort(2).distortvol(0.8)
 */
export const { distortvol } = registerControl('distortvol', 'distvol');

/**
 * Type of waveshaping distortion to apply.
 *
 * @name distorttype
 * @tags distortion, superdough, supradough
 * @synonyms disttype
 * @param {number | string | Pattern} type type of distortion to apply
 * @example
 * s("bd*4").bank("tr909").distort(2).distorttype("<0 1 2>")
 *
 * @example
 * s("sine").note("F1*2").release(1)
 *   .penv(24).pdecay(0.05)
 *   .distort(rand.range(1, 8))
 *   .distorttype("<fold chebyshev scurve diode asym sinefold>")
 */
export const { distorttype } = registerControl('distorttype', 'disttype');

/**
 * Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")`
 * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties)
 *
 * @name compressor
 * @tags superdough
 * @example
 * s("bd sd [~ bd] sd,hh*8")
 * .compressor("-20:20:10:.002:.02")
 *
 */
export const { compressor } = registerControl([
  'compressor',
  'compressorRatio',
  'compressorKnee',
  'compressorAttack',
  'compressorRelease',
]);
export const { compressorKnee } = registerControl('compressorKnee');
export const { compressorRatio } = registerControl('compressorRatio');
export const { compressorAttack } = registerControl('compressorAttack');
export const { compressorRelease } = registerControl('compressorRelease');
/**
 * Changes the speed of sample playback, i.e. a cheap way of changing pitch.
 *
 * @name speed
 * @tags pitch, samples
 * @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards.
 * @example
 * s("bd*6").speed("1 2 4 1 -2 -4")
 * @example
 * speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
 *
 */
export const { speed } = registerControl('speed');

/**
 * Changes the pitch of the sample without changing its speed.
 * The frequencies are multiplied by (factor + 1) for positive numbers
 * and by max(factor / 4 + 1, 0) for negative numbers.
 * So tuning up by octaves can be done with 1, 3, 7, ...
 * and tuning down by octaves with -2, -3, -3.5...
 *
 * @name stretch
 * @tags pitch, samples
 * @param {number | Pattern} factor between `-4` and `inf`. Positive increases pitch, 0 does nothing, negative decreases the pitch.
 * @example
 * s("gm_flute").stretch("<2 1 0 -2>")
 *
 */
export const { stretch } = registerControl('stretch');
/**
 * Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
 *
 * @name unit
 * @tags superdirt
 * @param {number | string | Pattern} unit see description above
 * @example
 * speed("1 2 .5 3").s("bd").unit("c").osc()
 * @superdirtOnly
 *
 */

export const { unit } = registerControl('unit');
/**
 * Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
 *
 * "A simplistic pitch-raising algorithm. It's not meant to sound natural; its sound is reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter, depending on the input. The algorithm works by cutting the signal into fragments (delimited by upwards-going zero-crossings) and squeezing those fragments in the time domain (i.e. simply playing them back faster than they came in), leaving silences inbetween. All the parameters apart from memlen can be modulated."
 *
 * @name squiz
 * @tags superdirt
 * @param {number | Pattern} squiz Try passing multiples of 2 to it - 2, 4, 8 etc.
 * @example
 * squiz("2 4/2 6 [8 16]").s("bd").osc()
 * @superdirtOnly
 *
 */
export const { squiz } = registerControl('squiz');
// TODO: what is this? not found in tidal doc
// ['stutterdepth'],
// TODO: what is this? not found in tidal doc
// ['stuttertime'],
// TODO: what is this? not found in tidal doc
// ['timescale'],
// TODO: what is this? not found in tidal doc
// ['timescalewin'],
// ['tomdecay'],
// ['vcfegint'],
// ['vcoegint'],
// TODO: Use a rest (~) to override the effect <- vowel
/**
 *
 * Formant filter to make things sound like vowels.
 *
 * @name vowel
 * @tags superdough
 * @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ.
 * @example
 * note("[c2 <eb2 <g2 g1>>]*2").s('sawtooth')
 * .vowel("<a e i <o u>>")
 * @example
 * s("bd sd mt ht bd [~ cp] ht lt").vowel("[a|e|i|o|u]")
 *
 */
export const { vowel } = registerControl('vowel');
/* // TODO: find out how it works
 * Made by Calum Gunn. Divides an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discards a fraction of them. Takes a number between 1 and 100, denoted the percentage of segments to drop. The SuperCollider manual describes the Waveloss effect this way:
 *
 * Divide an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discard a fraction of them (i.e. replace them with silence of the same length). The technique was described by Trevor Wishart in a lecture. Parameters: the filter drops drop out of out of chunks. mode can be 1 to drop chunks in a simple deterministic fashion (e.g. always dropping the first 30 out of a set of 40 segments), or 2 to drop chunks randomly but in an appropriate proportion.)
 *
 * mode: ?
 * waveloss: ?
 *
 * @name waveloss
 */
export const { waveloss } = registerControl('waveloss');
/**
 * crackle noise density
 *
 * @name density
 * @tags superdough
 * @param {number | Pattern} density between 0 and x
 * @example
 * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4))
 *
 */
export const { density } = registerControl('density');
// ['modwheel'],
export const { expression } = registerControl('expression');
export const { sustainpedal } = registerControl('sustainpedal');

export const { fshift } = registerControl('fshift');
export const { fshiftnote } = registerControl('fshiftnote');
export const { fshiftphase } = registerControl('fshiftphase');

export const { triode } = registerControl('triode');
export const { krush } = registerControl('krush');
export const { kcutoff } = registerControl('kcutoff');
export const { octer } = registerControl('octer');
export const { octersub } = registerControl('octersub');
export const { octersubsub } = registerControl('octersubsub');
export const { ring } = registerControl('ring');
export const { ringf } = registerControl('ringf');
export const { ringdf } = registerControl('ringdf');
export const { freeze } = registerControl('freeze');
export const { xsdelay } = registerControl('xsdelay');
export const { tsdelay } = registerControl('tsdelay');
export const { real } = registerControl('real');
export const { imag } = registerControl('imag');
export const { enhance } = registerControl('enhance');
export const { comb } = registerControl('comb');
export const { smear } = registerControl('smear');
export const { scram } = registerControl('scram');
export const { binshift } = registerControl('binshift');
export const { hbrick } = registerControl('hbrick');
export const { lbrick } = registerControl('lbrick');

export const { frameRate } = registerControl('frameRate');
export const { frames } = registerControl('frames');
export const { hours } = registerControl('hours');
export const { minutes } = registerControl('minutes');
export const { seconds } = registerControl('seconds');
export const { songPtr } = registerControl('songPtr');
export const { uid } = registerControl('uid');
export const { val } = registerControl('val');
export const { cps } = registerControl('cps');
/**
 * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration.
 *
 * @name clip
 * @tags superdough
 * @synonyms legato
 * @param {number | Pattern} factor >= 0
 * @example
 * note("c a f e").s("piano").clip("<.5 1 2>")
 *
 */
export const { clip, legato } = registerControl('clip', 'legato');

/**
 * Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration.
 *
 * @name duration
 * @tags superdough
 * @synonyms dur
 * @param {number | Pattern} seconds >= 0
 * @example
 * note("c a f e").s("piano").dur("<.5 1 2>")
 *
 */
export const { duration, dur } = registerControl('duration', 'dur');

// ZZFX
export const { zrand } = registerControl('zrand');
export const { curve } = registerControl('curve');
// superdirt duplicate
// export const {slide]} = registerControl('slide']);
export const { deltaSlide } = registerControl('deltaSlide');
export const { pitchJump } = registerControl('pitchJump');
export const { pitchJumpTime } = registerControl('pitchJumpTime');
// noise on the frequency or as bubo calls it "frequency fog" :)
export const { znoise } = registerControl('znoise');
export const { zmod } = registerControl('zmod');
// like crush but scaled differently
export const { zcrush } = registerControl('zcrush');
export const { zdelay } = registerControl('zdelay');
export const { zzfx } = registerControl('zzfx');

/**
 * Sets the color of the hap in visualizations like pianoroll or highlighting.
 * @name color
 * @tags visualization
 * @synonyms colour
 * @param {string} color Hexadecimal or CSS color name
 */
export const { color, colour } = registerControl(['color', 'colour']);

// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13

export let createParams = (...names) =>
  names.reduce((acc, name) => Object.assign(acc, { [name]: createParam(name) }), {});

/**
 * ADSR envelope: Combination of Attack, Decay, Sustain, and Release.
 *
 * @name adsr
 * @tags envelope, amplitude
 * @param {number | Pattern} time attack time in seconds
 * @param {number | Pattern} time decay time in seconds
 * @param {number | Pattern} gain sustain level (0 to 1)
 * @param {number | Pattern} time release time in seconds
 * @example
 * note("[c3 bb2 f3 eb3]*2").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2")
 */
export const adsr = register('adsr', (adsr, pat) => {
  adsr = !Array.isArray(adsr) ? [adsr] : adsr;
  const [attack, decay, sustain, release] = adsr;
  return pat.set({ attack, decay, sustain, release });
});
export const ad = register('ad', (t, pat) => {
  t = !Array.isArray(t) ? [t] : t;
  const [attack, decay = attack] = t;
  return pat.attack(attack).decay(decay);
});
export const ds = register('ds', (t, pat) => {
  t = !Array.isArray(t) ? [t] : t;
  const [decay, sustain = 0] = t;
  return pat.set({ decay, sustain });
});
export const ar = register('ar', (t, pat) => {
  t = !Array.isArray(t) ? [t] : t;
  const [attack, release = attack] = t;
  return pat.set({ attack, release });
});

//MIDI

/**
 * MIDI channel: Sets the MIDI channel for the event.
 *
 * @name midichan
 * @tags external_io, midi
 * @param {number | Pattern} channel MIDI channel number (0-15)
 * @example
 * note("c4").midichan(1).midi()
 */
export const { midichan } = registerControl('midichan');

export const { midimap } = registerControl('midimap');

/**
 * MIDI port: Sets the MIDI port for the event.
 *
 * @name midiport
 * @tags external_io, midi
 * @param {number | Pattern} port MIDI port
 * @example
 * note("c a f e").midiport("<0 1 2 3>").midi()
 */
export const { midiport } = registerControl('midiport');

/**
 * MIDI command: Sends a MIDI command message.
 *
 * @name midicmd
 * @tags external_io, midi
 * @param {number | Pattern} command MIDI command
 * @example
 * midicmd("clock*48,<start stop>/2").midi()
 */
export const { midicmd } = registerControl('midicmd');

/**
 * MIDI control: Sends a MIDI control change message.
 *
 * @name control
 * @tags external_io, midi
 * @param {number | Pattern}  MIDI control number (0-127)
 * @param {number | Pattern}  MIDI controller value (0-127)
 */
export const control = register('control', (args, pat) => {
  if (!Array.isArray(args)) {
    throw new Error('control expects an array of [ccn, ccv]');
  }
  const [_ccn, _ccv] = args;
  return pat.ccn(_ccn).ccv(_ccv);
});

/**
 * MIDI control number: Sends a MIDI control change message.
 *
 * @name ccn
 * @tags external_io, midi
 * @param {number | Pattern}  MIDI control number (0-127)
 */
export const { ccn } = registerControl('ccn');
/**
 * MIDI control value: Sends a MIDI control change message.
 *
 * @name ccv
 * @tags external_io, midi
 * @param {number | Pattern}  MIDI control value (0-127)
 */
export const { ccv } = registerControl('ccv');
export const { ctlNum } = registerControl('ctlNum');
// TODO: ctlVal?

/**
 * MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message.
 * @name nrpnn
 * @tags external_io, midi
 * @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127)
 * @example
 * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
 */
export const { nrpnn } = registerControl('nrpnn');
/**
 * MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message.
 * @name nrpv
 * @tags external_io, midi
 * @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127)
 * @example
 * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
 */
export const { nrpv } = registerControl('nrpv');

/**
 * MIDI program number: Sends a MIDI program change message.
 *
 * @name progNum
 * @tags external_io
 * @param {number | Pattern} program MIDI program number (0-127)
 * @example
 * note("c4").progNum(10).midichan(1).midi()
 */
export const { progNum } = registerControl('progNum');

/**
 * MIDI sysex: Sends a MIDI sysex message.
 * @name sysex
 * @tags external_io, midi
 * @param {number | Pattern} id Sysex ID
 * @param {number | Pattern} data Sysex data
 * @example
 * note("c4").sysex(["0x77", "0x01:0x02:0x03:0x04"]).midichan(1).midi()
 */
export const sysex = register('sysex', (args, pat) => {
  if (!Array.isArray(args)) {
    throw new Error('sysex expects an array of [id, data]');
  }
  const [id, data] = args;
  return pat.sysexid(id).sysexdata(data);
});
/**
 * MIDI sysex ID: Sends a MIDI sysex identifier message.
 * @name sysexid
 * @tags external_io, midi
 * @param {number | Pattern} id Sysex ID
 * @example
 * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
 */
export const { sysexid } = registerControl('sysexid');
/**
 * MIDI sysex data: Sends a MIDI sysex message.
 * @name sysexdata
 * @tags external_io, midi
 * @param {number | Pattern} data Sysex data
 * @example
 * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
 */
export const { sysexdata } = registerControl('sysexdata');

/**
 * MIDI pitch bend: Sends a MIDI pitch bend message.
 * @name midibend
 * @tags external_io, midi
 * @param {number | Pattern} midibend MIDI pitch bend (-1 - 1)
 * @example
 * note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi()
 */
export const { midibend } = registerControl('midibend');
/**
 * MIDI key after touch: Sends a MIDI key after touch message.
 * @name miditouch
 * @tags external_io, midi
 * @param {number | Pattern} miditouch MIDI key after touch (0-1)
 * @example
 * note("c4").miditouch(sine.slow(4).range(0,1)).midi()
 */
export const { miditouch } = registerControl('miditouch');

// TODO: what is this?
export const { polyTouch } = registerControl('polyTouch');

/**
 * The host to send open sound control messages to. Requires running the OSC bridge.
 * @name oschost
 * @tags external_io
 * @param {string | Pattern} oschost e.g. 'localhost'
 * @example
 * note("c4").oschost('127.0.0.1').oscport(57120).osc();
 */
export const { oschost } = registerControl('oschost');

/**
 * The port to send open sound control messages to. Requires running the OSC bridge.
 * @name oscport
 * @tags external_io
 * @param {number | Pattern} oscport e.g. 57120
 * @example
 * note("c4").oschost('127.0.0.1').oscport(57120).osc();
 */
export const { oscport } = registerControl('oscport');

export const getControlName = (alias) => {
  if (controlAlias.has(alias)) {
    return controlAlias.get(alias);
  }
  return alias;
};

/**
 * Sets properties in a batch.
 *
 * @name as
 * @tags combiners
 * @param {String | Array} mapping the control names that are set
 * @example
 * "c:.5 a:1 f:.25 e:.8".as("note:clip")
 * @example
 * "{0@2 0.25 0 0.5 .3 .5}%8".as("begin").s("sax_vib").clip(1)
 */
export const as = register('as', (mapping, pat) => {
  mapping = Array.isArray(mapping) ? mapping : [mapping];
  return pat.fmap((v) => {
    v = Array.isArray(v) ? v : [v];
    const entries = [];
    for (let i = 0; i < mapping.length; ++i) {
      if (v[i] !== undefined) {
        entries.push([getControlName(mapping[i]), v[i]]);
      }
    }
    return Object.fromEntries(entries);
  });
});

/**
 * Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file
 * in the optional array syntax ex: "0.5:2", the second value controls the speed of playback
 * @name scrub
 * @tags samples
 * @memberof Pattern
 * @returns Pattern
 * @example
 * samples('github:switchangel/pad')
 * s("swpad:0").scrub("{0.1!2 .25@3 0.7!2 <0.8:1.5>}%8")
 * @example
 * samples('github:yaxu/clean-breaks/main');
 * s("amen/4").fit().scrub("{0@3 0@2 4@3}%8".div(16))
 */

export const scrub = register(
  'scrub',
  (beginPat, pat) => {
    return beginPat.outerBind((v) => {
      if (!Array.isArray(v)) {
        v = [v];
      }
      const [beginVal, speedMultiplier = 1] = v;

      return pat.begin(beginVal).mul(speed(speedMultiplier)).clip(1);
    });
  },
  false,
);

const subControlAliases = new Map();
const registerSubControl = (control, subControl, ...aliases) => {
  const aliasMap = subControlAliases.get(control) ?? new Map();
  const allKeys = new Set([subControl, ...aliases]);
  for (const alias of allKeys) {
    aliasMap.set(String(alias).toLowerCase(), subControl);
  }
  subControlAliases.set(control, aliasMap);
};

const registerSubControls = (control, subControlAliases = []) => {
  for (const [subControl, ...aliases] of subControlAliases) {
    registerSubControl(control, subControl, ...aliases);
  }
};

const getMainSubcontrolName = (control, subKey) => {
  const aliasMap = subControlAliases.get(control);
  if (!aliasMap) return subKey;
  return aliasMap.get(String(subKey).toLowerCase()) ?? subKey;
};

registerSubControls('lfo', [
  ['control', 'c'],
  ['subControl', 'sc'],
  ['rate', 'r'],
  ['depth', 'dep', 'dr'],
  ['depthabs', 'da'],
  ['dcoffset', 'dc'],
  ['shape', 'sh'],
  ['skew', 'sk'],
  ['curve', 'cu'],
  ['sync', 's'],
  ['retrig', 'rt'],
  ['fxi'],
]);
registerSubControls('env', [
  ['control', 'c'],
  ['subControl', 'sc'],
  ['attack', 'att', 'a'],
  ['decay', 'dec', 'd'],
  ['sustain', 'sus', 's'],
  ['release', 'rel', 'r'],
  ['depth', 'dep', 'dr'],
  ['depthabs', 'da'],
  ['acurve', 'ac'],
  ['dcurve', 'dc'],
  ['rcurve', 'rc'],
  ['fxi'],
]);
registerSubControls('bmod', [
  ['bus', 'b'],
  ['control', 'c'],
  ['subControl', 'sc'],
  ['depth', 'dep', 'dr'],
  ['depthabs', 'da'],
  ['dc'],
  ['fxi'],
]);

Pattern.prototype.modulate = function (type, config, idPat) {
  config = { control: undefined, ...config };
  const modulatorKeys = ['lfo', 'env', 'bmod'];
  if (!modulatorKeys.includes(type)) {
    logger(`[core] Modulation type ${type} not found. Please use one of 'lfo', 'env', 'bmod'`);
    return this;
  }
  let output = this;
  let defaultValue = undefined;
  // Copy value into a temporary `v` container and attach a single `id` (to be shared across
  // each config entry). At the output we destructure and throw away the id
  output = output.fmap((v) => (id) => ({ v, id })).appLeft(reify(idPat));
  for (const [rawKey, value] of Object.entries(config)) {
    const key = getMainSubcontrolName(type, rawKey);
    const valuePat = reify(value);
    output = output
      .fmap(({ v, id }) => (c) => {
        if (defaultValue === undefined) {
          // default control to the control set just before this in the chain
          // e.g. pat.gain(0.5).lfo({..}) will be a gain-LFO
          let control = getControlName(Object.keys(v).at(-1));
          if (modulatorKeys.includes(control)) {
            control = `${control}_${[...v[control].__ids].at(-1)}`;
          }
          defaultValue = control;
        }
        v[type] ??= { __ids: new Set() };
        const t = v[type];
        id ??= t.__ids.size;
        t[id] ??= { control: defaultValue };
        t.__ids.add(id); // keeps track of insertion order
        if (c === undefined) return { v, id };
        if (key === 'control' || key === 'subControl') {
          t[id][key] = getControlName(c);
        } else {
          t[id][key] = c;
        }
        return { v, id };
      })
      .appLeft(valuePat);
  }
  return output.fmap(({ v }) => v);
};

/**
 * Configures an LFO. Can be called in sequence like pat.lfo(...).lfo(...) to set up multiple LFOs.
 * There are two ways to declare which control will be modulated:
 * 1. Explicitly put `control` in the config (e.g. `lfo({ c: "lpf" })`)
 * 2. If the control parameter is absent, the control _immediately before_ the `lfo` call will be used
 *   (e.g. `s("saw").lpf(500).lfo()` to modulate `lpf`)
 *
 * Modulators can be referred to by `id` so that they can be updated later e.g. inside
 * a `sometimes`. See example below.
 *
 * @name lfo
 * @tags lfo, superdough
 * @param {Object} config LFO configuration.
 * @param {string | Pattern} [config.control] Node to modulate. Aliases: c
 * @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc
 * @param {number | Pattern} [config.rate] Modulation rate. Aliases: r
 * @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
 * @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
 * @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
 * @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc
 * @param {number | Pattern} [config.shape] Shape index. Aliases: sh
 * @param {number | Pattern} [config.skew] Skew amount. Aliases: sk
 * @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: cu
 * @param {number | Pattern} [config.retrig] If > 0.5, the LFO will retrigger on each event. Aliases: rt
 * @param {number | Pattern} [config.fxi] FX index to target
 * @param {string | Pattern} id ID to use for this modulator
 * @returns Pattern
 *
 * @example
 * s("saw").note("F1").lpf(500).lfo()
 *
 * @example
 * s("saw").lfo().lpf(500).lfo({ s: 0.3 })
 *
 * @example
 * s("saw").lpf(500).diode(0.3)
 *   .lfo({ c: "lpf" })
 *
 * @example
 * s("pulse").lpf(500).lfo()
 *   .lfo({ c: "s" })
 *   .diode(0.3)
 *   .sometimes(x => x.lfo({ s: "8" }, 1)) // lfo #1 (0-indexed)
 *
 * @example
 * s("pulse").lpf(500).lfo({ depth: 4 }, 'lpf_mod')
 *   .lfo({ c: "s" })
 *   .diode(0.3)
 *   .sometimes(x => x.lfo({ s: "8" }, 'lpf_mod'))
 */
Pattern.prototype.lfo = function (config, id) {
  return this.modulate('lfo', config, id);
};
export const lfo = (config) => pure({}).lfo(config);

/**
 * Configures an envelope. Can be called in sequence like pat.env(...).env(...) to set up multiple envelopes
 * There are two ways to declare which control will be modulated:
 * 1. Explicitly put `control` in the config (e.g. `env({ c: "lpf" })`)
 * 2. If the control parameter is absent, the control _immediately before_ the `env` call will be used
 *   (e.g. `s("saw").lpf(500).env({ a: 1 })` to modulate `lpf`)
 *
 * Modulators can be referred to by `id` so that they can be updated later e.g. inside
 * a `sometimes`. See example below.
 *
 * @name env
 * @tags envelope, superdough
 * @param {Object} config Envelope configuration.
 * @param {string | Pattern} [config.control] Node to modulate. Aliases: c
 * @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc
 * @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
 * @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
 * @param {number | Pattern} [config.attack] Time to reach depth. Aliases: att, a
 * @param {number | Pattern} [config.decay] Time to reach sustain. Aliases: dec, d
 * @param {number | Pattern} [config.sustain] Sustain depth. Aliases: sus, s
 * @param {number | Pattern} [config.release] Time to return to nominal value. Aliases: rel, r
 * @param {number | Pattern} [config.acurve] Snappiness of attack curve (-1 = relaxed, 1 = snappy). Aliases: ac
 * @param {number | Pattern} [config.dcurve] Snappiness of decay curve (-1 = relaxed, 1 = snappy). Aliases: dc
 * @param {number | Pattern} [config.rcurve] Snappiness of release curve (-1 = relaxed, 1 = snappy). Aliases: rc
 * @param {number | Pattern} [config.fxi] FX index to target
 * @param {string | Pattern} id ID to use for this modulator
 * @returns Pattern
 *
 * @example
 * s("saw").note("F1").lpf(500).env({ a: 1 })
 *
 * @example
 * s("saw").env({ d: 1 }).note("F1")
 *   .lpq(4).lpf(50)
 *   .env({ a: 0.1, d: 1, ac: 0.8, dc: 0.3, depth: 50 })
 *
 * @example
 * s("saw").lpf(500).diode(0.3)
 *   .env({ c: "lpf", a: 0.5, d: 0.5 })
 *
 * @example
 * s("pulse").lpf(500).env({ a: 1 })
 *   .env({ c: "s", a: 1 })
 *   .diode(0.3)
 *   .sometimes(x => x.env({ a: "0.5" }, 1)) // envelope #1 (0-indexed)
 *
 * @example
 * s("pulse").lpf(500).env({ a: 1 }, 'lpf_mod')
 *   .env({ c: "s", a: 1 })
 *   .diode(0.3)
 *   .sometimes(x => x.env({ a: "0.5" }, 'lpf_mod'))
 */
Pattern.prototype.env = function (config, id) {
  return this.modulate('env', config, id);
};
export const env = (config) => pure({}).env(config);

/**
 * Modulates with the output from a given `bus`.
 * Can be called in sequence like pat.bmod(...).bmod(...) to set up multiple modulators
 *
 * Send to an audio bus with `otherPat.bus(..)`.
 *
 * There are two ways to declare which control will be modulated:
 * 1. Explicitly put `control` in the config (e.g. `bmod({ id: 2, c: "lpf" })`)
 * 2. If the control parameter is absent, the control _immediately before_ the `bmod` call will be used
 *   (e.g. `s("saw").lpf(500).bmod({ id: 2 })` to modulate `lpf`)
 *
 * Modulators can be referred to by `id` so that they can be updated later e.g. inside
 * a `sometimes`. See example below.
 *
 * @name bmod
 * @tags superdough
 * @param {Object} config Bus modulation configuration.
 * @param {string | Pattern} [config.bus] Bus to get modulation signal from
 * @param {string | Pattern} [config.control] Node to modulate. Aliases: c
 * @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc
 * @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
 * @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
 * @param {number | Pattern} [config.dc] DC offset prior to application
 * @param {number | Pattern} [config.fxi] FX index to target
 * @param {string | Pattern} id ID to use for this modulator
 * @returns Pattern
 *
 * @example
 * modulator: s("one").seg(64).gain(slider(0, 0, 1)).bus(1).dry(0)
 * carrier: s("saw").bmod({ b: 1 })
 *
 */
Pattern.prototype.bmod = function (config, id) {
  return this.modulate('bmod', config, id);
};
export const bmod = (config) => pure({}).bmod(config);

/**
 * Transient shaper. Gives independent control over the emphasis on transients
 * and sustains
 *
 * @name transient
 * @tags superdough
 * @param {number | Pattern} attack Emphasis on transients; between -1 (deaccentuate) and 1 (accentuate)
 * @param {number | Pattern} sustain Emphasis on the sustains; between -1 (deaccentuate) and 1 (accentuate)
 * @example
 * s("bd").transient("<-1 -0.5 0 0.5 1>")
 * @example
 * s("hh*16").bank("tr909").transient("<-1:1 1:-1>")
 */
export const { transient } = registerControl(['transient', 'transsustain']);

export const { FXrelease, FXrel, FXr, fxr } = registerControl('FXrelease', 'FXrel', 'FXr', 'fxr');
