/* controls.mjs - Copyright (C) 2022 Strudel contributors - see 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 . */ import { Pattern, register, sequence } from './pattern.mjs'; import { zipWith } from './util.mjs'; const controls = {}; const generic_params = [ /** * Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters * separated by ':'. * * @name s * @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") * */ [['s', 'n', 'gain'], 'sound'], /** * Define a custom webaudio node to use as a sound source. * * @name source * @param {function} getSource * @synonyms src * */ ['source', 'src'], /** * Selects the given index from the sample map. * Numbers too high will wrap around. * `n` can also be used to play midi numbers, but it is recommended to use `note` instead. * * @name n * @param {number | Pattern} value sample index starting from 0 * @example * s("bd sd [~ bd] sd,hh*6").n("<0 1>") */ // also see https://github.com/tidalcycles/strudel/pull/63 ['n'], /** * 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 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 * @example * note("c a f e") * @example * note("c4 a4 f4 e4") * @example * note("60 69 65 64") */ [['note', 'n']], /** * A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt. * * @name accelerate * @param {number | Pattern} amount acceleration. * @superdirtOnly * @example * s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc() * */ ['accelerate'], /** * Controls the gain by an exponential amount. * * @name gain * @param {number | Pattern} amount gain. * @example * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) * */ ['gain'], /** * Gain applied after all effects have been processed. * * @name postgain * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02").postgain(1.5) * */ ['postgain'], /** * Like `gain`, but linear. * * @name amp * @param {number | Pattern} amount gain. * @superdirtOnly * @example * s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc() * */ ['amp'], /** * Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * * @name attack * @param {number | Pattern} attack time in seconds. * @synonyms att * @example * note("c3 e3 f3 g3").attack("<0 .1 .5>") * */ ['attack', 'att'], /** * 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. * * @name fmh * @param {number | Pattern} harmonicity * @example * note("c e g b g e") * .fm(4) * .fmh("<1 2 1.5 1.61>") * .scope() * */ [['fmh', 'fmi'], 'fmh'], /** * Sets the Frequency Modulation of the synth. * Controls the modulation index, which defines the brightness of the sound. * * @name fm * @param {number | Pattern} brightness modulation index * @synonyms fmi * @example * note("c e g b g e") * .fm("<0 1 2 8 32>") * .scope() * */ [['fmi', 'fmh'], 'fm'], // fm envelope /** * Ramp type of fm envelope. Exp might be a bit broken.. * * @name fmenv * @param {number | Pattern} type lin | exp * @example * note("c e g b g e") * .fm(4) * .fmdecay(.2) * .fmsustain(0) * .fmenv("") * .scope() * */ ['fmenv'], /** * Attack time for the FM envelope: time it takes to reach maximum modulation * * @name fmattack * @param {number | Pattern} time attack time * @example * note("c e g b g e") * .fm(4) * .fmattack("<0 .05 .1 .2>") * .scope() * */ ['fmattack'], /** * Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase. * * @name fmdecay * @param {number | Pattern} time decay time * @example * note("c e g b g e") * .fm(4) * .fmdecay("<.01 .05 .1 .2>") * .fmsustain(.4) * .scope() * */ ['fmdecay'], /** * Sustain level for the FM envelope: how much modulation is applied after the decay phase * * @name fmsustain * @param {number | Pattern} level sustain level * @example * note("c e g b g e") * .fm(4) * .fmdecay(.1) * .fmsustain("<1 .75 .5 0>") * .scope() * */ ['fmsustain'], // these are not really useful... skipping for now ['fmrelease'], ['fmvelocity'], /** * 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 * @param {string | Pattern} bank the name of the bank * @example * s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") * */ ['bank'], ['analyze'], // analyser node send amount 0 - 1 (used by scope) ['fft'], // fftSize of analyser /** * 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 * @param {number | Pattern} time decay time in seconds * @example * note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0) * */ ['decay', 'dec'], /** * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * * @name sustain * @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>") * */ ['sustain', 'sus'], /** * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * * @name release * @param {number | Pattern} time release time in seconds * @synonyms rel * @example * note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2") * */ ['release', 'rel'], ['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 * @param {number | Pattern} frequency center frequency * @synonyms bandf, bp * @example * s("bd sd [~ bd] sd,hh*6").bpf("<1000 2000 4000 8000>") * */ [['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'], // TODO: in tidal, it seems to be normalized /** * Sets the **b**and-**p**ass **q**-factor (resonance). * * @name bpq * @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://github.com/tidalcycles/strudel/issues/496 // ['bpq'], ['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. * * @memberof Pattern * @name begin * @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/master/') * s("rave").begin("<0 .25 .5 .75>").fast(2) * */ ['begin'], /** * The same as .begin, but cuts off the end off each sample. * * @memberof Pattern * @name end * @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) * */ ['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 * @param {number | Pattern} on If 1, the sample is looped * @example * s("casio").loop(1) * */ ['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 * @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() */ ['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 * @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() */ ['loopEnd', 'loope'], /** * bit crusher effect. * * @name crush * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). * @example * s(",hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>") * */ // TODO: currently duplicated with "native" legato // TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419 /** * 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 legato * @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time * @noAutocomplete * @example * "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>") * */ // ['legato'], // ['clhatdecay'], ['crush'], /** * fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * * @name coarse * @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>") * */ ['coarse'], /** * Allows you to set the output channels on the interface * * @name channels * @synonyms ch * * @param {number | Pattern} channels pattern the output channels * @example * note("e a d b g").channels("3:4") * */ ['channels', 'ch'], ['phaserrate', 'phasr'], // superdirt only /** * Phaser audio effect that approximates popular guitar pedals. * * @name phaser * @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>") * */ [['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph'], /** * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 * * @name phasersweep * @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>") * */ ['phasersweep', 'phs'], /** * The center frequency of the phaser in HZ. Defaults to 1000 * * @name phasercenter * @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>") * */ ['phasercenter', 'phc'], /** * The amount the signal is affected by the phaser effect. Defaults to 0.75 * * @name phaserdepth * @synonyms phd * @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>") * */ ['phaserdepth', 'phd', 'phasdp'], // also a superdirt control /** * choose the channel the pattern is sent to in superdirt * * @name channel * @param {number | Pattern} channel channel number * */ ['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 * @param {number | Pattern} group cut group number * @example * s("[oh hh]*4").cut(1) * */ ['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 * @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") * */ [['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'], /** * Sets the lowpass filter envelope modulation depth. * @name lpenv * @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(500) * .lpa(.5) * .lpenv("<4 2 1 0 -1 -2 -4>/4") */ ['lpenv', 'lpe'], /** * Sets the highpass filter envelope modulation depth. * @name hpenv * @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") */ ['hpenv', 'hpe'], /** * Sets the bandpass filter envelope modulation depth. * @name bpenv * @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") */ ['bpenv', 'bpe'], /** * Sets the attack duration for the lowpass filter envelope. * @name lpattack * @param {number | Pattern} attack time of the filter envelope * @synonyms lpa * @example * note("c2 e2 f2 g2") * .sound('sawtooth') * .lpf(500) * .lpa("<.5 .25 .1 .01>/4") * .lpenv(4) */ ['lpattack', 'lpa'], /** * Sets the attack duration for the highpass filter envelope. * @name hpattack * @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) */ ['hpattack', 'hpa'], /** * Sets the attack duration for the bandpass filter envelope. * @name bpattack * @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) */ ['bpattack', 'bpa'], /** * Sets the decay duration for the lowpass filter envelope. * @name lpdecay * @param {number | Pattern} decay time of the filter envelope * @synonyms lpd * @example * note("c2 e2 f2 g2") * .sound('sawtooth') * .lpf(500) * .lpd("<.5 .25 .1 0>/4") * .lps(0.2) * .lpenv(4) */ ['lpdecay', 'lpd'], /** * Sets the decay duration for the highpass filter envelope. * @name hpdecay * @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) */ ['hpdecay', 'hpd'], /** * Sets the decay duration for the bandpass filter envelope. * @name bpdecay * @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) */ ['bpdecay', 'bpd'], /** * Sets the sustain amplitude for the lowpass filter envelope. * @name lpsustain * @param {number | Pattern} sustain amplitude of the lowpass filter envelope * @synonyms lps * @example * note("c2 e2 f2 g2") * .sound('sawtooth') * .lpf(500) * .lpd(.5) * .lps("<0 .25 .5 1>/4") * .lpenv(4) */ ['lpsustain', 'lps'], /** * Sets the sustain amplitude for the highpass filter envelope. * @name hpsustain * @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) */ ['hpsustain', 'hps'], /** * Sets the sustain amplitude for the bandpass filter envelope. * @name bpsustain * @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) */ ['bpsustain', 'bps'], /** * Sets the release time for the lowpass filter envelope. * @name lprelease * @param {number | Pattern} release time of the filter envelope * @synonyms lpr * @example * note("c2 e2 f2 g2") * .sound('sawtooth') * .clip(.5) * .lpf(500) * .lpenv(4) * .lpr("<.5 .25 .1 0>/4") * .release(.5) */ ['lprelease', 'lpr'], /** * Sets the release time for the highpass filter envelope. * @name hprelease * @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) */ ['hprelease', 'hpr'], /** * Sets the release time for the bandpass filter envelope. * @name bprelease * @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) */ ['bprelease', 'bpr'], /** * Sets the filter type. The 24db filter is more aggressive. More types might be added in the future. * @name ftype * @param {number | Pattern} type 12db (default) or 24db * @example * note("c2 e2 f2 g2") * .sound('sawtooth') * .lpf(500) * .bpenv(4) * .ftype("12db 24db") */ ['ftype'], ['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 * @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://github.com/tidalcycles/strudel/issues/496 // ['hpf'], /** * Applies a vibrato to the frequency of the oscillator. * * @name vib * @synonyms vibrato, v * @param {number | Pattern} frequency of the vibrato in hertz * @example * note("a e") * .vib("<.5 1 2 4 8 16>") * @example * // change the modulation depth with ":" * note("a e") * .vib("<.5 1 2 4 8 16>:12") */ [['vib', 'vibmod'], 'vibrato', 'v'], /** * Adds pink noise to the mix * * @name noise * @param {number | Pattern} wet wet amount * @example * sound("/2") */ ['noise'], /** * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * * @name vibmod * @synonyms vmod * @param {number | Pattern} depth of vibrato (in semitones) * @example * note("a e").vib(4) * .vibmod("<.25 .5 1 2 12>") * @example * // change the vibrato frequency with ":" * note("a e") * .vibmod("<.25 .5 1 2 12>:8") */ [['vibmod', 'vib'], 'vmod'], [['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'], /** * Controls the **h**igh-**p**ass **q**-value. * * @name hpq * @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>") * */ ['hresonance', 'hpq'], /** * Controls the **l**ow-**p**ass **q**-value. * * @name lpq * @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://github.com/tidalcycles/strudel/issues/496 ['resonance', 'lpq'], /** * DJ filter, below 0.5 is low pass filter, above is high pass filter. * * @name djf * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter * @example * n("0 3 7 [10,24]").s('superzow').octave(3).djf("<.5 .25 .5 .75>").osc() * */ ['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 * @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") * */ [['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 * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example * s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>") * */ ['delayfeedback', 'delayfb', 'dfb'], /** * Sets the time of the delay effect. * * @name delaytime * @param {number | Pattern} seconds between 0 and Infinity * @synonyms delayt, dt * @example * s("bd bd").delay(.25).delaytime("<.125 .25 .5 1>") * */ ['delaytime', 'delayt', 'dt'], /* // TODO: test * Specifies whether delaytime is calculated relative to cps. * * @name lock * @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle. * @example * s("sd").delay().lock(1).osc() * */ ['lock'], /** * Set detune of oscillators. Works only with some synths, see tidal doc * * @name detune * @param {number | Pattern} amount between 0 and 1 * @synonyms det * @superdirtOnly * @example * n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc() * */ ['detune', 'det'], /** * Set dryness of reverb. See `room` and `size` for more information about reverb. * * @name dry * @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 * */ ['dry'], // TODO: does not seem to do anything /* * Used when using `begin`/`end` or `chop`/`striate` and friends, to change the fade out time of the 'grain' envelope. * * @name fadeTime * @param {number | Pattern} time between 0 and 1 * @example * s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc() * */ ['fadeTime', 'fadeOutTime'], // TODO: see above ['fadeInTime'], /** * Set frequency of sound. * * @name freq * @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() * */ ['freq'], // pitch envelope /** * Attack time of pitch envelope. * * @name pattack * @synonyms patt * @param {number | Pattern} time time in seconds * @example * note("c eb g bb").pattack("0 .1 .25 .5").slow(2) * */ ['pattack', 'patt'], /** * Decay time of pitch envelope. * * @name pdecay * @synonyms pdec * @param {number | Pattern} time time in seconds * @example * note("").pdecay("<0 .1 .25 .5>") * */ ['pdecay', 'pdec'], // TODO: how to use psustain?! ['psustain', 'psus'], /** * Release time of pitch envelope * * @name prelease * @synonyms prel * @param {number | Pattern} time time in seconds * @example * note(" ~") * .release(.5) // to hear the pitch release * .prelease("<0 .1 .25 .5>") * */ ['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 * @param {number | Pattern} semitones change in semitones * @example * note("c") * .penv("<12 7 1 .5 0 -1 -7 -12>") * */ ['penv'], /** * Curve of envelope. Defaults to linear. exponential is good for kicks * * @name pcurve * @param {number | Pattern} type 0 = linear, 1 = exponential * @example * note("g1*4") * .s("sine").pdec(.5) * .penv(32) * .pcurve("<0 1>") * */ ['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 * @param {number | Pattern} anchor anchor offset * @example * note("c c4").penv(12).panchor("<0 .5 1 .5>") * */ ['panchor'], // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate ['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 * @param {number | Pattern} wet between 0 and 1 * @example * n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc() * @superdirtOnly * */ ['leslie'], /** * Rate of modulation / rotation for leslie effect * * @name lrate * @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) ['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 * @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 * */ ['lsize'], /** * Sets the displayed text for an event on the pianoroll * * @name label * @param {string} label text to display */ ['activeLabel'], [['label', 'activeLabel']], // ['lfo'], // ['lfocutoffint'], // ['lfodelay'], // ['lfoint'], // ['lfopitchint'], // ['lfoshape'], // ['lfosync'], // ['lhitom'], // ['lkick'], // ['llotom'], // ['lophat'], // ['lsnare'], ['degree'], // TODO: what is this? not found in tidal doc ['mtranspose'], // TODO: what is this? not found in tidal doc ['ctranspose'], // TODO: what is this? not found in tidal doc ['harmonic'], // TODO: what is this? not found in tidal doc ['stepsPerOctave'], // TODO: what is this? not found in tidal doc ['octaveR'], // TODO: what is this? not found in tidal doc // 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) ['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 * @param {number | Pattern} octave octave number * @example * n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc() * @superDirtOnly */ ['octave'], // ['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 * @param {number | Pattern} number * @example * stack( * s("hh*6").delay(.5).delaytime(.25).orbit(1), * s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2) * ) */ ['orbit'], ['overgain'], // TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that ['overshape'], // TODO: what is this? not found in tidal doc. Similar to above, but limited to 1 /** * Sets position in stereo. * * @name pan * @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)) * */ ['pan'], // TODO: this has no effect (see example) /* * Controls how much multichannel output is fanned out * * @name panspan * @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() * */ ['panspan'], // TODO: this has no effect (see example) /* * Controls how much multichannel output is spread * * @name pansplay * @param {number | Pattern} spread between 0 and 1 * @example * s("[bd hh]*2").pan("<.5 1 .5 0>").pansplay("<0 .5 1>").osc() * */ ['pansplay'], ['panwidth'], ['panorient'], // ['pitch1'], // ['pitch2'], // ['pitch3'], // ['portamento'], // TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare ['rate'], // TODO: slide param for certain synths ['slide'], // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare ['semitone'], // TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano // ['velocity'], ['voice'], // TODO: synth param // voicings // https://github.com/tidalcycles/strudel/issues/506 ['chord'], // chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings ['dictionary', 'dict'], // which dictionary to use for the voicings ['anchor'], // the top note to align the voicing to, defaults to c5 ['offset'], // how the voicing is offset from the anchored position ['octaves'], // how many octaves are voicing steps spread apart, defaults to 1 [['mode', 'anchor']], // below = anchor note will be removed from the voicing, useful for melody harmonization /** * Sets the level of reverb. * * When using mininotation, you can also optionally add the 'size' parameter, separated by ':'. * * @name room * @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>") * */ [['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 * @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) */ ['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 * @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) * */ ['roomdim', 'rdim'], /** * Reverb fade time (in seconds). * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomfade * @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) * */ ['roomfade', 'rfade'], /** * Sets the sample to use as an impulse response for the reverb. * @name iresponse * @param {string | Pattern} sample to use as an impulse response * @synonyms ir * @example * s("bd sd [~ bd] sd").room(.8).ir("") * */ [['ir', 'i'], 'iresponse'], /** * 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 * @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? ['roomsize', 'size', 'sz', 'rsize'], // ['sagogo'], // ['sclap'], // ['sclaves'], // ['scrash'], /** * Wave shaping distortion. CAUTION: it might get loud * * @name shape * @param {number | Pattern} distortion between 0 and 1 * @example * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") * */ ['shape'], /** * 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 * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02") * */ [['compressor', 'compressorRatio', 'compressorKnee', 'compressorAttack', 'compressorRelease']], ['compressorKnee'], ['compressorRatio'], ['compressorAttack'], ['compressorRelease'], /** * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * * @name speed * @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) * */ ['speed'], /** * 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 * @param {number | string | Pattern} unit see description above * @example * speed("1 2 .5 3").s("bd").unit("c").osc() * @superdirtOnly * */ ['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 * @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 * */ ['squiz'], // ['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'], // TODO: what is this? not found in tidal doc // ['tomdecay'], // ['vcfegint'], // ['vcoegint'], // TODO: Use a rest (~) to override the effect <- vowel /** * * Formant filter to make things sound like vowels. * * @name vowel * @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 >]*2").s('sawtooth') * .vowel(">") * @example * s("bd sd mt ht bd [~ cp] ht lt").vowel("[a|e|i|o|u]") * */ ['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 */ ['waveloss'], /* * Noise crackle density * * @name density * @param {number | Pattern} density between 0 and x * @example * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)) * */ ['density'], // TODO: midi effects? ['dur'], // ['modwheel'], ['expression'], ['sustainpedal'], /* // TODO: doesn't seem to do anything * * Tremolo Audio DSP effect * * @name tremolodepth * @param {number | Pattern} depth between 0 and 1 * @example * n("0,4,7").tremolodepth("<0 .3 .6 .9>").osc() * */ ['tremolodepth', 'tremdp'], ['tremolorate', 'tremr'], ['fshift'], ['fshiftnote'], ['fshiftphase'], ['triode'], ['krush'], ['kcutoff'], ['octer'], ['octersub'], ['octersubsub'], ['ring'], ['ringf'], ['ringdf'], ['distort'], ['freeze'], ['xsdelay'], ['tsdelay'], ['real'], ['imag'], ['enhance'], ['partials'], ['comb'], ['smear'], ['scram'], ['binshift'], ['hbrick'], ['lbrick'], ['midichan'], ['control'], ['ccn'], ['ccv'], ['polyTouch'], ['midibend'], ['miditouch'], ['ctlNum'], ['frameRate'], ['frames'], ['hours'], ['midicmd'], ['minutes'], ['progNum'], ['seconds'], ['songPtr'], ['uid'], ['val'], ['cps'], /** * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. * In tidal, this would be done with legato, [which has a complicated history in strudel](https://github.com/tidalcycles/strudel/issues/111). * For now, if you're coming from tidal, just think clip = legato. * * @name clip * @param {number | Pattern} factor >= 0 * @example * note("c a f e").s("piano").clip("<.5 1 2>") * */ ['clip'], // ZZFX ['zrand'], ['curve'], ['slide'], // superdirt duplicate ['deltaSlide'], ['pitchJump'], ['pitchJumpTime'], ['lfo', 'repeatTime'], ['znoise'], // noise on the frequency or as bubo calls it "frequency fog" :) ['zmod'], ['zcrush'], // like crush but scaled differently ['zdelay'], ['tremolo'], ['zzfx'], ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 controls.createParam = function (names) { const name = Array.isArray(names) ? names[0] : names; var withVal; if (Array.isArray(names)) { withVal = (xs) => { if (Array.isArray(xs)) { const result = {}; xs.forEach((x, i) => { if (i < names.length) { result[names[i]] = x; } }); return result; } else { return { [name]: xs }; } }; } else { withVal = (x) => ({ [name]: x }); } const func = (...pats) => sequence(...pats).withValue(withVal); const setter = function (...pats) { if (!pats.length) { return this.fmap(withVal); } return this.set(func(...pats)); }; Pattern.prototype[name] = setter; return func; }; generic_params.forEach(([names, ...aliases]) => { const name = Array.isArray(names) ? names[0] : names; controls[name] = controls.createParam(names); aliases.forEach((alias) => { controls[alias] = controls[name]; Pattern.prototype[alias] = Pattern.prototype[name]; }); }); controls.createParams = (...names) => names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {}); /** * ADSR envelope: Combination of Attack, Decay, Sustain, and Release. * * @name adsr * @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") */ controls.adsr = register('adsr', (adsr, pat) => { adsr = !Array.isArray(adsr) ? [adsr] : adsr; const [attack, decay, sustain, release] = adsr; return pat.set({ attack, decay, sustain, release }); }); controls.ad = register('ad', (t, pat) => { t = !Array.isArray(t) ? [t] : t; const [attack, decay = attack] = t; return pat.attack(attack).decay(decay); }); controls.ds = register('ds', (t, pat) => { t = !Array.isArray(t) ? [t] : t; const [decay, sustain = 0] = t; return pat.set({ decay, sustain }); }); controls.ds = register('ar', (t, pat) => { t = !Array.isArray(t) ? [t] : t; const [attack, release = attack] = t; return pat.set({ attack, release }); }); export default controls;