From fd18508266086e756220dc4c2657455910930502 Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Sun, 1 Oct 2023 05:15:47 +0400 Subject: [PATCH 01/10] wip: use sample as an impulse response for the reverb --- packages/core/controls.mjs | 2373 ++++++++++++++-------------- packages/superdough/reverb.mjs | 18 +- packages/superdough/superdough.mjs | 31 +- 3 files changed, 1231 insertions(+), 1191 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 6cac6e540..76a278a5f 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -4,1220 +4,1231 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, register, sequence } from './pattern.mjs'; -import { zipWith } from './util.mjs'; +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,hh*3").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']], + /** + * 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,hh*3").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") - * - */ - ['gain'], - /** - * Like {@link 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").attack("<0 .1 .5>") - * - */ - ['attack', 'att'], + /** + * 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") + * + */ + ['gain'], + /** + * Like {@link 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").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") - * .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") - * .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") - * .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") - * .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") - * .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") - * .fm(4) - * .fmdecay(.1) - * .fmsustain("<1 .75 .5 0>") - * .scope() - * - */ - ['fmsustain'], - // these are not really useful... skipping for now - ['fmrelease'], - ['fmvelocity'], + /** + * 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") + * .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") + * .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") + * .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") + * .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") + * .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") + * .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").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") - * - */ - ['bank'], + /** + * 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").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") + * + */ + ['bank'], - ['analyze'], // analyser node send amount 0 - 1 (used by scope) - ['fft'], // fftSize of analyser + ['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").decay("<.1 .2 .3 .4>").sustain(0) - * - */ - ['decay'], - /** - * 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").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,hh*3").bpf("<1000 2000 4000 8000>") - * - */ - [['bandf', 'bandq'], '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").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>") - * - */ - ['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>") - * - */ - ['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,hh*4").coarse("<1 4 8 16 32>") - * - */ - ['coarse'], - /** - * 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("rd*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,hh*3").lpf("<4000 2000 1000 500 200 100>") - * @example - * s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30") - * - */ - [['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'], + /** + * 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").decay("<.1 .2 .3 .4>").sustain(0) + * + */ + ['decay'], + /** + * 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").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,hh*3").bpf("<1000 2000 4000 8000>") + * + */ + [['bandf', 'bandq'], '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").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>") + * + */ + ['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>") + * + */ + ['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,hh*4").coarse("<1 4 8 16 32>") + * + */ + ['coarse'], + /** + * 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("rd*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,hh*3").lpf("<4000 2000 1000 500 200 100>") + * @example + * s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30") + * + */ + [['cutoff', 'resonance'], '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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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,hh*4").hpf("<4000 2000 1000 500 200 100>") - * @example - * s("bd sd,hh*4").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") - * .vib("<.5 1 2 4 8 16>") - * @example - * // change the modulation depth with ":" - * note("a") - * .vib("<.5 1 2 4 8 16>:12") - */ - [['vib', 'vibmod'], 'vibrato', 'v'], - /** - * 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").vib(4) - * .vibmod("<.25 .5 1 2 12>") - * @example - * // change the vibrato frequency with ":" - * note("a") - * .vibmod("<.25 .5 1 2 12>:8") - */ - [['vibmod', 'vib'], 'vmod'], - [['hcutoff', 'hresonance'], '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,hh*4").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,hh*4").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").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>").slow(2) - * - */ - ['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").delay(.25).delaytime("<.125 .25 .5 1>").slow(2) - * - */ - ['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 {@link room} and {@link 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 {@link begin}/{@link end} or {@link chop}/{@link 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'], - // 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'], - // label for pianoroll - ['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'], + /** + * 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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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,hh*4").hpf("<4000 2000 1000 500 200 100>") + * @example + * s("bd sd,hh*4").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") + * .vib("<.5 1 2 4 8 16>") + * @example + * // change the modulation depth with ":" + * note("a") + * .vib("<.5 1 2 4 8 16>:12") + */ + [['vib', 'vibmod'], 'vibrato', 'v'], + /** + * 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").vib(4) + * .vibmod("<.25 .5 1 2 12>") + * @example + * // change the vibrato frequency with ":" + * note("a") + * .vibmod("<.25 .5 1 2 12>:8") + */ + [['vibmod', 'vib'], 'vmod'], + [['hcutoff', 'hresonance'], '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,hh*4").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,hh*4").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").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>").slow(2) + * + */ + ['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").delay(.25).delaytime("<.125 .25 .5 1>").slow(2) + * + */ + ['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 {@link room} and {@link 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 {@link begin}/{@link end} or {@link chop}/{@link 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'], + // 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'], + // label for pianoroll + ['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*3").delay(.5).delaytime(.25).orbit(1), - * s("~ 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>") - * - */ - ['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 + // ['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*3").delay(.5).delaytime(.25).orbit(1), + * s("~ 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>") + * + */ + ['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 + // 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").room("<0 .2 .4 .6 .8 1>") - * @example - * s("bd sd").room("<0.9:1 0.9:4>") - * - */ - [['room', 'size']], - /** - * Sets the room size of the reverb, see {@link room}. - * - * @name roomsize - * @param {number | Pattern} size between 0 and 10 - * @synonyms size, sz - * @example - * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") - * - */ - // TODO: find out why : - // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() - // .. does not work. Is it because room is only one effect? - ['size', 'sz', 'roomsize'], - // ['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,hh*4").shape("<0 .2 .4 .6 .8>") - * - */ - ['shape'], - /** - * 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").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 {@link 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. - * @example - * note("c2 >").s('sawtooth') - * .vowel(">") - * - */ - ['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'], - // 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'], - // TODO: doesn't seem to do anything - ['phaserdepth', 'phasdp'], - ['phaserrate', 'phasr'], + /** + * 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").room("<0 .2 .4 .6 .8 1>") + * @example + * s("bd sd").room("<0.9:1 0.9:4>") + * + */ + [['room', 'size']], + /** + * Sets the room size of the reverb, see {@link room}. + * + * @name roomsize + * @param {number | Pattern} size between 0 and 10 + * @synonyms size, sz + * @example + * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") + * + */ + // TODO: find out why : + // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() + // .. does not work. Is it because room is only one effect? + ['size', 'sz', 'roomsize'], + // ['sagogo'], + // ['sclap'], + // ['sclaves'], + // ['scrash'], - ['fshift'], - ['fshiftnote'], - ['fshiftphase'], + /** + * Sets the sample to use as an impulse response for the reverb. + * + * @name iresponse + * @param {string | Pattern} Sets the impulse response + * @example + * s("bd sd").room(.8).ir("") + * + */ + [['ir', 'i'], 'iresponse'], + /** + * Wave shaping distortion. CAUTION: it might get loud + * + * @name shape + * @param {number | Pattern} distortion between 0 and 1 + * @example + * s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>") + * + */ + ['shape'], + /** + * 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").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 {@link 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. + * @example + * note("c2 >").s('sawtooth') + * .vowel(">") + * + */ + ['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'], + // 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'], + // TODO: doesn't seem to do anything + ['phaserdepth', 'phasdp'], + ['phaserrate', 'phasr'], - ['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'], + ['fshift'], + ['fshiftnote'], + ['fshiftphase'], - // ZZFX - ['zrand'], - ['curve'], - ['slide'], // superdirt duplicate - ['deltaSlide'], - ['pitchJump'], - ['pitchJumpTime'], - ['lfo', 'repeatTime'], - ['noise'], - ['zmod'], - ['zcrush'], // like crush but scaled differently - ['zdelay'], - ['tremolo'], - ['zzfx'], + ['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'], + ['noise'], + ['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; + 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 }); - } + 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 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; + 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); + 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]; - }); + 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) }), {}); + names.reduce((acc, name) => Object.assign(acc, {[name]: controls.createParam(name)}), {}); 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 }); + adsr = !Array.isArray(adsr) ? [adsr] : adsr; + const [attack, decay, sustain, release] = adsr; + return pat.set({attack, decay, sustain, release}); }); controls.ds = register('ds', (ds, pat) => { - ds = !Array.isArray(ds) ? [ds] : ds; - const [decay, sustain] = ds; - return pat.set({ decay, sustain }); + ds = !Array.isArray(ds) ? [ds] : ds; + const [decay, sustain] = ds; + return pat.set({decay, sustain}); }); export default controls; diff --git a/packages/superdough/reverb.mjs b/packages/superdough/reverb.mjs index e6d31f6aa..727090852 100644 --- a/packages/superdough/reverb.mjs +++ b/packages/superdough/reverb.mjs @@ -7,14 +7,22 @@ if (typeof AudioContext !== 'undefined') { return impulse; }; - AudioContext.prototype.createReverb = function (duration) { + AudioContext.prototype.createReverb = function (duration, buffer) { const convolver = this.createConvolver(); - convolver.setDuration = (d) => { - convolver.buffer = this.impulseResponse(d); - convolver.duration = duration; + convolver.setDuration = (d, i) => { + convolver.buffer = i !== undefined ? buffer : this.impulseResponse(d); + convolver.duration = d; return convolver; }; - convolver.setDuration(duration); + convolver.setIR = (i) => { + convolver.buffer = i; + return convolver; + }; + if (buffer !== undefined) { + convolver.setIR(buffer); + } else { + convolver.setDuration(duration); + } return convolver; }; } diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 289e8d97a..2b46ae3c3 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -12,14 +12,18 @@ import workletsUrl from './worklets.mjs?url'; import { createFilter, gainNode } from './helpers.mjs'; import { map } from 'nanostores'; import { logger } from './logger.mjs'; +import { loadBuffer } from './sampler.mjs'; export const soundMap = map(); + export function registerSound(key, onTrigger, data = {}) { soundMap.setKey(key, { onTrigger, data }); } + export function getSound(s) { return soundMap.get()[s]; } + export const resetLoadedSounds = () => soundMap.set({}); let audioContext; @@ -46,6 +50,7 @@ export const panic = () => { }; let workletsLoading; + function loadWorklets() { if (workletsLoading) { return workletsLoading; @@ -89,6 +94,7 @@ export async function initAudioOnFirstClick(options) { let delays = {}; const maxfeedback = 0.98; + function getDelay(orbit, delaytime, delayfeedback, t) { if (delayfeedback > maxfeedback) { //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); @@ -107,10 +113,11 @@ function getDelay(orbit, delaytime, delayfeedback, t) { } let reverbs = {}; -function getReverb(orbit, duration = 2) { + +function getReverb(orbit, duration = 2, ir) { if (!reverbs[orbit]) { const ac = getAudioContext(); - const reverb = ac.createReverb(duration); + const reverb = ac.createReverb(duration, ir); reverb.connect(getDestination()); reverbs[orbit] = reverb; } @@ -118,10 +125,15 @@ function getReverb(orbit, duration = 2) { reverbs[orbit] = reverbs[orbit].setDuration(duration); reverbs[orbit].duration = duration; } + if (reverbs[orbit].ir !== ir) { + reverbs[orbit] = reverbs[orbit].setIR(ir); + reverbs[orbit].ir = ir; + } return reverbs[orbit]; } export let analyser, analyserData /* s = {} */; + export function getAnalyser(/* orbit, */ fftSize = 2048) { if (!analyser /*s [orbit] */) { const analyserNode = getAudioContext().createAnalyser(); @@ -151,7 +163,7 @@ export function getAnalyzerData(type = 'time') { return analyserData; } -function effectSend(input, effect, wet) { +function effectSend(input, effect, wet, size) { const send = gainNode(wet); input.connect(send); send.connect(effect); @@ -219,6 +231,8 @@ export const superdough = async (value, deadline, hapDuration) => { velocity = 1, analyze, // analyser wet fft = 8, // fftSize 0 - 10 + ir, + i = 0, } = value; gain *= velocity; // legacy fix for velocity let toDisconnect = []; // audio nodes that will be disconnected when the source has ended @@ -247,6 +261,7 @@ export const superdough = async (value, deadline, hapDuration) => { // this can be used for things like speed(0) in the sampler return; } + if (ac.currentTime > t) { logger('[webaudio] skip hap: still loading', ac.currentTime - t); return; @@ -352,10 +367,16 @@ export const superdough = async (value, deadline, hapDuration) => { delaySend = effectSend(post, delyNode, delay); } // reverb + let buffer; + if (ir !== undefined) { + let sample = getSound(ir); + let url = sample.data.samples[i % sample.data.samples.length]; + buffer = await loadBuffer(url, ac, ir, 0); + } let reverbSend; if (room > 0 && size > 0) { - const reverbNode = getReverb(orbit, size); - reverbSend = effectSend(post, reverbNode, room); + const reverbNode = getReverb(orbit, size, buffer); + reverbSend = effectSend(post, reverbNode, room, size); } // analyser From 755e24315e04223f75d22ac7680d05576b034c2f Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Sun, 1 Oct 2023 15:31:10 +0400 Subject: [PATCH 02/10] use sample as an impulse response for the reverb --- packages/core/controls.mjs | 2384 ++++++++++++++-------------- packages/superdough/reverb.mjs | 29 +- packages/superdough/superdough.mjs | 11 +- 3 files changed, 1220 insertions(+), 1204 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 76a278a5f..bc06ecc2b 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -4,1231 +4,1233 @@ Copyright (C) 2022 Strudel contributors - see . */ -import {Pattern, register, sequence} from './pattern.mjs'; -import {zipWith} from './util.mjs'; +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,hh*3").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']], + /** + * 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,hh*3").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") - * - */ - ['gain'], - /** - * Like {@link 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").attack("<0 .1 .5>") - * - */ - ['attack', 'att'], + /** + * 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") + * + */ + ['gain'], + /** + * Like {@link 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").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") - * .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") - * .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") - * .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") - * .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") - * .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") - * .fm(4) - * .fmdecay(.1) - * .fmsustain("<1 .75 .5 0>") - * .scope() - * - */ - ['fmsustain'], - // these are not really useful... skipping for now - ['fmrelease'], - ['fmvelocity'], + /** + * 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") + * .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") + * .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") + * .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") + * .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") + * .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") + * .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").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") - * - */ - ['bank'], + /** + * 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").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") + * + */ + ['bank'], - ['analyze'], // analyser node send amount 0 - 1 (used by scope) - ['fft'], // fftSize of analyser + ['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").decay("<.1 .2 .3 .4>").sustain(0) - * - */ - ['decay'], - /** - * 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").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,hh*3").bpf("<1000 2000 4000 8000>") - * - */ - [['bandf', 'bandq'], '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").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>") - * - */ - ['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>") - * - */ - ['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,hh*4").coarse("<1 4 8 16 32>") - * - */ - ['coarse'], - /** - * 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("rd*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,hh*3").lpf("<4000 2000 1000 500 200 100>") - * @example - * s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30") - * - */ - [['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'], + /** + * 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").decay("<.1 .2 .3 .4>").sustain(0) + * + */ + ['decay'], + /** + * 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").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,hh*3").bpf("<1000 2000 4000 8000>") + * + */ + [['bandf', 'bandq'], '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").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>") + * + */ + ['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>") + * + */ + ['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,hh*4").coarse("<1 4 8 16 32>") + * + */ + ['coarse'], + /** + * 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("rd*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,hh*3").lpf("<4000 2000 1000 500 200 100>") + * @example + * s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30") + * + */ + [['cutoff', 'resonance'], '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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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("") - * .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,hh*4").hpf("<4000 2000 1000 500 200 100>") - * @example - * s("bd sd,hh*4").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") - * .vib("<.5 1 2 4 8 16>") - * @example - * // change the modulation depth with ":" - * note("a") - * .vib("<.5 1 2 4 8 16>:12") - */ - [['vib', 'vibmod'], 'vibrato', 'v'], - /** - * 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").vib(4) - * .vibmod("<.25 .5 1 2 12>") - * @example - * // change the vibrato frequency with ":" - * note("a") - * .vibmod("<.25 .5 1 2 12>:8") - */ - [['vibmod', 'vib'], 'vmod'], - [['hcutoff', 'hresonance'], '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,hh*4").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,hh*4").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").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>").slow(2) - * - */ - ['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").delay(.25).delaytime("<.125 .25 .5 1>").slow(2) - * - */ - ['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 {@link room} and {@link 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 {@link begin}/{@link end} or {@link chop}/{@link 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'], - // 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'], - // label for pianoroll - ['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'], + /** + * 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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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("") + * .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,hh*4").hpf("<4000 2000 1000 500 200 100>") + * @example + * s("bd sd,hh*4").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") + * .vib("<.5 1 2 4 8 16>") + * @example + * // change the modulation depth with ":" + * note("a") + * .vib("<.5 1 2 4 8 16>:12") + */ + [['vib', 'vibmod'], 'vibrato', 'v'], + /** + * 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").vib(4) + * .vibmod("<.25 .5 1 2 12>") + * @example + * // change the vibrato frequency with ":" + * note("a") + * .vibmod("<.25 .5 1 2 12>:8") + */ + [['vibmod', 'vib'], 'vmod'], + [['hcutoff', 'hresonance'], '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,hh*4").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,hh*4").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").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>").slow(2) + * + */ + ['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").delay(.25).delaytime("<.125 .25 .5 1>").slow(2) + * + */ + ['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 {@link room} and {@link 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 {@link begin}/{@link end} or {@link chop}/{@link 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'], + // 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'], + // label for pianoroll + ['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*3").delay(.5).delaytime(.25).orbit(1), - * s("~ 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>") - * - */ - ['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 + // ['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*3").delay(.5).delaytime(.25).orbit(1), + * s("~ 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>") + * + */ + ['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 + // 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").room("<0 .2 .4 .6 .8 1>") - * @example - * s("bd sd").room("<0.9:1 0.9:4>") - * - */ - [['room', 'size']], - /** - * Sets the room size of the reverb, see {@link room}. - * - * @name roomsize - * @param {number | Pattern} size between 0 and 10 - * @synonyms size, sz - * @example - * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") - * - */ - // TODO: find out why : - // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() - // .. does not work. Is it because room is only one effect? - ['size', 'sz', 'roomsize'], - // ['sagogo'], - // ['sclap'], - // ['sclaves'], - // ['scrash'], + /** + * 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").room("<0 .2 .4 .6 .8 1>") + * @example + * s("bd sd").room("<0.9:1 0.9:4>") + * + */ + [['room', 'size']], + /** + * Sets the room size of the reverb, see {@link room}. + * + * @name roomsize + * @param {number | Pattern} size between 0 and 10 + * @synonyms size, sz + * @example + * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") + * + */ - /** - * Sets the sample to use as an impulse response for the reverb. - * - * @name iresponse - * @param {string | Pattern} Sets the impulse response - * @example - * s("bd sd").room(.8).ir("") - * - */ - [['ir', 'i'], 'iresponse'], - /** - * Wave shaping distortion. CAUTION: it might get loud - * - * @name shape - * @param {number | Pattern} distortion between 0 and 1 - * @example - * s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>") - * - */ - ['shape'], - /** - * 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").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 {@link 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. - * @example - * note("c2 >").s('sawtooth') - * .vowel(">") - * - */ - ['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'], - // 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'], - // TODO: doesn't seem to do anything - ['phaserdepth', 'phasdp'], - ['phaserrate', 'phasr'], + // TODO: find out why : + // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() + // .. does not work. Is it because room is only one effect? + ['size', 'sz', 'roomsize'], + // ['sagogo'], + // ['sclap'], + // ['sclaves'], + // ['scrash'], - ['fshift'], - ['fshiftnote'], - ['fshiftphase'], + /** + * Sets the sample to use as an impulse response for the reverb. + * + * @name iresponse + * @param {string | Pattern} Sets the impulse response + * @example + * s("bd sd").room(.8).ir("") + * + */ + [['ir', 'i'], 'iresponse'], - ['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'], + /** + * Wave shaping distortion. CAUTION: it might get loud + * + * @name shape + * @param {number | Pattern} distortion between 0 and 1 + * @example + * s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>") + * + */ + ['shape'], + /** + * 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").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 {@link 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. + * @example + * note("c2 >").s('sawtooth') + * .vowel(">") + * + */ + ['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'], + // 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'], + // TODO: doesn't seem to do anything + ['phaserdepth', 'phasdp'], + ['phaserrate', 'phasr'], - // ZZFX - ['zrand'], - ['curve'], - ['slide'], // superdirt duplicate - ['deltaSlide'], - ['pitchJump'], - ['pitchJumpTime'], - ['lfo', 'repeatTime'], - ['noise'], - ['zmod'], - ['zcrush'], // like crush but scaled differently - ['zdelay'], - ['tremolo'], - ['zzfx'], + ['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'], + ['noise'], + ['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; + 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}); - } + 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 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; + 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); + 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]; - }); + 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)}), {}); + names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {}); 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}); + adsr = !Array.isArray(adsr) ? [adsr] : adsr; + const [attack, decay, sustain, release] = adsr; + return pat.set({ attack, decay, sustain, release }); }); controls.ds = register('ds', (ds, pat) => { - ds = !Array.isArray(ds) ? [ds] : ds; - const [decay, sustain] = ds; - return pat.set({decay, sustain}); + ds = !Array.isArray(ds) ? [ds] : ds; + const [decay, sustain] = ds; + return pat.set({ decay, sustain }); }); export default controls; diff --git a/packages/superdough/reverb.mjs b/packages/superdough/reverb.mjs index 727090852..e72af033b 100644 --- a/packages/superdough/reverb.mjs +++ b/packages/superdough/reverb.mjs @@ -7,22 +7,31 @@ if (typeof AudioContext !== 'undefined') { return impulse; }; + AudioContext.prototype.adjustLength = function (duration, buffer) { + const newLength = buffer.sampleRate * duration; + const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate); + for (let channel = 0; channel < buffer.numberOfChannels; channel++) { + let oldData = buffer.getChannelData(channel); + let newData = newBuffer.getChannelData(channel); + + for (let i = 0; i < newLength; i++) { + newData[i] = oldData[i] || 0; + } + } + return newBuffer; + }; + AudioContext.prototype.createReverb = function (duration, buffer) { const convolver = this.createConvolver(); - convolver.setDuration = (d, i) => { - convolver.buffer = i !== undefined ? buffer : this.impulseResponse(d); - convolver.duration = d; + convolver.setDuration = (dur, imp) => { + convolver.buffer = imp ? this.adjustLength(dur, imp) : this.impulseResponse(dur); return convolver; }; - convolver.setIR = (i) => { - convolver.buffer = i; + convolver.setIR = (dur, imp) => { + convolver.buffer = imp ? this.adjustLength(dur, imp) : this.impulseResponse(dur); return convolver; }; - if (buffer !== undefined) { - convolver.setIR(buffer); - } else { - convolver.setDuration(duration); - } + convolver.setDuration(duration, buffer); return convolver; }; } diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 2b46ae3c3..b61c9568e 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -122,11 +122,11 @@ function getReverb(orbit, duration = 2, ir) { reverbs[orbit] = reverb; } if (reverbs[orbit].duration !== duration) { - reverbs[orbit] = reverbs[orbit].setDuration(duration); + reverbs[orbit] = reverbs[orbit].setDuration(duration, ir); reverbs[orbit].duration = duration; } if (reverbs[orbit].ir !== ir) { - reverbs[orbit] = reverbs[orbit].setIR(ir); + reverbs[orbit] = reverbs[orbit].setIR(duration, ir); reverbs[orbit].ir = ir; } return reverbs[orbit]; @@ -368,9 +368,14 @@ export const superdough = async (value, deadline, hapDuration) => { } // reverb let buffer; + let url; if (ir !== undefined) { let sample = getSound(ir); - let url = sample.data.samples[i % sample.data.samples.length]; + if (Array.isArray(sample)) { + url = sample.data.samples[i % sample.data.samples.length]; + } else if (typeof sample === 'object') { + url = Object.values(sample.data.samples)[i & Object.values(sample.data.samples).length]; + } buffer = await loadBuffer(url, ac, ir, 0); } let reverbSend; From ac9c629c0b271790b5b39d0efd5e4ac63480aa2a Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Sun, 1 Oct 2023 15:36:52 +0400 Subject: [PATCH 03/10] cleanup --- packages/superdough/superdough.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index b61c9568e..fdf42c02f 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -163,7 +163,7 @@ export function getAnalyzerData(type = 'time') { return analyserData; } -function effectSend(input, effect, wet, size) { +function effectSend(input, effect, wet) { const send = gainNode(wet); input.connect(send); send.connect(effect); @@ -381,7 +381,7 @@ export const superdough = async (value, deadline, hapDuration) => { let reverbSend; if (room > 0 && size > 0) { const reverbNode = getReverb(orbit, size, buffer); - reverbSend = effectSend(post, reverbNode, room, size); + reverbSend = effectSend(post, reverbNode, room); } // analyser From 13cb32903a8e44f2fb11a74a62a6776caf3d6624 Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Mon, 2 Oct 2023 12:13:44 +0400 Subject: [PATCH 04/10] Prepare to merge with PR #718 --- packages/core/controls.mjs | 66 +++++++-- packages/superdough/reverb.mjs | 51 +++++-- packages/superdough/reverbGen.mjs | 207 ++++++++++++++++++++++++++++ packages/superdough/superdough.mjs | 34 +++-- website/src/pages/learn/effects.mdx | 16 +++ 5 files changed, 339 insertions(+), 35 deletions(-) create mode 100644 packages/superdough/reverbGen.mjs diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index bc06ecc2b..d909423fd 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -980,26 +980,70 @@ const generic_params = [ * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") * */ - - // TODO: find out why : - // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() - // .. does not work. Is it because room is only one effect? - ['size', 'sz', 'roomsize'], - // ['sagogo'], - // ['sclap'], - // ['sclaves'], - // ['scrash'], - + /** + * Reverb lowpass starting frequency (in hertz). + * + * @name revlp + * @param {number} level between 0 and 20000hz + * @example + * s("bd sd").room(0.5).revlp(10000) + * @example + * s("bd sd").room(0.5).revlp(5000) + */ + ['revlp'], + /** + * Reverb lowpass frequency at -60dB (in hertz). + * + * @name revdim + * @param {number} level between 0 and 20000hz + * @example + * s("bd sd").room(0.5).revlp(10000).revdim(8000) + * @example + * s("bd sd").room(0.5).revlp(5000).revdim(400) + * + */ + ['revdim'], + /** + * Reverb fade time (in seconds). + * + * @name fade + * @param {number} seconds for the reverb to fade + * @example + * s("bd sd").room(0.5).revlp(10000).fade(0.5) + * @example + * s("bd sd").room(0.5).revlp(5000).fade(4) + * + */ + ['fade'], /** * Sets the sample to use as an impulse response for the reverb. * * @name iresponse - * @param {string | Pattern} Sets the impulse response + * @param {string | Pattern} sample sample to pick as an impulse response + * @synonyms ir * @example * s("bd sd").room(.8).ir("") * */ [['ir', 'i'], 'iresponse'], + /** + * Sets the room size of the reverb, see {@link room}. + * + * @name roomsize + * @param {number | Pattern} size between 0 and 10 + * @synonyms size, sz + * @example + * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") + * + */ + // TODO: find out why : + // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() + // .. does not work. Is it because room is only one effect? + ['size', 'sz', 'roomsize'], + // ['sagogo'], + // ['sclap'], + // ['sclaves'], + // ['scrash'], /** * Wave shaping distortion. CAUTION: it might get loud diff --git a/packages/superdough/reverb.mjs b/packages/superdough/reverb.mjs index e72af033b..5e4ce8f1b 100644 --- a/packages/superdough/reverb.mjs +++ b/packages/superdough/reverb.mjs @@ -1,11 +1,7 @@ +import reverbGen from './reverbGen.mjs'; + if (typeof AudioContext !== 'undefined') { - AudioContext.prototype.impulseResponse = function (duration, channels = 1) { - const length = this.sampleRate * duration; - const impulse = this.createBuffer(channels, length, this.sampleRate); - const IR = impulse.getChannelData(0); - for (let i = 0; i < length; i++) IR[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, duration); - return impulse; - }; + AudioContext.prototype.generateReverb = reverbGen.generateReverb; AudioContext.prototype.adjustLength = function (duration, buffer) { const newLength = buffer.sampleRate * duration; @@ -21,17 +17,44 @@ if (typeof AudioContext !== 'undefined') { return newBuffer; }; - AudioContext.prototype.createReverb = function (duration, buffer) { + AudioContext.prototype.createReverb = function (audioContext, duration, fade, revlp, revdim, imp) { const convolver = this.createConvolver(); - convolver.setDuration = (dur, imp) => { - convolver.buffer = imp ? this.adjustLength(dur, imp) : this.impulseResponse(dur); - return convolver; + + convolver.setDuration = (d, fade, revlp, revdim, imp) => { + if (imp) { + convolver.buffer = this.adjustLength(d, imp); + return convolver; + } else { + this.generateReverb( + { + audioContext, + sampleRate: 44100, + numChannels: 2, + decayTime: d, + fadeInTime: fade, + lpFreqStart: revlp, + lpFreqEnd: revdim, + }, + (buffer) => { + convolver.buffer = buffer; + }, + ); + convolver.duration = duration; + convolver.fade = fade; + convolver.revlp = revlp; + convolver.revdim = revdim; + return convolver; + } }; - convolver.setIR = (dur, imp) => { - convolver.buffer = imp ? this.adjustLength(dur, imp) : this.impulseResponse(dur); + convolver.setIR = (d, fade, revlp, revdim, imp) => { + if (imp) { + convolver.buffer = this.adjustLength(d, imp); + } else { + convolver.setDuration(d, fade, revlp, revdim, imp); + } return convolver; }; - convolver.setDuration(duration, buffer); + convolver.setDuration(duration, fade, revlp, revdim, imp); return convolver; }; } diff --git a/packages/superdough/reverbGen.mjs b/packages/superdough/reverbGen.mjs new file mode 100644 index 000000000..9fb461731 --- /dev/null +++ b/packages/superdough/reverbGen.mjs @@ -0,0 +1,207 @@ +// Copyright 2014 Alan deLespinasse +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var reverbGen = {}; + +/** Generates a reverb impulse response. + + @param {!Object} params TODO: Document the properties. + @param {!function(!AudioBuffer)} callback Function to call when + the impulse response has been generated. The impulse response + is passed to this function as its parameter. May be called + immediately within the current execution context, or later. */ +reverbGen.generateReverb = function (params, callback) { + var audioContext = params.audioContext || new AudioContext(); + var sampleRate = params.sampleRate || 44100; + var numChannels = params.numChannels || 2; + // params.decayTime is the -60dB fade time. We let it go 50% longer to get to -90dB. + var totalTime = params.decayTime * 1.5; + var decaySampleFrames = Math.round(params.decayTime * sampleRate); + var numSampleFrames = Math.round(totalTime * sampleRate); + var fadeInSampleFrames = Math.round((params.fadeInTime || 0) * sampleRate); + // 60dB is a factor of 1 million in power, or 1000 in amplitude. + var decayBase = Math.pow(1 / 1000, 1 / decaySampleFrames); + var reverbIR = audioContext.createBuffer(numChannels, numSampleFrames, sampleRate); + for (var i = 0; i < numChannels; i++) { + var chan = reverbIR.getChannelData(i); + for (var j = 0; j < numSampleFrames; j++) { + chan[j] = randomSample() * Math.pow(decayBase, j); + } + for (var j = 0; j < fadeInSampleFrames; j++) { + chan[j] *= j / fadeInSampleFrames; + } + } + + applyGradualLowpass(reverbIR, params.lpFreqStart || 0, params.lpFreqEnd || 0, params.decayTime, callback); +}; + +/** Creates a canvas element showing a graph of the given data. + + @param {!Float32Array} data An array of numbers, or a Float32Array. + @param {number} width Width in pixels of the canvas. + @param {number} height Height in pixels of the canvas. + @param {number} min Minimum value of data for the graph (lower edge). + @param {number} max Maximum value of data in the graph (upper edge). + @return {!CanvasElement} The generated canvas element. */ +reverbGen.generateGraph = function (data, width, height, min, max) { + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + var gc = canvas.getContext('2d'); + gc.fillStyle = '#000'; + gc.fillRect(0, 0, canvas.width, canvas.height); + gc.fillStyle = '#fff'; + var xscale = width / data.length; + var yscale = height / (max - min); + for (var i = 0; i < data.length; i++) { + gc.fillRect(i * xscale, height - (data[i] - min) * yscale, 1, 1); + } + return canvas; +}; + +/** Saves an AudioBuffer as a 16-bit WAV file on the client's host + file system. Normalizes it to peak at +-32767, and optionally + truncates it if there's a lot of "silence" at the end. + + @param {!AudioBuffer} buffer The buffer to save. + @param {string} name Name of file to create. + @param {number?} opt_minTail Defines what counts as "silence" for + auto-truncating the buffer. If there is a point past which every + value of every channel is less than opt_minTail, then the buffer + is truncated at that point. This is expressed as an integer, + applying to the post-normalized and integer-converted + buffer. The default is 0, meaning don't truncate. */ +reverbGen.saveWavFile = function (buffer, name, opt_minTail) { + var bitsPerSample = 16; + var bytesPerSample = 2; + var sampleRate = buffer.sampleRate; + var numChannels = buffer.numberOfChannels; + var channels = getAllChannelData(buffer); + var numSampleFrames = channels[0].length; + var scale = 32767; + // Find normalization constant. + var max = 0; + for (var i = 0; i < numChannels; i++) { + for (var j = 0; j < numSampleFrames; j++) { + max = Math.max(max, Math.abs(channels[i][j])); + } + } + if (max) { + scale = 32767 / max; + } + // Find truncation point. + if (opt_minTail) { + var truncateAt = 0; + for (var i = 0; i < numChannels; i++) { + for (var j = 0; j < numSampleFrames; j++) { + var absSample = Math.abs(Math.round(scale * channels[i][j])); + if (absSample > opt_minTail) { + truncateAt = j; + } + } + } + numSampleFrames = truncateAt + 1; + } + var sampleDataBytes = bytesPerSample * numChannels * numSampleFrames; + var fileBytes = sampleDataBytes + 44; + var arrayBuffer = new ArrayBuffer(fileBytes); + var dataView = new DataView(arrayBuffer); + dataView.setUint32(0, 1179011410, true); // "RIFF" + dataView.setUint32(4, fileBytes - 8, true); // file length + dataView.setUint32(8, 1163280727, true); // "WAVE" + dataView.setUint32(12, 544501094, true); // "fmt " + dataView.setUint32(16, 16, true); // fmt chunk length + dataView.setUint16(20, 1, true); // PCM format + dataView.setUint16(22, numChannels, true); // NumChannels + dataView.setUint32(24, sampleRate, true); // SampleRate + var bytesPerSampleFrame = numChannels * bytesPerSample; + dataView.setUint32(28, sampleRate * bytesPerSampleFrame, true); // ByteRate + dataView.setUint16(32, bytesPerSampleFrame, true); // BlockAlign + dataView.setUint16(34, bitsPerSample, true); // BitsPerSample + dataView.setUint32(36, 1635017060, true); // "data" + dataView.setUint32(40, sampleDataBytes, true); + for (var j = 0; j < numSampleFrames; j++) { + for (var i = 0; i < numChannels; i++) { + dataView.setInt16(44 + j * bytesPerSampleFrame + i * bytesPerSample, Math.round(scale * channels[i][j]), true); + } + } + var blob = new Blob([arrayBuffer], { type: 'audio/wav' }); + var url = window.URL.createObjectURL(blob); + var linkEl = document.createElement('a'); + linkEl.href = url; + linkEl.download = name; + linkEl.style.display = 'none'; + document.body.appendChild(linkEl); + linkEl.click(); +}; + +/** Applies a constantly changing lowpass filter to the given sound. + + @private + @param {!AudioBuffer} input + @param {number} lpFreqStart + @param {number} lpFreqEnd + @param {number} lpFreqEndAt + @param {!function(!AudioBuffer)} callback May be called + immediately within the current execution context, or later.*/ +var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) { + if (lpFreqStart == 0) { + callback(input); + return; + } + var channelData = getAllChannelData(input); + var context = new OfflineAudioContext(input.numberOfChannels, channelData[0].length, input.sampleRate); + var player = context.createBufferSource(); + player.buffer = input; + var filter = context.createBiquadFilter(); + + lpFreqStart = Math.min(lpFreqStart, input.sampleRate / 2); + lpFreqEnd = Math.min(lpFreqEnd, input.sampleRate / 2); + + filter.type = 'lowpass'; + filter.Q.value = 0.0001; + filter.frequency.setValueAtTime(lpFreqStart, 0); + filter.frequency.linearRampToValueAtTime(lpFreqEnd, lpFreqEndAt); + + player.connect(filter); + filter.connect(context.destination); + player.start(); + context.oncomplete = function (event) { + callback(event.renderedBuffer); + }; + context.startRendering(); + + window.filterNode = filter; +}; + +/** @private + @param {!AudioBuffer} buffer + @return {!Array.} An array containing the Float32Array of each channel's samples. */ +var getAllChannelData = function (buffer) { + var channels = []; + for (var i = 0; i < buffer.numberOfChannels; i++) { + channels[i] = buffer.getChannelData(i); + } + return channels; +}; + +/** @private + @return {number} A random number from -1 to 1. */ +var randomSample = function () { + return Math.random() * 2 - 1; +}; + +export default reverbGen; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index fdf42c02f..ec3b981ef 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -114,20 +114,31 @@ function getDelay(orbit, delaytime, delayfeedback, t) { let reverbs = {}; -function getReverb(orbit, duration = 2, ir) { +function getReverb(orbit, duration = 2, fade, revlp, revdim, imp) { if (!reverbs[orbit]) { const ac = getAudioContext(); - const reverb = ac.createReverb(duration, ir); + const reverb = ac.createReverb(getAudioContext(), duration, fade, revlp, revdim, imp); reverb.connect(getDestination()); reverbs[orbit] = reverb; } - if (reverbs[orbit].duration !== duration) { - reverbs[orbit] = reverbs[orbit].setDuration(duration, ir); + + const reverbOrbit = reverbs[orbit]; + + if ( + reverbs[orbit].duration !== duration || + reverbs[orbit].fade !== fade || + reverbs[orbit].revlp !== revlp || + reverbs[orbit].revdim !== revdim + ) { + reverbs[orbit].setDuration(duration, fade, revlp, revdim); reverbs[orbit].duration = duration; + reverbs[orbit].fade = fade; + reverbs[orbit].revlp = revlp; + reverbs[orbit].revdim = revdim; } - if (reverbs[orbit].ir !== ir) { - reverbs[orbit] = reverbs[orbit].setIR(duration, ir); - reverbs[orbit].ir = ir; + if (reverbs[orbit].ir !== imp) { + reverbs[orbit] = reverbs[orbit].setIR(duration, fade, revlp, revdim, imp); + reverbs[orbit].ir = imp; } return reverbs[orbit]; } @@ -227,12 +238,15 @@ export const superdough = async (value, deadline, hapDuration) => { delaytime = 0.25, orbit = 1, room, + fade = 0.1, + revlp = 15000, + revdim = 1000, size = 2, + ir, + i = 0, velocity = 1, analyze, // analyser wet fft = 8, // fftSize 0 - 10 - ir, - i = 0, } = value; gain *= velocity; // legacy fix for velocity let toDisconnect = []; // audio nodes that will be disconnected when the source has ended @@ -380,7 +394,7 @@ export const superdough = async (value, deadline, hapDuration) => { } let reverbSend; if (room > 0 && size > 0) { - const reverbNode = getReverb(orbit, size, buffer); + const reverbNode = getReverb(orbit, size, fade, revlp, revdim, buffer); reverbSend = effectSend(post, reverbNode, room); } diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index f77ab4c47..3ca43f698 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -203,4 +203,20 @@ global effects use the same chain for all events of the same orbit: +## fade + + + +## revlp + + + +## revdim + + + +## iresponse + + + Next, we'll look at strudel's support for [Csound](/learn/csound). From d6c8838fe26db2bf3b5db75c59145952e544f1ad Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Thu, 5 Oct 2023 03:49:50 +0400 Subject: [PATCH 05/10] Integrate with the new impulse generation functionality --- packages/core/controls.mjs | 71 +++++++++++++++-------------- packages/superdough/reverb.mjs | 34 ++++++-------- packages/superdough/superdough.mjs | 37 +++++++-------- website/src/pages/learn/effects.mdx | 28 +++++++----- 4 files changed, 86 insertions(+), 84 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index d909423fd..e23114ca1 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -655,6 +655,15 @@ const generic_params = [ * .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 * @@ -970,56 +979,50 @@ const generic_params = [ * */ [['room', 'size']], - /** - * Sets the room size of the reverb, see {@link room}. - * - * @name roomsize - * @param {number | Pattern} size between 0 and 10 - * @synonyms size, sz - * @example - * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") - * - */ /** * Reverb lowpass starting frequency (in hertz). + * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * - * @name revlp - * @param {number} level between 0 and 20000hz + * @name roomlp + * @synonyms rlp + * @param {number} frequency between 0 and 20000hz * @example - * s("bd sd").room(0.5).revlp(10000) + * s("bd sd").room(0.5).rlp(10000) * @example - * s("bd sd").room(0.5).revlp(5000) + * s("bd sd").room(0.5).rlp(5000) */ - ['revlp'], + ['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 revdim - * @param {number} level between 0 and 20000hz + * @name roomdim + * @synonyms rdim + * @param {number} frequency between 0 and 20000hz * @example - * s("bd sd").room(0.5).revlp(10000).revdim(8000) + * s("bd sd").room(0.5).rlp(10000).rdim(8000) * @example - * s("bd sd").room(0.5).revlp(5000).revdim(400) + * s("bd sd").room(0.5).rlp(5000).rdim(400) * */ - ['revdim'], + ['roomdim', 'rdim'], /** * Reverb fade time (in seconds). + * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * - * @name fade + * @name roomfade + * @synonyms rfade * @param {number} seconds for the reverb to fade * @example - * s("bd sd").room(0.5).revlp(10000).fade(0.5) + * s("bd sd").room(0.5).rlp(10000).rfade(0.5) * @example - * s("bd sd").room(0.5).revlp(5000).fade(4) + * s("bd sd").room(0.5).rlp(5000).rfade(4) * */ - ['fade'], + ['roomfade', 'rfade'], /** - * Sets the sample to use as an impulse response for the reverb. - * - * @name iresponse - * @param {string | Pattern} sample sample to pick as an impulse response + * 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").room(.8).ir("") @@ -1028,23 +1031,25 @@ const generic_params = [ [['ir', 'i'], 'iresponse'], /** * Sets the room size of the reverb, see {@link 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 size, sz + * @synonyms rsize, sz, size + * @example + * s("bd sd").room(.8).rsize(1) * @example - * s("bd sd").room(.8).roomsize("<0 1 2 4 8>") + * s("bd sd").room(.8).rsize(4) * */ // TODO: find out why : // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() // .. does not work. Is it because room is only one effect? - ['size', 'sz', 'roomsize'], + ['roomsize', 'size', 'sz', 'rsize'], // ['sagogo'], // ['sclap'], // ['sclaves'], // ['scrash'], - /** * Wave shaping distortion. CAUTION: it might get loud * @@ -1210,7 +1215,7 @@ const generic_params = [ ['pitchJump'], ['pitchJumpTime'], ['lfo', 'repeatTime'], - ['noise'], + ['znoise'], // noise on the frequency or as bubo calls it "frequency fog" :) ['zmod'], ['zcrush'], // like crush but scaled differently ['zdelay'], diff --git a/packages/superdough/reverb.mjs b/packages/superdough/reverb.mjs index 5e4ce8f1b..85be19aee 100644 --- a/packages/superdough/reverb.mjs +++ b/packages/superdough/reverb.mjs @@ -17,47 +17,43 @@ if (typeof AudioContext !== 'undefined') { return newBuffer; }; - AudioContext.prototype.createReverb = function (audioContext, duration, fade, revlp, revdim, imp) { + AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir) { const convolver = this.createConvolver(); - - convolver.setDuration = (d, fade, revlp, revdim, imp) => { - if (imp) { - convolver.buffer = this.adjustLength(d, imp); + convolver.generate = (d, fade, lp, dim, buf) => { + if (buf) { + convolver.buffer = this.adjustLength(d, buf); return convolver; } else { this.generateReverb( { - audioContext, + audioContext: this, sampleRate: 44100, numChannels: 2, decayTime: d, fadeInTime: fade, - lpFreqStart: revlp, - lpFreqEnd: revdim, + lpFreqStart: lp, + lpFreqEnd: dim, }, (buffer) => { convolver.buffer = buffer; }, ); - convolver.duration = duration; + convolver.duration = d; convolver.fade = fade; - convolver.revlp = revlp; - convolver.revdim = revdim; + convolver.lp = lp; + convolver.dim = dim; return convolver; } }; - convolver.setIR = (d, fade, revlp, revdim, imp) => { - if (imp) { - convolver.buffer = this.adjustLength(d, imp); + convolver.setIR = (d, fade, lp, dim, buf) => { + if (buf) { + convolver.buffer = this.adjustLength(d, buf); } else { - convolver.setDuration(d, fade, revlp, revdim, imp); + convolver.generate(d, fade, lp, dim, buf); } return convolver; }; - convolver.setDuration(duration, fade, revlp, revdim, imp); + convolver.generate(duration, fade, lp, dim, ir); return convolver; }; } - -// TODO: make the reverb more exciting -// check out https://blog.gskinner.com/archives/2019/02/reverb-web-audio-api.html diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index ec3b981ef..d7f0b14b7 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -114,32 +114,29 @@ function getDelay(orbit, delaytime, delayfeedback, t) { let reverbs = {}; -function getReverb(orbit, duration = 2, fade, revlp, revdim, imp) { +function getReverb(orbit, duration = 2, fade, lp, dim, ir) { if (!reverbs[orbit]) { const ac = getAudioContext(); - const reverb = ac.createReverb(getAudioContext(), duration, fade, revlp, revdim, imp); + const reverb = ac.createReverb(duration, fade, lp, dim, ir); reverb.connect(getDestination()); reverbs[orbit] = reverb; } - const reverbOrbit = reverbs[orbit]; - if ( reverbs[orbit].duration !== duration || reverbs[orbit].fade !== fade || - reverbs[orbit].revlp !== revlp || - reverbs[orbit].revdim !== revdim + reverbs[orbit].ir !== lp || + reverbs[orbit].dim !== dim || + reverbs[orbit].ir !== ir ) { - reverbs[orbit].setDuration(duration, fade, revlp, revdim); - reverbs[orbit].duration = duration; - reverbs[orbit].fade = fade; - reverbs[orbit].revlp = revlp; - reverbs[orbit].revdim = revdim; + reverbs[orbit].generate(duration, fade, lp, dim, ir); } - if (reverbs[orbit].ir !== imp) { - reverbs[orbit] = reverbs[orbit].setIR(duration, fade, revlp, revdim, imp); - reverbs[orbit].ir = imp; + + if (reverbs[orbit].ir !== ir) { + reverbs[orbit] = reverbs[orbit].setIR(duration, fade, lp, dim, ir); + reverbs[orbit].ir = ir; } + return reverbs[orbit]; } @@ -238,10 +235,10 @@ export const superdough = async (value, deadline, hapDuration) => { delaytime = 0.25, orbit = 1, room, - fade = 0.1, - revlp = 15000, - revdim = 1000, - size = 2, + roomfade = 0.1, + roomlp = 15000, + roomdim = 1000, + roomsize = 2, ir, i = 0, velocity = 1, @@ -393,8 +390,8 @@ export const superdough = async (value, deadline, hapDuration) => { buffer = await loadBuffer(url, ac, ir, 0); } let reverbSend; - if (room > 0 && size > 0) { - const reverbNode = getReverb(orbit, size, fade, revlp, revdim, buffer); + if (room > 0 && roomsize > 0) { + const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, buffer); reverbSend = effectSend(post, reverbNode, room); } diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index 3ca43f698..350527889 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -183,39 +183,43 @@ global effects use the same chain for all events of the same orbit: -## delay +## Delay + +### delay -## delaytime +### delaytime -## delayfeedback +### delayfeedback -## room +## Reverb + +### room -## roomsize +### roomsize -## fade +### roomfade - + -## revlp +### roomlp - + -## revdim +### roomdim - + -## iresponse +### iresponse From 1d4eb59e942687313c5f14c03ac771618379c5ac Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Thu, 5 Oct 2023 04:10:35 +0400 Subject: [PATCH 06/10] fix: reverbGen.mjs --- packages/superdough/reverbGen.mjs | 78 ------------------------------- 1 file changed, 78 deletions(-) diff --git a/packages/superdough/reverbGen.mjs b/packages/superdough/reverbGen.mjs index 9fb461731..d8024ccbe 100644 --- a/packages/superdough/reverbGen.mjs +++ b/packages/superdough/reverbGen.mjs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -'use strict'; - var reverbGen = {}; /** Generates a reverb impulse response. @@ -72,82 +70,6 @@ reverbGen.generateGraph = function (data, width, height, min, max) { return canvas; }; -/** Saves an AudioBuffer as a 16-bit WAV file on the client's host - file system. Normalizes it to peak at +-32767, and optionally - truncates it if there's a lot of "silence" at the end. - - @param {!AudioBuffer} buffer The buffer to save. - @param {string} name Name of file to create. - @param {number?} opt_minTail Defines what counts as "silence" for - auto-truncating the buffer. If there is a point past which every - value of every channel is less than opt_minTail, then the buffer - is truncated at that point. This is expressed as an integer, - applying to the post-normalized and integer-converted - buffer. The default is 0, meaning don't truncate. */ -reverbGen.saveWavFile = function (buffer, name, opt_minTail) { - var bitsPerSample = 16; - var bytesPerSample = 2; - var sampleRate = buffer.sampleRate; - var numChannels = buffer.numberOfChannels; - var channels = getAllChannelData(buffer); - var numSampleFrames = channels[0].length; - var scale = 32767; - // Find normalization constant. - var max = 0; - for (var i = 0; i < numChannels; i++) { - for (var j = 0; j < numSampleFrames; j++) { - max = Math.max(max, Math.abs(channels[i][j])); - } - } - if (max) { - scale = 32767 / max; - } - // Find truncation point. - if (opt_minTail) { - var truncateAt = 0; - for (var i = 0; i < numChannels; i++) { - for (var j = 0; j < numSampleFrames; j++) { - var absSample = Math.abs(Math.round(scale * channels[i][j])); - if (absSample > opt_minTail) { - truncateAt = j; - } - } - } - numSampleFrames = truncateAt + 1; - } - var sampleDataBytes = bytesPerSample * numChannels * numSampleFrames; - var fileBytes = sampleDataBytes + 44; - var arrayBuffer = new ArrayBuffer(fileBytes); - var dataView = new DataView(arrayBuffer); - dataView.setUint32(0, 1179011410, true); // "RIFF" - dataView.setUint32(4, fileBytes - 8, true); // file length - dataView.setUint32(8, 1163280727, true); // "WAVE" - dataView.setUint32(12, 544501094, true); // "fmt " - dataView.setUint32(16, 16, true); // fmt chunk length - dataView.setUint16(20, 1, true); // PCM format - dataView.setUint16(22, numChannels, true); // NumChannels - dataView.setUint32(24, sampleRate, true); // SampleRate - var bytesPerSampleFrame = numChannels * bytesPerSample; - dataView.setUint32(28, sampleRate * bytesPerSampleFrame, true); // ByteRate - dataView.setUint16(32, bytesPerSampleFrame, true); // BlockAlign - dataView.setUint16(34, bitsPerSample, true); // BitsPerSample - dataView.setUint32(36, 1635017060, true); // "data" - dataView.setUint32(40, sampleDataBytes, true); - for (var j = 0; j < numSampleFrames; j++) { - for (var i = 0; i < numChannels; i++) { - dataView.setInt16(44 + j * bytesPerSampleFrame + i * bytesPerSample, Math.round(scale * channels[i][j]), true); - } - } - var blob = new Blob([arrayBuffer], { type: 'audio/wav' }); - var url = window.URL.createObjectURL(blob); - var linkEl = document.createElement('a'); - linkEl.href = url; - linkEl.download = name; - linkEl.style.display = 'none'; - document.body.appendChild(linkEl); - linkEl.click(); -}; - /** Applies a constantly changing lowpass filter to the given sound. @private From 2f0798de3267556f275598d84c38a86d35d6cf7d Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Thu, 5 Oct 2023 13:17:17 +0400 Subject: [PATCH 07/10] fix: formatting --- packages/superdough/superdough.mjs | 1 - website/src/pages/learn/effects.mdx | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index d74e3182b..d7f0b14b7 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -114,7 +114,6 @@ function getDelay(orbit, delaytime, delayfeedback, t) { let reverbs = {}; - function getReverb(orbit, duration = 2, fade, lp, dim, ir) { if (!reverbs[orbit]) { const ac = getAudioContext(); diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index e2a0de8be..350527889 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -219,7 +219,6 @@ global effects use the same chain for all events of the same orbit: - ### iresponse From b25b9d815b87461a107da22f0b662a4c12fc6af2 Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Sat, 7 Oct 2023 19:30:07 +0400 Subject: [PATCH 08/10] fix: conflicts --- packages/superdough/reverb.mjs | 4 +--- packages/superdough/superdough.mjs | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/superdough/reverb.mjs b/packages/superdough/reverb.mjs index 85be19aee..f853d3fbc 100644 --- a/packages/superdough/reverb.mjs +++ b/packages/superdough/reverb.mjs @@ -19,10 +19,9 @@ if (typeof AudioContext !== 'undefined') { AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir) { const convolver = this.createConvolver(); - convolver.generate = (d, fade, lp, dim, buf) => { + convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, buf) => { if (buf) { convolver.buffer = this.adjustLength(d, buf); - return convolver; } else { this.generateReverb( { @@ -42,7 +41,6 @@ if (typeof AudioContext !== 'undefined') { convolver.fade = fade; convolver.lp = lp; convolver.dim = dim; - return convolver; } }; convolver.setIR = (d, fade, lp, dim, buf) => { diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index d7f0b14b7..20edbe331 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -114,6 +114,8 @@ function getDelay(orbit, delaytime, delayfeedback, t) { let reverbs = {}; +let hasChanged = (now, before) => now !== undefined && now !== before; + function getReverb(orbit, duration = 2, fade, lp, dim, ir) { if (!reverbs[orbit]) { const ac = getAudioContext(); @@ -123,13 +125,17 @@ function getReverb(orbit, duration = 2, fade, lp, dim, ir) { } if ( - reverbs[orbit].duration !== duration || - reverbs[orbit].fade !== fade || - reverbs[orbit].ir !== lp || - reverbs[orbit].dim !== dim || - reverbs[orbit].ir !== ir + hasChanged(duration, reverbs[orbit].duration) || + hasChanged(fade, reverbs[orbit].fade) || + hasChanged(lp, reverbs[orbit].lp) || + hasChanged(dim, reverbs[orbit].dim) ) { - reverbs[orbit].generate(duration, fade, lp, dim, ir); + // only regenerate when something has changed + // avoids endless regeneration on things like + // stack(s("a"), s("b").rsize(8)).room(.5) + // this only works when args may stay undefined until here + // setting default values breaks this + reverbs[orbit].generate(duration, fade, lp, dim); } if (reverbs[orbit].ir !== ir) { @@ -235,10 +241,10 @@ export const superdough = async (value, deadline, hapDuration) => { delaytime = 0.25, orbit = 1, room, - roomfade = 0.1, - roomlp = 15000, - roomdim = 1000, - roomsize = 2, + roomfade, + roomlp, + roomdim, + roomsize, ir, i = 0, velocity = 1, From 2d750a14b9bd5da47a86aada47b0f5d5836aca8c Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Sat, 7 Oct 2023 19:48:07 +0400 Subject: [PATCH 09/10] fix: conflicts --- packages/superdough/superdough.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 20edbe331..d25076723 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -116,7 +116,8 @@ let reverbs = {}; let hasChanged = (now, before) => now !== undefined && now !== before; -function getReverb(orbit, duration = 2, fade, lp, dim, ir) { +function getReverb(orbit, duration, fade, lp, dim, ir) { + // If no reverb has been created for a given orbit, create one if (!reverbs[orbit]) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir); From 57b166d13a9523350d42c5e682b0233678365816 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Oct 2023 22:49:38 +0200 Subject: [PATCH 10/10] fix: pitched sample as ir --- packages/superdough/superdough.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 1d615548d..e3033afe1 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -399,7 +399,7 @@ export const superdough = async (value, deadline, hapDuration) => { if (Array.isArray(sample)) { url = sample.data.samples[i % sample.data.samples.length]; } else if (typeof sample === 'object') { - url = Object.values(sample.data.samples)[i & Object.values(sample.data.samples).length]; + url = Object.values(sample.data.samples).flat()[i % Object.values(sample.data.samples).length]; } roomIR = await loadBuffer(url, ac, ir, 0); }