From 73965a3a39e5aef8d6b310d5ca7f9b3b9fa81382 Mon Sep 17 00:00:00 2001 From: robmckinnon Date: Sat, 28 Jun 2025 18:24:05 +0100 Subject: [PATCH 001/200] Add packages/edo for Equal Division of the Octave scales --- packages/edo/README.md | 41 ++++++ packages/edo/edo.mjs | 75 ++++++++++ packages/edo/edoscale.mjs | 213 ++++++++++++++++++++++++++++ packages/edo/index.mjs | 5 + packages/edo/intervals.mjs | 103 ++++++++++++++ packages/edo/package.json | 42 ++++++ packages/edo/pitches.mjs | 98 +++++++++++++ packages/edo/ratios.mjs | 95 +++++++++++++ packages/edo/test/edo.test.mjs | 34 +++++ packages/edo/test/edoscale.test.mjs | 21 +++ packages/edo/test/ratios.test.mjs | 18 +++ packages/edo/vite.config.js | 19 +++ 12 files changed, 764 insertions(+) create mode 100644 packages/edo/README.md create mode 100644 packages/edo/edo.mjs create mode 100644 packages/edo/edoscale.mjs create mode 100644 packages/edo/index.mjs create mode 100644 packages/edo/intervals.mjs create mode 100644 packages/edo/package.json create mode 100644 packages/edo/pitches.mjs create mode 100644 packages/edo/ratios.mjs create mode 100644 packages/edo/test/edo.test.mjs create mode 100644 packages/edo/test/edoscale.test.mjs create mode 100644 packages/edo/test/ratios.test.mjs create mode 100644 packages/edo/vite.config.js diff --git a/packages/edo/README.md b/packages/edo/README.md new file mode 100644 index 00000000..482ad9c1 --- /dev/null +++ b/packages/edo/README.md @@ -0,0 +1,41 @@ +# @strudel/edo + +This package adds EDO scale functions to strudel Patterns. + +## Install + +```sh +npm i @strudel/edo --save +``` + +## Example + +```js +import { n } from '@strudel/core'; +import '@strudel/edo'; + +// E.g. edoScale for Gorgo-6 scale, 16 EDO, LLsLLLs +// base note C3, large step size 3, small step size 1: +// C3:LLsLLL:3:1 +const [baseNote, sequence, largeStep, smallStep] = ['C3', 'LLsLLL', 3, 1] +const pattern = n("0 2 4 6 4 2").edoScale([baseNote, sequence, largeStep, smallStep]); + +const events = pattern.firstCycle().map((e) => e.show()); +console.log(events); +``` + +yields: +``` +[ + "[ 0/1 → 1/1 | + { + \"degree\":1, + \"degreeIndexes\":[0,3,6,7,10,13], + \"intLabels\":[null,\"S2\",\"d4\",\"N4\",\"s6\",\"s7\",\"P8\"], + \"root\":\"130.8128\", + \"freq\":130.813, + \"edo\":16 + } + ]" +] +``` diff --git a/packages/edo/edo.mjs b/packages/edo/edo.mjs new file mode 100644 index 00000000..22e44be6 --- /dev/null +++ b/packages/edo/edo.mjs @@ -0,0 +1,75 @@ +/* +edo.mjs - Equal division of the octave (EDO) scale functions for strudel +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ + +import { register, pure, noteToMidi, isNote, tokenizeNote } from '@strudel/core'; +import { EdoScale } from './edoscale.mjs'; +import { Intervals } from './intervals.mjs'; +import { Pitches } from './pitches.mjs'; + +const pitchesCache = new Map(); + +export const edoScale = register( + 'edoScale', + function (scaleDefinition, pat) { + // console.log(scaleDefinition); + + // if (Array.isArray(scale)) { + const key = scaleDefinition.flat().join(':'); + // } + // console.log(scaleDefinition); + let pitches; + if (pitchesCache.has(key)) { + pitches = pitchesCache.get(key); + } else { + // console.log({ key }); + const [base_note, sequence, large, small] = scaleDefinition; + const root_octave = tokenizeNote(base_note)[2] || 3; + // console.log({ root_octave }); + const scale = new EdoScale(large, small, sequence); + const intervals = new Intervals(scale); + // console.log({ intervals }); + pitches = new Pitches(scale, intervals, 440, noteToMidi(base_note), root_octave); + pitchesCache.set(key, pitches); + // console.log({ base_note: noteToMidi(base_note) }); + // console.log({ sequence }); + // console.log({ edivisions: scale.edivisions }); + // console.log({ intervals }); + // console.log({ pat }); + // console.log({ pitches: pitches }); + } + return pat + .fmap((value) => { + const isObject = typeof value === 'object'; + const n = isObject ? value.n : value; + if (isObject) { + delete value.n; // remove n so it won't cause trouble + } + if (isNote(n)) { + // legacy.. + return pure(n); + } + const deg = (typeof n === 'string' ? parseInt(n, 10) : n) + 1; + + const [oct, degree] = pitches.octdeg(deg); + const freq = pitches.octdegfreq(oct, degree); + const note = pitches.octdegmidi(oct, degree); + const edo = pitches.scale.edivisions; + const root = pitches.base_freq; + const degreeIndexes = pitches.scale.divisions; + const intLabels = pitches.intervals.intLabels; + // const color = 'red'; + value = pure(isObject ? { ...value, degree, degreeIndexes, intLabels, root, freq, edo } : note); + // value = pure(isObject ? { ...value, key } : note); + // value = pure(isObject ? { ...value, edo } : note); + // console.log({ value }); + return value; + }) + .outerJoin() + .withHap((hap) => hap.setContext({ ...hap.context, scaleDefinition })); + }, + true, + true, // preserve tactus +); diff --git a/packages/edo/edoscale.mjs b/packages/edo/edoscale.mjs new file mode 100644 index 00000000..03205408 --- /dev/null +++ b/packages/edo/edoscale.mjs @@ -0,0 +1,213 @@ +/* +edoscale.mjs - EdoScale defines equal division of the octave (EDO) scale in Ls notation + - Port of pitfalls/lib/Scale.lua - see +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ +const M = 2; +const L = 1; +const S = 0; +const LABELS = ['s', 'L', 'M']; + +export class EdoScale { + constructor(large, small, sequence, medium) { + this.stepbackup = [L, L, S, L, L, L, S, L, L, L, S, L, L, L, S, L]; + this.large = large; + this.medium = medium || large; + this.small = small; + this.divisions = []; + this.edivisions = null; + this.sequence = null; + this.tonic = 1; + this.mode = 1; + this.max_steps = 12; + this.min_steps = 3; + this.setSequence(sequence); + } + + hasMedium() { + return this.step.some((_, i) => this.step[this.offset(i)] === M); + } + + stepSize(i) { + return LABELS[this.step[this.offset(i)]]; + } + + sequence() { + return this.step.map((_, i) => this.stepSize(i)).join(''); + } + + stepValue(i) { + const step = this.step[this.offset(i)]; + return step === L ? this.large : step === M ? this.medium : this.small; + } + + offset(i) { + if (this.mode === 1) { + return i; + } else { + const offset = (this.mode - 1 + i) % this.length; + return offset === 0 ? this.length : offset; + } + } + + static setMaxSteps(max) { + this.max_steps = max; + } + + static setMinSteps(min) { + this.min_steps = min; + } + + setSequence(sequence) { + if (this.sequence !== sequence) { + this.sequence = sequence; + this.length = sequence.length; + this.step = []; + for (let i = 0; i < sequence.length; i++) { + const char = sequence[i]; + this.step[i] = char === 'L' ? L : char === 'M' ? M : S; + } + this.updateEdo(); + } else { + return false; + } + } + + setLarge(l) { + if (this.large !== l) { + this.large = l; + this.updateEdo(); + } else { + return false; + } + } + + setMedium(m) { + if (this.medium !== m) { + this.medium = m; + this.updateEdo(); + } else { + return false; + } + } + + setSmall(s) { + if (this.small !== s) { + this.small = s; + this.updateEdo(); + } else { + return false; + } + } + + setMode(mode) { + this.mode = mode; + } + + setTonic(tonic) { + this.tonic = tonic; + } + + changeMode(d) { + const orig = this.mode; + this.mode = Math.max(1, Math.min(this.mode + d, this.length)); + return orig !== this.mode; + } + + changeTonic(d) { + const orig = this.tonic; + this.tonic = Math.max(1, Math.min(this.tonic + d, this.edivisions)); + return orig !== this.tonic; + } + + updateEdo() { + const orig = this.edivisions; + this.edivisions = this.step.reduce((sum, _, i) => { + this.divisions[i] = sum; + return sum + this.stepValue(i); + }, 0); + // console.log(this.divisions); + const changed = orig !== this.edivisions; + if (changed) { + this.tonic = Math.max(1, Math.min(this.tonic, this.edivisions)); + } + return changed; + } + + changeStep(d, i) { + const index = this.offset(i); + const orig = this.step[index]; + this.step[index] = Math.max(S, Math.min(this.step[index] + d, M)); + this.stepbackup[index] = this.step[index]; + const changed = orig !== this.step[index]; + if (changed) { + this.updateEdo(); + } + return changed; + } + + changeLarge(d) { + const orig = this.large; + this.setLarge(Math.max(this.small + 1, Math.min(this.large + d, this.large + 1))); + const changed = this.large !== orig; + if (changed) { + if (this.large <= this.medium) { + this.setMedium(Math.max(this.small + 1, Math.min(this.large - 1, this.large))); + } + this.updateEdo(); + } + return changed; + } + + changeMedium(d) { + const orig = this.medium; + this.setMedium(Math.max(this.small + 1, Math.min(this.medium + d, this.large - 1))); + const changed = this.medium !== orig; + if (changed) { + this.updateEdo(); + } + return changed; + } + + changeSmall(d) { + const orig = this.small; + const value = this.small + d; + + if (this.hasMedium()) { + this.setSmall(Math.max(1, Math.min(value, this.medium - 1))); + } else { + this.setSmall(Math.max(1, Math.min(value, this.large - 1))); + } + + const changed = this.small !== orig; + if (changed) { + if (this.small >= this.medium) { + this.setMedium(Math.max(this.small + 1, Math.min(this.large))); + } + this.updateEdo(); + } + return changed; + } + + changeLength(d) { + const orig = this.length; + if (d === 1) { + this.length = Math.min(this.length + 1, this.max_steps); + } else if (d === -1) { + this.length = Math.max(this.length - 1, this.min_steps); + } + + const changed = this.length !== orig; + if (changed) { + this.mode = 1; + if (d === 1) { + this.step[this.length] = this.stepbackup[this.length] || L; + } else if (d === -1 && this.length >= this.min_steps) { + this.step.pop(); + } + this.updateEdo(); + } + return changed; + } +} diff --git a/packages/edo/index.mjs b/packages/edo/index.mjs new file mode 100644 index 00000000..0bd5a41a --- /dev/null +++ b/packages/edo/index.mjs @@ -0,0 +1,5 @@ +import './edo.mjs'; + +export * from './edo.mjs'; + +export const packageName = '@strudel/edo'; diff --git a/packages/edo/intervals.mjs b/packages/edo/intervals.mjs new file mode 100644 index 00000000..e79afe3d --- /dev/null +++ b/packages/edo/intervals.mjs @@ -0,0 +1,103 @@ +/* +intervals.mjs - defines Intervals for equal division of the octave (EDO) scale + - Port of pitfalls/lib/Intervals.lua - see +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ +import ratiointervals from './ratios.mjs'; + +function ratio(division, edivisions) { + return division === 0 ? 1 : Math.pow(2, division / edivisions); +} + +export class Intervals { + constructor(scale) { + this.scale = scale; + this.intLabels = []; + this.intNoms = []; + this.intRatios = []; + this.uniqLabels = []; + this.intErrors = []; + this.ratios = []; + + const BLANK = ''; + let division = 0; + const labToErr = {}; + const labToInd = {}; + + this.ratios[0] = 1; + + for (let i = 0; i < scale.length; i++) { + division += scale.stepValue(i); + this.ratios[i + 1] = ratio(division, scale.edivisions); + + if (i < scale.length) { + const nearest = ratiointervals.nearestInterval(this.ratios[i + 1]); + const closeness = nearest[0]; + const ratio = nearest[1]; + const intLabel = ratiointervals.key(ratio); + this.intLabels[i + 1] = intLabel; + this.intErrors[i + 1] = closeness; + this.intNoms[i + 1] = ratio ? ratiointervals.nom(ratio) : 0; + this.intRatios[i + 1] = ratio ? `${ratiointervals.nom(ratio)}/${ratiointervals.denom(ratio)}` : ''; + this.uniqLabels[i + 1] = BLANK; + + if (intLabel && intLabel !== 'P1' && intLabel !== 'P8') { + if (!labToErr[intLabel]) { + this.uniqLabels[i + 1] = intLabel; + labToInd[intLabel] = i + 1; + labToErr[intLabel] = closeness; + } else if (closeness < labToErr[intLabel]) { + this.uniqLabels[labToInd[intLabel]] = BLANK; + this.uniqLabels[i + 1] = intLabel; + labToInd[intLabel] = i + 1; + labToErr[intLabel] = closeness; + } + } + } + } + } + + ratio(i) { + return this.ratios[i]; + } + + intervalLabel(i) { + return this.intLabels[i]; + } + + intervalNominator(i) { + return this.intNoms[i]; + } + + intervalRatio(i) { + return this.intRatios[i]; + } + + uniqIntervalLabel(i) { + return this.uniqLabels[i]; + } + + intervalError(i) { + return this.intErrors[i]; + } + + nearestDegreeTo(r, threshold) { + let min = 1; + let degree = null; + + for (const [i, v] of Object.entries(this.ratios)) { + const diff = Math.abs((r - v) / r); + if (diff < min) { + min = diff; + degree = parseInt(i, 10); + } + } + + if (threshold == null) { + return degree; + } else { + return min < threshold ? degree : 1; + } + } +} diff --git a/packages/edo/package.json b/packages/edo/package.json new file mode 100644 index 00000000..42097279 --- /dev/null +++ b/packages/edo/package.json @@ -0,0 +1,42 @@ +{ + "name": "@strudel/edo", + "version": "0.1.0", + "description": "Equal division of the octave (EDO) scale functions for strudel", + "main": "index.mjs", + "publishConfig": { + "main": "dist/index.mjs" + }, + "scripts": { + "build": "vite build", + "test": "vitest run", + "prepublishOnly": "npm run build" + }, + "type": "module", + "repository": { + "type": "git", + "url": "git+https://codeberg.org/uzu/strudel.git" + }, + "keywords": [ + "tidalcycles", + "strudel", + "pattern", + "livecoding", + "algorave" + ], + "author": "Rob McKinnon ", + "license": "AGPL-3.0-or-later", + "bugs": { + "url": "https://codeberg.org/uzu/strudel/issues" + }, + "homepage": "https://codeberg.org/uzu/strudel#readme", + "dependencies": { + "@strudel/core": "workspace:*", + "@tonaljs/tonal": "^4.10.0", + "chord-voicings": "^0.0.1", + "webmidi": "^3.1.12" + }, + "devDependencies": { + "vite": "^6.0.11", + "vitest": "^3.0.4" + } +} diff --git a/packages/edo/pitches.mjs b/packages/edo/pitches.mjs new file mode 100644 index 00000000..1cd5187b --- /dev/null +++ b/packages/edo/pitches.mjs @@ -0,0 +1,98 @@ +/* +pitches.mjs - defines Pitches for equal division of the octave (EDO) scale + - Port of pitfalls/lib/Pitches.lua - see +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ + +function ratio(division, edivisions) { + return division === 0 ? 1 : Math.pow(2, division / edivisions); +} + +// This function gets frequency based on index, edo, octave, and base frequency +function get_freq(base_freq, edo, index, oct, base_octave) { + let f = base_freq * ratio(index - 1, edo); + if (oct < base_octave) { + f /= Math.pow(2, base_octave - oct); + } else if (oct > base_octave) { + f *= Math.pow(2, oct - base_octave); + } + return f; +} + +// Convert MIDI number to Hz using a given tuning multiplier +function midi_to_hz(n, tuning) { + return tuning * Math.pow(2, (n - 69) / 12.0); +} + +const denom = Math.log(2); +function hz_to_midi(freq, tuning) { + return 12.0 * (Math.log(freq / tuning) / denom) + 69; +} + +export class Pitches { + constructor(scale, intervals, tuning, midi_start, root_octave) { + this.scale = scale; + this.intervals = intervals; + this.base_freq = midi_to_hz(midi_start, tuning); + this.root_octave = root_octave; + this.freqs = {}; + this.midis = {}; + this.degrees = {}; + this.octdegfreqs = {}; + this.octdegmidis = {}; + let index = 0; + let f = null; + + for (let oct = 0; oct <= 8; oct++) { + this.octdegfreqs[oct] = {}; + this.octdegmidis[oct] = {}; + f = get_freq(this.base_freq, scale.edivisions, scale.tonic, oct, this.root_octave); + for (let deg = 0; deg < scale.length; deg++) { + index = index + 1; + this.freqs[index] = parseFloat((f * intervals.ratio(deg)).toFixed(3)); + this.midis[index] = parseFloat(hz_to_midi(f * intervals.ratio(deg), tuning).toFixed(4)); + this.degrees[index] = deg; + this.octdegfreqs[oct][deg + 1] = this.freqs[index]; + this.octdegmidis[oct][deg + 1] = this.midis[index]; + } + } + this.base_freq = parseFloat(this.base_freq).toFixed(4); + } + + base_freq() { + return this.base_freq; + } + + degree(index) { + return this.degrees[index]; + } + + freq(index) { + return this.freqs[index]; + } + + octdeg(deg) { + const higherOcatve = deg > this.scale.length; + const octave = this.root_octave + (higherOcatve ? Math.floor((deg - 1) / this.scale.length) : 0); + const degree = higherOcatve ? (deg % this.scale.length === 0 ? this.scale.length : deg % this.scale.length) : deg; + console.log([octave, degree]); + return [octave, degree]; + } + + octdegfreq(oct, deg) { + if (this.octdegfreqs[oct]) { + return this.octdegfreqs[oct][deg]; + } else { + return null; + } + } + + octdegmidi(oct, deg) { + if (this.octdegmidis[oct]) { + return this.octdegmidis[oct][deg]; + } else { + return null; + } + } +} diff --git a/packages/edo/ratios.mjs b/packages/edo/ratios.mjs new file mode 100644 index 00000000..b431a237 --- /dev/null +++ b/packages/edo/ratios.mjs @@ -0,0 +1,95 @@ +/* +ratios.mjs - lists whole number ratios for pitch intervals + - Port of pitfalls/lib/ratios.lua - see +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ +const ratiointervals = {}; + +// FJS Calculators - https://misotanni.github.io/fjs/en/calc.html +// List of pitch intervals - https://en.wikipedia.org/wiki/List_of_pitch_intervals +// Gallery of just intervals - https://en.xen.wiki/w/Gallery_of_just_intervals +// Two letter codes changed to make different interval labels more unique. +ratiointervals.list = new Map([ + [1, ['P1', 'P1', 'P1', 1, 1]], // unison P1 + [16 / 15, ['m2', 'm2', 'm2_5', 16, 15]], // minor second m2 + [15 / 14, ['A1', 'A1', 'A1^5_7', 15, 14]], // augmented unison + [13 / 12, ['t2', 'm2', 'm2^13', 13, 12]], // tridecimal neutral second + [12 / 11, ['N2', 'M2', 'M2_11', 12, 11]], // undecimal neutral second + [11 / 10, ['n2', 'm2', 'm2^11_5', 11, 10]], // undecimal submajor second + [10 / 9, ['T2', 'M2', 'M2^5', 10, 9]], // classic (whole) tone + [9 / 8, ['M2', 'M2', 'M2', 9, 8]], // major second M2 + [8 / 7, ['S2', 'M2', 'M2_7', 8, 7]], // septimal major second + [7 / 6, ['s3', 'm3', 'm3^7', 7, 6]], // septimal minor third + [19 / 16, ['o3', 'm3', 'm3^19', 19, 16]], // otonal minor third + [6 / 5, ['m3', 'm3', 'm3_5', 6, 5]], // minor third m3 + [17 / 14, ['t3', 'm3', 'm3^17_7', 17, 14]], // septendecimal supraminor third + [11 / 9, ['n3', 'm3', 'm3^11', 11, 9]], // undecimal neutral third + [5 / 4, ['M3', 'M3', 'M3^5', 5, 4]], // major third M3 + [9 / 7, ['S3', 'M3', 'M3_7', 9, 7]], // septimal major third SM3 + [13 / 10, ['d4', 'd4', 'd4^13_5', 13, 10]], // Barbados third + [4 / 3, ['P4', 'P4', 'P4', 4, 3]], // perfect fourth P4 + [19 / 14, ['N4', 'P4', 'P4^19_7', 19, 14]], // undevicesimal wide fourth + [11 / 8, ['n4', 'P4', 'P4^11', 11, 8]], // super-fourth + [25 / 18, ['a4', 'A4', 'A4^5,5', 25, 18]], // classic augmented fourth + [7 / 5, ['sT', 'd5', 'd5^7_5', 7, 5]], // lesser septimal tritone + [45 / 32, ['A4', 'A4', 'A4^5', 45, 32]], // just augmented fourth + [17 / 12, ['d5', 'd5', 'd5^17', 17, 12]], // larger septendecimal tritone + [10 / 7, ['ST', 'A4', 'A4^5_7', 10, 7]], // greater septimal tritone + [13 / 9, ['t5', 'd5', 'd5^13', 13, 9]], // tridecimal diminished fifth + [3 / 2, ['P5', 'P5', 'P5', 3, 2]], // perfect fifth P5 + [14 / 9, ['s6', 'M6', 'm6^7', 14, 9]], // subminor sixth or septimal sixth + [25 / 16, ['a5', 'A5', 'A5^5,5', 25, 16]], // classic augmented fifth + [11 / 7, ['A5', 'P5', 'P5^11_7', 11, 7]], // undecimal minor sixth + [8 / 5, ['m6', 'm6', 'm6_5', 8, 5]], // minor sixth m6 + [13 / 8, ['N6', 'm6', 'm6^13', 13, 8]], // tridecimal neutral sixth + [18 / 11, ['n6', 'M6', 'M6_11', 18, 11]], // undecimal neutral sixth + [5 / 3, ['M6', 'M6', 'M6^5', 5, 3]], // just major sixth M6 + [128 / 75, ['d7', 'd7', 'd7_5,5', 128, 75]], // diminished seventh + [17 / 10, ['T6', 'd7', 'd7^17_5', 17, 10]], // septendecimal diminished seventh + [12 / 7, ['S6', 'M6', 'M6_7', 12, 7]], // septimal major sixth + [7 / 4, ['s7', 'm7', 'm7^7', 7, 4]], // septimal minor seventh + [16 / 9, ['m7', 'm7', 'm7', 16, 9]], // lesser minor seventh + [9 / 5, ['g7', 'm7', 'm7_5', 9, 5]], // greater just minor seventh + [11 / 6, ['n7', 'm7', 'm7^11', 11, 6]], // undecimal neutral seventh + [13 / 7, ['N7', 'm7', 'm7^13_7', 13, 7]], // tridecimal neutral seventh + [15 / 8, ['M7', 'M7', 'M7^5', 15, 8]], // major seventh + [17 / 9, ['T7', 'd8', 'd8^17', 17, 9]], // large septendecimal major seventh + [19 / 10, ['d8', 'd8', 'd8^19_5', 19, 10]], // large undevicesimal major seventh + [2, ['P8', 'P8', 'P8', 2, 1]], // octave P8 +]); + +ratiointervals.key = function (ratio) { + return ratio == null ? '' : ratiointervals.list.get(ratio)?.[0] || ''; +}; + +ratiointervals.label = function (ratio) { + return ratio == null ? '' : ratiointervals.list.get(ratio)?.[1] || ''; +}; + +ratiointervals.fjs = function (ratio) { + return ratio == null ? '' : ratiointervals.list.get(ratio)?.[2] || ''; +}; + +ratiointervals.nom = function (ratio) { + return ratio == null ? null : ratiointervals.list.get(ratio)?.[3] || null; +}; + +ratiointervals.denom = function (ratio) { + return ratio == null ? null : ratiointervals.list.get(ratio)?.[4] || null; +}; + +ratiointervals.nearestInterval = function (v) { + let min = 1; + let match = null; + for (const [ratio, _labels] of ratiointervals.list) { + const diff = Math.abs((ratio - v) / ratio); + if (diff < min) { + min = diff; + match = ratio; + } + } + return min < 0.01 ? [min, match] : [null, null]; +}; + +export default ratiointervals; diff --git a/packages/edo/test/edo.test.mjs b/packages/edo/test/edo.test.mjs new file mode 100644 index 00000000..7cebc34b --- /dev/null +++ b/packages/edo/test/edo.test.mjs @@ -0,0 +1,34 @@ +/* +edo.test.mjs - tests of edo.mjs +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ +import '../edo.mjs'; +import { n } from '@strudel/core'; +import { describe, it, expect } from 'vitest'; + +describe('edoScale', () => { + it('Should run tonal functions ', () => { + // base note A3 = 220Hz + let baseNote = 'A3'; + let root = 220; + let freq = 220; + let pattern = n('0').edoScale([baseNote, 'LLsLLLs', 2, 1]); + let cycle = pattern.firstCycleValues[0]; + expect(cycle.freq).toEqual(freq); + expect(cycle.edo).toEqual(12); + expect(cycle.degree).toEqual(1); + expect(parseFloat(cycle.root).toFixed(0)).toEqual(root.toFixed(0)); + + // base note A4 = 440Hz + baseNote = 'A4'; + root = 440; + freq = 880; + pattern = n('7').edoScale([baseNote, 'LLsLLLs', 2, 1]); + cycle = pattern.firstCycleValues[0]; + expect(cycle.freq).toEqual(freq); + expect(cycle.edo).toEqual(12); + expect(cycle.degree).toEqual(1); + expect(parseFloat(cycle.root).toFixed(0)).toEqual(root.toFixed(0)); + }); +}); diff --git a/packages/edo/test/edoscale.test.mjs b/packages/edo/test/edoscale.test.mjs new file mode 100644 index 00000000..531a1b57 --- /dev/null +++ b/packages/edo/test/edoscale.test.mjs @@ -0,0 +1,21 @@ +/* +edoscale.test.mjs - tests of EdoScale from edoscale.mjs +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ +import '../edoscale.mjs'; // need to import this to add prototypes +import { EdoScale } from '../edoscale.mjs'; +import { describe, it, expect } from 'vitest'; + +describe('EdoScale', () => { + it('updates edivisions', () => { + let scale = new EdoScale(2, 1, ['L', 'L', 's', 'L', 'L', 'L', 's']); + expect(scale.stepSize(0)).toEqual('L'); + expect(scale.stepValue(0)).toEqual(2); + + expect(scale.stepSize(2)).toEqual('s'); + expect(scale.stepValue(2)).toEqual(1); + + expect(scale.edivisions).toEqual(12); + }); +}); diff --git a/packages/edo/test/ratios.test.mjs b/packages/edo/test/ratios.test.mjs new file mode 100644 index 00000000..f833177d --- /dev/null +++ b/packages/edo/test/ratios.test.mjs @@ -0,0 +1,18 @@ +/* +ratios.test.mjs - tests of ratios.mjs +Copyright (C) 2025 Rob McKinnon and Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ +import ratiointervals from '../ratios.mjs'; +import { describe, it, expect } from 'vitest'; + +describe('ratiointervals', () => { + it('updates edivisions', () => { + let ratio = 9 / 7; + expect(ratiointervals.key(ratio)).toEqual('S3'); + expect(ratiointervals.label(ratio)).toEqual('M3'); + expect(ratiointervals.fjs(ratio)).toEqual('M3_7'); + expect(ratiointervals.nom(ratio)).toEqual(9); + expect(ratiointervals.denom(ratio)).toEqual(7); + }); +}); diff --git a/packages/edo/vite.config.js b/packages/edo/vite.config.js new file mode 100644 index 00000000..5df3edc1 --- /dev/null +++ b/packages/edo/vite.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite'; +import { dependencies } from './package.json'; +import { resolve } from 'path'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [], + build: { + lib: { + entry: resolve(__dirname, 'index.mjs'), + formats: ['es'], + fileName: (ext) => ({ es: 'index.mjs' })[ext], + }, + rollupOptions: { + external: [...Object.keys(dependencies)], + }, + target: 'esnext', + }, +}); From 43293dc80f203932e17e841eb6e866862d15fdee Mon Sep 17 00:00:00 2001 From: robmckinnon Date: Sat, 28 Jun 2025 19:13:41 +0100 Subject: [PATCH 002/200] Import packages/edo into strudel --- examples/codemirror-repl/main.js | 1 + examples/codemirror-repl/package.json | 1 + index.mjs | 1 + package.json | 1 + packages/edo/edo.mjs | 41 ++++++++++- packages/edo/pitches.mjs | 2 +- packages/repl/package.json | 1 + packages/repl/prebake.mjs | 1 + packages/web/package.json | 1 + packages/web/web.mjs | 2 + pnpm-lock.yaml | 38 ++++++++++ test/__snapshots__/examples.test.mjs.snap | 87 +++++++++++++++++++++++ test/runtime.mjs | 2 + website/package.json | 1 + website/src/repl/util.mjs | 1 + 15 files changed, 179 insertions(+), 2 deletions(-) diff --git a/examples/codemirror-repl/main.js b/examples/codemirror-repl/main.js index 5b5714fb..5b5f2028 100644 --- a/examples/codemirror-repl/main.js +++ b/examples/codemirror-repl/main.js @@ -29,6 +29,7 @@ const editor = new StrudelMirror({ import('@strudel/core'), import('@strudel/draw'), import('@strudel/mini'), + import('@strudel/edo'), import('@strudel/tonal'), import('@strudel/webaudio'), ); diff --git a/examples/codemirror-repl/package.json b/examples/codemirror-repl/package.json index e0ce35b8..55b152f3 100644 --- a/examples/codemirror-repl/package.json +++ b/examples/codemirror-repl/package.json @@ -15,6 +15,7 @@ "@strudel/codemirror": "workspace:*", "@strudel/core": "workspace:*", "@strudel/draw": "workspace:*", + "@strudel/edo": "workspace:*", "@strudel/mini": "workspace:*", "@strudel/soundfonts": "workspace:*", "@strudel/tonal": "workspace:*", diff --git a/index.mjs b/index.mjs index 08339bde..6576be47 100644 --- a/index.mjs +++ b/index.mjs @@ -4,6 +4,7 @@ export * from './packages/core/index.mjs'; export * from './packages/csound/index.mjs'; export * from './packages/desktopbridge/index.mjs'; export * from './packages/draw/index.mjs'; +export * from './packages/edo/index.mjs'; export * from './packages/embed/index.mjs'; export * from './packages/hydra/index.mjs'; export * from './packages/midi/index.mjs'; diff --git a/package.json b/package.json index d18291eb..399bd7b1 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "homepage": "https://strudel.cc", "dependencies": { "@strudel/core": "workspace:*", + "@strudel/edo": "workspace:*", "@strudel/mini": "workspace:*", "@strudel/tonal": "workspace:*", "@strudel/transpiler": "workspace:*", diff --git a/packages/edo/edo.mjs b/packages/edo/edo.mjs index 22e44be6..1e99c243 100644 --- a/packages/edo/edo.mjs +++ b/packages/edo/edo.mjs @@ -11,6 +11,45 @@ import { Pitches } from './pitches.mjs'; const pitchesCache = new Map(); +/** + * Turns numbers into notes in the given EDO scale (zero indexed). + * + * An EDO scale definition looks like this: + * + * e.g. C:LLsLLLs:2:1 <- this is the C major scale, 12 EDO + * + * e.g. C:LLsLLL:3:1 <- this is the Gorgo 6 note scale, 16 EDO + * + * An EDO scale, e.g. C:LLsLLLs:2:1, consists of a root note (e.g. C) + * followed by semicolon (':') + * and then a [Large/small step notation sequence](https://en.xen.wiki/w/MOS_scale) + * (e.g. LLsLLLs) + * followed by semicolon, then the large step size (e.g. 2) + * followed by semicolon, then the small step size (e.g. 1). + * + * The number of divisions of the octave is calculated as the sum + * of the steps in the EDO scale definition. + * + * e.g. C:LLsLLLs:2:1 is 2+2+1+2+2+2+1 = 12 EDO, 7 note scale + * + * e.g. C:LLsLLL:3:1 is 3+3+1+3+3+3 = 16 EDO, 6 note scale + * + * The root note defaults to octave 3, if no octave number is given. + * + * @name edoScale + * @param {string} scale Definition of EDO scale. + * @returns Pattern + * @example + * n("0 2 4 6 4 2").edoScale("C:LLsLLLs:2:1") + * @example + * n("[0,7] 4 [2,7] 4") + * .edoScale("G2::3:1") + * .s("piano")._pitchwheel() + * @example + * n(rand.range(0,5).segment(6)) + * .edoScale(":LLsLL:3:1") + * .s("piano")._pitchwheel() + */ export const edoScale = register( 'edoScale', function (scaleDefinition, pat) { @@ -51,7 +90,7 @@ export const edoScale = register( // legacy.. return pure(n); } - const deg = (typeof n === 'string' ? parseInt(n, 10) : n) + 1; + const deg = (typeof n === 'string' ? parseInt(n, 10) : Number.isInteger(n) ? n : Math.round(n)) + 1; const [oct, degree] = pitches.octdeg(deg); const freq = pitches.octdegfreq(oct, degree); diff --git a/packages/edo/pitches.mjs b/packages/edo/pitches.mjs index 1cd5187b..003e15b7 100644 --- a/packages/edo/pitches.mjs +++ b/packages/edo/pitches.mjs @@ -76,7 +76,7 @@ export class Pitches { const higherOcatve = deg > this.scale.length; const octave = this.root_octave + (higherOcatve ? Math.floor((deg - 1) / this.scale.length) : 0); const degree = higherOcatve ? (deg % this.scale.length === 0 ? this.scale.length : deg % this.scale.length) : deg; - console.log([octave, degree]); + // console.log([octave, degree]); return [octave, degree]; } diff --git a/packages/repl/package.json b/packages/repl/package.json index bfa404c7..8fae08f4 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -36,6 +36,7 @@ "@strudel/codemirror": "workspace:*", "@strudel/core": "workspace:*", "@strudel/draw": "workspace:*", + "@strudel/edo": "workspace:*", "@strudel/hydra": "workspace:*", "@strudel/midi": "workspace:*", "@strudel/mini": "workspace:*", diff --git a/packages/repl/prebake.mjs b/packages/repl/prebake.mjs index dd0023fb..0c60101a 100644 --- a/packages/repl/prebake.mjs +++ b/packages/repl/prebake.mjs @@ -8,6 +8,7 @@ export async function prebake() { core, import('@strudel/draw'), import('@strudel/mini'), + import('@strudel/edo'), import('@strudel/tonal'), import('@strudel/webaudio'), import('@strudel/codemirror'), diff --git a/packages/web/package.json b/packages/web/package.json index 0feddc82..6d9e0430 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -34,6 +34,7 @@ "homepage": "https://codeberg.org/uzu/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", + "@strudel/edo": "workspace:*", "@strudel/mini": "workspace:*", "@strudel/tonal": "workspace:*", "@strudel/transpiler": "workspace:*", diff --git a/packages/web/web.mjs b/packages/web/web.mjs index 2e2df84d..eebe6bdc 100644 --- a/packages/web/web.mjs +++ b/packages/web/web.mjs @@ -3,6 +3,7 @@ export * from '@strudel/webaudio'; //export * from '@strudel/soundfonts'; export * from '@strudel/transpiler'; export * from '@strudel/mini'; +export * from '@strudel/edo'; export * from '@strudel/tonal'; export * from '@strudel/webaudio'; import { Pattern, evalScope, setTime } from '@strudel/core'; @@ -17,6 +18,7 @@ export async function defaultPrebake() { evalScope, import('@strudel/core'), import('@strudel/mini'), + import('@strudel/edo'), import('@strudel/tonal'), import('@strudel/webaudio'), { hush, evaluate }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aecd35b..c88da078 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@strudel/core': specifier: workspace:* version: link:packages/core + '@strudel/edo': + specifier: workspace:* + version: link:packages/edo '@strudel/mini': specifier: workspace:* version: link:packages/mini @@ -93,6 +96,9 @@ importers: '@strudel/draw': specifier: workspace:* version: link:../../packages/draw + '@strudel/edo': + specifier: workspace:* + version: link:../../packages/edo '@strudel/mini': specifier: workspace:* version: link:../../packages/mini @@ -271,6 +277,28 @@ importers: specifier: ^6.0.11 version: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0) + packages/edo: + dependencies: + '@strudel/core': + specifier: workspace:* + version: link:../core + '@tonaljs/tonal': + specifier: ^4.10.0 + version: 4.10.0 + chord-voicings: + specifier: ^0.0.1 + version: 0.0.1 + webmidi: + specifier: ^3.1.12 + version: 3.1.12 + devDependencies: + vite: + specifier: ^6.0.11 + version: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0) + vitest: + specifier: ^3.0.4 + version: 3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0) + packages/embed: {} packages/gamepad: @@ -434,6 +462,9 @@ importers: '@strudel/draw': specifier: workspace:* version: link:../draw + '@strudel/edo': + specifier: workspace:* + version: link:../edo '@strudel/hydra': specifier: workspace:* version: link:../hydra @@ -591,6 +622,9 @@ importers: '@strudel/core': specifier: workspace:* version: link:../core + '@strudel/edo': + specifier: workspace:* + version: link:../edo '@strudel/mini': specifier: workspace:* version: link:../mini @@ -702,6 +736,9 @@ importers: '@strudel/draw': specifier: workspace:* version: link:../packages/draw + '@strudel/edo': + specifier: workspace:* + version: link:../packages/edo '@strudel/gamepad': specifier: workspace:* version: link:../packages/gamepad @@ -7657,6 +7694,7 @@ packages: workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained workbox-navigation-preload@7.0.0: resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==} diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index cc45848b..806b9a1d 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3149,6 +3149,93 @@ exports[`runs examples > example "echoWith" example index 0 1`] = ` ] `; +exports[`runs examples > example "edoScale" example index 0 1`] = ` +[ + "[ 0/1 → 1/6 | degree:1 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:130.813 edo:12 ]", + "[ 1/6 → 1/3 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", + "[ 1/3 → 1/2 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 1/2 → 2/3 | degree:7 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:246.942 edo:12 ]", + "[ 2/3 → 5/6 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 5/6 → 1/1 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", + "[ 1/1 → 7/6 | degree:1 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:130.813 edo:12 ]", + "[ 7/6 → 4/3 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", + "[ 4/3 → 3/2 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 3/2 → 5/3 | degree:7 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:246.942 edo:12 ]", + "[ 5/3 → 11/6 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 11/6 → 2/1 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", + "[ 2/1 → 13/6 | degree:1 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:130.813 edo:12 ]", + "[ 13/6 → 7/3 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", + "[ 7/3 → 5/2 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 5/2 → 8/3 | degree:7 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:246.942 edo:12 ]", + "[ 8/3 → 17/6 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 17/6 → 3/1 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", + "[ 3/1 → 19/6 | degree:1 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:130.813 edo:12 ]", + "[ 19/6 → 10/3 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", + "[ 10/3 → 7/2 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 7/2 → 11/3 | degree:7 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:246.942 edo:12 ]", + "[ 11/3 → 23/6 | degree:5 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:195.998 edo:12 ]", + "[ 23/6 → 4/1 | degree:3 degreeIndexes:[0 2 4 5 7 9 11] intLabels:[null M2 M3 P4 P5 M6 T7 P8] root:130.8128 freq:164.814 edo:12 ]", +] +`; + +exports[`runs examples > example "edoScale" example index 1 1`] = ` +[ + "[ 0/1 → 1/4 | degree:1 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:97.999 edo:16 s:piano ]", + "[ 0/1 → 1/4 | degree:2 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 1/4 → 1/2 | degree:5 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:151.135 edo:16 s:piano ]", + "[ 1/2 → 3/4 | degree:3 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:127.089 edo:16 s:piano ]", + "[ 1/2 → 3/4 | degree:2 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 3/4 → 1/1 | degree:5 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:151.135 edo:16 s:piano ]", + "[ 1/1 → 5/4 | degree:1 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:97.999 edo:16 s:piano ]", + "[ 1/1 → 5/4 | degree:2 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 5/4 → 3/2 | degree:5 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:164.814 edo:16 s:piano ]", + "[ 3/2 → 7/4 | degree:3 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:127.089 edo:16 s:piano ]", + "[ 3/2 → 7/4 | degree:2 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 7/4 → 2/1 | degree:5 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:164.814 edo:16 s:piano ]", + "[ 2/1 → 9/4 | degree:1 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:97.999 edo:16 s:piano ]", + "[ 2/1 → 9/4 | degree:2 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 9/4 → 5/2 | degree:5 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:151.135 edo:16 s:piano ]", + "[ 5/2 → 11/4 | degree:3 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:127.089 edo:16 s:piano ]", + "[ 5/2 → 11/4 | degree:2 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 11/4 → 3/1 | degree:5 degreeIndexes:[0 3 6 7 10 13] intLabels:[null S2 d4 N4 s6 s7 P8] root:97.9989 freq:151.135 edo:16 s:piano ]", + "[ 3/1 → 13/4 | degree:1 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:97.999 edo:16 s:piano ]", + "[ 3/1 → 13/4 | degree:2 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 13/4 → 7/2 | degree:5 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:164.814 edo:16 s:piano ]", + "[ 7/2 → 15/4 | degree:3 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:127.089 edo:16 s:piano ]", + "[ 7/2 → 15/4 | degree:2 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:223.2 edo:16 s:piano ]", + "[ 15/4 → 4/1 | degree:5 degreeIndexes:[0 3 6 9 12 13] intLabels:[null S2 d4 M6 s7 P8] root:97.9989 freq:164.814 edo:16 s:piano ]", +] +`; + +exports[`runs examples > example "edoScale" example index 2 1`] = ` +[ + "[ 0/1 → 1/6 | degree:1 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:97.999 edo:13 s:piano ]", + "[ 1/6 → 1/3 | degree:5 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:167.025 edo:13 s:piano ]", + "[ 1/3 → 1/2 | degree:3 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:134.945 edo:13 s:piano ]", + "[ 1/2 → 2/3 | degree:2 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:114.998 edo:13 s:piano ]", + "[ 2/3 → 5/6 | degree:3 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:134.945 edo:13 s:piano ]", + "[ 5/6 → 1/1 | degree:1 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:97.999 edo:13 s:piano ]", + "[ 1/1 → 7/6 | degree:4 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:189.995 edo:13 s:piano ]", + "[ 7/6 → 4/3 | degree:2 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:153.504 edo:13 s:piano ]", + "[ 4/3 → 3/2 | degree:3 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:180.13 edo:13 s:piano ]", + "[ 3/2 → 5/3 | degree:4 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:189.995 edo:13 s:piano ]", + "[ 5/3 → 11/6 | degree:1 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:130.813 edo:13 s:piano ]", + "[ 11/6 → 2/1 | degree:5 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:222.952 edo:13 s:piano ]", + "[ 2/1 → 13/6 | degree:1 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:195.998 edo:13 s:piano ]", + "[ 13/6 → 7/3 | degree:4 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:142.336 edo:13 s:piano ]", + "[ 7/3 → 5/2 | degree:3 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:134.945 edo:13 s:piano ]", + "[ 5/2 → 8/3 | degree:3 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:134.945 edo:13 s:piano ]", + "[ 8/3 → 17/6 | degree:4 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:142.336 edo:13 s:piano ]", + "[ 17/6 → 3/1 | degree:2 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:97.9989 freq:114.998 edo:13 s:piano ]", + "[ 3/1 → 19/6 | degree:2 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:153.504 edo:13 s:piano ]", + "[ 19/6 → 10/3 | degree:2 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:153.504 edo:13 s:piano ]", + "[ 10/3 → 7/2 | degree:1 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:261.626 edo:13 s:piano ]", + "[ 7/2 → 11/3 | degree:3 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:180.13 edo:13 s:piano ]", + "[ 11/3 → 23/6 | degree:3 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:180.13 edo:13 s:piano ]", + "[ 23/6 → 4/1 | degree:4 degreeIndexes:[0 3 6 7 10] intLabels:[null s3 n4 t5 d7 P8] root:130.8128 freq:189.995 edo:13 s:piano ]", +] +`; + exports[`runs examples > example "end" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:oh end:0.1 ]", diff --git a/test/runtime.mjs b/test/runtime.mjs index 2a29de3f..1125550f 100644 --- a/test/runtime.mjs +++ b/test/runtime.mjs @@ -13,6 +13,7 @@ import { mini, m } from '@strudel/mini/mini.mjs'; // import euclid from '@strudel/core/euclid.mjs'; //import '@strudel/midi/midi.mjs'; import * as tonalHelpers from '@strudel/tonal'; +import * as edoHelpers from '@strudel/edo'; import '@strudel/xen/xen.mjs'; // import '@strudel/xen/tune.mjs'; // import '@strudel/core/euclid.mjs'; @@ -140,6 +141,7 @@ evalScope( toneHelpersMocked, uiHelpersMocked, webaudio, + edoHelpers, tonalHelpers, gamepadHelpers, /* diff --git a/website/package.json b/website/package.json index 49975814..f3cd6b71 100644 --- a/website/package.json +++ b/website/package.json @@ -29,6 +29,7 @@ "@strudel/csound": "workspace:*", "@strudel/desktopbridge": "workspace:*", "@strudel/draw": "workspace:*", + "@strudel/edo": "workspace:*", "@strudel/gamepad": "workspace:*", "@strudel/hydra": "workspace:*", "@strudel/midi": "workspace:*", diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index ac27158f..5ea823f6 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -71,6 +71,7 @@ export function loadModules() { let modules = [ import('@strudel/core'), import('@strudel/draw'), + import('@strudel/edo'), import('@strudel/tonal'), import('@strudel/mini'), import('@strudel/xen'), From ad331c8c2eb1a78ddcc0fcb0359422e0ef6b0a78 Mon Sep 17 00:00:00 2001 From: robmckinnon Date: Sat, 28 Jun 2025 19:14:20 +0100 Subject: [PATCH 003/200] Show EDO intervals in _pitchwheel() display --- packages/draw/pitchwheel.mjs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/draw/pitchwheel.mjs b/packages/draw/pitchwheel.mjs index ba76df07..c151da40 100644 --- a/packages/draw/pitchwheel.mjs +++ b/packages/draw/pitchwheel.mjs @@ -54,11 +54,42 @@ export function pitchwheel({ } if (edo) { + edo = haps.length >= 1 && haps[0].value && haps[0].value.edo ? haps[0].value.edo : edo; + root = haps.length >= 1 && haps[0].value && haps[0].value.root ? haps[0].value.root : root; + const degreeIndexes = + haps.length >= 1 && haps[0].value && haps[0].value.degreeIndexes ? haps[0].value.degreeIndexes : null; + const intLabels = haps.length >= 1 && haps[0].value && haps[0].value.intLabels ? haps[0].value.intLabels : null; + ctx.font = '20px sans-serif'; + const label = `${edo} EDO`; + // Draw EDO label: + ctx.fillText(label, centerX + radius - ctx.measureText(label).width + 15, centerY + radius); Array.from({ length: edo }, (_, i) => { const angle = freq2angle(root * Math.pow(2, i / edo), root); + const [x, y] = circlePos(centerX, centerY, radius, angle); ctx.beginPath(); - ctx.arc(x, y, hapRadius, 0, 2 * Math.PI); + // Draw interval label for degree i when it exists: + if (degreeIndexes === null || degreeIndexes.includes(i)) { + ctx.globalAlpha = 1; + ctx.arc(x, y, hapRadius, 0, 2 * Math.PI); + if (intLabels !== null) { + const degree = degreeIndexes.indexOf(i); + if (intLabels[degree]) { + if (angle < 0.32 && angle > 0.125) { + ctx.fillText(intLabels[degree], x - 34, y); + } else { + if (angle < 0.1 && angle > -1.125) { + ctx.fillText(intLabels[degree], x - 7, y - 12); + } else { + ctx.fillText(intLabels[degree], x + 9, y); + } + } + } + } + } else { + ctx.globalAlpha = 0.15; + ctx.arc(x, y, hapRadius, 0, 2 * Math.PI); + } ctx.fill(); }); ctx.stroke(); From df544047332a9ea5f71e744b12b33bbbb393721b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Wed, 1 Oct 2025 22:21:03 +0200 Subject: [PATCH 004/200] Group by category --- jsdoc/jsdoc-synonyms.js | 8 +++ packages/motion/motion.mjs | 15 +++++ .../src/repl/components/panel/Reference.jsx | 60 ++++++++++++------- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/jsdoc/jsdoc-synonyms.js b/jsdoc/jsdoc-synonyms.js index d59c8dac..aed0dcc0 100644 --- a/jsdoc/jsdoc-synonyms.js +++ b/jsdoc/jsdoc-synonyms.js @@ -12,6 +12,14 @@ function defineTags(dictionary) { doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/); }, }); + + dictionary.defineTag('group', { + mustHaveValue: true, + onTagged: function (doclet, tag) { + doclet.group = tag.value; + console.log('TAGGED GROUP:', doclet.group); + }, + }); } module.exports = { defineTags: defineTags }; diff --git a/packages/motion/motion.mjs b/packages/motion/motion.mjs index 875704ea..cba814a8 100644 --- a/packages/motion/motion.mjs +++ b/packages/motion/motion.mjs @@ -7,6 +7,7 @@ import { signal } from '../core/signal.mjs'; * @name accelerationX * @return {Pattern} * @synonyms accX + * @group external_io * @example * n(accelerationX.segment(4).range(0,7)).scale("C:minor") * @@ -17,6 +18,7 @@ import { signal } from '../core/signal.mjs'; * @name accelerationY * @return {Pattern} * @synonyms accY + * @group external_io * @example * n(accelerationY.segment(4).range(0,7)).scale("C:minor") * @@ -27,6 +29,7 @@ import { signal } from '../core/signal.mjs'; * @name accelerationZ * @return {Pattern} * @synonyms accZ + * @group external_io * @example * n(accelerationZ.segment(4).range(0,7)).scale("C:minor") * @@ -37,6 +40,7 @@ import { signal } from '../core/signal.mjs'; * @name gravityX * @return {Pattern} * @synonyms gravX + * @group external_io * @example * n(gravityX.segment(4).range(0,7)).scale("C:minor") * @@ -47,6 +51,7 @@ import { signal } from '../core/signal.mjs'; * @name gravityY * @return {Pattern} * @synonyms gravY + * @group external_io * @example * n(gravityY.segment(4).range(0,7)).scale("C:minor") * @@ -57,6 +62,7 @@ import { signal } from '../core/signal.mjs'; * @name gravityZ * @return {Pattern} * @synonyms gravZ + * @group external_io * @example * n(gravityZ.segment(4).range(0,7)).scale("C:minor") * @@ -67,6 +73,7 @@ import { signal } from '../core/signal.mjs'; * @name rotationAlpha * @return {Pattern} * @synonyms rotA, rotZ, rotationZ + * @group external_io * @example * n(rotationAlpha.segment(4).range(0,7)).scale("C:minor") * @@ -77,6 +84,7 @@ import { signal } from '../core/signal.mjs'; * @name rotationBeta * @return {Pattern} * @synonyms rotB, rotX, rotationX + * @group external_io * @example * n(rotationBeta.segment(4).range(0,7)).scale("C:minor") * @@ -87,6 +95,7 @@ import { signal } from '../core/signal.mjs'; * @name rotationGamma * @return {Pattern} * @synonyms rotG, rotY, rotationY + * @group external_io * @example * n(rotationGamma.segment(4).range(0,7)).scale("C:minor") * @@ -97,6 +106,7 @@ import { signal } from '../core/signal.mjs'; * @name orientationAlpha * @return {Pattern} * @synonyms oriA, oriZ, orientationZ + * @group external_io * @example * n(orientationAlpha.segment(4).range(0,7)).scale("C:minor") * @@ -107,6 +117,7 @@ import { signal } from '../core/signal.mjs'; * @name orientationBeta * @return {Pattern} * @synonyms oriB, oriX, orientationX + * @group external_io * @example * n(orientationBeta.segment(4).range(0,7)).scale("C:minor") * @@ -117,6 +128,7 @@ import { signal } from '../core/signal.mjs'; * @name orientationGamma * @return {Pattern} * @synonyms oriG, oriY, orientationY + * @group external_io * @example * n(orientationGamma.segment(4).range(0,7)).scale("C:minor") * @@ -127,6 +139,7 @@ import { signal } from '../core/signal.mjs'; * @name absoluteOrientationAlpha * @return {Pattern} * @synonyms absOriA, absOriZ, absoluteOrientationZ + * @group external_io * @example * n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor") * @@ -137,6 +150,7 @@ import { signal } from '../core/signal.mjs'; * @name absoluteOrientationBeta * @return {Pattern} * @synonyms absOriB, absOriX, absoluteOrientationX + * @group external_io * @example * n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor") * @@ -147,6 +161,7 @@ import { signal } from '../core/signal.mjs'; * @name absoluteOrientationGamma * @return {Pattern} * @synonyms absOriG, absOriY, absoluteOrientationY + * @group external_io * @example * n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor") * diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 6007667f..a7b4702c 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -17,13 +17,13 @@ const availableFunctions = (() => { seen.add(s); // Swap `doc.name` in for `s` in the list of synonyms const synonymsWithDoc = [doc.name, ...synonyms].filter((x) => x && x !== s); - functions.push({ - ...doc, - name: s, // update names for the synonym - longname: s, - synonyms: synonymsWithDoc, - synonyms_text: synonymsWithDoc.join(', '), - }); + // functions.push({ + // ...doc, + // name: s, // update names for the synonym + // longname: s, + // synonyms: synonymsWithDoc, + // synonyms_text: synonymsWithDoc.join(', '), + // }); } } return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name)); @@ -52,6 +52,19 @@ export function Reference() { }); }, [search]); + const visibleFunctionsByGroup = (() => { + const groups = {}; + for (const doc of visibleFunctions) { + const group = doc.group || 'Ungrouped'; + if (!groups[group]) { + groups[group] = []; + } + groups[group].push(doc); + } + return groups; + })(); + console.log(visibleFunctionsByGroup); + return ( @@ -86,7 +106,7 @@ export function Reference() {

{visibleFunctions.map((entry, i) => (
-

{entry.name}

+

{entry.name}

{!!entry.synonyms_text && (

Synonyms: {entry.synonyms_text} From c754dab1ef257b8873c59380eb9e5e4b5e7b5d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Wed, 1 Oct 2025 22:31:03 +0200 Subject: [PATCH 005/200] Order groups --- packages/supradough/dough.mjs | 1 + .../src/repl/components/panel/Reference.jsx | 30 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/supradough/dough.mjs b/packages/supradough/dough.mjs index 1cb9241a..7eeb7a50 100644 --- a/packages/supradough/dough.mjs +++ b/packages/supradough/dough.mjs @@ -18,6 +18,7 @@ function applyGainCurve(val) { * @param {number} a - Signal A (can be a single value or an array value in buffer processing). * @param {number} b - Signal B (can be a single value or an array value in buffer processing). * @param {number} m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix). + * @group effects * @returns {number} Crossfaded output value. */ function crossfade(a, b, m) { diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index a7b4702c..aa0453c3 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -35,6 +35,14 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; +const GROUP_DISPLAY_NAMES = { + external_io: 'External I/O', + effects: 'Effects', + ungrouped: 'Ungrouped', +}; + +const GROUP_ORDER = ['effects', 'external_io', 'ungrouped']; + export function Reference() { const [search, setSearch] = useState(''); @@ -55,7 +63,7 @@ export function Reference() { const visibleFunctionsByGroup = (() => { const groups = {}; for (const doc of visibleFunctions) { - const group = doc.group || 'Ungrouped'; + const group = doc.group || 'ungrouped'; if (!groups[group]) { groups[group] = []; } @@ -63,7 +71,17 @@ export function Reference() { } return groups; })(); - console.log(visibleFunctionsByGroup); + // console.log(visibleFunctionsByGroup); + + // Sort and map group entries + const sortedGroups = Object.entries(visibleFunctionsByGroup).sort(([a], [b]) => { + const ai = GROUP_ORDER.indexOf(a); + const bi = GROUP_ORDER.indexOf(b); + if (ai === -1 && bi === -1) return a.localeCompare(b); + if (ai === -1) return 1; + if (bi === -1) return -1; + return ai - bi; + }); return (

@@ -72,14 +90,14 @@ export function Reference() {
- {Object.entries(visibleFunctionsByGroup).map(([groupName, groupEntries]) => ( + {sortedGroups.map(([groupId, groupEntries]) => ( <> -

- {groupName} +

+ {GROUP_DISPLAY_NAMES[groupId] || groupId}

{groupEntries.map((entry, i) => ( { const el = document.getElementById(`doc-${entry.name}`); From 4a395f372dc60d77ae77d63b1608f0c63157f5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Wed, 1 Oct 2025 23:45:48 +0200 Subject: [PATCH 006/200] Annote pattern.mjs and signal.mjs --- jsdoc/jsdoc-synonyms.js | 1 - packages/core/pattern.mjs | 146 +++++++++++++++++- packages/core/signal.mjs | 52 +++++++ .../src/repl/components/panel/Reference.jsx | 6 +- 4 files changed, 198 insertions(+), 7 deletions(-) diff --git a/jsdoc/jsdoc-synonyms.js b/jsdoc/jsdoc-synonyms.js index aed0dcc0..423c7ad6 100644 --- a/jsdoc/jsdoc-synonyms.js +++ b/jsdoc/jsdoc-synonyms.js @@ -17,7 +17,6 @@ function defineTags(dictionary) { mustHaveValue: true, onTagged: function (doclet, tag) { doclet.group = tag.value; - console.log('TAGGED GROUP:', doclet.group); }, }); } diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index d0be3f59..e3d4f5f9 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -84,6 +84,7 @@ export class Pattern { /** * Returns a new pattern, with the function applied to the value of * each hap. It has the alias `fmap`. + * @group functional * @synonyms fmap * @param {Function} func to to apply to the value * @returns Pattern @@ -116,6 +117,7 @@ export class Pattern { * Assumes 'this' is a pattern of functions, and given a function to * resolve wholes, applies a given pattern of values to that * pattern of functions. + * @group functional * @param {Function} whole_func * @param {Function} func * @noAutocomplete @@ -153,6 +155,7 @@ export class Pattern { * In this `_appBoth` variant, where timespans of the function and value haps * are not the same but do intersect, the resulting hap has a timespan of the * intersection. This applies to both the part and the whole timespan. + * @group functional * @param {Pattern} pat_val * @noAutocomplete * @returns Pattern @@ -180,6 +183,7 @@ export class Pattern { * on. In practice, this means that the pattern structure, including onsets, * are preserved from the pattern of functions (often referred to as the left * hand or inner pattern). + * @group functional * @param {Pattern} pat_val * @noAutocomplete * @returns Pattern @@ -213,6 +217,7 @@ export class Pattern { * As with `appLeft`, but `whole` timespans are instead taken from the * pattern of values, i.e. structure is preserved from the right hand/outer * pattern. + * @group functional * @param {Pattern} pat_val * @noAutocomplete * @returns Pattern @@ -403,6 +408,7 @@ export class Pattern { /** * Query haps inside the given time span. * + * @group internals * @param {Fraction | number} begin from time * @param {Fraction | number} end to time * @returns Hap[] @@ -426,6 +432,7 @@ export class Pattern { * Returns a new pattern, with queries split at cycle boundaries. This makes * some calculations easier to express, as all haps are then constrained to * happen within a cycle. + * @group internals * @returns Pattern * @noAutocomplete */ @@ -440,6 +447,7 @@ export class Pattern { /** * Returns a new pattern, where the given function is applied to the query * timespan before passing it to the original pattern. + * @group internals * @param {Function} func the function to apply * @returns Pattern * @noAutocomplete @@ -462,6 +470,7 @@ export class Pattern { /** * As with `withQuerySpan`, but the function is applied to both the * begin and end time of the query timespan. + * @group internals * @param {Function} func the function to apply * @returns Pattern * @noAutocomplete @@ -474,6 +483,7 @@ export class Pattern { * Similar to `withQuerySpan`, but the function is applied to the timespans * of all haps returned by pattern queries (both `part` timespans, and where * present, `whole` timespans). + * @group internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -485,6 +495,7 @@ export class Pattern { /** * As with `withHapSpan`, but the function is applied to both the * begin and end time of the hap timespans. + * @group internals * @param {Function} func the function to apply * @returns Pattern * @noAutocomplete @@ -495,6 +506,7 @@ export class Pattern { /** * Returns a new pattern with the given function applied to the list of haps returned by every query. + * @group internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -507,6 +519,7 @@ export class Pattern { /** * As with `withHaps`, but applies the function to every hap, rather than every list of haps. + * @group internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -517,6 +530,7 @@ export class Pattern { /** * Returns a new pattern with the context field set to every hap set to the given value. + * @group internals * @param {*} context * @returns Pattern * @noAutocomplete @@ -527,6 +541,7 @@ export class Pattern { /** * Returns a new pattern with the given function applied to the context field of every hap. + * @group internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -542,6 +557,7 @@ export class Pattern { /** * Returns a new pattern with the context field of every hap set to an empty object. + * @group internals * @returns Pattern * @noAutocomplete */ @@ -552,6 +568,7 @@ export class Pattern { /** * Returns a new pattern with the given location information added to the * context of every hap. + * @group internals * @param {Number} start start offset * @param {Number} end end offset * @returns Pattern @@ -575,6 +592,7 @@ export class Pattern { /** * Returns a new Pattern, which only returns haps that meet the given test. + * @group internals * @param {Function} hap_test - a function which returns false for haps to be removed from the pattern * @returns Pattern * @noAutocomplete @@ -586,6 +604,7 @@ export class Pattern { /** * As with `filterHaps`, but the function is applied to values * inside haps. + * @group internals * @param {Function} value_test * @returns Pattern * @noAutocomplete @@ -597,6 +616,7 @@ export class Pattern { /** * Returns a new pattern, with haps containing undefined values removed from * query results. + * @group internals * @returns Pattern * @noAutocomplete */ @@ -608,6 +628,7 @@ export class Pattern { * Returns a new pattern, with all haps without onsets filtered out. A hap * with an onset is one with a `whole` timespan that begins at the same time * as its `part` timespan. + * @group internals * @returns Pattern * @noAutocomplete */ @@ -621,6 +642,7 @@ export class Pattern { /** * Returns a new pattern, with 'continuous' haps (those without 'whole' * timespans) removed from query results. + * @group internals * @returns Pattern * @noAutocomplete */ @@ -632,6 +654,7 @@ export class Pattern { /** * Combines adjacent haps with the same value and whole. Only * intended for use in tests. + * @group internals * @noAutocomplete */ defragmentHaps() { @@ -684,6 +707,7 @@ export class Pattern { /** * Queries the pattern for the first cycle, returning Haps. Mainly of use when * debugging a pattern. + * @group internals * @param {Boolean} with_context - set to true, otherwise the context field * will be stripped from the resulting haps. * @returns [Hap] @@ -699,6 +723,7 @@ export class Pattern { /** * Accessor for a list of values returned by querying the first cycle. + * @group internals * @noAutocomplete */ get firstCycleValues() { @@ -707,6 +732,7 @@ export class Pattern { /** * More human-readable version of the `firstCycleValues` accessor. + * @group internals * @noAutocomplete */ get showFirstCycle() { @@ -718,6 +744,7 @@ export class Pattern { /** * Returns a new pattern, which returns haps sorted in temporal order. Mainly * of use when comparing two patterns for equality, in tests. + * @group internals * @returns Pattern * @noAutocomplete */ @@ -732,6 +759,10 @@ export class Pattern { ); } + /** + * Returns a new pattern with all values parsed as numerals. + * @group internals + */ asNumber() { return this.fmap(parseNumeral); } @@ -782,6 +813,7 @@ export class Pattern { /** * Layers the result of the given function(s). Like `superimpose`, but without the original pattern: * @name layer + * @group combiners * @memberof Pattern * @synonyms apply * @returns Pattern @@ -797,6 +829,7 @@ export class Pattern { /** * Superimposes the result of the given function(s) on top of the original pattern: * @name superimpose + * @group combiners * @memberof Pattern * @returns Pattern * @example @@ -856,6 +889,7 @@ export class Pattern { /** * Writes the content of the current event to the console (visible in the side menu). + * @group visualizers * @name log * @memberof Pattern * @example @@ -870,6 +904,7 @@ export class Pattern { /** * A simplified version of `log` which writes all "values" (various configurable parameters) * within the event to the console (visible in the side menu). + * @group visualizers * @name logValues * @memberof Pattern * @example @@ -903,6 +938,7 @@ export class Pattern { * source pattern to be looped, and for an (optional) given function to be * applied. False values result in the corresponding part of the source pattern * to be played unchanged. + * @group structure * @name into * @memberof Pattern * @example @@ -942,6 +978,7 @@ Pattern.prototype.collect = function () { /** * Selects indices in in stacked notes. + * @group transforms * @example * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") * .arpWith(haps => haps[2]) @@ -956,6 +993,7 @@ export const arpWith = register('arpWith', (func, pat) => { /** * Selects indices in in stacked notes. + * @group transforms * @example * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") * .arp("0 [0,2] 1 [0,2]") @@ -1038,6 +1076,7 @@ function _composeOp(a, b, func) { * note("c3 e3 g3".add("<0 5 7 0>")) * // Behind the scenes, the notes are converted to midi numbers: * // note("48 52 55".add("<0 5 7 0>")) + * @group transforms */ add: [numeralArgs((a, b) => a + b)], // support string concatenation /** @@ -1150,6 +1189,7 @@ function _composeOp(a, b, func) { /** * Applies the given structure to the pattern: * + * @group structure * @example * note("c,eb,g") * .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") @@ -1164,6 +1204,7 @@ function _composeOp(a, b, func) { /** * Returns silence when mask is 0 or "~" * + * @group structure * @example * note("c [eb,g] d [eb,g]").mask("<1 [0 1]>") */ @@ -1176,6 +1217,7 @@ function _composeOp(a, b, func) { /** * Resets the pattern to the start of the cycle for each onset of the reset pattern. * + * @group structure * @example * s("[ sd]*2, hh*8").reset("") */ @@ -1189,6 +1231,7 @@ function _composeOp(a, b, func) { * Restarts the pattern for each onset of the restart pattern. * While reset will only reset the current cycle, restart will start from cycle 0. * + * @group structure * @example * s("[ sd]*2, hh*8").restart("") */ @@ -1229,6 +1272,7 @@ export const pm = polymeter; /** * Does absolutely nothing, but with a given metrical 'steps' * @name gap + * @group generators * @param {number} steps * @example * gap(3) // "~@3" @@ -1238,6 +1282,7 @@ export const gap = (steps) => new Pattern(() => [], steps); /** * Does absolutely nothing.. * @name silence + * @group generators * @example * silence // "~" */ @@ -1249,6 +1294,7 @@ export const nothing = gap(0); /** * A discrete value that repeats once per cycle. * + * @group generators * @returns {Pattern} * @example * pure('e4') // "e4" @@ -1290,7 +1336,10 @@ export function reify(thing) { return pure(thing); } -/** Takes a list of patterns, and returns a pattern of lists. +/** + * Takes a list of patterns, and returns a pattern of lists. + * + * @group transforms */ export function sequenceP(pats) { let result = pure([]); @@ -1303,6 +1352,7 @@ export function sequenceP(pats) { /** * The given items are played at the same time at the same length. * + * @group structure * @return {Pattern} * @synonyms polyrhythm, pr * @example @@ -1387,6 +1437,7 @@ export function stackBy(by, ...pats) { /** * Concatenation: combines a list of patterns, switching between them successively, one per cycle. * + * @group combiners * @return {Pattern} * @synonyms cat * @example @@ -1420,6 +1471,7 @@ export function slowcat(...pats) { } /** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles. + * @group combiners * @param {...any} items - The items to concatenate * @return {Pattern} */ @@ -1435,6 +1487,7 @@ export function slowcatPrime(...pats) { /** The given items are con**cat**enated, where each one takes one cycle. * + * @group combiners * @param {...any} items - The items to concatenate * @synonyms slowcat * @return {Pattern} @@ -1456,6 +1509,7 @@ export function cat(...pats) { * Allows to arrange multiple patterns together over multiple cycles. * Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use. * + * @group combiners * @return {Pattern} * @example * arrange( @@ -1473,6 +1527,7 @@ export function arrange(...sections) { * Similarly to `arrange`, allows you to arrange multiple patterns together over multiple cycles. * Unlike `arrange`, you specify a start and stop time for each pattern rather than duration, which * means that patterns can overlap. + * @group combiners * @return {Pattern} * @example seqPLoop([0, 2, "bd(3,8)"], @@ -1517,6 +1572,7 @@ export function sequence(...pats) { } /** Like **cat**, but the items are crammed into one cycle. + * @group combiners * @synonyms sequence, fastcat * @example * seq("e5", "b4", ["d5", "c5"]).note() @@ -1690,6 +1746,7 @@ function stepRegister(name, func, patternify = true, preserveSteps = false, join * Assumes a numerical pattern. Returns a new pattern with all values rounded * to the nearest integer. * @name round + * @group math * @memberof Pattern * @returns Pattern * @example @@ -1698,13 +1755,13 @@ function stepRegister(name, func, patternify = true, preserveSteps = false, join export const round = register('round', function (pat) { return pat.asNumber().fmap((v) => Math.round(v)); }); - /** * Assumes a numerical pattern. Returns a new pattern with all values set to * their mathematical floor. E.g. `3.7` replaced with to `3`, and `-4.2` * replaced with `-5`. * @name floor * @memberof Pattern + * @group math * @returns Pattern * @example * note("42 42.1 42.5 43".floor()) @@ -1719,6 +1776,7 @@ export const floor = register('floor', function (pat) { * replaced with `-4`. * @name ceil * @memberof Pattern + * @group math * @returns Pattern * @example * note("42 42.1 42.5 43".ceil()) @@ -1729,6 +1787,7 @@ export const ceil = register('ceil', function (pat) { /** * Assumes a numerical pattern, containing unipolar values in the range 0 .. * 1. Returns a new pattern with values scaled to the bipolar range -1 .. 1 + * @group math * @returns Pattern * @noAutocomplete */ @@ -1739,6 +1798,7 @@ export const toBipolar = register('toBipolar', function (pat) { /** * Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 * Returns a new pattern with values scaled to the unipolar range 0 .. 1 + * @group math * @returns Pattern * @noAutocomplete */ @@ -1752,6 +1812,7 @@ export const fromBipolar = register('fromBipolar', function (pat) { * Most useful in combination with continuous patterns. * @name range * @memberof Pattern + * @group math * @returns Pattern * @example * s("[bd sd]*2,hh*8") @@ -1767,6 +1828,7 @@ export const range = register('range', function (min, max, pat) { * following an exponential curve. * @name rangex * @memberof Pattern + * @group math * @returns Pattern * @example * s("[bd sd]*2,hh*8") @@ -1781,6 +1843,7 @@ export const rangex = register('rangex', function (min, max, pat) { * Returns a new pattern with values scaled to the given min/max range. * @name range2 * @memberof Pattern + * @group math * @returns Pattern * @example * s("[bd sd]*2,hh*8") @@ -1795,6 +1858,7 @@ export const range2 = register('range2', function (min, max, pat) { * Returns a new pattern with just numbers. * @name ratio * @memberof Pattern + * @group math * @returns Pattern * @example * ratio("1, 5:4, 3:2").mul(110) @@ -1813,6 +1877,7 @@ export const ratio = register('ratio', (pat) => // Structural and temporal transformations /** Compress each cycle into the given timespan, leaving a gap + * @group structure * @example * cat( * s("bd sd").compress(.25,.75), @@ -1834,6 +1899,7 @@ export const { compressSpan, compressspan } = register(['compressSpan', 'compres /** * speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast. + * @group structure * @name fastGap * @synonyms fastgap * @example @@ -1871,6 +1937,7 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f /** * Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle + * @group structure * @example * s("bd hh sd hh").focus(1/4, 3/4) */ @@ -1888,6 +1955,7 @@ export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], fun }); /** The ply function repeats each event the given number of times. + * @group structure * @example * s("bd ~ sd cp").ply("<1 2 3>") */ @@ -1902,6 +1970,7 @@ export const ply = register('ply', function (factor, pat) { /** * Speed up a pattern by the given factor. Used by "*" in mini notation. * + * @group structure * @name fast * @synonyms density * @memberof Pattern @@ -1926,6 +1995,7 @@ export const { fast, density } = register( /** * Both speeds up the pattern (like 'fast') and the sample playback (like 'speed'). + * @group structure * @example * s("bd sd:2").hurry("<1 2 4 3>").slow(1.5) */ @@ -1936,6 +2006,7 @@ export const hurry = register('hurry', function (r, pat) { /** * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. * + * @group structure * @name slow * @synonyms sparsity * @memberof Pattern @@ -1953,6 +2024,7 @@ export const { slow, sparsity } = register(['slow', 'sparsity'], function (facto /** * Carries out an operation 'inside' a cycle. + * @group structure * @example * "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note() * // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note() @@ -1963,6 +2035,7 @@ export const inside = register('inside', function (factor, f, pat) { /** * Carries out an operation 'outside' a cycle. + * @group structure * @example * "<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note() * // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note() @@ -1973,6 +2046,7 @@ export const outside = register('outside', function (factor, f, pat) { /** * Applies the given function every n cycles, starting from the last cycle. + * @group structure * @name lastOf * @memberof Pattern * @param {number} n how many cycles @@ -1989,6 +2063,7 @@ export const lastOf = register('lastOf', function (n, func, pat) { /** * Applies the given function every n cycles, starting from the first cycle. + * @group structure * @name firstOf * @memberof Pattern * @param {number} n how many cycles @@ -2000,6 +2075,7 @@ export const lastOf = register('lastOf', function (n, func, pat) { /** * An alias for `firstOf` + * @group structure * @name every * @memberof Pattern * @param {number} n how many cycles @@ -2016,8 +2092,8 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu /** * Like layer, but with a single function: + * @group structure * @name apply - * @memberof Pattern * @example * "".scale('C minor').apply(scaleTranspose("0,2,4")).note() */ @@ -2028,6 +2104,7 @@ export const apply = register('apply', function (func, pat) { /** * Plays the pattern at the given cycles per minute. + * @group structure * @deprecated * @example * s(",hh*2").cpm(90) // = 90 bpm @@ -2040,6 +2117,7 @@ export const cpm = register('cpm', function (cpm, pat) { /** * Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator * + * @group structure * @name early * @memberof Pattern * @param {number | Pattern} cycles number of cycles to nudge left @@ -2060,6 +2138,7 @@ export const early = register( /** * Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator * + * @group structure * @name late * @memberof Pattern * @param {number | Pattern} cycles number of cycles to nudge right @@ -2080,6 +2159,7 @@ export const late = register( /** * Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern: * + * @group structure * @example * s("bd*2 hh*3 [sd bd]*2 perc").zoom(0.25, 0.75) * // s("hh*3 [sd bd]*2") // equivalent @@ -2106,6 +2186,7 @@ export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a /** * Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers. * Similar to `slice`, but slices up patterns rather than sound samples. + * @group structure * @param {number} number of slices * @param {number} slices to play * @example @@ -2133,6 +2214,7 @@ export const bite = register( /** * Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle. + * @group structure * @param {number} fraction fraction to select * @example * s("lt ht mt cp, [hh oh]*2").linger("<1 .5 .25 .125>") @@ -2153,6 +2235,7 @@ export const linger = register( /** * Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one. + * @group structure * @name segment * @synonyms seg * @param {number} segments number of segments per cycle @@ -2165,6 +2248,7 @@ export const { segment, seg } = register(['segment', 'seg'], function (rate, pat /** * The function `swingBy x n` breaks each cycle into `n` slices, and then delays events in the second half of each slice by the amount `x`, which is relative to the size of the (half) slice. So if `x` is 0 it does nothing, `0.5` delays for half the note duration, and 1 will wrap around to doing nothing again. The end result is a shuffle or swing-like rhythm + * @group structure * @param {number} subdivision * @param {number} offset * @example @@ -2174,6 +2258,7 @@ export const swingBy = register('swingBy', (swing, n, pat) => pat.inside(n, late /** * Shorthand for swingBy with 1/3: + * @group structure * @param {number} subdivision * @example * s("hh*8").swing(4) @@ -2183,6 +2268,7 @@ export const swing = register('swing', (n, pat) => pat.swingBy(1 / 3, n)); /** * Swaps 1s and 0s in a binary pattern. + * @group structure * @name invert * @synonyms inv * @example @@ -2200,6 +2286,7 @@ export const { invert, inv } = register( /** * Applies the given function whenever the given pattern is in a true state. + * @group structure * @name when * @memberof Pattern * @param {Pattern} binary_pat @@ -2214,6 +2301,7 @@ export const when = register('when', function (on, func, pat) { /** * Superimposes the function result on top of the original pattern, delayed by the given time. + * @group structure * @name off * @memberof Pattern * @param {Pattern | number} time offset time @@ -2230,6 +2318,7 @@ export const off = register('off', function (time_pat, func, pat) { * Returns a new pattern where every other cycle is played once, twice as * fast, and offset in time by one quarter of a cycle. Creates a kind of * breakbeat feel. + * @group structure * @returns Pattern */ export const brak = register('brak', function (pat) { @@ -2239,6 +2328,7 @@ export const brak = register('brak', function (pat) { /** * Reverse all haps in a pattern * + * @group structure * @name rev * @memberof Pattern * @returns Pattern @@ -2272,6 +2362,7 @@ export const rev = register( /** Like press, but allows you to specify the amount by which each * event is shifted. pressBy(0.5) is the same as press, while * pressBy(1/3) shifts each event by a third of its timespan. + * @group structure * @example * stack(s("hh*4"), * s("bd mt sd ht").pressBy("<0 0.5 0.25>") @@ -2283,6 +2374,7 @@ export const pressBy = register('pressBy', function (r, pat) { /** * Syncopates a rhythm, by shifting each event halfway into its timespan. + * @group structure * @example * stack(s("hh*4"), * s("bd mt sd ht").every(4, press) @@ -2294,6 +2386,7 @@ export const press = register('press', function (pat) { /** * Silences a pattern. + * @group structure * @example * stack( * s("bd").hush(), @@ -2306,6 +2399,7 @@ Pattern.prototype.hush = function () { /** * Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards. + * @group structure * @example * note("c d e g").palindrome() */ @@ -2320,6 +2414,7 @@ export const palindrome = register( /** * Jux with adjustable stereo width. 0 = mono, 1 = full stereo. + * @group structure * @name juxBy * @synonyms juxby * @example @@ -2341,6 +2436,7 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, /** * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. + * @group structure * @example * s("bd lt [~ ht] mt cp ~ bd hh").jux(rev) * @example @@ -2354,6 +2450,7 @@ export const jux = register('jux', function (func, pat) { /** * Superimpose and offset multiple times, applying the given function each time. + * @group structure * @name echoWith * @synonyms echowith, stutWith, stutwith * @param {number} times how many times to repeat @@ -2373,6 +2470,7 @@ export const { echoWith, echowith, stutWith, stutwith } = register( /** * Superimpose and offset multiple times, gradually decreasing the velocity + * @group structure * @name echo * @memberof Pattern * @returns Pattern @@ -2388,6 +2486,7 @@ export const echo = register('echo', function (times, time, feedback, pat) { /** * Deprecated. Like echo, but the last 2 parameters are flipped. + * @group structure * @name stut * @param {number} times how many times to repeat * @param {number} feedback velocity multiplicator for each iteration @@ -2409,6 +2508,7 @@ export const applyN = register('applyN', function (n, func, p) { /** * The plyWith function repeats each event the given number of times, applying the given function to each event.\n + * @group structure * @name plyWith * @synonyms plywith * @param {number} factor how many times to repeat @@ -2431,6 +2531,7 @@ export const plyWith = register(['plyWith', 'plywith'], function (factor, func, /** * The plyForEach function repeats each event the given number of times, applying the given function to each event. * This version of ply uses the iteration index as an argument to the function, similar to echoWith. + * @group structure * @name plyForEach * @synonyms plyforeach * @param {number} factor how many times to repeat @@ -2452,6 +2553,7 @@ export const plyForEach = register(['plyForEach', 'plyforeach'], function (facto /** * Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played. + * @group structure * @name iter * @memberof Pattern * @returns Pattern @@ -2479,6 +2581,7 @@ export const iter = register( /** * Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles + * @group structure * @name iterBack * @synonyms iterback * @memberof Pattern @@ -2497,6 +2600,7 @@ export const { iterBack, iterback } = register( /** * Repeats each cycle the given number of times. + * @group structure * @name repeatCycles * @memberof Pattern * @returns Pattern @@ -2520,6 +2624,7 @@ export const { repeatCycles } = register( /** * Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). + * @group structure * @name chunk * @synonyms slowChunk, slowchunk * @memberof Pattern @@ -2551,6 +2656,7 @@ export const { chunk, slowchunk, slowChunk } = register( /** * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles + * @group structure * @name chunkBack * @synonyms chunkback * @memberof Pattern @@ -2571,6 +2677,7 @@ export const { chunkBack, chunkback } = register( /** * Like `chunk`, but the cycles of the source pattern aren't repeated * for each set of chunks. + * @group structure * @name fastChunk * @synonyms fastchunk * @memberof Pattern @@ -2591,6 +2698,7 @@ export const { fastchunk, fastChunk } = register( /** * Like `chunk`, but the function is applied to a looped subcycle of the source pattern. + * @group structure * @name chunkInto * @synonyms chunkinto * @memberof Pattern @@ -2604,6 +2712,7 @@ export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], fun /** * Like `chunkInto`, but moves backwards through the chunks. + * @group structure * @name chunkBackInto * @synonyms chunkbackinto * @memberof Pattern @@ -2634,6 +2743,7 @@ export const bypass = register( /** * Loops the pattern inside an `offset` for `cycles`. * If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it. + * @group structure * @name ribbon * @synonyms rib * @param {number} offset start point of loop in cycles @@ -2662,6 +2772,7 @@ export const hsl = register('hsl', (h, s, l, pat) => { /** * Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array). * @name tag + * @group structure * @noAutocomplete * @param {string} tag anything unique */ @@ -2672,6 +2783,7 @@ Pattern.prototype.tag = function (tag) { /** * Filters haps using the given function * @name filter + * @group structure * @param {Function} test function to test Hap * @example * s("hh!7 oh").filter(hap => hap.value.s==='hh') @@ -2681,6 +2793,7 @@ export const filter = register('filter', (test, pat) => pat.withHaps((haps) => h /** * Filters haps by their begin time * @name filterWhen + * @group structure * @noAutocomplete * @param {Function} test function to test Hap.whole.begin */ @@ -2689,6 +2802,7 @@ export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) = /** * Use within to apply a function to only a part of a pattern. * @name within + * @group structure * @param {number} start start within cycle (0 - 1) * @param {number} end end within cycle (0 - 1). Must be > start * @param {Function} func function to be applied to the sub-pattern @@ -2763,6 +2877,7 @@ export function _match(span, hap_p) { * *Experimental* * * Speeds a pattern up or down, to fit to the given number of steps per cycle. + * @group structure * @example * sound("bd sd cp").pace(4) * // The same as sound("{bd sd cp}%4") or sound("*4") @@ -2804,6 +2919,7 @@ export function _polymeterListSteps(steps, ...args) { * *Experimental* * * Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps. + * @group structure * @synonyms pm * @example * // The same as note("{c eb g, c2 g2}%6") @@ -2836,6 +2952,7 @@ export function polymeter(...args) { * The steps can either be inferred from the pattern, or provided as a [length, pattern] pair. * Has the alias `timecat`. * @name stepcat + * @group combiners * @synonyms timeCat, timecat * @return {Pattern} * @example @@ -2893,6 +3010,7 @@ export function stepcat(...timepats) { * Concatenates patterns stepwise, according to an inferred 'steps per cycle'. * Similar to `stepcat`, but if an argument is a list, the whole pattern will alternate between the elements in the list. * + * @group combiners * @return {Pattern} * @example * stepalt(["bd cp", "mt"], "bd").sound() @@ -2919,6 +3037,7 @@ export function stepalt(...groups) { * * Takes the given number of steps from a pattern (dropping the rest). * A positive number will take steps from the start of a pattern, and a negative number from the end. + * @group structure * @return {Pattern} * @example * "bd cp ht mt".take("2").sound() @@ -2963,6 +3082,7 @@ export const take = stepRegister('take', function (i, pat) { * * Drops the given number of steps from a pattern. * A positive number will drop steps from the start of a pattern, and a negative number from the end. + * @group structure * @return {Pattern} * @example * "tha dhi thom nam".drop("1").sound().bank("mridangam") @@ -2991,6 +3111,7 @@ export const drop = stepRegister('drop', function (i, pat) { * `extend` is similar to `fast` in that it increases its density, but it also increases the step count * accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas * `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`. + * @group structure * @example * stepcat( * sound("bd bd - cp").extend(2), @@ -3005,6 +3126,7 @@ export const extend = stepRegister('extend', function (factor, pat) { * *Experimental* * * Expands the step size of the pattern by the given factor. + * @group structure * @example * sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8) */ @@ -3016,6 +3138,7 @@ export const expand = stepRegister('expand', function (factor, pat) { * *Experimental* * * Contracts the step size of the pattern by the given factor. See also `expand`. + * @group structure * @example * sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8) */ @@ -3070,6 +3193,7 @@ export const shrinklist = (amount, pat) => pat.shrinklist(amount); * Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with `:`), * that number of times. * A positive number will progressively drop steps from the start of a pattern, and a negative number from the end. + * @group structure * @return {Pattern} * @example * "tha dhi thom nam".shrink("1").sound() @@ -3109,6 +3233,7 @@ export const shrink = register( * Progressively grows the pattern by 'n' steps until the full pattern is played, or if a second value is given (using mininotation list syntax with `:`), * that number of times. * A positive number will progressively grow steps from the start of a pattern, and a negative number from the end. + * @group structure * @return {Pattern} * @example * "tha dhi thom nam".grow("1").sound() @@ -3148,7 +3273,8 @@ export const grow = register( * Inserts a pattern into a list of patterns. On the first repetition it will be inserted at the end of the list, then moved backwards through the list * on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using `pace` to set the * number of steps per cycle is therefore usually recommended. - * + * + * @group combiners * @return {Pattern} * @example * "[c g]".tour("e f", "e f g", "g f e c").note() @@ -3175,6 +3301,7 @@ Pattern.prototype.tour = function (...many) { * 'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle. * Using `pace` to set the number of steps per cycle is therefore usually recommended. * + * @group combiners * @returns {Pattern} * @example * zip("e f", "e f g", "g [f e] a f4 c").note() @@ -3226,6 +3353,7 @@ Pattern.prototype.steps = Pattern.prototype.pace; * Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'. * It turns a pattern of samples into a pattern of parts of samples. * @name chop + * @group transforms * @memberof Pattern * @returns Pattern * @example @@ -3256,6 +3384,7 @@ export const chop = register('chop', function (n, pat) { /** * Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop. * @name striate + * @group transforms * @memberof Pattern * @returns Pattern * @example @@ -3274,6 +3403,7 @@ export const striate = register('striate', function (n, pat) { /** * Makes the sample fit the given number of cycles by changing the speed. * @name loopAt + * @group transforms * @memberof Pattern * @returns Pattern * @example @@ -3292,6 +3422,7 @@ const _loopAt = function (factor, pat, cps = 0.5) { * Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. * Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points. * @name slice + * @group transforms * @memberof Pattern * @returns Pattern * @example @@ -3327,6 +3458,7 @@ export const slice = register( * make something happen on event time * uses browser timeout which is innacurate for audio tasks * @name onTriggerTime + * @group external_io * @memberof Pattern * @returns Pattern * @example @@ -3344,6 +3476,7 @@ Pattern.prototype.onTriggerTime = function (func) { /** * Works the same as slice, but changes the playback speed of each slice to match the duration of its step. * @name splice + * @group transforms * @example * samples('github:tidalcycles/dirt-samples') * s("breaks165") @@ -3381,6 +3514,7 @@ export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (facto * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. * Similar to `loopAt`. * @name fit + * @group transforms * @example * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) * s("rhodes/2").fit() @@ -3406,6 +3540,7 @@ export const fit = register('fit', (pat) => * given by a global clock and this function will be * deprecated/removed. * @name loopAtCps + * @group transforms * @memberof Pattern * @returns Pattern * @example @@ -3432,6 +3567,7 @@ let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5); * - 1 = (no left, full right) * * @name xfade + * @group combiners * @example * xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8")) */ @@ -3453,6 +3589,7 @@ Pattern.prototype.xfade = function (pos, b) { * creates a structure pattern from divisions of a cycle * especially useful for creating rhythms * @name beat + * @group structure * @example * s("bd").beat("0,7,10", 16) * @example @@ -3529,6 +3666,7 @@ export const _morph = (from, to, by) => { * sine.slow(8) // slowly morph between the rhythms * ) * ) + * @group structure */ export const morph = (frompat, topat, bypat) => { frompat = reify(frompat); diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 6bd6fde6..9f6636c8 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -24,6 +24,7 @@ export const signal = (func) => { * A sawtooth signal between 0 and 1. * * @return {Pattern} + * @group generators * @example * note("*8") * .clip(saw.slow(2)) @@ -38,6 +39,7 @@ export const saw = signal((t) => t % 1); * A sawtooth signal between -1 and 1 (like `saw`, but bipolar). * * @return {Pattern} + * @group generators */ export const saw2 = saw.toBipolar(); @@ -45,6 +47,7 @@ export const saw2 = saw.toBipolar(); * A sawtooth signal between 1 and 0 (like `saw`, but flipped). * * @return {Pattern} + * @group generators * @example * note("*8") * .clip(isaw.slow(2)) @@ -59,6 +62,7 @@ export const isaw = signal((t) => 1 - (t % 1)); * A sawtooth signal between 1 and -1 (like `saw2`, but flipped). * * @return {Pattern} + * @group generators */ export const isaw2 = isaw.toBipolar(); @@ -66,12 +70,14 @@ export const isaw2 = isaw.toBipolar(); * A sine signal between -1 and 1 (like `sine`, but bipolar). * * @return {Pattern} + * @group generators */ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t)); /** * A sine signal between 0 and 1. * @return {Pattern} + * @group generators * @example * n(sine.segment(16).range(0,15)) * .scale("C:minor") @@ -83,6 +89,7 @@ export const sine = sine2.fromBipolar(); * A cosine signal between 0 and 1. * * @return {Pattern} + * @group generators * @example * n(stack(sine,cosine).segment(16).range(0,15)) * .scale("C:minor") @@ -94,12 +101,14 @@ export const cosine = sine._early(Fraction(1).div(4)); * A cosine signal between -1 and 1 (like `cosine`, but bipolar). * * @return {Pattern} + * @group generators */ export const cosine2 = sine2._early(Fraction(1).div(4)); /** * A square signal between 0 and 1. * @return {Pattern} + * @group generators * @example * n(square.segment(4).range(0,7)).scale("C:minor") * @@ -110,6 +119,7 @@ export const square = signal((t) => Math.floor((t * 2) % 2)); * A square signal between -1 and 1 (like `square`, but bipolar). * * @return {Pattern} + * @group generators */ export const square2 = square.toBipolar(); @@ -117,6 +127,7 @@ export const square2 = square.toBipolar(); * A triangle signal between 0 and 1. * * @return {Pattern} + * @group generators * @example * n(tri.segment(8).range(0,7)).scale("C:minor") * @@ -127,6 +138,7 @@ export const tri = fastcat(saw, isaw); * A triangle signal between -1 and 1 (like `tri`, but bipolar). * * @return {Pattern} + * @group generators */ export const tri2 = fastcat(saw2, isaw2); @@ -134,6 +146,7 @@ export const tri2 = fastcat(saw2, isaw2); * An inverted triangle signal between 1 and 0 (like `tri`, but flipped). * * @return {Pattern} + * @group generators * @example * n(itri.segment(8).range(0,7)).scale("C:minor") * @@ -144,6 +157,7 @@ export const itri = fastcat(isaw, saw); * An inverted triangle signal between -1 and 1 (like `itri`, but bipolar). * * @return {Pattern} + * @group generators */ export const itri2 = fastcat(isaw2, saw2); @@ -151,6 +165,7 @@ export const itri2 = fastcat(isaw2, saw2); * A signal representing the cycle time. * * @return {Pattern} + * @group generators */ export const time = signal(id); @@ -158,6 +173,7 @@ export const time = signal(id); * The mouse's x position value ranges from 0 to 1. * @name mousex * @return {Pattern} + * @group external_io * @example * n(mousex.segment(4).range(0,7)).scale("C:minor") * @@ -167,6 +183,7 @@ export const time = signal(id); * The mouse's y position value ranges from 0 to 1. * @name mousey * @return {Pattern} + * @group external_io * @example * n(mousey.segment(4).range(0,7)).scale("C:minor") * @@ -221,6 +238,7 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); /** * A discrete pattern of numbers from 0 to n-1 + * @group generators * @example * n(run(4)).scale("C4:pentatonic") * // n("0 1 2 3").scale("C4:pentatonic") @@ -231,6 +249,7 @@ export const run = (n) => saw.range(0, n).round().segment(n); * Creates a pattern from a binary number. * * @name binary + * @group generators * @param {number} n - input number to convert to binary * @example * "hh".s().struct(binary(5)) @@ -245,6 +264,7 @@ export const binary = (n) => { * Creates a pattern from a binary number, padded to n bits long. * * @name binaryN + * @group generators * @param {number} n - input number to convert to binary * @param {number} nBits - pattern length, defaults to 16 * @example @@ -280,6 +300,7 @@ const _rearrangeWith = (ipat, n, pat) => { * Slices a pattern into the given number of parts, then plays those parts in random order. * Each part will be played exactly once per cycle. * @name shuffle + * @group transforms * @example * note("c d e f").sound("piano").shuffle(4) * @example @@ -293,6 +314,7 @@ export const shuffle = register('shuffle', (n, pat) => { * Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`, * but parts might be played more than once, or not at all, per cycle. * @name scramble + * @group transforms * @example * note("c d e f").sound("piano").scramble(4) * @example @@ -306,6 +328,7 @@ export const scramble = register('scramble', (n, pat) => { * A continuous pattern of random numbers, between 0 and 1. * * @name rand + * @group generators * @example * // randomly change the cutoff * s("bd*4,hh*8").cutoff(rand.range(500,8000)) @@ -323,6 +346,7 @@ export const _brandBy = (p) => rand.fmap((x) => x < p); * A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1 * * @name brandBy + * @group generators * @param {number} probability - a number between 0 and 1 * @example * s("hh*10").pan(brandBy(0.2)) @@ -333,6 +357,7 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin(); * A continuous pattern of 0 or 1 (binary random) * * @name brand + * @group generators * @example * s("hh*10").pan(brand) */ @@ -344,6 +369,7 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); * A continuous pattern of random integers, between 0 and n-1. * * @name irand + * @group generators * @param {number} n max value (exclusive) * @example * // randomly select scale notes from 0 - 7 (= C to C) @@ -366,6 +392,7 @@ export const __chooseWith = (pat, xs) => { /** * Choose from the list of values (or patterns of values) using the given * pattern of numbers, which should be in the range of 0..1 + * @group transforms * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -379,6 +406,7 @@ export const chooseWith = (pat, xs) => { /** * As with {chooseWith}, but the structure comes from the chosen values, rather * than the pattern you're using to choose with. + * @group transforms * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -389,6 +417,7 @@ export const chooseInWith = (pat, xs) => { /** * Chooses randomly from the given list of elements. + * @group transforms * @param {...any} xs values / patterns to choose from. * @returns {Pattern} - a continuous pattern. * @example @@ -404,6 +433,7 @@ export const chooseOut = choose; * Chooses from the given list of values (or patterns of values), according * to the pattern that the method is called on. The pattern should be in * the range 0 .. 1. + * @group transforms * @param {...any} xs * @returns {Pattern} */ @@ -414,6 +444,7 @@ Pattern.prototype.choose = function (...xs) { /** * As with choose, but the pattern that this method is called on should be * in the range -1 .. 1 + * @group transforms * @param {...any} xs * @returns {Pattern} */ @@ -423,6 +454,7 @@ Pattern.prototype.choose2 = function (...xs) { /** * Picks one of the elements at random each cycle. + * @group transforms * @synonyms randcat * @returns {Pattern} * @example @@ -465,6 +497,7 @@ const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); /** * Chooses randomly from the given list of elements by giving a probability to each element + * @group transforms * @param {...any} pairs arrays of value and weight * @returns {Pattern} - a continuous pattern. * @example @@ -474,6 +507,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); /** * Picks one of the elements at random each cycle by giving a probability to each element + * @group transforms * @synonyms wrandcat * @returns {Pattern} * @example @@ -521,6 +555,7 @@ export const berlinWith = (tpat) => { /** * Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1. * + * @group generators * @name perlin * @example * // randomly change the cutoff @@ -533,6 +568,7 @@ export const perlin = perlinWith(time.fmap((v) => Number(v))); * Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful, * like perlin noise but with sawtooth waves), in the range 0..1. * + * @group generators * @name berlin * @example * // ascending arpeggios @@ -553,6 +589,7 @@ export const degradeByWith = register( * 0 = 0% chance of removal * 1 = 100% chance of removal * + * @group transforms * @name degradeBy * @memberof Pattern * @param {number} amount - a number between 0 and 1 @@ -578,6 +615,7 @@ export const degradeBy = register( * * Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)` * + * @group transforms * @name degrade * @memberof Pattern * @returns Pattern @@ -594,6 +632,7 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, t * 1 = 0% chance of removal * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). * + * @group transforms * @name undegradeBy * @memberof Pattern * @param {number} amount - a number between 0 and 1 @@ -622,6 +661,7 @@ export const undegradeBy = register( * Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` * Events that would be removed by degrade are let through by undegrade and vice versa (see second example). * + * @group transforms * @name undegrade * @memberof Pattern * @returns Pattern @@ -640,6 +680,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t * Randomly applies the given function by the given probability. * Similar to `someCyclesBy` * + * @group transforms * @name sometimesBy * @memberof Pattern * @param {number | Pattern} probability - a number between 0 and 1 @@ -659,6 +700,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) { * * Applies the given function with a 50% chance * + * @group transforms * @name sometimes * @memberof Pattern * @param {function} function - the transformation to apply @@ -680,6 +722,7 @@ export const sometimes = register('sometimes', function (func, pat) { * @param {number | Pattern} probability - a number between 0 and 1 * @param {function} function - the transformation to apply * @returns Pattern + * @group transforms * @example * s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5")) */ @@ -702,6 +745,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) * @name someCycles * @memberof Pattern * @returns Pattern + * @group transforms * @example * s("bd,hh*8").someCycles(x=>x.speed("0.5")) */ @@ -716,6 +760,7 @@ export const someCycles = register('someCycles', function (func, pat) { * @name often * @memberof Pattern * @returns Pattern + * @group transforms * @example * s("hh*8").often(x=>x.speed("0.5")) */ @@ -730,6 +775,7 @@ export const often = register('often', function (func, pat) { * @name rarely * @memberof Pattern * @returns Pattern + * @group transforms * @example * s("hh*8").rarely(x=>x.speed("0.5")) */ @@ -741,6 +787,7 @@ export const rarely = register('rarely', function (func, pat) { * * Shorthand for `.sometimesBy(0.1, fn)` * + * @group transforms * @name almostNever * @memberof Pattern * @returns Pattern @@ -755,6 +802,7 @@ export const almostNever = register('almostNever', function (func, pat) { * * Shorthand for `.sometimesBy(0.9, fn)` * + * @group transforms * @name almostAlways * @memberof Pattern * @returns Pattern @@ -769,6 +817,7 @@ export const almostAlways = register('almostAlways', function (func, pat) { * * Shorthand for `.sometimesBy(0, fn)` (never calls fn) * + * @group transforms * @name never * @memberof Pattern * @returns Pattern @@ -783,6 +832,7 @@ export const never = register('never', function (_, pat) { * * Shorthand for `.sometimesBy(1, fn)` (always calls fn) * + * @group transforms * @name always * @memberof Pattern * @returns Pattern @@ -811,6 +861,7 @@ export function _keyDown(keyname) { * Do something on a keypress, or array of keypresses * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) * + * @group external_io * @name whenKey * @memberof Pattern * @returns Pattern @@ -827,6 +878,7 @@ export const whenKey = register('whenKey', function (input, func, pat) { * returns true when a key or array of keys is held * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) * + * @group external_io * @name keyDown * @memberof Pattern * @returns Pattern diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index aa0453c3..552551b5 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -39,9 +39,11 @@ const GROUP_DISPLAY_NAMES = { external_io: 'External I/O', effects: 'Effects', ungrouped: 'Ungrouped', + structure: 'Structure', + transforms: 'Transforms', }; -const GROUP_ORDER = ['effects', 'external_io', 'ungrouped']; +const GROUP_ORDER = ['effects', 'transforms', 'structure', 'ungrouped', 'external_io']; export function Reference() { const [search, setSearch] = useState(''); @@ -93,7 +95,7 @@ export function Reference() { {sortedGroups.map(([groupId, groupEntries]) => ( <>

- {GROUP_DISPLAY_NAMES[groupId] || groupId} + {GROUP_DISPLAY_NAMES[groupId] || groupId} ({groupEntries.length})

{groupEntries.map((entry, i) => (
Date: Thu, 2 Oct 2025 21:59:45 +0200 Subject: [PATCH 007/200] Annotate controls.mjs --- packages/core/controls.mjs | 162 ++++++++++++++++++++++++++++++ packages/superdough/reverbGen.mjs | 4 +- 2 files changed, 165 insertions(+), 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 7e3c2a8e..e43af896 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -77,6 +77,7 @@ export function registerControl(names, ...aliases) { * separated by ':'. * * @name s + * @group samples * @param {string | Pattern} sound The sound / pattern of sounds to pick * @synonyms sound * @example @@ -91,6 +92,7 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); * Position in the wavetable of the wavetable oscillator * * @name wt + * @group effects * @param {number | Pattern} position Position in the wavetable from 0 to 1 * @synonyms wavetablePosition * @example @@ -102,6 +104,7 @@ export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePositio * Amount of envelope applied wavetable oscillator's position envelope * * @name wtenv + * @group effects * @param {number | Pattern} amount between 0 and 1 */ export const { wtenv } = registerControl('wtenv'); @@ -109,6 +112,7 @@ export const { wtenv } = registerControl('wtenv'); * Attack time of the wavetable oscillator's position envelope * * @name wtattack + * @group effects * @synonyms wtatt * @param {number | Pattern} time attack time in seconds */ @@ -118,6 +122,7 @@ export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt'); * Decay time of the wavetable oscillator's position envelope * * @name wtdecay + * @group effects * @synonyms wtdec * @param {number | Pattern} time decay time in seconds */ @@ -127,6 +132,7 @@ export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec'); * Sustain time of the wavetable oscillator's position envelope * * @name wtsustain + * @group effects * @synonyms wtsus * @param {number | Pattern} gain sustain level (0 to 1) */ @@ -136,6 +142,7 @@ export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus'); * Release time of the wavetable oscillator's position envelope * * @name wtrelease + * @group effects * @synonyms wtrel * @param {number | Pattern} time release time in seconds */ @@ -145,6 +152,7 @@ export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel'); * Rate of the LFO for the wavetable oscillator's position * * @name wtrate + * @group effects * @param {number | Pattern} rate rate in hertz */ export const { wtrate } = registerControl('wtrate'); @@ -152,6 +160,7 @@ export const { wtrate } = registerControl('wtrate'); * cycle synced rate of the LFO for the wavetable oscillator's position * * @name wtsync + * @group effects * @param {number | Pattern} rate rate in cycles */ export const { wtsync } = registerControl('wtsync'); @@ -160,6 +169,7 @@ export const { wtsync } = registerControl('wtsync'); * Depth of the LFO for the wavetable oscillator's position * * @name wtdepth + * @group effects * @param {number | Pattern} depth depth of modulation */ export const { wtdepth } = registerControl('wtdepth'); @@ -168,6 +178,7 @@ export const { wtdepth } = registerControl('wtdepth'); * Shape of the LFO for the wavetable oscillator's position * * @name wtshape + * @group effects * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { wtshape } = registerControl('wtshape'); @@ -176,6 +187,7 @@ export const { wtshape } = registerControl('wtshape'); * DC offset of the LFO for the wavetable oscillator's position * * @name wtdc + * @group effects * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { wtdc } = registerControl('wtdc'); @@ -184,6 +196,7 @@ export const { wtdc } = registerControl('wtdc'); * Skew of the LFO for the wavetable oscillator's position * * @name wtskew + * @group effects * @param {number | Pattern} skew How much to bend the LFO shape */ export const { wtskew } = registerControl('wtskew'); @@ -192,6 +205,7 @@ export const { wtskew } = registerControl('wtskew'); * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator * * @name warp + * @group effects * @param {number | Pattern} amount Warp of the wavetable from 0 to 1 * @synonyms wavetableWarp * @example @@ -204,6 +218,7 @@ export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp'); * Attack time of the wavetable oscillator's warp envelope * * @name warpattack + * @group effects * @synonyms warpatt * @param {number | Pattern} time attack time in seconds */ @@ -213,6 +228,7 @@ export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt'); * Decay time of the wavetable oscillator's warp envelope * * @name warpdecay + * @group effects * @synonyms warpdec * @param {number | Pattern} time decay time in seconds */ @@ -222,6 +238,7 @@ export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec'); * Sustain time of the wavetable oscillator's warp envelope * * @name warpsustain + * @group effects * @synonyms warpsus * @param {number | Pattern} gain sustain level (0 to 1) */ @@ -231,6 +248,7 @@ export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus' * Release time of the wavetable oscillator's warp envelope * * @name warprelease + * @group effects * @synonyms warprel * @param {number | Pattern} time release time in seconds */ @@ -240,6 +258,7 @@ export const { warprelease, warprel } = registerControl('warprelease', 'warprel' * Rate of the LFO for the wavetable oscillator's warp * * @name warprate + * @group effects * @param {number | Pattern} rate rate in hertz */ export const { warprate } = registerControl('warprate'); @@ -248,6 +267,7 @@ export const { warprate } = registerControl('warprate'); * Depth of the LFO for the wavetable oscillator's warp * * @name warpdepth + * @group effects * @param {number | Pattern} depth depth of modulation */ export const { warpdepth } = registerControl('warpdepth'); @@ -256,6 +276,7 @@ export const { warpdepth } = registerControl('warpdepth'); * Shape of the LFO for the wavetable oscillator's warp * * @name warpshape + * @group effects * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { warpshape } = registerControl('warpshape'); @@ -264,6 +285,7 @@ export const { warpshape } = registerControl('warpshape'); * DC offset of the LFO for the wavetable oscillator's warp * * @name warpdc + * @group effects * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { warpdc } = registerControl('warpdc'); @@ -272,6 +294,7 @@ export const { warpdc } = registerControl('warpdc'); * Skew of the LFO for the wavetable oscillator's warp * * @name warpskew + * @group effects * @param {number | Pattern} skew How much to bend the LFO shape */ export const { warpskew } = registerControl('warpskew'); @@ -283,6 +306,7 @@ export const { warpskew } = registerControl('warpskew'); * spin, chaos, primes, binary, brownian, reciprocal, wormhole, logistic, sigmoid, fractal, flip * * @name warpmode + * @group effects * @param {number | string | Pattern} mode Warp mode * @synonyms wavetableWarpMode * @example @@ -296,6 +320,7 @@ export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wave * Amount of randomness of the initial phase of the wavetable oscillator. * * @name wtphaserand + * @group effects * @param {number | Pattern} amount Randomness of the initial phase. Between 0 (not random) and 1 (fully random) * @synonyms wavetablePhaseRand * @example @@ -308,6 +333,7 @@ export const { wtphaserand, wavetablePhaseRand } = registerControl('wtphaserand' * Amount of envelope applied wavetable oscillator's position envelope * * @name warpenv + * @group effects * @param {number | Pattern} amount between 0 and 1 */ export const { warpenv } = registerControl('warpenv'); @@ -316,6 +342,7 @@ export const { warpenv } = registerControl('warpenv'); * cycle synced rate of the LFO for the wavetable warp position * * @name warpsync + * @group effects * @param {number | Pattern} rate rate in cycles */ export const { warpsync } = registerControl('warpsync'); @@ -324,6 +351,7 @@ export const { warpsync } = registerControl('warpsync'); * Define a custom webaudio node to use as a sound source. * * @name source + * @group external_io * @synonyms src * @param {function} getSource * @synonyms src @@ -336,6 +364,7 @@ export const { source, src } = registerControl('source', 'src'); * `n` can also be used to play midi numbers, but it is recommended to use `note` instead. * * @name n + * @group samples * @param {number | Pattern} value sample index starting from 0 * @example * s("bd sd [~ bd] sd,hh*6").n("<0 1>") @@ -354,6 +383,7 @@ export const { n } = registerControl('n'); * You can also use midi numbers instead of note names, where 69 is mapped to A4 440Hz in 12EDO. * * @name note + * @group music_theory * @example * note("c a f e") * @example @@ -369,6 +399,7 @@ export const { note } = registerControl(['note', 'n']); * A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt. * * @name accelerate + * @group samples * @param {number | Pattern} amount acceleration. * @superdirtOnly * @example @@ -380,6 +411,7 @@ export const { accelerate } = registerControl('accelerate'); * Sets the velocity from 0 to 1. Is multiplied together with gain. * * @name velocity + * @group effects * @example * s("hh*8") * .gain(".4!2 1 .4!2 1 .4 1") @@ -390,6 +422,7 @@ export const { velocity } = registerControl('velocity'); * Controls the gain by an exponential amount. * * @name gain + * @group effects * @param {number | Pattern} amount gain. * @example * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) @@ -400,6 +433,7 @@ export const { gain } = registerControl('gain'); * Gain applied after all effects have been processed. * * @name postgain + * @group effects * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02").postgain(1.5) @@ -410,6 +444,7 @@ export const { postgain } = registerControl('postgain'); * Like `gain`, but linear. * * @name amp + * @group effects * @param {number | Pattern} amount gain. * @superdirtOnly * @example @@ -421,6 +456,7 @@ export const { amp } = registerControl('amp'); * Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * * @name attack + * @group effects * @param {number | Pattern} attack time in seconds. * @synonyms att * @example @@ -436,6 +472,7 @@ export const { attack, att } = registerControl('attack', 'att'); * while decimal numbers and complex ratios sound metallic. * * @name fmh + * @group effects * @param {number | Pattern} harmonicity * @example * note("c e g b g e") @@ -450,6 +487,7 @@ export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh'); * Controls the modulation index, which defines the brightness of the sound. * * @name fm + * @group effects * @param {number | Pattern} brightness modulation index * @synonyms fmi * @example @@ -464,6 +502,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); * Ramp type of fm envelope. Exp might be a bit broken.. * * @name fmenv + * @group effects * @param {number | Pattern} type lin | exp * @example * note("c e g b g e") @@ -479,6 +518,7 @@ export const { fmenv } = registerControl('fmenv'); * Attack time for the FM envelope: time it takes to reach maximum modulation * * @name fmattack + * @group effects * @param {number | Pattern} time attack time * @example * note("c e g b g e") @@ -493,6 +533,7 @@ export const { fmattack } = registerControl('fmattack'); * Waveform of the fm modulator * * @name fmwave + * @group effects * @param {number | Pattern} wave waveform * @example * n("0 1 2 3".fast(4)).scale("d:minor").s("sine").fmwave("").fm(4).fmh(2.01) @@ -506,6 +547,7 @@ export const { fmwave } = registerControl('fmwave'); * Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase. * * @name fmdecay + * @group effects * @param {number | Pattern} time decay time * @example * note("c e g b g e") @@ -520,6 +562,7 @@ export const { fmdecay } = registerControl('fmdecay'); * Sustain level for the FM envelope: how much modulation is applied after the decay phase * * @name fmsustain + * @group effects * @param {number | Pattern} level sustain level * @example * note("c e g b g e") @@ -538,6 +581,7 @@ export const { fmvelocity } = registerControl('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 + * @group samples * @param {string | Pattern} bank the name of the bank * @example * s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") @@ -549,6 +593,7 @@ export const { bank } = registerControl('bank'); * mix control for the chorus effect * * @name chorus + * @group effects * @param {string | Pattern} chorus mix amount between 0 and 1 * @example * note("d d a# a").s("sawtooth").chorus(.5) @@ -566,6 +611,7 @@ export const { fft } = registerControl('fft'); * Note that the decay is only audible if the sustain value is lower than 1. * * @name decay + * @group effects * @param {number | Pattern} time decay time in seconds * @synonyms dec * @example @@ -577,6 +623,7 @@ export const { decay, dec } = registerControl('decay', 'dec'); * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * * @name sustain + * @group effects * @param {number | Pattern} gain sustain level between 0 and 1 * @synonyms sus * @example @@ -588,6 +635,7 @@ export const { sustain, sus } = registerControl('sustain', 'sus'); * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * * @name release + * @group effects * @param {number | Pattern} time release time in seconds * @synonyms rel * @example @@ -602,6 +650,7 @@ export const { hold } = registerControl('hold'); * can also optionally supply the 'bpq' parameter separated by ':'. * * @name bpf + * @group effects * @param {number | Pattern} frequency center frequency * @synonyms bandf, bp * @example @@ -614,6 +663,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' * Sets the **b**and-**p**ass **q**-factor (resonance). * * @name bpq + * @group effects * @param {number | Pattern} q q factor * @synonyms bandq * @example @@ -628,6 +678,7 @@ export const { bandq, bpq } = registerControl('bandq', 'bpq'); * * @memberof Pattern * @name begin + * @group samples * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample * @example * samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples') @@ -640,6 +691,7 @@ export const { begin } = registerControl('begin'); * * @memberof Pattern * @name end + * @group samples * @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc.. * @example * s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2) @@ -652,6 +704,7 @@ export const { end } = registerControl('end'); * To change the loop region, use loopBegin / loopEnd. * * @name loop + * @group samples * @param {number | Pattern} on If 1, the sample is looped * @example * s("casio").loop(1) @@ -664,6 +717,7 @@ export const { loop } = registerControl('loop'); * Note: Samples starting with wt_ will automatically loop! (wt = wavetable) * * @name loopBegin + * @group samples * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample * @synonyms loopb * @example @@ -677,6 +731,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb'); * Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`! * * @name loopEnd + * @group samples * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample * @synonyms loope * @example @@ -688,6 +743,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * Bit crusher effect. * * @name crush + * @group effects * @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>") @@ -699,6 +755,7 @@ export const { crush } = registerControl('crush'); * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * * @name coarse + * @group effects * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @example * s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>") @@ -710,6 +767,7 @@ export const { coarse } = registerControl('coarse'); * Modulate the amplitude of a sound with a continuous waveform * * @name tremolo + * @group effects * @synonyms trem * @param {number | Pattern} speed modulation speed in HZ * @example @@ -722,6 +780,7 @@ export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremolos * Modulate the amplitude of a sound with a continuous waveform * * @name tremolosync + * @group effects * @synonyms tremsync * @param {number | Pattern} cycles modulation speed in cycles * @example @@ -737,6 +796,7 @@ export const { tremolosync } = registerControl( * Depth of amplitude modulation * * @name tremolodepth + * @group effects * @synonyms tremdepth * @param {number | Pattern} depth * @example @@ -748,6 +808,7 @@ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); * Alter the shape of the modulation waveform * * @name tremoloskew + * @group effects * @synonyms tremskew * @param {number | Pattern} amount between 0 & 1, the shape of the waveform * @example @@ -760,6 +821,7 @@ export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); * Alter the phase of the modulation waveform * * @name tremolophase + * @group effects * @synonyms tremphase * @param {number | Pattern} offset the offset in cycles of the modulation * @example @@ -772,6 +834,7 @@ export const { tremolophase } = registerControl('tremolophase', 'tremphase'); * Shape of amplitude modulation * * @name tremoloshape + * @group effects * @synonyms tremshape * @param {number | Pattern} shape tri | square | sine | saw | ramp * @example @@ -783,6 +846,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); * Filter overdrive for supported filter types * * @name drive + * @group effects * @param {number | Pattern} amount * @example * note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>") @@ -796,6 +860,7 @@ export const { drive } = registerControl('drive'); * Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")` * * @name duckorbit + * @group effects * @synonyms duck * @param {number | Pattern} orbit target orbit * @example @@ -816,6 +881,7 @@ export const { duck } = registerControl('duckorbit', 'duck'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckdepth + * @group effects * @param {number | Pattern} depth depth of modulation from 0 to 1 * @example * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>")) @@ -835,6 +901,7 @@ export const { duckdepth } = registerControl('duckdepth'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckonset + * @group effects * @synonyms duckons * * @param {number | Pattern} time The onset time in seconds @@ -862,6 +929,7 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckattack + * @group effects * @synonyms duckatt * * @param {number | Pattern} time The attack time in seconds @@ -906,6 +974,7 @@ export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', * Allows you to set the output channels on the interface * * @name channels + * @group external_io * @synonyms ch * * @param {number | Pattern} channels pattern the output channels @@ -919,6 +988,7 @@ export const { channels, ch } = registerControl('channels', 'ch'); * Controls the pulsewidth of the pulse oscillator * * @name pw + * @group effects * @param {number | Pattern} pulsewidth * @example * note("{f a c e}%16").s("pulse").pw(".8:1:.2") @@ -931,6 +1001,7 @@ export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); * Controls the lfo rate for the pulsewidth of the pulse oscillator * * @name pwrate + * @group effects * @param {number | Pattern} rate * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") @@ -943,6 +1014,7 @@ export const { pwrate } = registerControl('pwrate'); * Controls the lfo sweep for the pulsewidth of the pulse oscillator * * @name pwsweep + * @group effects * @param {number | Pattern} sweep * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") @@ -954,6 +1026,7 @@ export const { pwsweep } = registerControl('pwsweep'); * Phaser audio effect that approximates popular guitar pedals. * * @name phaser + * @group effects * @synonyms ph * @param {number | Pattern} speed speed of modulation * @example @@ -971,6 +1044,7 @@ export const { phaserrate, ph, phaser } = registerControl( * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 * * @name phasersweep + * @group effects * @synonyms phs * @param {number | Pattern} phasersweep most useful values are between 0 and 4000 * @example @@ -984,6 +1058,7 @@ export const { phasersweep, phs } = registerControl('phasersweep', 'phs'); * The center frequency of the phaser in HZ. Defaults to 1000 * * @name phasercenter + * @group effects * @synonyms phc * @param {number | Pattern} centerfrequency in HZ * @example @@ -998,6 +1073,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); * The amount the signal is affected by the phaser effect. Defaults to 0.75 * * @name phaserdepth + * @group effects * @synonyms phd, phasdp * @param {number | Pattern} depth number between 0 and 1 * @example @@ -1012,6 +1088,7 @@ export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd' * Choose the channel the pattern is sent to in superdirt * * @name channel + * @group effects * @param {number | Pattern} channel channel number * */ @@ -1020,6 +1097,7 @@ export const { channel } = registerControl('channel'); * In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open. * * @name cut + * @group effects * @param {number | Pattern} group cut group number * @example * s("[oh hh]*4").cut(1) @@ -1032,6 +1110,7 @@ export const { cut } = registerControl('cut'); * When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'. * * @name lpf + * @group effects * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms cutoff, ctf, lp * @example @@ -1045,6 +1124,7 @@ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', /** * Sets the lowpass filter envelope modulation depth. * @name lpenv + * @group effects * @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_ * @synonyms lpe * @example @@ -1058,6 +1138,7 @@ export const { lpenv, lpe } = registerControl('lpenv', 'lpe'); /** * Sets the highpass filter envelope modulation depth. * @name hpenv + * @group effects * @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_ * @synonyms hpe * @example @@ -1071,6 +1152,7 @@ export const { hpenv, hpe } = registerControl('hpenv', 'hpe'); /** * Sets the bandpass filter envelope modulation depth. * @name bpenv + * @group effects * @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_ * @synonyms bpe * @example @@ -1084,6 +1166,7 @@ export const { bpenv, bpe } = registerControl('bpenv', 'bpe'); /** * Sets the attack duration for the lowpass filter envelope. * @name lpattack + * @group effects * @param {number | Pattern} attack time of the filter envelope * @synonyms lpa * @example @@ -1097,6 +1180,7 @@ export const { lpattack, lpa } = registerControl('lpattack', 'lpa'); /** * Sets the attack duration for the highpass filter envelope. * @name hpattack + * @group effects * @param {number | Pattern} attack time of the highpass filter envelope * @synonyms hpa * @example @@ -1110,6 +1194,7 @@ export const { hpattack, hpa } = registerControl('hpattack', 'hpa'); /** * Sets the attack duration for the bandpass filter envelope. * @name bpattack + * @group effects * @param {number | Pattern} attack time of the bandpass filter envelope * @synonyms bpa * @example @@ -1123,6 +1208,7 @@ export const { bpattack, bpa } = registerControl('bpattack', 'bpa'); /** * Sets the decay duration for the lowpass filter envelope. * @name lpdecay + * @group effects * @param {number | Pattern} decay time of the filter envelope * @synonyms lpd * @example @@ -1136,6 +1222,7 @@ export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd'); /** * Sets the decay duration for the highpass filter envelope. * @name hpdecay + * @group effects * @param {number | Pattern} decay time of the highpass filter envelope * @synonyms hpd * @example @@ -1150,6 +1237,7 @@ export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd'); /** * Sets the decay duration for the bandpass filter envelope. * @name bpdecay + * @group effects * @param {number | Pattern} decay time of the bandpass filter envelope * @synonyms bpd * @example @@ -1164,6 +1252,7 @@ export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd'); /** * Sets the sustain amplitude for the lowpass filter envelope. * @name lpsustain + * @group effects * @param {number | Pattern} sustain amplitude of the lowpass filter envelope * @synonyms lps * @example @@ -1178,6 +1267,7 @@ export const { lpsustain, lps } = registerControl('lpsustain', 'lps'); /** * Sets the sustain amplitude for the highpass filter envelope. * @name hpsustain + * @group effects * @param {number | Pattern} sustain amplitude of the highpass filter envelope * @synonyms hps * @example @@ -1192,6 +1282,7 @@ export const { hpsustain, hps } = registerControl('hpsustain', 'hps'); /** * Sets the sustain amplitude for the bandpass filter envelope. * @name bpsustain + * @group effects * @param {number | Pattern} sustain amplitude of the bandpass filter envelope * @synonyms bps * @example @@ -1206,6 +1297,7 @@ export const { bpsustain, bps } = registerControl('bpsustain', 'bps'); /** * Sets the release time for the lowpass filter envelope. * @name lprelease + * @group effects * @param {number | Pattern} release time of the filter envelope * @synonyms lpr * @example @@ -1221,6 +1313,7 @@ export const { lprelease, lpr } = registerControl('lprelease', 'lpr'); /** * Sets the release time for the highpass filter envelope. * @name hprelease + * @group effects * @param {number | Pattern} release time of the highpass filter envelope * @synonyms hpr * @example @@ -1236,6 +1329,7 @@ export const { hprelease, hpr } = registerControl('hprelease', 'hpr'); /** * Sets the release time for the bandpass filter envelope. * @name bprelease + * @group effects * @param {number | Pattern} release time of the bandpass filter envelope * @synonyms bpr * @example @@ -1251,6 +1345,7 @@ export const { bprelease, bpr } = registerControl('bprelease', 'bpr'); /** * Sets the filter type. The ladder filter is more aggressive. More types might be added in the future. * @name ftype + * @group effects * @param {number | Pattern} type 12db (0), ladder (1), or 24db (2) * @example * note("{f g g c d a a#}%8").s("sawtooth").lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1) @@ -1266,6 +1361,7 @@ export const { ftype } = registerControl('ftype'); /** * controls the center of the filter envelope. 0 is unipolar positive, .5 is bipolar, 1 is unipolar negative * @name fanchor + * @group effects * @param {number | Pattern} center 0 to 1 * @example * note("{f g g c d a a#}%8").s("sawtooth").lpf("{1000}%2") @@ -1278,6 +1374,7 @@ export const { fanchor } = registerControl('fanchor'); * When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'. * * @name hpf + * @group effects * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms hp, hcutoff * @example @@ -1292,6 +1389,7 @@ export const { fanchor } = registerControl('fanchor'); * Applies a vibrato to the frequency of the oscillator. * * @name vib + * @group effects * @synonyms vibrato, v * @param {number | Pattern} frequency of the vibrato in hertz * @example @@ -1309,6 +1407,7 @@ export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', * Adds pink noise to the mix * * @name noise + * @group generators * @param {number | Pattern} wet wet amount * @example * sound("/2") @@ -1318,6 +1417,7 @@ export const { noise } = registerControl('noise'); * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * * @name vibmod + * @group effects * @synonyms vmod * @param {number | Pattern} depth of vibrato (in semitones) * @example @@ -1336,6 +1436,7 @@ export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'h * Controls the **h**igh-**p**ass **q**-value. * * @name hpq + * @group effects * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms hresonance * @example @@ -1347,6 +1448,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); * Controls the **l**ow-**p**ass **q**-value. * * @name lpq + * @group effects * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms resonance * @example @@ -1359,6 +1461,7 @@ export const { resonance, lpq } = registerControl('resonance', 'lpq'); * DJ filter, below 0.5 is low pass filter, above is high pass filter. * * @name djf + * @group effects * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter * @example * n(irand(16).seg(8)).scale("d:phrygian").s("supersaw").djf("<.5 .3 .2 .75>") @@ -1375,6 +1478,7 @@ export const { djf } = registerControl('djf'); * * * @name delay + * @group effects * @param {number | Pattern} level between 0 and 1 * @example * s("bd bd").delay("<0 .25 .5 1>") @@ -1388,6 +1492,7 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback'] * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback + * @group effects * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1401,6 +1506,7 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback + * @group effects * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1412,6 +1518,7 @@ export const { delayspeed } = registerControl('delayspeed'); * Sets the time of the delay effect. * * @name delayspeed + * @group effects * @param {number | Pattern} delayspeed controls the pitch of the delay feedback * @synonyms delayt, dt * @example @@ -1424,6 +1531,7 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', * Sets the time of the delay effect in cycles. * * @name delaysync + * @group effects * @param {number | Pattern} cycles delay length in cycles * @synonyms delayt, dt * @example @@ -1436,6 +1544,7 @@ export const { delaysync } = registerControl('delaysync'); * Specifies whether delaytime is calculated relative to cps. * * @name lock + * @group effects * @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle. * @superdirtOnly * @example @@ -1449,6 +1558,7 @@ export const { lock } = registerControl('lock'); * Set detune for stacked voices of supported oscillators * * @name detune + * @group effects * @param {number | Pattern} amount * @synonyms det * @example @@ -1460,6 +1570,7 @@ export const { detune, det } = registerControl('detune', 'det'); * Set number of stacked voices for supported oscillators * * @name unison + * @group effects * @param {number | Pattern} numvoices * @example * note("d f a a# a d3").fast(2).s("supersaw").unison("<1 2 7>") @@ -1471,6 +1582,7 @@ export const { unison } = registerControl('unison'); * Set the stereo pan spread for supported oscillators * * @name spread + * @group effects * @param {number | Pattern} spread between 0 and 1 * @example * note("d f a a# a d3").fast(2).s("supersaw").spread("<0 .3 1>") @@ -1481,6 +1593,7 @@ export const { spread } = registerControl('spread'); * Set dryness of reverb. See `room` and `size` for more information about reverb. * * @name dry + * @group effects * @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() @@ -1506,6 +1619,7 @@ export const { fadeInTime } = registerControl('fadeInTime'); * Set frequency of sound. * * @name freq + * @group transforms * @param {number | Pattern} frequency in Hz. the audible range is between 20 and 20000 Hz * @example * freq("220 110 440 110").s("superzow").osc() @@ -1519,6 +1633,7 @@ export const { freq } = registerControl('freq'); * Attack time of pitch envelope. * * @name pattack + * @group effects * @synonyms patt * @param {number | Pattern} time time in seconds * @example @@ -1530,6 +1645,7 @@ export const { pattack, patt } = registerControl('pattack', 'patt'); * Decay time of pitch envelope. * * @name pdecay + * @group effects * @synonyms pdec * @param {number | Pattern} time time in seconds * @example @@ -1543,6 +1659,7 @@ export const { psustain, psus } = registerControl('psustain', 'psus'); * Release time of pitch envelope * * @name prelease + * @group effects * @synonyms prel * @param {number | Pattern} time time in seconds * @example @@ -1557,6 +1674,7 @@ export const { prelease, prel } = registerControl('prelease', 'prel'); * If you don't set other pitch envelope controls, `pattack:.2` will be the default. * * @name penv + * @group effects * @param {number | Pattern} semitones change in semitones * @example * note("c") @@ -1568,6 +1686,7 @@ export const { penv } = registerControl('penv'); * Curve of envelope. Defaults to linear. exponential is good for kicks * * @name pcurve + * @group effects * @param {number | Pattern} type 0 = linear, 1 = exponential * @example * note("g1*4") @@ -1584,6 +1703,7 @@ export const { pcurve } = registerControl('pcurve'); * If you don't set an anchor, the value will default to the psustain value. * * @name panchor + * @group effects * @param {number | Pattern} anchor anchor offset * @example * note("c c4").penv(12).panchor("<0 .5 1 .5>") @@ -1605,6 +1725,7 @@ export const { gate, gat } = registerControl('gate', 'gat'); * Emulation of a Leslie speaker: speakers rotating in a wooden amplified cabinet. * * @name leslie + * @group effects * @param {number | Pattern} wet between 0 and 1 * @example * n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc() @@ -1616,6 +1737,7 @@ export const { leslie } = registerControl('leslie'); * Rate of modulation / rotation for leslie effect * * @name lrate + * @group effects * @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() @@ -1628,6 +1750,7 @@ export const { lrate } = registerControl('lrate'); * Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble) * * @name lsize + * @group effects * @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() @@ -1639,6 +1762,7 @@ export const { lsize } = registerControl('lsize'); * Sets the displayed text for an event on the pianoroll * * @name label + * @group visualization * @param {string} label text to display */ export const { activeLabel } = registerControl('activeLabel'); @@ -1704,6 +1828,7 @@ export const { overshape } = registerControl('overshape'); * Sets position in stereo. * * @name pan + * @group effects * @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>") @@ -1769,6 +1894,7 @@ export const { mode } = registerControl(['mode', 'anchor']); * When using mininotation, you can also optionally add the 'size' parameter, separated by ':'. * * @name room + * @group effects * @param {number | Pattern} level between 0 and 1 * @example * s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>") @@ -1782,6 +1908,7 @@ export const { room } = registerControl(['room', 'size']); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomlp + * @group effects * @synonyms rlp * @param {number} frequency between 0 and 20000hz * @example @@ -1795,6 +1922,7 @@ export const { roomlp, rlp } = registerControl('roomlp', 'rlp'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomdim + * @group effects * @synonyms rdim * @param {number} frequency between 0 and 20000hz * @example @@ -1809,6 +1937,7 @@ export const { roomdim, rdim } = registerControl('roomdim', 'rdim'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomfade + * @group effects * @synonyms rfade * @param {number} seconds for the reverb to fade * @example @@ -1821,6 +1950,7 @@ export const { roomfade, rfade } = registerControl('roomfade', 'rfade'); /** * Sets the sample to use as an impulse response for the reverb. * @name iresponse + * @group effects * @param {string | Pattern} sample to use as an impulse response * @synonyms ir * @example @@ -1832,6 +1962,7 @@ export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse'); /** * Sets speed of the sample for the impulse response. * @name irspeed + * @group effects * @param {string | Pattern} speed * @example * samples('github:switchangel/pad') @@ -1843,6 +1974,7 @@ export const { irspeed } = registerControl('irspeed'); /** * Sets the beginning of the IR response sample * @name irbegin + * @group effects * @param {string | Pattern} begin between 0 and 1 * @synonyms ir * @example @@ -1856,6 +1988,7 @@ export const { irbegin } = registerControl('irbegin'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomsize + * @group effects * @param {number | Pattern} size between 0 and 10 * @synonyms rsize, sz, size * @example @@ -1879,6 +2012,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', * * * @name shape + * @group effects * @param {number | Pattern} distortion between 0 and 1 * @example * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") @@ -1891,6 +2025,7 @@ export const { shape } = registerControl(['shape', 'shapevol']); * Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;) * * @name distort + * @group effects * @synonyms dist * @param {number | Pattern} distortion * @example @@ -1905,6 +2040,7 @@ export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dis * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) * * @name compressor + * @group effects * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02") @@ -1925,6 +2061,7 @@ export const { compressorRelease } = registerControl('compressorRelease'); * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * * @name speed + * @group effects * @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards. * @example * s("bd*6").speed("1 2 4 1 -2 -4") @@ -1938,6 +2075,7 @@ export const { speed } = registerControl('speed'); * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * * @name stretch + * @group effects * @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards. * @example * s("gm_flute").stretch("1 2 .5") @@ -1948,6 +2086,7 @@ export const { stretch } = registerControl('stretch'); * Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * * @name unit + * @group effects * @param {number | string | Pattern} unit see description above * @example * speed("1 2 .5 3").s("bd").unit("c").osc() @@ -1962,6 +2101,7 @@ export const { unit } = registerControl('unit'); * "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 + * @group effects * @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() @@ -1986,6 +2126,7 @@ export const { squiz } = registerControl('squiz'); * Formant filter to make things sound like vowels. * * @name vowel + * @group effects * @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ. * @example * note("[c2 >]*2").s('sawtooth') @@ -2010,6 +2151,7 @@ export const { waveloss } = registerControl('waveloss'); * Noise crackle density * * @name density + * @group effects * @param {number | Pattern} density between 0 and x * @example * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)) @@ -2060,6 +2202,7 @@ export const { cps } = registerControl('cps'); * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. * * @name clip + * @group transforms * @synonyms legato * @param {number | Pattern} factor >= 0 * @example @@ -2072,6 +2215,7 @@ export const { clip, legato } = registerControl('clip', 'legato'); * Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration. * * @name duration + * @group transforms * @synonyms dur * @param {number | Pattern} seconds >= 0 * @example @@ -2100,6 +2244,7 @@ export const { zzfx } = registerControl('zzfx'); /** * Sets the color of the hap in visualizations like pianoroll or highlighting. * @name color + * @group visualization * @synonyms colour * @param {string} color Hexadecimal or CSS color name */ @@ -2114,6 +2259,7 @@ export let createParams = (...names) => * ADSR envelope: Combination of Attack, Decay, Sustain, and Release. * * @name adsr + * @group effects * @param {number | Pattern} time attack time in seconds * @param {number | Pattern} time decay time in seconds * @param {number | Pattern} gain sustain level (0 to 1) @@ -2148,6 +2294,7 @@ export const ar = register('ar', (t, pat) => { * MIDI channel: Sets the MIDI channel for the event. * * @name midichan + * @group examples * @param {number | Pattern} channel MIDI channel number (0-15) * @example * note("c4").midichan(1).midi() @@ -2160,6 +2307,7 @@ export const { midimap } = registerControl('midimap'); * MIDI port: Sets the MIDI port for the event. * * @name midiport + * @group examples * @param {number | Pattern} port MIDI port * @example * note("c a f e").midiport("<0 1 2 3>").midi() @@ -2170,6 +2318,7 @@ export const { midiport } = registerControl('midiport'); * MIDI command: Sends a MIDI command message. * * @name midicmd + * @group examples * @param {number | Pattern} command MIDI command * @example * midicmd("clock*48,/2").midi() @@ -2180,6 +2329,7 @@ export const { midicmd } = registerControl('midicmd'); * MIDI control: Sends a MIDI control change message. * * @name control + * @group examples * @param {number | Pattern} MIDI control number (0-127) * @param {number | Pattern} MIDI controller value (0-127) */ @@ -2195,6 +2345,7 @@ export const control = register('control', (args, pat) => { * MIDI control number: Sends a MIDI control change message. * * @name ccn + * @group examples * @param {number | Pattern} MIDI control number (0-127) */ export const { ccn } = registerControl('ccn'); @@ -2202,6 +2353,7 @@ export const { ccn } = registerControl('ccn'); * MIDI control value: Sends a MIDI control change message. * * @name ccv + * @group examples * @param {number | Pattern} MIDI control value (0-127) */ export const { ccv } = registerControl('ccv'); @@ -2211,6 +2363,7 @@ export const { ctlNum } = registerControl('ctlNum'); /** * MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message. * @name nrpnn + * @group examples * @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127) * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() @@ -2219,6 +2372,7 @@ export const { nrpnn } = registerControl('nrpnn'); /** * MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message. * @name nrpv + * @group examples * @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127) * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() @@ -2229,6 +2383,7 @@ export const { nrpv } = registerControl('nrpv'); * MIDI program number: Sends a MIDI program change message. * * @name progNum + * @group examples * @param {number | Pattern} program MIDI program number (0-127) * @example * note("c4").progNum(10).midichan(1).midi() @@ -2238,6 +2393,7 @@ export const { progNum } = registerControl('progNum'); /** * MIDI sysex: Sends a MIDI sysex message. * @name sysex + * @group examples * @param {number | Pattern} id Sysex ID * @param {number | Pattern} data Sysex data * @example @@ -2253,6 +2409,7 @@ export const sysex = register('sysex', (args, pat) => { /** * MIDI sysex ID: Sends a MIDI sysex identifier message. * @name sysexid + * @group examples * @param {number | Pattern} id Sysex ID * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() @@ -2261,6 +2418,7 @@ export const { sysexid } = registerControl('sysexid'); /** * MIDI sysex data: Sends a MIDI sysex message. * @name sysexdata + * @group examples * @param {number | Pattern} data Sysex data * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() @@ -2270,6 +2428,7 @@ export const { sysexdata } = registerControl('sysexdata'); /** * MIDI pitch bend: Sends a MIDI pitch bend message. * @name midibend + * @group examples * @param {number | Pattern} midibend MIDI pitch bend (-1 - 1) * @example * note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi() @@ -2278,6 +2437,7 @@ export const { midibend } = registerControl('midibend'); /** * MIDI key after touch: Sends a MIDI key after touch message. * @name miditouch + * @group examples * @param {number | Pattern} miditouch MIDI key after touch (0-1) * @example * note("c4").miditouch(sine.slow(4).range(0,1)).midi() @@ -2298,6 +2458,7 @@ export const getControlName = (alias) => { * Sets properties in a batch. * * @name as + * @group transforms * @param {String | Array} mapping the control names that are set * @example * "c:.5 a:1 f:.25 e:.8".as("note:clip") @@ -2317,6 +2478,7 @@ export const as = register('as', (mapping, pat) => { * Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file * in the optional array syntax ex: "0.5:2", the second value controls the speed of playback * @name scrub + * @group samples * @memberof Pattern * @returns Pattern * @example diff --git a/packages/superdough/reverbGen.mjs b/packages/superdough/reverbGen.mjs index d2533cae..0d5fee26 100644 --- a/packages/superdough/reverbGen.mjs +++ b/packages/superdough/reverbGen.mjs @@ -79,7 +79,9 @@ reverbGen.generateGraph = function (data, width, height, min, max) { @param {number} lpFreqEnd @param {number} lpFreqEndAt @param {!function(!AudioBuffer)} callback May be called - immediately within the current execution context, or later.*/ + immediately within the current execution context, or later. + @group effects + */ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) { if (lpFreqStart == 0) { callback(input); From 5032b4e966ecd3c92c47497457a5bf0576d85a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 11 Oct 2025 14:11:50 +0200 Subject: [PATCH 008/200] Rename group -> tags --- jsdoc/jsdoc-synonyms.js | 4 +- packages/core/controls.mjs | 324 +++++++++++++++--------------- packages/core/pattern.mjs | 270 ++++++++++++------------- packages/core/signal.mjs | 104 +++++----- packages/motion/motion.mjs | 30 +-- packages/superdough/reverbGen.mjs | 2 +- packages/supradough/dough.mjs | 2 +- 7 files changed, 368 insertions(+), 368 deletions(-) diff --git a/jsdoc/jsdoc-synonyms.js b/jsdoc/jsdoc-synonyms.js index 423c7ad6..c5860626 100644 --- a/jsdoc/jsdoc-synonyms.js +++ b/jsdoc/jsdoc-synonyms.js @@ -13,10 +13,10 @@ function defineTags(dictionary) { }, }); - dictionary.defineTag('group', { + dictionary.defineTag('tags', { mustHaveValue: true, onTagged: function (doclet, tag) { - doclet.group = tag.value; + doclet.tags = tag.value.split(/[ ,]+/); }, }); } diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index e43af896..44b358e6 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -77,7 +77,7 @@ export function registerControl(names, ...aliases) { * separated by ':'. * * @name s - * @group samples + * @tags samples * @param {string | Pattern} sound The sound / pattern of sounds to pick * @synonyms sound * @example @@ -92,7 +92,7 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); * Position in the wavetable of the wavetable oscillator * * @name wt - * @group effects + * @tags effects * @param {number | Pattern} position Position in the wavetable from 0 to 1 * @synonyms wavetablePosition * @example @@ -104,7 +104,7 @@ export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePositio * Amount of envelope applied wavetable oscillator's position envelope * * @name wtenv - * @group effects + * @tags effects * @param {number | Pattern} amount between 0 and 1 */ export const { wtenv } = registerControl('wtenv'); @@ -112,7 +112,7 @@ export const { wtenv } = registerControl('wtenv'); * Attack time of the wavetable oscillator's position envelope * * @name wtattack - * @group effects + * @tags effects * @synonyms wtatt * @param {number | Pattern} time attack time in seconds */ @@ -122,7 +122,7 @@ export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt'); * Decay time of the wavetable oscillator's position envelope * * @name wtdecay - * @group effects + * @tags effects * @synonyms wtdec * @param {number | Pattern} time decay time in seconds */ @@ -132,7 +132,7 @@ export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec'); * Sustain time of the wavetable oscillator's position envelope * * @name wtsustain - * @group effects + * @tags effects * @synonyms wtsus * @param {number | Pattern} gain sustain level (0 to 1) */ @@ -142,7 +142,7 @@ export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus'); * Release time of the wavetable oscillator's position envelope * * @name wtrelease - * @group effects + * @tags effects * @synonyms wtrel * @param {number | Pattern} time release time in seconds */ @@ -152,7 +152,7 @@ export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel'); * Rate of the LFO for the wavetable oscillator's position * * @name wtrate - * @group effects + * @tags effects * @param {number | Pattern} rate rate in hertz */ export const { wtrate } = registerControl('wtrate'); @@ -160,7 +160,7 @@ export const { wtrate } = registerControl('wtrate'); * cycle synced rate of the LFO for the wavetable oscillator's position * * @name wtsync - * @group effects + * @tags effects * @param {number | Pattern} rate rate in cycles */ export const { wtsync } = registerControl('wtsync'); @@ -169,7 +169,7 @@ export const { wtsync } = registerControl('wtsync'); * Depth of the LFO for the wavetable oscillator's position * * @name wtdepth - * @group effects + * @tags effects * @param {number | Pattern} depth depth of modulation */ export const { wtdepth } = registerControl('wtdepth'); @@ -178,7 +178,7 @@ export const { wtdepth } = registerControl('wtdepth'); * Shape of the LFO for the wavetable oscillator's position * * @name wtshape - * @group effects + * @tags effects * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { wtshape } = registerControl('wtshape'); @@ -187,7 +187,7 @@ export const { wtshape } = registerControl('wtshape'); * DC offset of the LFO for the wavetable oscillator's position * * @name wtdc - * @group effects + * @tags effects * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { wtdc } = registerControl('wtdc'); @@ -196,7 +196,7 @@ export const { wtdc } = registerControl('wtdc'); * Skew of the LFO for the wavetable oscillator's position * * @name wtskew - * @group effects + * @tags effects * @param {number | Pattern} skew How much to bend the LFO shape */ export const { wtskew } = registerControl('wtskew'); @@ -205,7 +205,7 @@ export const { wtskew } = registerControl('wtskew'); * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator * * @name warp - * @group effects + * @tags effects * @param {number | Pattern} amount Warp of the wavetable from 0 to 1 * @synonyms wavetableWarp * @example @@ -218,7 +218,7 @@ export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp'); * Attack time of the wavetable oscillator's warp envelope * * @name warpattack - * @group effects + * @tags effects * @synonyms warpatt * @param {number | Pattern} time attack time in seconds */ @@ -228,7 +228,7 @@ export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt'); * Decay time of the wavetable oscillator's warp envelope * * @name warpdecay - * @group effects + * @tags effects * @synonyms warpdec * @param {number | Pattern} time decay time in seconds */ @@ -238,7 +238,7 @@ export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec'); * Sustain time of the wavetable oscillator's warp envelope * * @name warpsustain - * @group effects + * @tags effects * @synonyms warpsus * @param {number | Pattern} gain sustain level (0 to 1) */ @@ -248,7 +248,7 @@ export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus' * Release time of the wavetable oscillator's warp envelope * * @name warprelease - * @group effects + * @tags effects * @synonyms warprel * @param {number | Pattern} time release time in seconds */ @@ -258,7 +258,7 @@ export const { warprelease, warprel } = registerControl('warprelease', 'warprel' * Rate of the LFO for the wavetable oscillator's warp * * @name warprate - * @group effects + * @tags effects * @param {number | Pattern} rate rate in hertz */ export const { warprate } = registerControl('warprate'); @@ -267,7 +267,7 @@ export const { warprate } = registerControl('warprate'); * Depth of the LFO for the wavetable oscillator's warp * * @name warpdepth - * @group effects + * @tags effects * @param {number | Pattern} depth depth of modulation */ export const { warpdepth } = registerControl('warpdepth'); @@ -276,7 +276,7 @@ export const { warpdepth } = registerControl('warpdepth'); * Shape of the LFO for the wavetable oscillator's warp * * @name warpshape - * @group effects + * @tags effects * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { warpshape } = registerControl('warpshape'); @@ -285,7 +285,7 @@ export const { warpshape } = registerControl('warpshape'); * DC offset of the LFO for the wavetable oscillator's warp * * @name warpdc - * @group effects + * @tags effects * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { warpdc } = registerControl('warpdc'); @@ -294,7 +294,7 @@ export const { warpdc } = registerControl('warpdc'); * Skew of the LFO for the wavetable oscillator's warp * * @name warpskew - * @group effects + * @tags effects * @param {number | Pattern} skew How much to bend the LFO shape */ export const { warpskew } = registerControl('warpskew'); @@ -306,7 +306,7 @@ export const { warpskew } = registerControl('warpskew'); * spin, chaos, primes, binary, brownian, reciprocal, wormhole, logistic, sigmoid, fractal, flip * * @name warpmode - * @group effects + * @tags effects * @param {number | string | Pattern} mode Warp mode * @synonyms wavetableWarpMode * @example @@ -320,7 +320,7 @@ export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wave * Amount of randomness of the initial phase of the wavetable oscillator. * * @name wtphaserand - * @group effects + * @tags effects * @param {number | Pattern} amount Randomness of the initial phase. Between 0 (not random) and 1 (fully random) * @synonyms wavetablePhaseRand * @example @@ -333,7 +333,7 @@ export const { wtphaserand, wavetablePhaseRand } = registerControl('wtphaserand' * Amount of envelope applied wavetable oscillator's position envelope * * @name warpenv - * @group effects + * @tags effects * @param {number | Pattern} amount between 0 and 1 */ export const { warpenv } = registerControl('warpenv'); @@ -342,7 +342,7 @@ export const { warpenv } = registerControl('warpenv'); * cycle synced rate of the LFO for the wavetable warp position * * @name warpsync - * @group effects + * @tags effects * @param {number | Pattern} rate rate in cycles */ export const { warpsync } = registerControl('warpsync'); @@ -351,7 +351,7 @@ export const { warpsync } = registerControl('warpsync'); * Define a custom webaudio node to use as a sound source. * * @name source - * @group external_io + * @tags external_io * @synonyms src * @param {function} getSource * @synonyms src @@ -364,7 +364,7 @@ export const { source, src } = registerControl('source', 'src'); * `n` can also be used to play midi numbers, but it is recommended to use `note` instead. * * @name n - * @group samples + * @tags samples * @param {number | Pattern} value sample index starting from 0 * @example * s("bd sd [~ bd] sd,hh*6").n("<0 1>") @@ -383,7 +383,7 @@ export const { n } = registerControl('n'); * You can also use midi numbers instead of note names, where 69 is mapped to A4 440Hz in 12EDO. * * @name note - * @group music_theory + * @tags music_theory * @example * note("c a f e") * @example @@ -399,7 +399,7 @@ export const { note } = registerControl(['note', 'n']); * A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt. * * @name accelerate - * @group samples + * @tags samples * @param {number | Pattern} amount acceleration. * @superdirtOnly * @example @@ -411,7 +411,7 @@ export const { accelerate } = registerControl('accelerate'); * Sets the velocity from 0 to 1. Is multiplied together with gain. * * @name velocity - * @group effects + * @tags effects * @example * s("hh*8") * .gain(".4!2 1 .4!2 1 .4 1") @@ -422,7 +422,7 @@ export const { velocity } = registerControl('velocity'); * Controls the gain by an exponential amount. * * @name gain - * @group effects + * @tags effects * @param {number | Pattern} amount gain. * @example * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) @@ -433,7 +433,7 @@ export const { gain } = registerControl('gain'); * Gain applied after all effects have been processed. * * @name postgain - * @group effects + * @tags effects * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02").postgain(1.5) @@ -444,7 +444,7 @@ export const { postgain } = registerControl('postgain'); * Like `gain`, but linear. * * @name amp - * @group effects + * @tags effects * @param {number | Pattern} amount gain. * @superdirtOnly * @example @@ -456,7 +456,7 @@ export const { amp } = registerControl('amp'); * Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * * @name attack - * @group effects + * @tags effects * @param {number | Pattern} attack time in seconds. * @synonyms att * @example @@ -472,7 +472,7 @@ export const { attack, att } = registerControl('attack', 'att'); * while decimal numbers and complex ratios sound metallic. * * @name fmh - * @group effects + * @tags effects * @param {number | Pattern} harmonicity * @example * note("c e g b g e") @@ -487,7 +487,7 @@ export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh'); * Controls the modulation index, which defines the brightness of the sound. * * @name fm - * @group effects + * @tags effects * @param {number | Pattern} brightness modulation index * @synonyms fmi * @example @@ -502,7 +502,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); * Ramp type of fm envelope. Exp might be a bit broken.. * * @name fmenv - * @group effects + * @tags effects * @param {number | Pattern} type lin | exp * @example * note("c e g b g e") @@ -518,7 +518,7 @@ export const { fmenv } = registerControl('fmenv'); * Attack time for the FM envelope: time it takes to reach maximum modulation * * @name fmattack - * @group effects + * @tags effects * @param {number | Pattern} time attack time * @example * note("c e g b g e") @@ -533,7 +533,7 @@ export const { fmattack } = registerControl('fmattack'); * Waveform of the fm modulator * * @name fmwave - * @group effects + * @tags effects * @param {number | Pattern} wave waveform * @example * n("0 1 2 3".fast(4)).scale("d:minor").s("sine").fmwave("").fm(4).fmh(2.01) @@ -547,7 +547,7 @@ export const { fmwave } = registerControl('fmwave'); * Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase. * * @name fmdecay - * @group effects + * @tags effects * @param {number | Pattern} time decay time * @example * note("c e g b g e") @@ -562,7 +562,7 @@ export const { fmdecay } = registerControl('fmdecay'); * Sustain level for the FM envelope: how much modulation is applied after the decay phase * * @name fmsustain - * @group effects + * @tags effects * @param {number | Pattern} level sustain level * @example * note("c e g b g e") @@ -581,7 +581,7 @@ export const { fmvelocity } = registerControl('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 - * @group samples + * @tags samples * @param {string | Pattern} bank the name of the bank * @example * s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") @@ -593,7 +593,7 @@ export const { bank } = registerControl('bank'); * mix control for the chorus effect * * @name chorus - * @group effects + * @tags effects * @param {string | Pattern} chorus mix amount between 0 and 1 * @example * note("d d a# a").s("sawtooth").chorus(.5) @@ -611,7 +611,7 @@ export const { fft } = registerControl('fft'); * Note that the decay is only audible if the sustain value is lower than 1. * * @name decay - * @group effects + * @tags effects * @param {number | Pattern} time decay time in seconds * @synonyms dec * @example @@ -623,7 +623,7 @@ export const { decay, dec } = registerControl('decay', 'dec'); * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * * @name sustain - * @group effects + * @tags effects * @param {number | Pattern} gain sustain level between 0 and 1 * @synonyms sus * @example @@ -635,7 +635,7 @@ export const { sustain, sus } = registerControl('sustain', 'sus'); * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * * @name release - * @group effects + * @tags effects * @param {number | Pattern} time release time in seconds * @synonyms rel * @example @@ -650,7 +650,7 @@ export const { hold } = registerControl('hold'); * can also optionally supply the 'bpq' parameter separated by ':'. * * @name bpf - * @group effects + * @tags effects * @param {number | Pattern} frequency center frequency * @synonyms bandf, bp * @example @@ -663,7 +663,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' * Sets the **b**and-**p**ass **q**-factor (resonance). * * @name bpq - * @group effects + * @tags effects * @param {number | Pattern} q q factor * @synonyms bandq * @example @@ -678,7 +678,7 @@ export const { bandq, bpq } = registerControl('bandq', 'bpq'); * * @memberof Pattern * @name begin - * @group samples + * @tags samples * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample * @example * samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples') @@ -691,7 +691,7 @@ export const { begin } = registerControl('begin'); * * @memberof Pattern * @name end - * @group samples + * @tags samples * @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc.. * @example * s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2) @@ -704,7 +704,7 @@ export const { end } = registerControl('end'); * To change the loop region, use loopBegin / loopEnd. * * @name loop - * @group samples + * @tags samples * @param {number | Pattern} on If 1, the sample is looped * @example * s("casio").loop(1) @@ -717,7 +717,7 @@ export const { loop } = registerControl('loop'); * Note: Samples starting with wt_ will automatically loop! (wt = wavetable) * * @name loopBegin - * @group samples + * @tags samples * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample * @synonyms loopb * @example @@ -731,7 +731,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb'); * Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`! * * @name loopEnd - * @group samples + * @tags samples * @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample * @synonyms loope * @example @@ -743,7 +743,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * Bit crusher effect. * * @name crush - * @group effects + * @tags effects * @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>") @@ -755,7 +755,7 @@ export const { crush } = registerControl('crush'); * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * * @name coarse - * @group effects + * @tags effects * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @example * s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>") @@ -767,7 +767,7 @@ export const { coarse } = registerControl('coarse'); * Modulate the amplitude of a sound with a continuous waveform * * @name tremolo - * @group effects + * @tags effects * @synonyms trem * @param {number | Pattern} speed modulation speed in HZ * @example @@ -780,7 +780,7 @@ export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremolos * Modulate the amplitude of a sound with a continuous waveform * * @name tremolosync - * @group effects + * @tags effects * @synonyms tremsync * @param {number | Pattern} cycles modulation speed in cycles * @example @@ -796,7 +796,7 @@ export const { tremolosync } = registerControl( * Depth of amplitude modulation * * @name tremolodepth - * @group effects + * @tags effects * @synonyms tremdepth * @param {number | Pattern} depth * @example @@ -808,7 +808,7 @@ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); * Alter the shape of the modulation waveform * * @name tremoloskew - * @group effects + * @tags effects * @synonyms tremskew * @param {number | Pattern} amount between 0 & 1, the shape of the waveform * @example @@ -821,7 +821,7 @@ export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); * Alter the phase of the modulation waveform * * @name tremolophase - * @group effects + * @tags effects * @synonyms tremphase * @param {number | Pattern} offset the offset in cycles of the modulation * @example @@ -834,7 +834,7 @@ export const { tremolophase } = registerControl('tremolophase', 'tremphase'); * Shape of amplitude modulation * * @name tremoloshape - * @group effects + * @tags effects * @synonyms tremshape * @param {number | Pattern} shape tri | square | sine | saw | ramp * @example @@ -846,7 +846,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); * Filter overdrive for supported filter types * * @name drive - * @group effects + * @tags effects * @param {number | Pattern} amount * @example * note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>") @@ -860,7 +860,7 @@ export const { drive } = registerControl('drive'); * Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")` * * @name duckorbit - * @group effects + * @tags effects * @synonyms duck * @param {number | Pattern} orbit target orbit * @example @@ -881,7 +881,7 @@ export const { duck } = registerControl('duckorbit', 'duck'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckdepth - * @group effects + * @tags effects * @param {number | Pattern} depth depth of modulation from 0 to 1 * @example * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>")) @@ -901,7 +901,7 @@ export const { duckdepth } = registerControl('duckdepth'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckonset - * @group effects + * @tags effects * @synonyms duckons * * @param {number | Pattern} time The onset time in seconds @@ -929,7 +929,7 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckattack - * @group effects + * @tags effects * @synonyms duckatt * * @param {number | Pattern} time The attack time in seconds @@ -974,7 +974,7 @@ export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', * Allows you to set the output channels on the interface * * @name channels - * @group external_io + * @tags external_io * @synonyms ch * * @param {number | Pattern} channels pattern the output channels @@ -988,7 +988,7 @@ export const { channels, ch } = registerControl('channels', 'ch'); * Controls the pulsewidth of the pulse oscillator * * @name pw - * @group effects + * @tags effects * @param {number | Pattern} pulsewidth * @example * note("{f a c e}%16").s("pulse").pw(".8:1:.2") @@ -1001,7 +1001,7 @@ export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); * Controls the lfo rate for the pulsewidth of the pulse oscillator * * @name pwrate - * @group effects + * @tags effects * @param {number | Pattern} rate * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") @@ -1014,7 +1014,7 @@ export const { pwrate } = registerControl('pwrate'); * Controls the lfo sweep for the pulsewidth of the pulse oscillator * * @name pwsweep - * @group effects + * @tags effects * @param {number | Pattern} sweep * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") @@ -1026,7 +1026,7 @@ export const { pwsweep } = registerControl('pwsweep'); * Phaser audio effect that approximates popular guitar pedals. * * @name phaser - * @group effects + * @tags effects * @synonyms ph * @param {number | Pattern} speed speed of modulation * @example @@ -1044,7 +1044,7 @@ export const { phaserrate, ph, phaser } = registerControl( * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 * * @name phasersweep - * @group effects + * @tags effects * @synonyms phs * @param {number | Pattern} phasersweep most useful values are between 0 and 4000 * @example @@ -1058,7 +1058,7 @@ export const { phasersweep, phs } = registerControl('phasersweep', 'phs'); * The center frequency of the phaser in HZ. Defaults to 1000 * * @name phasercenter - * @group effects + * @tags effects * @synonyms phc * @param {number | Pattern} centerfrequency in HZ * @example @@ -1073,7 +1073,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); * The amount the signal is affected by the phaser effect. Defaults to 0.75 * * @name phaserdepth - * @group effects + * @tags effects * @synonyms phd, phasdp * @param {number | Pattern} depth number between 0 and 1 * @example @@ -1088,7 +1088,7 @@ export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd' * Choose the channel the pattern is sent to in superdirt * * @name channel - * @group effects + * @tags effects * @param {number | Pattern} channel channel number * */ @@ -1097,7 +1097,7 @@ export const { channel } = registerControl('channel'); * In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open. * * @name cut - * @group effects + * @tags effects * @param {number | Pattern} group cut group number * @example * s("[oh hh]*4").cut(1) @@ -1110,7 +1110,7 @@ export const { cut } = registerControl('cut'); * When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'. * * @name lpf - * @group effects + * @tags effects * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms cutoff, ctf, lp * @example @@ -1124,7 +1124,7 @@ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', /** * Sets the lowpass filter envelope modulation depth. * @name lpenv - * @group effects + * @tags effects * @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_ * @synonyms lpe * @example @@ -1138,7 +1138,7 @@ export const { lpenv, lpe } = registerControl('lpenv', 'lpe'); /** * Sets the highpass filter envelope modulation depth. * @name hpenv - * @group effects + * @tags effects * @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_ * @synonyms hpe * @example @@ -1152,7 +1152,7 @@ export const { hpenv, hpe } = registerControl('hpenv', 'hpe'); /** * Sets the bandpass filter envelope modulation depth. * @name bpenv - * @group effects + * @tags effects * @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_ * @synonyms bpe * @example @@ -1166,7 +1166,7 @@ export const { bpenv, bpe } = registerControl('bpenv', 'bpe'); /** * Sets the attack duration for the lowpass filter envelope. * @name lpattack - * @group effects + * @tags effects * @param {number | Pattern} attack time of the filter envelope * @synonyms lpa * @example @@ -1180,7 +1180,7 @@ export const { lpattack, lpa } = registerControl('lpattack', 'lpa'); /** * Sets the attack duration for the highpass filter envelope. * @name hpattack - * @group effects + * @tags effects * @param {number | Pattern} attack time of the highpass filter envelope * @synonyms hpa * @example @@ -1194,7 +1194,7 @@ export const { hpattack, hpa } = registerControl('hpattack', 'hpa'); /** * Sets the attack duration for the bandpass filter envelope. * @name bpattack - * @group effects + * @tags effects * @param {number | Pattern} attack time of the bandpass filter envelope * @synonyms bpa * @example @@ -1208,7 +1208,7 @@ export const { bpattack, bpa } = registerControl('bpattack', 'bpa'); /** * Sets the decay duration for the lowpass filter envelope. * @name lpdecay - * @group effects + * @tags effects * @param {number | Pattern} decay time of the filter envelope * @synonyms lpd * @example @@ -1222,7 +1222,7 @@ export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd'); /** * Sets the decay duration for the highpass filter envelope. * @name hpdecay - * @group effects + * @tags effects * @param {number | Pattern} decay time of the highpass filter envelope * @synonyms hpd * @example @@ -1237,7 +1237,7 @@ export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd'); /** * Sets the decay duration for the bandpass filter envelope. * @name bpdecay - * @group effects + * @tags effects * @param {number | Pattern} decay time of the bandpass filter envelope * @synonyms bpd * @example @@ -1252,7 +1252,7 @@ export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd'); /** * Sets the sustain amplitude for the lowpass filter envelope. * @name lpsustain - * @group effects + * @tags effects * @param {number | Pattern} sustain amplitude of the lowpass filter envelope * @synonyms lps * @example @@ -1267,7 +1267,7 @@ export const { lpsustain, lps } = registerControl('lpsustain', 'lps'); /** * Sets the sustain amplitude for the highpass filter envelope. * @name hpsustain - * @group effects + * @tags effects * @param {number | Pattern} sustain amplitude of the highpass filter envelope * @synonyms hps * @example @@ -1282,7 +1282,7 @@ export const { hpsustain, hps } = registerControl('hpsustain', 'hps'); /** * Sets the sustain amplitude for the bandpass filter envelope. * @name bpsustain - * @group effects + * @tags effects * @param {number | Pattern} sustain amplitude of the bandpass filter envelope * @synonyms bps * @example @@ -1297,7 +1297,7 @@ export const { bpsustain, bps } = registerControl('bpsustain', 'bps'); /** * Sets the release time for the lowpass filter envelope. * @name lprelease - * @group effects + * @tags effects * @param {number | Pattern} release time of the filter envelope * @synonyms lpr * @example @@ -1313,7 +1313,7 @@ export const { lprelease, lpr } = registerControl('lprelease', 'lpr'); /** * Sets the release time for the highpass filter envelope. * @name hprelease - * @group effects + * @tags effects * @param {number | Pattern} release time of the highpass filter envelope * @synonyms hpr * @example @@ -1329,7 +1329,7 @@ export const { hprelease, hpr } = registerControl('hprelease', 'hpr'); /** * Sets the release time for the bandpass filter envelope. * @name bprelease - * @group effects + * @tags effects * @param {number | Pattern} release time of the bandpass filter envelope * @synonyms bpr * @example @@ -1345,7 +1345,7 @@ export const { bprelease, bpr } = registerControl('bprelease', 'bpr'); /** * Sets the filter type. The ladder filter is more aggressive. More types might be added in the future. * @name ftype - * @group effects + * @tags effects * @param {number | Pattern} type 12db (0), ladder (1), or 24db (2) * @example * note("{f g g c d a a#}%8").s("sawtooth").lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1) @@ -1361,7 +1361,7 @@ export const { ftype } = registerControl('ftype'); /** * controls the center of the filter envelope. 0 is unipolar positive, .5 is bipolar, 1 is unipolar negative * @name fanchor - * @group effects + * @tags effects * @param {number | Pattern} center 0 to 1 * @example * note("{f g g c d a a#}%8").s("sawtooth").lpf("{1000}%2") @@ -1374,7 +1374,7 @@ export const { fanchor } = registerControl('fanchor'); * When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'. * * @name hpf - * @group effects + * @tags effects * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms hp, hcutoff * @example @@ -1389,7 +1389,7 @@ export const { fanchor } = registerControl('fanchor'); * Applies a vibrato to the frequency of the oscillator. * * @name vib - * @group effects + * @tags effects * @synonyms vibrato, v * @param {number | Pattern} frequency of the vibrato in hertz * @example @@ -1407,7 +1407,7 @@ export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', * Adds pink noise to the mix * * @name noise - * @group generators + * @tags generators * @param {number | Pattern} wet wet amount * @example * sound("/2") @@ -1417,7 +1417,7 @@ export const { noise } = registerControl('noise'); * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * * @name vibmod - * @group effects + * @tags effects * @synonyms vmod * @param {number | Pattern} depth of vibrato (in semitones) * @example @@ -1436,7 +1436,7 @@ export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'h * Controls the **h**igh-**p**ass **q**-value. * * @name hpq - * @group effects + * @tags effects * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms hresonance * @example @@ -1448,7 +1448,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); * Controls the **l**ow-**p**ass **q**-value. * * @name lpq - * @group effects + * @tags effects * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms resonance * @example @@ -1461,7 +1461,7 @@ export const { resonance, lpq } = registerControl('resonance', 'lpq'); * DJ filter, below 0.5 is low pass filter, above is high pass filter. * * @name djf - * @group effects + * @tags effects * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter * @example * n(irand(16).seg(8)).scale("d:phrygian").s("supersaw").djf("<.5 .3 .2 .75>") @@ -1478,7 +1478,7 @@ export const { djf } = registerControl('djf'); * * * @name delay - * @group effects + * @tags effects * @param {number | Pattern} level between 0 and 1 * @example * s("bd bd").delay("<0 .25 .5 1>") @@ -1492,7 +1492,7 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback'] * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @group effects + * @tags effects * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1506,7 +1506,7 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @group effects + * @tags effects * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1518,7 +1518,7 @@ export const { delayspeed } = registerControl('delayspeed'); * Sets the time of the delay effect. * * @name delayspeed - * @group effects + * @tags effects, foo * @param {number | Pattern} delayspeed controls the pitch of the delay feedback * @synonyms delayt, dt * @example @@ -1531,7 +1531,7 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', * Sets the time of the delay effect in cycles. * * @name delaysync - * @group effects + * @tags effects * @param {number | Pattern} cycles delay length in cycles * @synonyms delayt, dt * @example @@ -1544,7 +1544,7 @@ export const { delaysync } = registerControl('delaysync'); * Specifies whether delaytime is calculated relative to cps. * * @name lock - * @group effects + * @tags effects, foo * @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle. * @superdirtOnly * @example @@ -1558,7 +1558,7 @@ export const { lock } = registerControl('lock'); * Set detune for stacked voices of supported oscillators * * @name detune - * @group effects + * @tags effects * @param {number | Pattern} amount * @synonyms det * @example @@ -1570,7 +1570,7 @@ export const { detune, det } = registerControl('detune', 'det'); * Set number of stacked voices for supported oscillators * * @name unison - * @group effects + * @tags effects * @param {number | Pattern} numvoices * @example * note("d f a a# a d3").fast(2).s("supersaw").unison("<1 2 7>") @@ -1582,7 +1582,7 @@ export const { unison } = registerControl('unison'); * Set the stereo pan spread for supported oscillators * * @name spread - * @group effects + * @tags effects * @param {number | Pattern} spread between 0 and 1 * @example * note("d f a a# a d3").fast(2).s("supersaw").spread("<0 .3 1>") @@ -1593,7 +1593,7 @@ export const { spread } = registerControl('spread'); * Set dryness of reverb. See `room` and `size` for more information about reverb. * * @name dry - * @group effects + * @tags effects * @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() @@ -1619,7 +1619,7 @@ export const { fadeInTime } = registerControl('fadeInTime'); * Set frequency of sound. * * @name freq - * @group transforms + * @tags transforms * @param {number | Pattern} frequency in Hz. the audible range is between 20 and 20000 Hz * @example * freq("220 110 440 110").s("superzow").osc() @@ -1633,7 +1633,7 @@ export const { freq } = registerControl('freq'); * Attack time of pitch envelope. * * @name pattack - * @group effects + * @tags effects * @synonyms patt * @param {number | Pattern} time time in seconds * @example @@ -1645,7 +1645,7 @@ export const { pattack, patt } = registerControl('pattack', 'patt'); * Decay time of pitch envelope. * * @name pdecay - * @group effects + * @tags effects * @synonyms pdec * @param {number | Pattern} time time in seconds * @example @@ -1659,7 +1659,7 @@ export const { psustain, psus } = registerControl('psustain', 'psus'); * Release time of pitch envelope * * @name prelease - * @group effects + * @tags effects * @synonyms prel * @param {number | Pattern} time time in seconds * @example @@ -1674,7 +1674,7 @@ export const { prelease, prel } = registerControl('prelease', 'prel'); * If you don't set other pitch envelope controls, `pattack:.2` will be the default. * * @name penv - * @group effects + * @tags effects * @param {number | Pattern} semitones change in semitones * @example * note("c") @@ -1686,7 +1686,7 @@ export const { penv } = registerControl('penv'); * Curve of envelope. Defaults to linear. exponential is good for kicks * * @name pcurve - * @group effects + * @tags effects * @param {number | Pattern} type 0 = linear, 1 = exponential * @example * note("g1*4") @@ -1703,7 +1703,7 @@ export const { pcurve } = registerControl('pcurve'); * If you don't set an anchor, the value will default to the psustain value. * * @name panchor - * @group effects + * @tags effects * @param {number | Pattern} anchor anchor offset * @example * note("c c4").penv(12).panchor("<0 .5 1 .5>") @@ -1725,7 +1725,7 @@ export const { gate, gat } = registerControl('gate', 'gat'); * Emulation of a Leslie speaker: speakers rotating in a wooden amplified cabinet. * * @name leslie - * @group effects + * @tags effects * @param {number | Pattern} wet between 0 and 1 * @example * n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc() @@ -1737,7 +1737,7 @@ export const { leslie } = registerControl('leslie'); * Rate of modulation / rotation for leslie effect * * @name lrate - * @group effects + * @tags effects * @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() @@ -1750,7 +1750,7 @@ export const { lrate } = registerControl('lrate'); * Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble) * * @name lsize - * @group effects + * @tags effects * @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() @@ -1762,7 +1762,7 @@ export const { lsize } = registerControl('lsize'); * Sets the displayed text for an event on the pianoroll * * @name label - * @group visualization + * @tags visualization * @param {string} label text to display */ export const { activeLabel } = registerControl('activeLabel'); @@ -1828,7 +1828,7 @@ export const { overshape } = registerControl('overshape'); * Sets position in stereo. * * @name pan - * @group effects + * @tags effects * @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>") @@ -1894,7 +1894,7 @@ export const { mode } = registerControl(['mode', 'anchor']); * When using mininotation, you can also optionally add the 'size' parameter, separated by ':'. * * @name room - * @group effects + * @tags effects * @param {number | Pattern} level between 0 and 1 * @example * s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>") @@ -1908,7 +1908,7 @@ export const { room } = registerControl(['room', 'size']); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomlp - * @group effects + * @tags effects * @synonyms rlp * @param {number} frequency between 0 and 20000hz * @example @@ -1922,7 +1922,7 @@ export const { roomlp, rlp } = registerControl('roomlp', 'rlp'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomdim - * @group effects + * @tags effects * @synonyms rdim * @param {number} frequency between 0 and 20000hz * @example @@ -1937,7 +1937,7 @@ export const { roomdim, rdim } = registerControl('roomdim', 'rdim'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomfade - * @group effects + * @tags effects * @synonyms rfade * @param {number} seconds for the reverb to fade * @example @@ -1950,7 +1950,7 @@ export const { roomfade, rfade } = registerControl('roomfade', 'rfade'); /** * Sets the sample to use as an impulse response for the reverb. * @name iresponse - * @group effects + * @tags effects * @param {string | Pattern} sample to use as an impulse response * @synonyms ir * @example @@ -1962,7 +1962,7 @@ export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse'); /** * Sets speed of the sample for the impulse response. * @name irspeed - * @group effects + * @tags effects * @param {string | Pattern} speed * @example * samples('github:switchangel/pad') @@ -1974,7 +1974,7 @@ export const { irspeed } = registerControl('irspeed'); /** * Sets the beginning of the IR response sample * @name irbegin - * @group effects + * @tags effects * @param {string | Pattern} begin between 0 and 1 * @synonyms ir * @example @@ -1988,7 +1988,7 @@ export const { irbegin } = registerControl('irbegin'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomsize - * @group effects + * @tags effects * @param {number | Pattern} size between 0 and 10 * @synonyms rsize, sz, size * @example @@ -2012,7 +2012,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', * * * @name shape - * @group effects + * @tags effects * @param {number | Pattern} distortion between 0 and 1 * @example * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") @@ -2025,7 +2025,7 @@ export const { shape } = registerControl(['shape', 'shapevol']); * Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;) * * @name distort - * @group effects + * @tags effects * @synonyms dist * @param {number | Pattern} distortion * @example @@ -2040,7 +2040,7 @@ export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dis * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) * * @name compressor - * @group effects + * @tags effects * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02") @@ -2061,7 +2061,7 @@ export const { compressorRelease } = registerControl('compressorRelease'); * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * * @name speed - * @group effects + * @tags effects * @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards. * @example * s("bd*6").speed("1 2 4 1 -2 -4") @@ -2075,7 +2075,7 @@ export const { speed } = registerControl('speed'); * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * * @name stretch - * @group effects + * @tags effects * @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards. * @example * s("gm_flute").stretch("1 2 .5") @@ -2086,7 +2086,7 @@ export const { stretch } = registerControl('stretch'); * Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * * @name unit - * @group effects + * @tags effects * @param {number | string | Pattern} unit see description above * @example * speed("1 2 .5 3").s("bd").unit("c").osc() @@ -2101,7 +2101,7 @@ export const { unit } = registerControl('unit'); * "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 - * @group effects + * @tags effects * @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() @@ -2126,7 +2126,7 @@ export const { squiz } = registerControl('squiz'); * Formant filter to make things sound like vowels. * * @name vowel - * @group effects + * @tags effects * @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ. * @example * note("[c2 >]*2").s('sawtooth') @@ -2151,7 +2151,7 @@ export const { waveloss } = registerControl('waveloss'); * Noise crackle density * * @name density - * @group effects + * @tags effects * @param {number | Pattern} density between 0 and x * @example * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)) @@ -2202,7 +2202,7 @@ export const { cps } = registerControl('cps'); * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. * * @name clip - * @group transforms + * @tags transforms * @synonyms legato * @param {number | Pattern} factor >= 0 * @example @@ -2215,7 +2215,7 @@ export const { clip, legato } = registerControl('clip', 'legato'); * Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration. * * @name duration - * @group transforms + * @tags transforms * @synonyms dur * @param {number | Pattern} seconds >= 0 * @example @@ -2244,7 +2244,7 @@ export const { zzfx } = registerControl('zzfx'); /** * Sets the color of the hap in visualizations like pianoroll or highlighting. * @name color - * @group visualization + * @tags visualization * @synonyms colour * @param {string} color Hexadecimal or CSS color name */ @@ -2259,7 +2259,7 @@ export let createParams = (...names) => * ADSR envelope: Combination of Attack, Decay, Sustain, and Release. * * @name adsr - * @group effects + * @tags effects * @param {number | Pattern} time attack time in seconds * @param {number | Pattern} time decay time in seconds * @param {number | Pattern} gain sustain level (0 to 1) @@ -2294,7 +2294,7 @@ export const ar = register('ar', (t, pat) => { * MIDI channel: Sets the MIDI channel for the event. * * @name midichan - * @group examples + * @tags examples * @param {number | Pattern} channel MIDI channel number (0-15) * @example * note("c4").midichan(1).midi() @@ -2307,7 +2307,7 @@ export const { midimap } = registerControl('midimap'); * MIDI port: Sets the MIDI port for the event. * * @name midiport - * @group examples + * @tags examples * @param {number | Pattern} port MIDI port * @example * note("c a f e").midiport("<0 1 2 3>").midi() @@ -2318,7 +2318,7 @@ export const { midiport } = registerControl('midiport'); * MIDI command: Sends a MIDI command message. * * @name midicmd - * @group examples + * @tags examples * @param {number | Pattern} command MIDI command * @example * midicmd("clock*48,/2").midi() @@ -2329,7 +2329,7 @@ export const { midicmd } = registerControl('midicmd'); * MIDI control: Sends a MIDI control change message. * * @name control - * @group examples + * @tags examples * @param {number | Pattern} MIDI control number (0-127) * @param {number | Pattern} MIDI controller value (0-127) */ @@ -2345,7 +2345,7 @@ export const control = register('control', (args, pat) => { * MIDI control number: Sends a MIDI control change message. * * @name ccn - * @group examples + * @tags examples * @param {number | Pattern} MIDI control number (0-127) */ export const { ccn } = registerControl('ccn'); @@ -2353,7 +2353,7 @@ export const { ccn } = registerControl('ccn'); * MIDI control value: Sends a MIDI control change message. * * @name ccv - * @group examples + * @tags examples * @param {number | Pattern} MIDI control value (0-127) */ export const { ccv } = registerControl('ccv'); @@ -2363,7 +2363,7 @@ export const { ctlNum } = registerControl('ctlNum'); /** * MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message. * @name nrpnn - * @group examples + * @tags examples * @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127) * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() @@ -2372,7 +2372,7 @@ export const { nrpnn } = registerControl('nrpnn'); /** * MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message. * @name nrpv - * @group examples + * @tags examples * @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127) * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() @@ -2383,7 +2383,7 @@ export const { nrpv } = registerControl('nrpv'); * MIDI program number: Sends a MIDI program change message. * * @name progNum - * @group examples + * @tags examples * @param {number | Pattern} program MIDI program number (0-127) * @example * note("c4").progNum(10).midichan(1).midi() @@ -2393,7 +2393,7 @@ export const { progNum } = registerControl('progNum'); /** * MIDI sysex: Sends a MIDI sysex message. * @name sysex - * @group examples + * @tags examples * @param {number | Pattern} id Sysex ID * @param {number | Pattern} data Sysex data * @example @@ -2409,7 +2409,7 @@ export const sysex = register('sysex', (args, pat) => { /** * MIDI sysex ID: Sends a MIDI sysex identifier message. * @name sysexid - * @group examples + * @tags examples * @param {number | Pattern} id Sysex ID * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() @@ -2418,7 +2418,7 @@ export const { sysexid } = registerControl('sysexid'); /** * MIDI sysex data: Sends a MIDI sysex message. * @name sysexdata - * @group examples + * @tags examples * @param {number | Pattern} data Sysex data * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() @@ -2428,7 +2428,7 @@ export const { sysexdata } = registerControl('sysexdata'); /** * MIDI pitch bend: Sends a MIDI pitch bend message. * @name midibend - * @group examples + * @tags examples * @param {number | Pattern} midibend MIDI pitch bend (-1 - 1) * @example * note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi() @@ -2437,7 +2437,7 @@ export const { midibend } = registerControl('midibend'); /** * MIDI key after touch: Sends a MIDI key after touch message. * @name miditouch - * @group examples + * @tags examples * @param {number | Pattern} miditouch MIDI key after touch (0-1) * @example * note("c4").miditouch(sine.slow(4).range(0,1)).midi() @@ -2458,7 +2458,7 @@ export const getControlName = (alias) => { * Sets properties in a batch. * * @name as - * @group transforms + * @tags transforms * @param {String | Array} mapping the control names that are set * @example * "c:.5 a:1 f:.25 e:.8".as("note:clip") @@ -2478,7 +2478,7 @@ export const as = register('as', (mapping, pat) => { * Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file * in the optional array syntax ex: "0.5:2", the second value controls the speed of playback * @name scrub - * @group samples + * @tags samples * @memberof Pattern * @returns Pattern * @example diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index e3d4f5f9..39d6555f 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -84,7 +84,7 @@ export class Pattern { /** * Returns a new pattern, with the function applied to the value of * each hap. It has the alias `fmap`. - * @group functional + * @tags functional * @synonyms fmap * @param {Function} func to to apply to the value * @returns Pattern @@ -117,7 +117,7 @@ export class Pattern { * Assumes 'this' is a pattern of functions, and given a function to * resolve wholes, applies a given pattern of values to that * pattern of functions. - * @group functional + * @tags functional * @param {Function} whole_func * @param {Function} func * @noAutocomplete @@ -155,7 +155,7 @@ export class Pattern { * In this `_appBoth` variant, where timespans of the function and value haps * are not the same but do intersect, the resulting hap has a timespan of the * intersection. This applies to both the part and the whole timespan. - * @group functional + * @tags functional * @param {Pattern} pat_val * @noAutocomplete * @returns Pattern @@ -183,7 +183,7 @@ export class Pattern { * on. In practice, this means that the pattern structure, including onsets, * are preserved from the pattern of functions (often referred to as the left * hand or inner pattern). - * @group functional + * @tags functional * @param {Pattern} pat_val * @noAutocomplete * @returns Pattern @@ -217,7 +217,7 @@ export class Pattern { * As with `appLeft`, but `whole` timespans are instead taken from the * pattern of values, i.e. structure is preserved from the right hand/outer * pattern. - * @group functional + * @tags functional * @param {Pattern} pat_val * @noAutocomplete * @returns Pattern @@ -408,7 +408,7 @@ export class Pattern { /** * Query haps inside the given time span. * - * @group internals + * @tags internals * @param {Fraction | number} begin from time * @param {Fraction | number} end to time * @returns Hap[] @@ -432,7 +432,7 @@ export class Pattern { * Returns a new pattern, with queries split at cycle boundaries. This makes * some calculations easier to express, as all haps are then constrained to * happen within a cycle. - * @group internals + * @tags internals * @returns Pattern * @noAutocomplete */ @@ -447,7 +447,7 @@ export class Pattern { /** * Returns a new pattern, where the given function is applied to the query * timespan before passing it to the original pattern. - * @group internals + * @tags internals * @param {Function} func the function to apply * @returns Pattern * @noAutocomplete @@ -470,7 +470,7 @@ export class Pattern { /** * As with `withQuerySpan`, but the function is applied to both the * begin and end time of the query timespan. - * @group internals + * @tags internals * @param {Function} func the function to apply * @returns Pattern * @noAutocomplete @@ -483,7 +483,7 @@ export class Pattern { * Similar to `withQuerySpan`, but the function is applied to the timespans * of all haps returned by pattern queries (both `part` timespans, and where * present, `whole` timespans). - * @group internals + * @tags internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -495,7 +495,7 @@ export class Pattern { /** * As with `withHapSpan`, but the function is applied to both the * begin and end time of the hap timespans. - * @group internals + * @tags internals * @param {Function} func the function to apply * @returns Pattern * @noAutocomplete @@ -506,7 +506,7 @@ export class Pattern { /** * Returns a new pattern with the given function applied to the list of haps returned by every query. - * @group internals + * @tags internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -519,7 +519,7 @@ export class Pattern { /** * As with `withHaps`, but applies the function to every hap, rather than every list of haps. - * @group internals + * @tags internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -530,7 +530,7 @@ export class Pattern { /** * Returns a new pattern with the context field set to every hap set to the given value. - * @group internals + * @tags internals * @param {*} context * @returns Pattern * @noAutocomplete @@ -541,7 +541,7 @@ export class Pattern { /** * Returns a new pattern with the given function applied to the context field of every hap. - * @group internals + * @tags internals * @param {Function} func * @returns Pattern * @noAutocomplete @@ -557,7 +557,7 @@ export class Pattern { /** * Returns a new pattern with the context field of every hap set to an empty object. - * @group internals + * @tags internals * @returns Pattern * @noAutocomplete */ @@ -568,7 +568,7 @@ export class Pattern { /** * Returns a new pattern with the given location information added to the * context of every hap. - * @group internals + * @tags internals * @param {Number} start start offset * @param {Number} end end offset * @returns Pattern @@ -592,7 +592,7 @@ export class Pattern { /** * Returns a new Pattern, which only returns haps that meet the given test. - * @group internals + * @tags internals * @param {Function} hap_test - a function which returns false for haps to be removed from the pattern * @returns Pattern * @noAutocomplete @@ -604,7 +604,7 @@ export class Pattern { /** * As with `filterHaps`, but the function is applied to values * inside haps. - * @group internals + * @tags internals * @param {Function} value_test * @returns Pattern * @noAutocomplete @@ -616,7 +616,7 @@ export class Pattern { /** * Returns a new pattern, with haps containing undefined values removed from * query results. - * @group internals + * @tags internals * @returns Pattern * @noAutocomplete */ @@ -628,7 +628,7 @@ export class Pattern { * Returns a new pattern, with all haps without onsets filtered out. A hap * with an onset is one with a `whole` timespan that begins at the same time * as its `part` timespan. - * @group internals + * @tags internals * @returns Pattern * @noAutocomplete */ @@ -642,7 +642,7 @@ export class Pattern { /** * Returns a new pattern, with 'continuous' haps (those without 'whole' * timespans) removed from query results. - * @group internals + * @tags internals * @returns Pattern * @noAutocomplete */ @@ -654,7 +654,7 @@ export class Pattern { /** * Combines adjacent haps with the same value and whole. Only * intended for use in tests. - * @group internals + * @tags internals * @noAutocomplete */ defragmentHaps() { @@ -707,7 +707,7 @@ export class Pattern { /** * Queries the pattern for the first cycle, returning Haps. Mainly of use when * debugging a pattern. - * @group internals + * @tags internals * @param {Boolean} with_context - set to true, otherwise the context field * will be stripped from the resulting haps. * @returns [Hap] @@ -723,7 +723,7 @@ export class Pattern { /** * Accessor for a list of values returned by querying the first cycle. - * @group internals + * @tags internals * @noAutocomplete */ get firstCycleValues() { @@ -732,7 +732,7 @@ export class Pattern { /** * More human-readable version of the `firstCycleValues` accessor. - * @group internals + * @tags internals * @noAutocomplete */ get showFirstCycle() { @@ -744,7 +744,7 @@ export class Pattern { /** * Returns a new pattern, which returns haps sorted in temporal order. Mainly * of use when comparing two patterns for equality, in tests. - * @group internals + * @tags internals * @returns Pattern * @noAutocomplete */ @@ -761,7 +761,7 @@ export class Pattern { /** * Returns a new pattern with all values parsed as numerals. - * @group internals + * @tags internals */ asNumber() { return this.fmap(parseNumeral); @@ -813,7 +813,7 @@ export class Pattern { /** * Layers the result of the given function(s). Like `superimpose`, but without the original pattern: * @name layer - * @group combiners + * @tags combiners * @memberof Pattern * @synonyms apply * @returns Pattern @@ -829,7 +829,7 @@ export class Pattern { /** * Superimposes the result of the given function(s) on top of the original pattern: * @name superimpose - * @group combiners + * @tags combiners * @memberof Pattern * @returns Pattern * @example @@ -889,7 +889,7 @@ export class Pattern { /** * Writes the content of the current event to the console (visible in the side menu). - * @group visualizers + * @tags visualizers * @name log * @memberof Pattern * @example @@ -904,7 +904,7 @@ export class Pattern { /** * A simplified version of `log` which writes all "values" (various configurable parameters) * within the event to the console (visible in the side menu). - * @group visualizers + * @tags visualizers * @name logValues * @memberof Pattern * @example @@ -938,7 +938,7 @@ export class Pattern { * source pattern to be looped, and for an (optional) given function to be * applied. False values result in the corresponding part of the source pattern * to be played unchanged. - * @group structure + * @tags structure * @name into * @memberof Pattern * @example @@ -978,7 +978,7 @@ Pattern.prototype.collect = function () { /** * Selects indices in in stacked notes. - * @group transforms + * @tags transforms * @example * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") * .arpWith(haps => haps[2]) @@ -993,7 +993,7 @@ export const arpWith = register('arpWith', (func, pat) => { /** * Selects indices in in stacked notes. - * @group transforms + * @tags transforms * @example * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") * .arp("0 [0,2] 1 [0,2]") @@ -1076,7 +1076,7 @@ function _composeOp(a, b, func) { * note("c3 e3 g3".add("<0 5 7 0>")) * // Behind the scenes, the notes are converted to midi numbers: * // note("48 52 55".add("<0 5 7 0>")) - * @group transforms + * @tags math */ add: [numeralArgs((a, b) => a + b)], // support string concatenation /** @@ -1189,7 +1189,7 @@ function _composeOp(a, b, func) { /** * Applies the given structure to the pattern: * - * @group structure + * @tags structure * @example * note("c,eb,g") * .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") @@ -1204,7 +1204,7 @@ function _composeOp(a, b, func) { /** * Returns silence when mask is 0 or "~" * - * @group structure + * @tags structure * @example * note("c [eb,g] d [eb,g]").mask("<1 [0 1]>") */ @@ -1217,7 +1217,7 @@ function _composeOp(a, b, func) { /** * Resets the pattern to the start of the cycle for each onset of the reset pattern. * - * @group structure + * @tags structure * @example * s("[ sd]*2, hh*8").reset("") */ @@ -1231,7 +1231,7 @@ function _composeOp(a, b, func) { * Restarts the pattern for each onset of the restart pattern. * While reset will only reset the current cycle, restart will start from cycle 0. * - * @group structure + * @tags structure * @example * s("[ sd]*2, hh*8").restart("") */ @@ -1272,7 +1272,7 @@ export const pm = polymeter; /** * Does absolutely nothing, but with a given metrical 'steps' * @name gap - * @group generators + * @tags generators * @param {number} steps * @example * gap(3) // "~@3" @@ -1282,7 +1282,7 @@ export const gap = (steps) => new Pattern(() => [], steps); /** * Does absolutely nothing.. * @name silence - * @group generators + * @tags generators * @example * silence // "~" */ @@ -1294,7 +1294,7 @@ export const nothing = gap(0); /** * A discrete value that repeats once per cycle. * - * @group generators + * @tags generators * @returns {Pattern} * @example * pure('e4') // "e4" @@ -1339,7 +1339,7 @@ export function reify(thing) { /** * Takes a list of patterns, and returns a pattern of lists. * - * @group transforms + * @tags transforms */ export function sequenceP(pats) { let result = pure([]); @@ -1352,7 +1352,7 @@ export function sequenceP(pats) { /** * The given items are played at the same time at the same length. * - * @group structure + * @tags structure * @return {Pattern} * @synonyms polyrhythm, pr * @example @@ -1437,7 +1437,7 @@ export function stackBy(by, ...pats) { /** * Concatenation: combines a list of patterns, switching between them successively, one per cycle. * - * @group combiners + * @tags combiners * @return {Pattern} * @synonyms cat * @example @@ -1471,7 +1471,7 @@ export function slowcat(...pats) { } /** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles. - * @group combiners + * @tags combiners * @param {...any} items - The items to concatenate * @return {Pattern} */ @@ -1487,7 +1487,7 @@ export function slowcatPrime(...pats) { /** The given items are con**cat**enated, where each one takes one cycle. * - * @group combiners + * @tags combiners * @param {...any} items - The items to concatenate * @synonyms slowcat * @return {Pattern} @@ -1509,7 +1509,7 @@ export function cat(...pats) { * Allows to arrange multiple patterns together over multiple cycles. * Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use. * - * @group combiners + * @tags combiners * @return {Pattern} * @example * arrange( @@ -1527,7 +1527,7 @@ export function arrange(...sections) { * Similarly to `arrange`, allows you to arrange multiple patterns together over multiple cycles. * Unlike `arrange`, you specify a start and stop time for each pattern rather than duration, which * means that patterns can overlap. - * @group combiners + * @tags combiners * @return {Pattern} * @example seqPLoop([0, 2, "bd(3,8)"], @@ -1572,7 +1572,7 @@ export function sequence(...pats) { } /** Like **cat**, but the items are crammed into one cycle. - * @group combiners + * @tags combiners * @synonyms sequence, fastcat * @example * seq("e5", "b4", ["d5", "c5"]).note() @@ -1746,7 +1746,7 @@ function stepRegister(name, func, patternify = true, preserveSteps = false, join * Assumes a numerical pattern. Returns a new pattern with all values rounded * to the nearest integer. * @name round - * @group math + * @tags math * @memberof Pattern * @returns Pattern * @example @@ -1761,7 +1761,7 @@ export const round = register('round', function (pat) { * replaced with `-5`. * @name floor * @memberof Pattern - * @group math + * @tags math * @returns Pattern * @example * note("42 42.1 42.5 43".floor()) @@ -1776,7 +1776,7 @@ export const floor = register('floor', function (pat) { * replaced with `-4`. * @name ceil * @memberof Pattern - * @group math + * @tags math * @returns Pattern * @example * note("42 42.1 42.5 43".ceil()) @@ -1787,7 +1787,7 @@ export const ceil = register('ceil', function (pat) { /** * Assumes a numerical pattern, containing unipolar values in the range 0 .. * 1. Returns a new pattern with values scaled to the bipolar range -1 .. 1 - * @group math + * @tags math * @returns Pattern * @noAutocomplete */ @@ -1798,7 +1798,7 @@ export const toBipolar = register('toBipolar', function (pat) { /** * Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 * Returns a new pattern with values scaled to the unipolar range 0 .. 1 - * @group math + * @tags math * @returns Pattern * @noAutocomplete */ @@ -1812,7 +1812,7 @@ export const fromBipolar = register('fromBipolar', function (pat) { * Most useful in combination with continuous patterns. * @name range * @memberof Pattern - * @group math + * @tags math * @returns Pattern * @example * s("[bd sd]*2,hh*8") @@ -1828,7 +1828,7 @@ export const range = register('range', function (min, max, pat) { * following an exponential curve. * @name rangex * @memberof Pattern - * @group math + * @tags math * @returns Pattern * @example * s("[bd sd]*2,hh*8") @@ -1843,7 +1843,7 @@ export const rangex = register('rangex', function (min, max, pat) { * Returns a new pattern with values scaled to the given min/max range. * @name range2 * @memberof Pattern - * @group math + * @tags math * @returns Pattern * @example * s("[bd sd]*2,hh*8") @@ -1858,7 +1858,7 @@ export const range2 = register('range2', function (min, max, pat) { * Returns a new pattern with just numbers. * @name ratio * @memberof Pattern - * @group math + * @tags math * @returns Pattern * @example * ratio("1, 5:4, 3:2").mul(110) @@ -1877,7 +1877,7 @@ export const ratio = register('ratio', (pat) => // Structural and temporal transformations /** Compress each cycle into the given timespan, leaving a gap - * @group structure + * @tags structure * @example * cat( * s("bd sd").compress(.25,.75), @@ -1899,7 +1899,7 @@ export const { compressSpan, compressspan } = register(['compressSpan', 'compres /** * speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast. - * @group structure + * @tags structure * @name fastGap * @synonyms fastgap * @example @@ -1937,7 +1937,7 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f /** * Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle - * @group structure + * @tags structure * @example * s("bd hh sd hh").focus(1/4, 3/4) */ @@ -1955,7 +1955,7 @@ export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], fun }); /** The ply function repeats each event the given number of times. - * @group structure + * @tags structure * @example * s("bd ~ sd cp").ply("<1 2 3>") */ @@ -1970,7 +1970,7 @@ export const ply = register('ply', function (factor, pat) { /** * Speed up a pattern by the given factor. Used by "*" in mini notation. * - * @group structure + * @tags structure * @name fast * @synonyms density * @memberof Pattern @@ -1995,7 +1995,7 @@ export const { fast, density } = register( /** * Both speeds up the pattern (like 'fast') and the sample playback (like 'speed'). - * @group structure + * @tags structure * @example * s("bd sd:2").hurry("<1 2 4 3>").slow(1.5) */ @@ -2006,7 +2006,7 @@ export const hurry = register('hurry', function (r, pat) { /** * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. * - * @group structure + * @tags structure * @name slow * @synonyms sparsity * @memberof Pattern @@ -2024,7 +2024,7 @@ export const { slow, sparsity } = register(['slow', 'sparsity'], function (facto /** * Carries out an operation 'inside' a cycle. - * @group structure + * @tags structure * @example * "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note() * // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note() @@ -2035,7 +2035,7 @@ export const inside = register('inside', function (factor, f, pat) { /** * Carries out an operation 'outside' a cycle. - * @group structure + * @tags structure * @example * "<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note() * // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note() @@ -2046,7 +2046,7 @@ export const outside = register('outside', function (factor, f, pat) { /** * Applies the given function every n cycles, starting from the last cycle. - * @group structure + * @tags structure * @name lastOf * @memberof Pattern * @param {number} n how many cycles @@ -2063,7 +2063,7 @@ export const lastOf = register('lastOf', function (n, func, pat) { /** * Applies the given function every n cycles, starting from the first cycle. - * @group structure + * @tags structure * @name firstOf * @memberof Pattern * @param {number} n how many cycles @@ -2075,7 +2075,7 @@ export const lastOf = register('lastOf', function (n, func, pat) { /** * An alias for `firstOf` - * @group structure + * @tags structure * @name every * @memberof Pattern * @param {number} n how many cycles @@ -2092,7 +2092,7 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu /** * Like layer, but with a single function: - * @group structure + * @tags structure * @name apply * @example * "".scale('C minor').apply(scaleTranspose("0,2,4")).note() @@ -2104,7 +2104,7 @@ export const apply = register('apply', function (func, pat) { /** * Plays the pattern at the given cycles per minute. - * @group structure + * @tags structure * @deprecated * @example * s(",hh*2").cpm(90) // = 90 bpm @@ -2117,7 +2117,7 @@ export const cpm = register('cpm', function (cpm, pat) { /** * Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator * - * @group structure + * @tags structure * @name early * @memberof Pattern * @param {number | Pattern} cycles number of cycles to nudge left @@ -2138,7 +2138,7 @@ export const early = register( /** * Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator * - * @group structure + * @tags structure * @name late * @memberof Pattern * @param {number | Pattern} cycles number of cycles to nudge right @@ -2159,7 +2159,7 @@ export const late = register( /** * Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern: * - * @group structure + * @tags structure * @example * s("bd*2 hh*3 [sd bd]*2 perc").zoom(0.25, 0.75) * // s("hh*3 [sd bd]*2") // equivalent @@ -2186,7 +2186,7 @@ export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a /** * Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers. * Similar to `slice`, but slices up patterns rather than sound samples. - * @group structure + * @tags structure * @param {number} number of slices * @param {number} slices to play * @example @@ -2214,7 +2214,7 @@ export const bite = register( /** * Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle. - * @group structure + * @tags structure * @param {number} fraction fraction to select * @example * s("lt ht mt cp, [hh oh]*2").linger("<1 .5 .25 .125>") @@ -2235,7 +2235,7 @@ export const linger = register( /** * Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one. - * @group structure + * @tags structure * @name segment * @synonyms seg * @param {number} segments number of segments per cycle @@ -2248,7 +2248,7 @@ export const { segment, seg } = register(['segment', 'seg'], function (rate, pat /** * The function `swingBy x n` breaks each cycle into `n` slices, and then delays events in the second half of each slice by the amount `x`, which is relative to the size of the (half) slice. So if `x` is 0 it does nothing, `0.5` delays for half the note duration, and 1 will wrap around to doing nothing again. The end result is a shuffle or swing-like rhythm - * @group structure + * @tags structure * @param {number} subdivision * @param {number} offset * @example @@ -2258,7 +2258,7 @@ export const swingBy = register('swingBy', (swing, n, pat) => pat.inside(n, late /** * Shorthand for swingBy with 1/3: - * @group structure + * @tags structure * @param {number} subdivision * @example * s("hh*8").swing(4) @@ -2268,7 +2268,7 @@ export const swing = register('swing', (n, pat) => pat.swingBy(1 / 3, n)); /** * Swaps 1s and 0s in a binary pattern. - * @group structure + * @tags structure * @name invert * @synonyms inv * @example @@ -2286,7 +2286,7 @@ export const { invert, inv } = register( /** * Applies the given function whenever the given pattern is in a true state. - * @group structure + * @tags structure * @name when * @memberof Pattern * @param {Pattern} binary_pat @@ -2301,7 +2301,7 @@ export const when = register('when', function (on, func, pat) { /** * Superimposes the function result on top of the original pattern, delayed by the given time. - * @group structure + * @tags structure * @name off * @memberof Pattern * @param {Pattern | number} time offset time @@ -2318,7 +2318,7 @@ export const off = register('off', function (time_pat, func, pat) { * Returns a new pattern where every other cycle is played once, twice as * fast, and offset in time by one quarter of a cycle. Creates a kind of * breakbeat feel. - * @group structure + * @tags structure * @returns Pattern */ export const brak = register('brak', function (pat) { @@ -2328,7 +2328,7 @@ export const brak = register('brak', function (pat) { /** * Reverse all haps in a pattern * - * @group structure + * @tags structure * @name rev * @memberof Pattern * @returns Pattern @@ -2362,7 +2362,7 @@ export const rev = register( /** Like press, but allows you to specify the amount by which each * event is shifted. pressBy(0.5) is the same as press, while * pressBy(1/3) shifts each event by a third of its timespan. - * @group structure + * @tags structure * @example * stack(s("hh*4"), * s("bd mt sd ht").pressBy("<0 0.5 0.25>") @@ -2374,7 +2374,7 @@ export const pressBy = register('pressBy', function (r, pat) { /** * Syncopates a rhythm, by shifting each event halfway into its timespan. - * @group structure + * @tags structure * @example * stack(s("hh*4"), * s("bd mt sd ht").every(4, press) @@ -2386,7 +2386,7 @@ export const press = register('press', function (pat) { /** * Silences a pattern. - * @group structure + * @tags structure * @example * stack( * s("bd").hush(), @@ -2399,7 +2399,7 @@ Pattern.prototype.hush = function () { /** * Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards. - * @group structure + * @tags structure * @example * note("c d e g").palindrome() */ @@ -2414,7 +2414,7 @@ export const palindrome = register( /** * Jux with adjustable stereo width. 0 = mono, 1 = full stereo. - * @group structure + * @tags structure * @name juxBy * @synonyms juxby * @example @@ -2436,7 +2436,7 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, /** * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. - * @group structure + * @tags structure * @example * s("bd lt [~ ht] mt cp ~ bd hh").jux(rev) * @example @@ -2450,7 +2450,7 @@ export const jux = register('jux', function (func, pat) { /** * Superimpose and offset multiple times, applying the given function each time. - * @group structure + * @tags structure * @name echoWith * @synonyms echowith, stutWith, stutwith * @param {number} times how many times to repeat @@ -2470,7 +2470,7 @@ export const { echoWith, echowith, stutWith, stutwith } = register( /** * Superimpose and offset multiple times, gradually decreasing the velocity - * @group structure + * @tags structure * @name echo * @memberof Pattern * @returns Pattern @@ -2486,7 +2486,7 @@ export const echo = register('echo', function (times, time, feedback, pat) { /** * Deprecated. Like echo, but the last 2 parameters are flipped. - * @group structure + * @tags structure * @name stut * @param {number} times how many times to repeat * @param {number} feedback velocity multiplicator for each iteration @@ -2508,7 +2508,7 @@ export const applyN = register('applyN', function (n, func, p) { /** * The plyWith function repeats each event the given number of times, applying the given function to each event.\n - * @group structure + * @tags structure * @name plyWith * @synonyms plywith * @param {number} factor how many times to repeat @@ -2531,7 +2531,7 @@ export const plyWith = register(['plyWith', 'plywith'], function (factor, func, /** * The plyForEach function repeats each event the given number of times, applying the given function to each event. * This version of ply uses the iteration index as an argument to the function, similar to echoWith. - * @group structure + * @tags structure * @name plyForEach * @synonyms plyforeach * @param {number} factor how many times to repeat @@ -2553,7 +2553,7 @@ export const plyForEach = register(['plyForEach', 'plyforeach'], function (facto /** * Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played. - * @group structure + * @tags structure * @name iter * @memberof Pattern * @returns Pattern @@ -2581,7 +2581,7 @@ export const iter = register( /** * Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles - * @group structure + * @tags structure * @name iterBack * @synonyms iterback * @memberof Pattern @@ -2600,7 +2600,7 @@ export const { iterBack, iterback } = register( /** * Repeats each cycle the given number of times. - * @group structure + * @tags structure * @name repeatCycles * @memberof Pattern * @returns Pattern @@ -2624,7 +2624,7 @@ export const { repeatCycles } = register( /** * Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). - * @group structure + * @tags structure * @name chunk * @synonyms slowChunk, slowchunk * @memberof Pattern @@ -2656,7 +2656,7 @@ export const { chunk, slowchunk, slowChunk } = register( /** * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles - * @group structure + * @tags structure * @name chunkBack * @synonyms chunkback * @memberof Pattern @@ -2677,7 +2677,7 @@ export const { chunkBack, chunkback } = register( /** * Like `chunk`, but the cycles of the source pattern aren't repeated * for each set of chunks. - * @group structure + * @tags structure * @name fastChunk * @synonyms fastchunk * @memberof Pattern @@ -2698,7 +2698,7 @@ export const { fastchunk, fastChunk } = register( /** * Like `chunk`, but the function is applied to a looped subcycle of the source pattern. - * @group structure + * @tags structure * @name chunkInto * @synonyms chunkinto * @memberof Pattern @@ -2712,7 +2712,7 @@ export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], fun /** * Like `chunkInto`, but moves backwards through the chunks. - * @group structure + * @tags structure * @name chunkBackInto * @synonyms chunkbackinto * @memberof Pattern @@ -2743,7 +2743,7 @@ export const bypass = register( /** * Loops the pattern inside an `offset` for `cycles`. * If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it. - * @group structure + * @tags structure * @name ribbon * @synonyms rib * @param {number} offset start point of loop in cycles @@ -2772,7 +2772,7 @@ export const hsl = register('hsl', (h, s, l, pat) => { /** * Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array). * @name tag - * @group structure + * @tags structure * @noAutocomplete * @param {string} tag anything unique */ @@ -2783,7 +2783,7 @@ Pattern.prototype.tag = function (tag) { /** * Filters haps using the given function * @name filter - * @group structure + * @tags structure * @param {Function} test function to test Hap * @example * s("hh!7 oh").filter(hap => hap.value.s==='hh') @@ -2793,7 +2793,7 @@ export const filter = register('filter', (test, pat) => pat.withHaps((haps) => h /** * Filters haps by their begin time * @name filterWhen - * @group structure + * @tags structure * @noAutocomplete * @param {Function} test function to test Hap.whole.begin */ @@ -2802,7 +2802,7 @@ export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) = /** * Use within to apply a function to only a part of a pattern. * @name within - * @group structure + * @tags structure * @param {number} start start within cycle (0 - 1) * @param {number} end end within cycle (0 - 1). Must be > start * @param {Function} func function to be applied to the sub-pattern @@ -2877,7 +2877,7 @@ export function _match(span, hap_p) { * *Experimental* * * Speeds a pattern up or down, to fit to the given number of steps per cycle. - * @group structure + * @tags structure * @example * sound("bd sd cp").pace(4) * // The same as sound("{bd sd cp}%4") or sound("*4") @@ -2919,7 +2919,7 @@ export function _polymeterListSteps(steps, ...args) { * *Experimental* * * Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps. - * @group structure + * @tags structure * @synonyms pm * @example * // The same as note("{c eb g, c2 g2}%6") @@ -2952,7 +2952,7 @@ export function polymeter(...args) { * The steps can either be inferred from the pattern, or provided as a [length, pattern] pair. * Has the alias `timecat`. * @name stepcat - * @group combiners + * @tags combiners * @synonyms timeCat, timecat * @return {Pattern} * @example @@ -3010,7 +3010,7 @@ export function stepcat(...timepats) { * Concatenates patterns stepwise, according to an inferred 'steps per cycle'. * Similar to `stepcat`, but if an argument is a list, the whole pattern will alternate between the elements in the list. * - * @group combiners + * @tags combiners * @return {Pattern} * @example * stepalt(["bd cp", "mt"], "bd").sound() @@ -3037,7 +3037,7 @@ export function stepalt(...groups) { * * Takes the given number of steps from a pattern (dropping the rest). * A positive number will take steps from the start of a pattern, and a negative number from the end. - * @group structure + * @tags structure * @return {Pattern} * @example * "bd cp ht mt".take("2").sound() @@ -3082,7 +3082,7 @@ export const take = stepRegister('take', function (i, pat) { * * Drops the given number of steps from a pattern. * A positive number will drop steps from the start of a pattern, and a negative number from the end. - * @group structure + * @tags structure * @return {Pattern} * @example * "tha dhi thom nam".drop("1").sound().bank("mridangam") @@ -3111,7 +3111,7 @@ export const drop = stepRegister('drop', function (i, pat) { * `extend` is similar to `fast` in that it increases its density, but it also increases the step count * accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas * `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`. - * @group structure + * @tags structure * @example * stepcat( * sound("bd bd - cp").extend(2), @@ -3126,7 +3126,7 @@ export const extend = stepRegister('extend', function (factor, pat) { * *Experimental* * * Expands the step size of the pattern by the given factor. - * @group structure + * @tags structure * @example * sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8) */ @@ -3138,7 +3138,7 @@ export const expand = stepRegister('expand', function (factor, pat) { * *Experimental* * * Contracts the step size of the pattern by the given factor. See also `expand`. - * @group structure + * @tags structure * @example * sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8) */ @@ -3193,7 +3193,7 @@ export const shrinklist = (amount, pat) => pat.shrinklist(amount); * Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with `:`), * that number of times. * A positive number will progressively drop steps from the start of a pattern, and a negative number from the end. - * @group structure + * @tags structure * @return {Pattern} * @example * "tha dhi thom nam".shrink("1").sound() @@ -3233,7 +3233,7 @@ export const shrink = register( * Progressively grows the pattern by 'n' steps until the full pattern is played, or if a second value is given (using mininotation list syntax with `:`), * that number of times. * A positive number will progressively grow steps from the start of a pattern, and a negative number from the end. - * @group structure + * @tags structure * @return {Pattern} * @example * "tha dhi thom nam".grow("1").sound() @@ -3274,7 +3274,7 @@ export const grow = register( * on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using `pace` to set the * number of steps per cycle is therefore usually recommended. * - * @group combiners + * @tags combiners * @return {Pattern} * @example * "[c g]".tour("e f", "e f g", "g f e c").note() @@ -3301,7 +3301,7 @@ Pattern.prototype.tour = function (...many) { * 'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle. * Using `pace` to set the number of steps per cycle is therefore usually recommended. * - * @group combiners + * @tags combiners * @returns {Pattern} * @example * zip("e f", "e f g", "g [f e] a f4 c").note() @@ -3353,7 +3353,7 @@ Pattern.prototype.steps = Pattern.prototype.pace; * Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'. * It turns a pattern of samples into a pattern of parts of samples. * @name chop - * @group transforms + * @tags transforms * @memberof Pattern * @returns Pattern * @example @@ -3384,7 +3384,7 @@ export const chop = register('chop', function (n, pat) { /** * Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop. * @name striate - * @group transforms + * @tags transforms * @memberof Pattern * @returns Pattern * @example @@ -3403,7 +3403,7 @@ export const striate = register('striate', function (n, pat) { /** * Makes the sample fit the given number of cycles by changing the speed. * @name loopAt - * @group transforms + * @tags transforms * @memberof Pattern * @returns Pattern * @example @@ -3422,7 +3422,7 @@ const _loopAt = function (factor, pat, cps = 0.5) { * Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. * Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points. * @name slice - * @group transforms + * @tags transforms * @memberof Pattern * @returns Pattern * @example @@ -3458,7 +3458,7 @@ export const slice = register( * make something happen on event time * uses browser timeout which is innacurate for audio tasks * @name onTriggerTime - * @group external_io + * @tags external_io * @memberof Pattern * @returns Pattern * @example @@ -3476,7 +3476,7 @@ Pattern.prototype.onTriggerTime = function (func) { /** * Works the same as slice, but changes the playback speed of each slice to match the duration of its step. * @name splice - * @group transforms + * @tags transforms * @example * samples('github:tidalcycles/dirt-samples') * s("breaks165") @@ -3514,7 +3514,7 @@ export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (facto * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. * Similar to `loopAt`. * @name fit - * @group transforms + * @tags transforms * @example * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) * s("rhodes/2").fit() @@ -3540,7 +3540,7 @@ export const fit = register('fit', (pat) => * given by a global clock and this function will be * deprecated/removed. * @name loopAtCps - * @group transforms + * @tags transforms * @memberof Pattern * @returns Pattern * @example @@ -3567,7 +3567,7 @@ let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5); * - 1 = (no left, full right) * * @name xfade - * @group combiners + * @tags combiners * @example * xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8")) */ @@ -3589,7 +3589,7 @@ Pattern.prototype.xfade = function (pos, b) { * creates a structure pattern from divisions of a cycle * especially useful for creating rhythms * @name beat - * @group structure + * @tags structure * @example * s("bd").beat("0,7,10", 16) * @example @@ -3666,7 +3666,7 @@ export const _morph = (from, to, by) => { * sine.slow(8) // slowly morph between the rhythms * ) * ) - * @group structure + * @tags structure */ export const morph = (frompat, topat, bypat) => { frompat = reify(frompat); diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 9f6636c8..03dd641a 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -24,7 +24,7 @@ export const signal = (func) => { * A sawtooth signal between 0 and 1. * * @return {Pattern} - * @group generators + * @tags generators * @example * note("*8") * .clip(saw.slow(2)) @@ -39,7 +39,7 @@ export const saw = signal((t) => t % 1); * A sawtooth signal between -1 and 1 (like `saw`, but bipolar). * * @return {Pattern} - * @group generators + * @tags generators */ export const saw2 = saw.toBipolar(); @@ -47,7 +47,7 @@ export const saw2 = saw.toBipolar(); * A sawtooth signal between 1 and 0 (like `saw`, but flipped). * * @return {Pattern} - * @group generators + * @tags generators * @example * note("*8") * .clip(isaw.slow(2)) @@ -62,7 +62,7 @@ export const isaw = signal((t) => 1 - (t % 1)); * A sawtooth signal between 1 and -1 (like `saw2`, but flipped). * * @return {Pattern} - * @group generators + * @tags generators */ export const isaw2 = isaw.toBipolar(); @@ -70,14 +70,14 @@ export const isaw2 = isaw.toBipolar(); * A sine signal between -1 and 1 (like `sine`, but bipolar). * * @return {Pattern} - * @group generators + * @tags generators */ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t)); /** * A sine signal between 0 and 1. * @return {Pattern} - * @group generators + * @tags generators * @example * n(sine.segment(16).range(0,15)) * .scale("C:minor") @@ -89,7 +89,7 @@ export const sine = sine2.fromBipolar(); * A cosine signal between 0 and 1. * * @return {Pattern} - * @group generators + * @tags generators * @example * n(stack(sine,cosine).segment(16).range(0,15)) * .scale("C:minor") @@ -101,14 +101,14 @@ export const cosine = sine._early(Fraction(1).div(4)); * A cosine signal between -1 and 1 (like `cosine`, but bipolar). * * @return {Pattern} - * @group generators + * @tags generators */ export const cosine2 = sine2._early(Fraction(1).div(4)); /** * A square signal between 0 and 1. * @return {Pattern} - * @group generators + * @tags generators * @example * n(square.segment(4).range(0,7)).scale("C:minor") * @@ -119,7 +119,7 @@ export const square = signal((t) => Math.floor((t * 2) % 2)); * A square signal between -1 and 1 (like `square`, but bipolar). * * @return {Pattern} - * @group generators + * @tags generators */ export const square2 = square.toBipolar(); @@ -127,7 +127,7 @@ export const square2 = square.toBipolar(); * A triangle signal between 0 and 1. * * @return {Pattern} - * @group generators + * @tags generators * @example * n(tri.segment(8).range(0,7)).scale("C:minor") * @@ -138,7 +138,7 @@ export const tri = fastcat(saw, isaw); * A triangle signal between -1 and 1 (like `tri`, but bipolar). * * @return {Pattern} - * @group generators + * @tags generators */ export const tri2 = fastcat(saw2, isaw2); @@ -146,7 +146,7 @@ export const tri2 = fastcat(saw2, isaw2); * An inverted triangle signal between 1 and 0 (like `tri`, but flipped). * * @return {Pattern} - * @group generators + * @tags generators * @example * n(itri.segment(8).range(0,7)).scale("C:minor") * @@ -157,7 +157,7 @@ export const itri = fastcat(isaw, saw); * An inverted triangle signal between -1 and 1 (like `itri`, but bipolar). * * @return {Pattern} - * @group generators + * @tags generators */ export const itri2 = fastcat(isaw2, saw2); @@ -165,7 +165,7 @@ export const itri2 = fastcat(isaw2, saw2); * A signal representing the cycle time. * * @return {Pattern} - * @group generators + * @tags generators */ export const time = signal(id); @@ -173,7 +173,7 @@ export const time = signal(id); * The mouse's x position value ranges from 0 to 1. * @name mousex * @return {Pattern} - * @group external_io + * @tags external_io * @example * n(mousex.segment(4).range(0,7)).scale("C:minor") * @@ -183,7 +183,7 @@ export const time = signal(id); * The mouse's y position value ranges from 0 to 1. * @name mousey * @return {Pattern} - * @group external_io + * @tags external_io * @example * n(mousey.segment(4).range(0,7)).scale("C:minor") * @@ -238,7 +238,7 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); /** * A discrete pattern of numbers from 0 to n-1 - * @group generators + * @tags generators * @example * n(run(4)).scale("C4:pentatonic") * // n("0 1 2 3").scale("C4:pentatonic") @@ -249,7 +249,7 @@ export const run = (n) => saw.range(0, n).round().segment(n); * Creates a pattern from a binary number. * * @name binary - * @group generators + * @tags generators * @param {number} n - input number to convert to binary * @example * "hh".s().struct(binary(5)) @@ -264,7 +264,7 @@ export const binary = (n) => { * Creates a pattern from a binary number, padded to n bits long. * * @name binaryN - * @group generators + * @tags generators * @param {number} n - input number to convert to binary * @param {number} nBits - pattern length, defaults to 16 * @example @@ -300,7 +300,7 @@ const _rearrangeWith = (ipat, n, pat) => { * Slices a pattern into the given number of parts, then plays those parts in random order. * Each part will be played exactly once per cycle. * @name shuffle - * @group transforms + * @tags transforms * @example * note("c d e f").sound("piano").shuffle(4) * @example @@ -314,7 +314,7 @@ export const shuffle = register('shuffle', (n, pat) => { * Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`, * but parts might be played more than once, or not at all, per cycle. * @name scramble - * @group transforms + * @tags transforms * @example * note("c d e f").sound("piano").scramble(4) * @example @@ -328,7 +328,7 @@ export const scramble = register('scramble', (n, pat) => { * A continuous pattern of random numbers, between 0 and 1. * * @name rand - * @group generators + * @tags generators * @example * // randomly change the cutoff * s("bd*4,hh*8").cutoff(rand.range(500,8000)) @@ -346,7 +346,7 @@ export const _brandBy = (p) => rand.fmap((x) => x < p); * A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1 * * @name brandBy - * @group generators + * @tags generators * @param {number} probability - a number between 0 and 1 * @example * s("hh*10").pan(brandBy(0.2)) @@ -357,7 +357,7 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin(); * A continuous pattern of 0 or 1 (binary random) * * @name brand - * @group generators + * @tags generators * @example * s("hh*10").pan(brand) */ @@ -369,7 +369,7 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); * A continuous pattern of random integers, between 0 and n-1. * * @name irand - * @group generators + * @tags generators * @param {number} n max value (exclusive) * @example * // randomly select scale notes from 0 - 7 (= C to C) @@ -392,7 +392,7 @@ export const __chooseWith = (pat, xs) => { /** * Choose from the list of values (or patterns of values) using the given * pattern of numbers, which should be in the range of 0..1 - * @group transforms + * @tags transforms * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -406,7 +406,7 @@ export const chooseWith = (pat, xs) => { /** * As with {chooseWith}, but the structure comes from the chosen values, rather * than the pattern you're using to choose with. - * @group transforms + * @tags transforms * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -417,7 +417,7 @@ export const chooseInWith = (pat, xs) => { /** * Chooses randomly from the given list of elements. - * @group transforms + * @tags transforms * @param {...any} xs values / patterns to choose from. * @returns {Pattern} - a continuous pattern. * @example @@ -433,7 +433,7 @@ export const chooseOut = choose; * Chooses from the given list of values (or patterns of values), according * to the pattern that the method is called on. The pattern should be in * the range 0 .. 1. - * @group transforms + * @tags transforms * @param {...any} xs * @returns {Pattern} */ @@ -444,7 +444,7 @@ Pattern.prototype.choose = function (...xs) { /** * As with choose, but the pattern that this method is called on should be * in the range -1 .. 1 - * @group transforms + * @tags transforms * @param {...any} xs * @returns {Pattern} */ @@ -454,7 +454,7 @@ Pattern.prototype.choose2 = function (...xs) { /** * Picks one of the elements at random each cycle. - * @group transforms + * @tags transforms * @synonyms randcat * @returns {Pattern} * @example @@ -497,7 +497,7 @@ const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); /** * Chooses randomly from the given list of elements by giving a probability to each element - * @group transforms + * @tags transforms * @param {...any} pairs arrays of value and weight * @returns {Pattern} - a continuous pattern. * @example @@ -507,7 +507,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); /** * Picks one of the elements at random each cycle by giving a probability to each element - * @group transforms + * @tags transforms * @synonyms wrandcat * @returns {Pattern} * @example @@ -555,7 +555,7 @@ export const berlinWith = (tpat) => { /** * Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1. * - * @group generators + * @tags generators * @name perlin * @example * // randomly change the cutoff @@ -568,7 +568,7 @@ export const perlin = perlinWith(time.fmap((v) => Number(v))); * Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful, * like perlin noise but with sawtooth waves), in the range 0..1. * - * @group generators + * @tags generators * @name berlin * @example * // ascending arpeggios @@ -589,7 +589,7 @@ export const degradeByWith = register( * 0 = 0% chance of removal * 1 = 100% chance of removal * - * @group transforms + * @tags transforms * @name degradeBy * @memberof Pattern * @param {number} amount - a number between 0 and 1 @@ -615,7 +615,7 @@ export const degradeBy = register( * * Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)` * - * @group transforms + * @tags transforms * @name degrade * @memberof Pattern * @returns Pattern @@ -632,7 +632,7 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, t * 1 = 0% chance of removal * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). * - * @group transforms + * @tags transforms * @name undegradeBy * @memberof Pattern * @param {number} amount - a number between 0 and 1 @@ -661,7 +661,7 @@ export const undegradeBy = register( * Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` * Events that would be removed by degrade are let through by undegrade and vice versa (see second example). * - * @group transforms + * @tags transforms * @name undegrade * @memberof Pattern * @returns Pattern @@ -680,7 +680,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t * Randomly applies the given function by the given probability. * Similar to `someCyclesBy` * - * @group transforms + * @tags transforms * @name sometimesBy * @memberof Pattern * @param {number | Pattern} probability - a number between 0 and 1 @@ -700,7 +700,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) { * * Applies the given function with a 50% chance * - * @group transforms + * @tags transforms * @name sometimes * @memberof Pattern * @param {function} function - the transformation to apply @@ -722,7 +722,7 @@ export const sometimes = register('sometimes', function (func, pat) { * @param {number | Pattern} probability - a number between 0 and 1 * @param {function} function - the transformation to apply * @returns Pattern - * @group transforms + * @tags transforms * @example * s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5")) */ @@ -745,7 +745,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) * @name someCycles * @memberof Pattern * @returns Pattern - * @group transforms + * @tags transforms * @example * s("bd,hh*8").someCycles(x=>x.speed("0.5")) */ @@ -760,7 +760,7 @@ export const someCycles = register('someCycles', function (func, pat) { * @name often * @memberof Pattern * @returns Pattern - * @group transforms + * @tags transforms * @example * s("hh*8").often(x=>x.speed("0.5")) */ @@ -775,7 +775,7 @@ export const often = register('often', function (func, pat) { * @name rarely * @memberof Pattern * @returns Pattern - * @group transforms + * @tags transforms * @example * s("hh*8").rarely(x=>x.speed("0.5")) */ @@ -787,7 +787,7 @@ export const rarely = register('rarely', function (func, pat) { * * Shorthand for `.sometimesBy(0.1, fn)` * - * @group transforms + * @tags transforms * @name almostNever * @memberof Pattern * @returns Pattern @@ -802,7 +802,7 @@ export const almostNever = register('almostNever', function (func, pat) { * * Shorthand for `.sometimesBy(0.9, fn)` * - * @group transforms + * @tags transforms * @name almostAlways * @memberof Pattern * @returns Pattern @@ -817,7 +817,7 @@ export const almostAlways = register('almostAlways', function (func, pat) { * * Shorthand for `.sometimesBy(0, fn)` (never calls fn) * - * @group transforms + * @tags transforms * @name never * @memberof Pattern * @returns Pattern @@ -832,7 +832,7 @@ export const never = register('never', function (_, pat) { * * Shorthand for `.sometimesBy(1, fn)` (always calls fn) * - * @group transforms + * @tags transforms * @name always * @memberof Pattern * @returns Pattern @@ -861,7 +861,7 @@ export function _keyDown(keyname) { * Do something on a keypress, or array of keypresses * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) * - * @group external_io + * @tags external_io * @name whenKey * @memberof Pattern * @returns Pattern @@ -878,7 +878,7 @@ export const whenKey = register('whenKey', function (input, func, pat) { * returns true when a key or array of keys is held * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) * - * @group external_io + * @tags external_io * @name keyDown * @memberof Pattern * @returns Pattern diff --git a/packages/motion/motion.mjs b/packages/motion/motion.mjs index cba814a8..fed0fbe0 100644 --- a/packages/motion/motion.mjs +++ b/packages/motion/motion.mjs @@ -7,7 +7,7 @@ import { signal } from '../core/signal.mjs'; * @name accelerationX * @return {Pattern} * @synonyms accX - * @group external_io + * @tags external_io * @example * n(accelerationX.segment(4).range(0,7)).scale("C:minor") * @@ -18,7 +18,7 @@ import { signal } from '../core/signal.mjs'; * @name accelerationY * @return {Pattern} * @synonyms accY - * @group external_io + * @tags external_io * @example * n(accelerationY.segment(4).range(0,7)).scale("C:minor") * @@ -29,7 +29,7 @@ import { signal } from '../core/signal.mjs'; * @name accelerationZ * @return {Pattern} * @synonyms accZ - * @group external_io + * @tags external_io * @example * n(accelerationZ.segment(4).range(0,7)).scale("C:minor") * @@ -40,7 +40,7 @@ import { signal } from '../core/signal.mjs'; * @name gravityX * @return {Pattern} * @synonyms gravX - * @group external_io + * @tags external_io * @example * n(gravityX.segment(4).range(0,7)).scale("C:minor") * @@ -51,7 +51,7 @@ import { signal } from '../core/signal.mjs'; * @name gravityY * @return {Pattern} * @synonyms gravY - * @group external_io + * @tags external_io * @example * n(gravityY.segment(4).range(0,7)).scale("C:minor") * @@ -62,7 +62,7 @@ import { signal } from '../core/signal.mjs'; * @name gravityZ * @return {Pattern} * @synonyms gravZ - * @group external_io + * @tags external_io * @example * n(gravityZ.segment(4).range(0,7)).scale("C:minor") * @@ -73,7 +73,7 @@ import { signal } from '../core/signal.mjs'; * @name rotationAlpha * @return {Pattern} * @synonyms rotA, rotZ, rotationZ - * @group external_io + * @tags external_io * @example * n(rotationAlpha.segment(4).range(0,7)).scale("C:minor") * @@ -84,7 +84,7 @@ import { signal } from '../core/signal.mjs'; * @name rotationBeta * @return {Pattern} * @synonyms rotB, rotX, rotationX - * @group external_io + * @tags external_io * @example * n(rotationBeta.segment(4).range(0,7)).scale("C:minor") * @@ -95,7 +95,7 @@ import { signal } from '../core/signal.mjs'; * @name rotationGamma * @return {Pattern} * @synonyms rotG, rotY, rotationY - * @group external_io + * @tags external_io * @example * n(rotationGamma.segment(4).range(0,7)).scale("C:minor") * @@ -106,7 +106,7 @@ import { signal } from '../core/signal.mjs'; * @name orientationAlpha * @return {Pattern} * @synonyms oriA, oriZ, orientationZ - * @group external_io + * @tags external_io * @example * n(orientationAlpha.segment(4).range(0,7)).scale("C:minor") * @@ -117,7 +117,7 @@ import { signal } from '../core/signal.mjs'; * @name orientationBeta * @return {Pattern} * @synonyms oriB, oriX, orientationX - * @group external_io + * @tags external_io * @example * n(orientationBeta.segment(4).range(0,7)).scale("C:minor") * @@ -128,7 +128,7 @@ import { signal } from '../core/signal.mjs'; * @name orientationGamma * @return {Pattern} * @synonyms oriG, oriY, orientationY - * @group external_io + * @tags external_io * @example * n(orientationGamma.segment(4).range(0,7)).scale("C:minor") * @@ -139,7 +139,7 @@ import { signal } from '../core/signal.mjs'; * @name absoluteOrientationAlpha * @return {Pattern} * @synonyms absOriA, absOriZ, absoluteOrientationZ - * @group external_io + * @tags external_io * @example * n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor") * @@ -150,7 +150,7 @@ import { signal } from '../core/signal.mjs'; * @name absoluteOrientationBeta * @return {Pattern} * @synonyms absOriB, absOriX, absoluteOrientationX - * @group external_io + * @tags external_io * @example * n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor") * @@ -161,7 +161,7 @@ import { signal } from '../core/signal.mjs'; * @name absoluteOrientationGamma * @return {Pattern} * @synonyms absOriG, absOriY, absoluteOrientationY - * @group external_io + * @tags external_io * @example * n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor") * diff --git a/packages/superdough/reverbGen.mjs b/packages/superdough/reverbGen.mjs index 0d5fee26..42da7c6d 100644 --- a/packages/superdough/reverbGen.mjs +++ b/packages/superdough/reverbGen.mjs @@ -80,7 +80,7 @@ reverbGen.generateGraph = function (data, width, height, min, max) { @param {number} lpFreqEndAt @param {!function(!AudioBuffer)} callback May be called immediately within the current execution context, or later. - @group effects + @tags effects */ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) { if (lpFreqStart == 0) { diff --git a/packages/supradough/dough.mjs b/packages/supradough/dough.mjs index 7eeb7a50..23dfbee0 100644 --- a/packages/supradough/dough.mjs +++ b/packages/supradough/dough.mjs @@ -18,7 +18,7 @@ function applyGainCurve(val) { * @param {number} a - Signal A (can be a single value or an array value in buffer processing). * @param {number} b - Signal B (can be a single value or an array value in buffer processing). * @param {number} m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix). - * @group effects + * @tags effects * @returns {number} Crossfaded output value. */ function crossfade(a, b, m) { From d516d765d5556617adb1a5cfe8b672c35a09effa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 11 Oct 2025 14:12:45 +0200 Subject: [PATCH 009/200] Redo frontend for tags --- .../src/repl/components/panel/Reference.jsx | 92 +++++++++++++------ 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 552551b5..3faa5c6f 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -10,8 +10,10 @@ const availableFunctions = (() => { const functions = []; for (const doc of jsdocJson.docs) { if (!isValid(doc)) continue; + if (seen.has(doc.name)) continue; functions.push(doc); const synonyms = doc.synonyms || []; + seen.add(doc.name); for (const s of synonyms) { if (!s || seen.has(s)) continue; seen.add(s); @@ -38,18 +40,33 @@ const getInnerText = (html) => { const GROUP_DISPLAY_NAMES = { external_io: 'External I/O', effects: 'Effects', - ungrouped: 'Ungrouped', + untagged: 'Untagged', structure: 'Structure', transforms: 'Transforms', }; -const GROUP_ORDER = ['effects', 'transforms', 'structure', 'ungrouped', 'external_io']; +const GROUP_ORDER = ['effects', 'transforms', 'structure', 'untagged', 'external_io']; export function Reference() { const [search, setSearch] = useState(''); + const [selectedTag, setSelectedTag] = useState(null); + + const toggleTag = (tag) => { + if (selectedTag === tag) { + setSelectedTag(null); + } else { + setSelectedTag(tag); + } + }; const visibleFunctions = useMemo(() => { return availableFunctions.filter((entry) => { + if (selectedTag) { + if (!(entry.tags || ['untagged']).includes(selectedTag)) { + return false; + } + } + if (!search) { return true; } @@ -60,12 +77,12 @@ export function Reference() { (entry.synonyms?.some((s) => s.includes(lowCaseSearch)) ?? false) ); }); - }, [search]); + }, [search, selectedTag]); const visibleFunctionsByGroup = (() => { const groups = {}; for (const doc of visibleFunctions) { - const group = doc.group || 'ungrouped'; + const group = (doc.tags || ['untagged'])[0]; if (!groups[group]) { groups[group] = []; } @@ -85,37 +102,56 @@ export function Reference() { return ai - bi; }); + const tagCounts = {}; + for (const doc of availableFunctions) { + (doc.tags || ['untagged']).forEach((t) => { + if (typeof t === 'string' && t) { + tagCounts[t] = (tagCounts[t] || 0) + 1; + } + }); + } + return ( -
-
-
+
+
From c62428ca27b34f62598db1506dd42af36f23f80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 12 Oct 2025 16:07:11 +0200 Subject: [PATCH 010/200] Document remaining functions --- packages/codemirror/codemirror.mjs | 1 + packages/codemirror/slider.mjs | 1 + packages/core/controls.mjs | 7 +- packages/core/drawLine.mjs | 1 + packages/core/euclid.mjs | 5 + packages/core/pattern.mjs | 159 ++++++++++++++------------- packages/core/pick.mjs | 13 +++ packages/core/repl.mjs | 5 + packages/core/signal.mjs | 1 + packages/csound/index.mjs | 2 + packages/draw/pianoroll.mjs | 2 + packages/draw/pitchwheel.mjs | 1 + packages/draw/spiral.mjs | 1 + packages/midi/midi.mjs | 4 + packages/osc/osc.mjs | 1 + packages/superdough/ola-processor.js | 29 +++-- packages/superdough/reverbGen.mjs | 5 +- packages/superdough/sampler.mjs | 1 + packages/superdough/superdough.mjs | 3 + packages/superdough/wavetable.mjs | 1 + packages/superdough/worklets.mjs | 20 +++- packages/tonal/tonal.mjs | 3 + packages/tonal/voicings.mjs | 4 + packages/webaudio/scope.mjs | 2 + packages/webaudio/spectrum.mjs | 1 + 25 files changed, 179 insertions(+), 94 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 4dc23996..193b93e3 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -379,6 +379,7 @@ function s4() { /** * Overrides the css of highlighted events. Make sure to use single quotes! * @name markcss + * @tag visualization * @example * note("c a f e") * .markcss('text-decoration:underline') diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 72f95125..cc224a6c 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -117,6 +117,7 @@ export const sliderPlugin = ViewPlugin.fromClass( * Displays a slider widget to allow the user manipulate a value * * @name slider + * @tags external_io, visualization * @param {number} value Initial value * @param {number} min Minimum value - optional, defaults to 0 * @param {number} max Maximum value - optional, defaults to 1 diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 44b358e6..4e60fc48 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -949,6 +949,7 @@ export const { duckattack } = registerControl('duckattack', 'duckatt'); * * @name byteBeatExpression * @synonyms bbexpr + * @tags effects * * @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat * @example @@ -962,6 +963,7 @@ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpressio * * @name byteBeatStartTime * @synonyms bbst + * @tags effects * * @param {number | Pattern} byteBeatStartTime in samples (t) * @example @@ -1518,7 +1520,7 @@ export const { delayspeed } = registerControl('delayspeed'); * Sets the time of the delay effect. * * @name delayspeed - * @tags effects, foo + * @tags effects * @param {number | Pattern} delayspeed controls the pitch of the delay feedback * @synonyms delayt, dt * @example @@ -1544,7 +1546,7 @@ export const { delaysync } = registerControl('delaysync'); * Specifies whether delaytime is calculated relative to cps. * * @name lock - * @tags effects, foo + * @tags effects * @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle. * @superdirtOnly * @example @@ -1812,6 +1814,7 @@ export const { octave } = registerControl('octave'); * An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects. * * @name orbit + * @tags effects * @param {number | Pattern} number * @example * stack( diff --git a/packages/core/drawLine.mjs b/packages/core/drawLine.mjs index 7509c0f6..212d0627 100644 --- a/packages/core/drawLine.mjs +++ b/packages/core/drawLine.mjs @@ -15,6 +15,7 @@ import Fraction, { gcd } from './fraction.mjs'; * - "-" hold previous value * - "." silence * + * @tags visualization * @param {Pattern} pattern the pattern to use * @param {number} chars max number of characters (approximately) * @returns string diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index 44ab07f1..e8252a98 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -61,6 +61,7 @@ export const bjork = function (ons, steps) { * * @memberof Pattern * @name euclid + * @tags temporal * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill * @returns Pattern @@ -73,6 +74,7 @@ export const bjork = function (ons, steps) { * Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence. * @memberof Pattern * @name euclidRot + * @tags temporal * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill * @param {number} rotation offset in steps @@ -156,6 +158,7 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun * so there will be no gaps. * @name euclidLegato * @memberof Pattern + * @tags temporal * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill * @param rotation offset in steps @@ -187,6 +190,7 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps, * the resulting sequence * @name euclidLegatoRot * @memberof Pattern + * @tags temporal * @param {number} pulses the number of onsets/beats * @param {number} steps the number of steps to fill * @param {number} rotation offset in steps @@ -208,6 +212,7 @@ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, s * @name euclidish * @synonyms eish * @memberof Pattern + * @tags temporal * @param {number} pulses the number of onsets * @param {number} steps the number of steps to fill * @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 39d6555f..2d6459a8 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -889,7 +889,7 @@ export class Pattern { /** * Writes the content of the current event to the console (visible in the side menu). - * @tags visualizers + * @tags visualization * @name log * @memberof Pattern * @example @@ -904,7 +904,7 @@ export class Pattern { /** * A simplified version of `log` which writes all "values" (various configurable parameters) * within the event to the console (visible in the side menu). - * @tags visualizers + * @tags visualization * @name logValues * @memberof Pattern * @example @@ -938,7 +938,7 @@ export class Pattern { * source pattern to be looped, and for an (optional) given function to be * applied. False values result in the corresponding part of the source pattern * to be played unchanged. - * @tags structure + * @tags temporal * @name into * @memberof Pattern * @example @@ -1066,6 +1066,7 @@ function _composeOp(a, b, func) { * Assumes a pattern of numbers. Adds the given number to each item in the pattern. * @name add * @memberof Pattern + * @tags math * @example * // Here, the triad 0, 2, 4 is shifted by different amounts * n("0 2 4".add("<0 3 4 0>")).scale("C:major") @@ -1076,7 +1077,6 @@ function _composeOp(a, b, func) { * note("c3 e3 g3".add("<0 5 7 0>")) * // Behind the scenes, the notes are converted to midi numbers: * // note("48 52 55".add("<0 5 7 0>")) - * @tags math */ add: [numeralArgs((a, b) => a + b)], // support string concatenation /** @@ -1084,6 +1084,7 @@ function _composeOp(a, b, func) { * Like add, but the given numbers are subtracted. * @name sub * @memberof Pattern + * @tags math * @example * n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor") * // See add for more information. @@ -1094,6 +1095,7 @@ function _composeOp(a, b, func) { * Multiplies each number by the given factor. * @name mul * @memberof Pattern + * @tags math * @example * "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq() */ @@ -1103,6 +1105,7 @@ function _composeOp(a, b, func) { * Divides each number by the given factor. * @name div * @memberof Pattern + * @tags math */ div: [numeralArgs((a, b) => a / b)], mod: [numeralArgs(_mod)], @@ -1189,7 +1192,7 @@ function _composeOp(a, b, func) { /** * Applies the given structure to the pattern: * - * @tags structure + * @tags temporal * @example * note("c,eb,g") * .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") @@ -1204,7 +1207,7 @@ function _composeOp(a, b, func) { /** * Returns silence when mask is 0 or "~" * - * @tags structure + * @tags temporal * @example * note("c [eb,g] d [eb,g]").mask("<1 [0 1]>") */ @@ -1217,7 +1220,7 @@ function _composeOp(a, b, func) { /** * Resets the pattern to the start of the cycle for each onset of the reset pattern. * - * @tags structure + * @tags temporal * @example * s("[ sd]*2, hh*8").reset("") */ @@ -1231,7 +1234,7 @@ function _composeOp(a, b, func) { * Restarts the pattern for each onset of the restart pattern. * While reset will only reset the current cycle, restart will start from cycle 0. * - * @tags structure + * @tags temporal * @example * s("[ sd]*2, hh*8").restart("") */ @@ -1352,7 +1355,7 @@ export function sequenceP(pats) { /** * The given items are played at the same time at the same length. * - * @tags structure + * @tags temporal * @return {Pattern} * @synonyms polyrhythm, pr * @example @@ -1566,14 +1569,9 @@ export function fastcat(...pats) { return result; } -/** See `fastcat` */ -export function sequence(...pats) { - return fastcat(...pats); -} - /** Like **cat**, but the items are crammed into one cycle. * @tags combiners - * @synonyms sequence, fastcat + * @synonyms seq, fastcat * @example * seq("e5", "b4", ["d5", "c5"]).note() * // "e5 b4 [d5 c5]".note() @@ -1584,6 +1582,9 @@ export function sequence(...pats) { * note("c4(5,8)") * ) */ +export function sequence(...pats) { + return fastcat(...pats); +} export function seq(...pats) { return fastcat(...pats); @@ -1877,7 +1878,7 @@ export const ratio = register('ratio', (pat) => // Structural and temporal transformations /** Compress each cycle into the given timespan, leaving a gap - * @tags structure + * @tags temporal * @example * cat( * s("bd sd").compress(.25,.75), @@ -1899,7 +1900,7 @@ export const { compressSpan, compressspan } = register(['compressSpan', 'compres /** * speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast. - * @tags structure + * @tags temporal * @name fastGap * @synonyms fastgap * @example @@ -1937,7 +1938,7 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f /** * Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle - * @tags structure + * @tags temporal * @example * s("bd hh sd hh").focus(1/4, 3/4) */ @@ -1955,7 +1956,7 @@ export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], fun }); /** The ply function repeats each event the given number of times. - * @tags structure + * @tags temporal * @example * s("bd ~ sd cp").ply("<1 2 3>") */ @@ -1970,7 +1971,7 @@ export const ply = register('ply', function (factor, pat) { /** * Speed up a pattern by the given factor. Used by "*" in mini notation. * - * @tags structure + * @tags temporal * @name fast * @synonyms density * @memberof Pattern @@ -1995,7 +1996,7 @@ export const { fast, density } = register( /** * Both speeds up the pattern (like 'fast') and the sample playback (like 'speed'). - * @tags structure + * @tags temporal * @example * s("bd sd:2").hurry("<1 2 4 3>").slow(1.5) */ @@ -2006,7 +2007,7 @@ export const hurry = register('hurry', function (r, pat) { /** * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. * - * @tags structure + * @tags temporal * @name slow * @synonyms sparsity * @memberof Pattern @@ -2024,7 +2025,7 @@ export const { slow, sparsity } = register(['slow', 'sparsity'], function (facto /** * Carries out an operation 'inside' a cycle. - * @tags structure + * @tags temporal * @example * "0 1 2 3 4 3 2 1".inside(4, rev).scale('C major').note() * // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note() @@ -2035,7 +2036,7 @@ export const inside = register('inside', function (factor, f, pat) { /** * Carries out an operation 'outside' a cycle. - * @tags structure + * @tags temporal * @example * "<[0 1] 2 [3 4] 5>".outside(4, rev).scale('C major').note() * // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note() @@ -2046,7 +2047,7 @@ export const outside = register('outside', function (factor, f, pat) { /** * Applies the given function every n cycles, starting from the last cycle. - * @tags structure + * @tags temporal * @name lastOf * @memberof Pattern * @param {number} n how many cycles @@ -2063,7 +2064,7 @@ export const lastOf = register('lastOf', function (n, func, pat) { /** * Applies the given function every n cycles, starting from the first cycle. - * @tags structure + * @tags temporal * @name firstOf * @memberof Pattern * @param {number} n how many cycles @@ -2075,7 +2076,7 @@ export const lastOf = register('lastOf', function (n, func, pat) { /** * An alias for `firstOf` - * @tags structure + * @tags temporal * @name every * @memberof Pattern * @param {number} n how many cycles @@ -2092,7 +2093,7 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu /** * Like layer, but with a single function: - * @tags structure + * @tags temporal * @name apply * @example * "".scale('C minor').apply(scaleTranspose("0,2,4")).note() @@ -2104,7 +2105,7 @@ export const apply = register('apply', function (func, pat) { /** * Plays the pattern at the given cycles per minute. - * @tags structure + * @tags temporal * @deprecated * @example * s(",hh*2").cpm(90) // = 90 bpm @@ -2117,7 +2118,7 @@ export const cpm = register('cpm', function (cpm, pat) { /** * Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator * - * @tags structure + * @tags temporal * @name early * @memberof Pattern * @param {number | Pattern} cycles number of cycles to nudge left @@ -2138,7 +2139,7 @@ export const early = register( /** * Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator * - * @tags structure + * @tags temporal * @name late * @memberof Pattern * @param {number | Pattern} cycles number of cycles to nudge right @@ -2159,7 +2160,7 @@ export const late = register( /** * Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern: * - * @tags structure + * @tags temporal * @example * s("bd*2 hh*3 [sd bd]*2 perc").zoom(0.25, 0.75) * // s("hh*3 [sd bd]*2") // equivalent @@ -2186,7 +2187,7 @@ export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a /** * Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers. * Similar to `slice`, but slices up patterns rather than sound samples. - * @tags structure + * @tags temporal * @param {number} number of slices * @param {number} slices to play * @example @@ -2214,7 +2215,7 @@ export const bite = register( /** * Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle. - * @tags structure + * @tags temporal * @param {number} fraction fraction to select * @example * s("lt ht mt cp, [hh oh]*2").linger("<1 .5 .25 .125>") @@ -2235,7 +2236,7 @@ export const linger = register( /** * Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one. - * @tags structure + * @tags temporal * @name segment * @synonyms seg * @param {number} segments number of segments per cycle @@ -2248,7 +2249,7 @@ export const { segment, seg } = register(['segment', 'seg'], function (rate, pat /** * The function `swingBy x n` breaks each cycle into `n` slices, and then delays events in the second half of each slice by the amount `x`, which is relative to the size of the (half) slice. So if `x` is 0 it does nothing, `0.5` delays for half the note duration, and 1 will wrap around to doing nothing again. The end result is a shuffle or swing-like rhythm - * @tags structure + * @tags temporal * @param {number} subdivision * @param {number} offset * @example @@ -2258,7 +2259,7 @@ export const swingBy = register('swingBy', (swing, n, pat) => pat.inside(n, late /** * Shorthand for swingBy with 1/3: - * @tags structure + * @tags temporal * @param {number} subdivision * @example * s("hh*8").swing(4) @@ -2268,7 +2269,7 @@ export const swing = register('swing', (n, pat) => pat.swingBy(1 / 3, n)); /** * Swaps 1s and 0s in a binary pattern. - * @tags structure + * @tags temporal * @name invert * @synonyms inv * @example @@ -2286,7 +2287,7 @@ export const { invert, inv } = register( /** * Applies the given function whenever the given pattern is in a true state. - * @tags structure + * @tags temporal * @name when * @memberof Pattern * @param {Pattern} binary_pat @@ -2301,7 +2302,7 @@ export const when = register('when', function (on, func, pat) { /** * Superimposes the function result on top of the original pattern, delayed by the given time. - * @tags structure + * @tags temporal * @name off * @memberof Pattern * @param {Pattern | number} time offset time @@ -2318,7 +2319,7 @@ export const off = register('off', function (time_pat, func, pat) { * Returns a new pattern where every other cycle is played once, twice as * fast, and offset in time by one quarter of a cycle. Creates a kind of * breakbeat feel. - * @tags structure + * @tags temporal * @returns Pattern */ export const brak = register('brak', function (pat) { @@ -2328,7 +2329,7 @@ export const brak = register('brak', function (pat) { /** * Reverse all haps in a pattern * - * @tags structure + * @tags temporal * @name rev * @memberof Pattern * @returns Pattern @@ -2362,7 +2363,7 @@ export const rev = register( /** Like press, but allows you to specify the amount by which each * event is shifted. pressBy(0.5) is the same as press, while * pressBy(1/3) shifts each event by a third of its timespan. - * @tags structure + * @tags temporal * @example * stack(s("hh*4"), * s("bd mt sd ht").pressBy("<0 0.5 0.25>") @@ -2374,7 +2375,7 @@ export const pressBy = register('pressBy', function (r, pat) { /** * Syncopates a rhythm, by shifting each event halfway into its timespan. - * @tags structure + * @tags temporal * @example * stack(s("hh*4"), * s("bd mt sd ht").every(4, press) @@ -2386,7 +2387,7 @@ export const press = register('press', function (pat) { /** * Silences a pattern. - * @tags structure + * @tags temporal * @example * stack( * s("bd").hush(), @@ -2399,7 +2400,7 @@ Pattern.prototype.hush = function () { /** * Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards. - * @tags structure + * @tags temporal * @example * note("c d e g").palindrome() */ @@ -2414,7 +2415,7 @@ export const palindrome = register( /** * Jux with adjustable stereo width. 0 = mono, 1 = full stereo. - * @tags structure + * @tags temporal * @name juxBy * @synonyms juxby * @example @@ -2436,7 +2437,7 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, /** * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. - * @tags structure + * @tags temporal * @example * s("bd lt [~ ht] mt cp ~ bd hh").jux(rev) * @example @@ -2450,7 +2451,7 @@ export const jux = register('jux', function (func, pat) { /** * Superimpose and offset multiple times, applying the given function each time. - * @tags structure + * @tags temporal * @name echoWith * @synonyms echowith, stutWith, stutwith * @param {number} times how many times to repeat @@ -2470,7 +2471,7 @@ export const { echoWith, echowith, stutWith, stutwith } = register( /** * Superimpose and offset multiple times, gradually decreasing the velocity - * @tags structure + * @tags temporal * @name echo * @memberof Pattern * @returns Pattern @@ -2486,7 +2487,7 @@ export const echo = register('echo', function (times, time, feedback, pat) { /** * Deprecated. Like echo, but the last 2 parameters are flipped. - * @tags structure + * @tags temporal * @name stut * @param {number} times how many times to repeat * @param {number} feedback velocity multiplicator for each iteration @@ -2508,7 +2509,7 @@ export const applyN = register('applyN', function (n, func, p) { /** * The plyWith function repeats each event the given number of times, applying the given function to each event.\n - * @tags structure + * @tags temporal * @name plyWith * @synonyms plywith * @param {number} factor how many times to repeat @@ -2531,7 +2532,7 @@ export const plyWith = register(['plyWith', 'plywith'], function (factor, func, /** * The plyForEach function repeats each event the given number of times, applying the given function to each event. * This version of ply uses the iteration index as an argument to the function, similar to echoWith. - * @tags structure + * @tags temporal * @name plyForEach * @synonyms plyforeach * @param {number} factor how many times to repeat @@ -2553,7 +2554,7 @@ export const plyForEach = register(['plyForEach', 'plyforeach'], function (facto /** * Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played. - * @tags structure + * @tags temporal * @name iter * @memberof Pattern * @returns Pattern @@ -2581,7 +2582,7 @@ export const iter = register( /** * Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles - * @tags structure + * @tags temporal * @name iterBack * @synonyms iterback * @memberof Pattern @@ -2600,7 +2601,7 @@ export const { iterBack, iterback } = register( /** * Repeats each cycle the given number of times. - * @tags structure + * @tags temporal * @name repeatCycles * @memberof Pattern * @returns Pattern @@ -2624,7 +2625,7 @@ export const { repeatCycles } = register( /** * Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). - * @tags structure + * @tags temporal * @name chunk * @synonyms slowChunk, slowchunk * @memberof Pattern @@ -2656,7 +2657,7 @@ export const { chunk, slowchunk, slowChunk } = register( /** * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles - * @tags structure + * @tags temporal * @name chunkBack * @synonyms chunkback * @memberof Pattern @@ -2677,7 +2678,7 @@ export const { chunkBack, chunkback } = register( /** * Like `chunk`, but the cycles of the source pattern aren't repeated * for each set of chunks. - * @tags structure + * @tags temporal * @name fastChunk * @synonyms fastchunk * @memberof Pattern @@ -2698,7 +2699,7 @@ export const { fastchunk, fastChunk } = register( /** * Like `chunk`, but the function is applied to a looped subcycle of the source pattern. - * @tags structure + * @tags temporal * @name chunkInto * @synonyms chunkinto * @memberof Pattern @@ -2712,7 +2713,7 @@ export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], fun /** * Like `chunkInto`, but moves backwards through the chunks. - * @tags structure + * @tags temporal * @name chunkBackInto * @synonyms chunkbackinto * @memberof Pattern @@ -2743,7 +2744,7 @@ export const bypass = register( /** * Loops the pattern inside an `offset` for `cycles`. * If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it. - * @tags structure + * @tags temporal * @name ribbon * @synonyms rib * @param {number} offset start point of loop in cycles @@ -2772,7 +2773,7 @@ export const hsl = register('hsl', (h, s, l, pat) => { /** * Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array). * @name tag - * @tags structure + * @tags temporal * @noAutocomplete * @param {string} tag anything unique */ @@ -2783,7 +2784,7 @@ Pattern.prototype.tag = function (tag) { /** * Filters haps using the given function * @name filter - * @tags structure + * @tags temporal * @param {Function} test function to test Hap * @example * s("hh!7 oh").filter(hap => hap.value.s==='hh') @@ -2793,7 +2794,7 @@ export const filter = register('filter', (test, pat) => pat.withHaps((haps) => h /** * Filters haps by their begin time * @name filterWhen - * @tags structure + * @tags temporal * @noAutocomplete * @param {Function} test function to test Hap.whole.begin */ @@ -2802,7 +2803,7 @@ export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) = /** * Use within to apply a function to only a part of a pattern. * @name within - * @tags structure + * @tags temporal * @param {number} start start within cycle (0 - 1) * @param {number} end end within cycle (0 - 1). Must be > start * @param {Function} func function to be applied to the sub-pattern @@ -2877,7 +2878,7 @@ export function _match(span, hap_p) { * *Experimental* * * Speeds a pattern up or down, to fit to the given number of steps per cycle. - * @tags structure + * @tags temporal * @example * sound("bd sd cp").pace(4) * // The same as sound("{bd sd cp}%4") or sound("*4") @@ -2919,7 +2920,7 @@ export function _polymeterListSteps(steps, ...args) { * *Experimental* * * Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps. - * @tags structure + * @tags temporal * @synonyms pm * @example * // The same as note("{c eb g, c2 g2}%6") @@ -3037,7 +3038,7 @@ export function stepalt(...groups) { * * Takes the given number of steps from a pattern (dropping the rest). * A positive number will take steps from the start of a pattern, and a negative number from the end. - * @tags structure + * @tags temporal * @return {Pattern} * @example * "bd cp ht mt".take("2").sound() @@ -3082,7 +3083,7 @@ export const take = stepRegister('take', function (i, pat) { * * Drops the given number of steps from a pattern. * A positive number will drop steps from the start of a pattern, and a negative number from the end. - * @tags structure + * @tags temporal * @return {Pattern} * @example * "tha dhi thom nam".drop("1").sound().bank("mridangam") @@ -3111,7 +3112,7 @@ export const drop = stepRegister('drop', function (i, pat) { * `extend` is similar to `fast` in that it increases its density, but it also increases the step count * accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas * `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`. - * @tags structure + * @tags temporal * @example * stepcat( * sound("bd bd - cp").extend(2), @@ -3126,7 +3127,7 @@ export const extend = stepRegister('extend', function (factor, pat) { * *Experimental* * * Expands the step size of the pattern by the given factor. - * @tags structure + * @tags temporal * @example * sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8) */ @@ -3138,7 +3139,7 @@ export const expand = stepRegister('expand', function (factor, pat) { * *Experimental* * * Contracts the step size of the pattern by the given factor. See also `expand`. - * @tags structure + * @tags temporal * @example * sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8) */ @@ -3193,7 +3194,7 @@ export const shrinklist = (amount, pat) => pat.shrinklist(amount); * Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with `:`), * that number of times. * A positive number will progressively drop steps from the start of a pattern, and a negative number from the end. - * @tags structure + * @tags temporal * @return {Pattern} * @example * "tha dhi thom nam".shrink("1").sound() @@ -3233,7 +3234,7 @@ export const shrink = register( * Progressively grows the pattern by 'n' steps until the full pattern is played, or if a second value is given (using mininotation list syntax with `:`), * that number of times. * A positive number will progressively grow steps from the start of a pattern, and a negative number from the end. - * @tags structure + * @tags temporal * @return {Pattern} * @example * "tha dhi thom nam".grow("1").sound() @@ -3552,7 +3553,9 @@ export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], fun return _loopAt(factor, pat, cps); }); -/** exposes a custom value at query time. basically allows mutating state without evaluation */ +/** exposes a custom value at query time. basically allows mutating state without evaluation + * @tags internals + */ export const ref = (accessor) => pure(1) .withValue(() => reify(accessor())) @@ -3589,7 +3592,7 @@ Pattern.prototype.xfade = function (pos, b) { * creates a structure pattern from divisions of a cycle * especially useful for creating rhythms * @name beat - * @tags structure + * @tags temporal * @example * s("bd").beat("0,7,10", 16) * @example @@ -3666,7 +3669,7 @@ export const _morph = (from, to, by) => { * sine.slow(8) // slowly morph between the rhythms * ) * ) - * @tags structure + * @tags temporal */ export const morph = (frompat, topat, bypat) => { frompat = reify(frompat); diff --git a/packages/core/pick.mjs b/packages/core/pick.mjs index 206fa619..cce69190 100644 --- a/packages/core/pick.mjs +++ b/packages/core/pick.mjs @@ -28,6 +28,7 @@ const _pick = function (lookup, pat, modulo = true) { /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `inhabit`, but maintains the structure of the original patterns. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -57,6 +58,7 @@ const __pick = register('pick', function (lookup, pat) { * it wraps around, rather than sticking at the maximum value. * For example, if you pick the fifth pattern of a list of three, you'll get the * second one. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -67,6 +69,7 @@ export const pickmod = register('pickmod', function (lookup, pat) { }); /** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern. + * @tags combiners, functional * @param {Pattern} pat * @param {Pattern} lookup a pattern of indices * @param {function[]} funcs the array of functions from which to pull @@ -83,6 +86,7 @@ export const pickF = register('pickF', function (lookup, funcs, pat) { /** * The same as `pickF`, but if you pick a number greater than the size of the functions list, * it wraps around, rather than sticking at the maximum value. + * @tags combiners * @param {Pattern} pat * @param {Pattern} lookup a pattern of indices * @param {function[]} funcs the array of functions from which to pull @@ -93,6 +97,7 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { }); /** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -103,6 +108,7 @@ export const pickOut = register('pickOut', function (lookup, pat) { /** * The same as `pickOut`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -112,6 +118,7 @@ export const pickmodOut = register('pickmodOut', function (lookup, pat) { }); /** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -122,6 +129,7 @@ export const pickRestart = register('pickRestart', function (lookup, pat) { /** * The same as `pickRestart`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -138,6 +146,7 @@ export const pickmodRestart = register('pickmodRestart', function (lookup, pat) }); /** * Similar to `pick`, but the choosen pattern is reset when its index is triggered. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -148,6 +157,7 @@ export const pickReset = register('pickReset', function (lookup, pat) { /** * The same as `pickReset`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -159,6 +169,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) { /** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. * @name inhabit + * @tags combiners * @synonyms pickSqueeze * @param {Pattern} pat * @param {*} xs @@ -180,6 +191,7 @@ export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], fun * second one. * @name inhabitmod * @synonyms pickmodSqueeze + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -192,6 +204,7 @@ export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSq /** * Pick from the list of values (or patterns of values) via the index using the given * pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event + * @tags combiners * @param {Pattern} pat * @param {*} xs * @returns {Pattern} diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 53562f21..b8bab3d4 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -102,6 +102,7 @@ export function repl({ * Changes the global tempo to the given cycles per minute * * @name setcpm + * @tags temporal * @alias setCpm * @param {number} cpm cycles per minute * @example @@ -127,6 +128,8 @@ export function repl({ * $: sound("hh*8") * all(x => x.pianoroll()) * ``` + * + * @tags combiners */ let allTransforms = []; const all = function (transform) { @@ -134,11 +137,13 @@ export function repl({ return silence; }; /** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern. + * * ``` * $: sound("bd - cp sd") * $: sound("hh*8") * each(fast("<2 3>")) * ``` + * @tags combiners */ const each = function (transform) { eachTransform = transform; diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 03dd641a..2c3a6332 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -337,6 +337,7 @@ export const scramble = register('scramble', (n, pat) => { export const rand = signal(timeToRand); /** * A continuous pattern of random numbers, between -1 and 1 + * @tags generators */ export const rand2 = rand.toBipolar(); diff --git a/packages/csound/index.mjs b/packages/csound/index.mjs index e4476522..e459f387 100644 --- a/packages/csound/index.mjs +++ b/packages/csound/index.mjs @@ -135,6 +135,8 @@ export async function loadOrc(url) { * p4 -- MIDI key number (as a real number, not an integer but in [0, 127]. * p5 -- MIDI velocity (as a real number, not an integer but in [0, 127]. * p6 -- Strudel controls, as a string. + * + * @tags external_io */ export const csoundm = register('csoundm', (instrument, pat) => { let p1 = instrument; diff --git a/packages/draw/pianoroll.mjs b/packages/draw/pianoroll.mjs index 1cf218fa..6fb1dfc7 100644 --- a/packages/draw/pianoroll.mjs +++ b/packages/draw/pianoroll.mjs @@ -42,6 +42,7 @@ const getValue = (e) => { * * @name pianoroll * @synonyms punchcard + * @tags visualization * @param {Object} options Object containing all the optional following parameters as key value pairs: * @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4 * @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5 @@ -299,6 +300,7 @@ Pattern.prototype.punchcard = function (options) { * Supports all the same options as pianoroll. * * @name wordfall + * @tags visualization */ Pattern.prototype.wordfall = function (options) { return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options }); diff --git a/packages/draw/pitchwheel.mjs b/packages/draw/pitchwheel.mjs index ba76df07..f9dacc53 100644 --- a/packages/draw/pitchwheel.mjs +++ b/packages/draw/pitchwheel.mjs @@ -116,6 +116,7 @@ export function pitchwheel({ /** * Renders a pitch circle to visualize frequencies within one octave * @name pitchwheel + * @tags visualization * @param {number} hapcircles * @param {number} circle * @param {number} edo diff --git a/packages/draw/spiral.mjs b/packages/draw/spiral.mjs index cebf3d37..c18c66c5 100644 --- a/packages/draw/spiral.mjs +++ b/packages/draw/spiral.mjs @@ -129,6 +129,7 @@ function drawSpiral(options) { * Displays a spiral visual. * * @name spiral + * @tags visualization * @param {Object} options Object containing all the optional following parameters as key value pairs: * @param {number} stretch controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees * @param {number} size the diameter of the spiral diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index ae983d20..bff36c87 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -123,6 +123,7 @@ function githubPath(base, subpath = '') { /** * configures the default midimap, which is used when no "midimap" port is set + * @tags external_io * @example * defaultmidimap({ lpf: 74 }) * $: note("c a f e").midi(); @@ -136,6 +137,7 @@ let loadCache = {}; /** * Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers. + * @tags external_io * @example * midimaps({ mymap: { lpf: 74 } }) * $: note("c a f e") @@ -278,6 +280,7 @@ function sendNote(note, velocity, duration, device, midichan, timeOffsetString) /** * MIDI output: Opens a MIDI output port. + * @tags external_io * @param {string | number} midiport MIDI device name or index defaulting to 0 * @param {object} options Additional MIDI configuration options * @example @@ -465,6 +468,7 @@ const refs = {}; /** * MIDI input: Opens a MIDI input port to receive MIDI control change messages. + * @tags external_io * @param {string | number} input MIDI device name or index defaulting to 0 * @returns {Function} * @example diff --git a/packages/osc/osc.mjs b/packages/osc/osc.mjs index fe069152..1358ad2d 100644 --- a/packages/osc/osc.mjs +++ b/packages/osc/osc.mjs @@ -78,6 +78,7 @@ export async function oscTrigger(hap, currentTime, cps = 1, targetTime) { * For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output/) * * @name osc + * @tags external_io * @memberof Pattern * @returns Pattern */ diff --git a/packages/superdough/ola-processor.js b/packages/superdough/ola-processor.js index 38d45a25..0000d1bb 100644 --- a/packages/superdough/ola-processor.js +++ b/packages/superdough/ola-processor.js @@ -35,7 +35,9 @@ class OLAProcessor extends AudioWorkletProcessor { } /** Handles dynamic reallocation of input/output channels buffer - (channel numbers may lety during lifecycle) **/ + * (channel numbers may vary during lifecycle) + * @tags internals + **/ reallocateChannelsIfNeeded(inputs, outputs) { for (let i = 0; i < this.nbInputs; i++) { let nbChannels = inputs[i].length; @@ -88,7 +90,10 @@ class OLAProcessor extends AudioWorkletProcessor { } } - /** Read next web audio block to input buffers **/ + /** + * Read next web audio block to input buffers + * @tags internals + **/ readInputs(inputs) { // when playback is paused, we may stop receiving new samples if (inputs[0].length && inputs[0][0].length == 0) { @@ -108,7 +113,9 @@ class OLAProcessor extends AudioWorkletProcessor { } } - /** Write next web audio block from output buffers **/ + /** Write next web audio block from output buffers + * @tags internals + **/ writeOutputs(outputs) { for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { @@ -118,7 +125,9 @@ class OLAProcessor extends AudioWorkletProcessor { } } - /** Shift left content of input buffers to receive new web audio block **/ + /** Shift left content of input buffers to receive new web audio block + * @tags internals + **/ shiftInputBuffers() { for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { @@ -127,7 +136,9 @@ class OLAProcessor extends AudioWorkletProcessor { } } - /** Shift left content of output buffers to receive new web audio block **/ + /** Shift left content of output buffers to receive new web audio block + * @tags internals + **/ shiftOutputBuffers() { for (let i = 0; i < this.nbOutputs; i++) { for (let j = 0; j < this.outputBuffers[i].length; j++) { @@ -137,7 +148,9 @@ class OLAProcessor extends AudioWorkletProcessor { } } - /** Copy contents of input buffers to buffer actually sent to process **/ + /** Copy contents of input buffers to buffer actually sent to process + * @tags internals + **/ prepareInputBuffersToSend() { for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { @@ -146,7 +159,9 @@ class OLAProcessor extends AudioWorkletProcessor { } } - /** Add contents of output buffers just processed to output buffers **/ + /** Add contents of output buffers just processed to output buffers + * @tags internals + **/ handleOutputBuffersToRetrieve() { for (let i = 0; i < this.nbOutputs; i++) { for (let j = 0; j < this.outputBuffers[i].length; j++) { diff --git a/packages/superdough/reverbGen.mjs b/packages/superdough/reverbGen.mjs index 42da7c6d..c52188b0 100644 --- a/packages/superdough/reverbGen.mjs +++ b/packages/superdough/reverbGen.mjs @@ -16,6 +16,7 @@ var reverbGen = {}; /** Generates a reverb impulse response. + @tags internals @param {!Object} params TODO: Document the properties. @param {!function(!AudioBuffer)} callback Function to call when the impulse response has been generated. The impulse response @@ -48,7 +49,7 @@ reverbGen.generateReverb = function (params, callback) { /** Creates a canvas element showing a graph of the given data. - + @tags internals @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. @@ -80,7 +81,7 @@ reverbGen.generateGraph = function (data, width, height, min, max) { @param {number} lpFreqEndAt @param {!function(!AudioBuffer)} callback May be called immediately within the current execution context, or later. - @tags effects + @tags internals */ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) { if (lpFreqStart == 0) { diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index b195ba79..69a949ad 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -221,6 +221,7 @@ export async function fetchSampleMap(url) { /** * Loads a collection of samples to use with `s` + * @tags samples * @example * samples('github:tidalcycles/dirt-samples'); * s("[bd ~]*2, [~ hh]*2, ~ sd") diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 52ed9bff..873fdfa5 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -91,6 +91,8 @@ async function aliasBankPath(path) { * Optionally accepts a single argument string of a path to a JSON file containing bank aliases. * @param {string} bank - The bank to alias * @param {string} alias - The alias to use for the bank + * + * @tags samples */ export async function aliasBank(...args) { switch (args.length) { @@ -109,6 +111,7 @@ export async function aliasBank(...args) { /** * Register an alias for a sound. + * @tags samples * @param {string} original - The original sound name * @param {string} alias - The alias to use for the sound */ diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index 44367f78..cfc669c8 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -180,6 +180,7 @@ export function registerWaveTable(key, tables, params) { * Loads a collection of wavetables to use with `s` * * @name tables + * @tags effects */ export const tables = async (url, frameLen, json, options = {}) => { if (json !== undefined) return _processTables(json, url, frameLen); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index ccfaf510..3e827622 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -635,14 +635,18 @@ class PhaseVocoderProcessor extends OLAProcessor { this.timeCursor += this.hopSize; } - /** Apply Hann window in-place */ + /** Apply Hann window in-place + * @tags internals + */ applyHannWindow(input) { for (var i = 0; i < this.blockSize; i++) { input[i] = input[i] * this.hannWindow[i] * 1.62; } } - /** Compute squared magnitudes for peak finding **/ + /** Compute squared magnitudes for peak finding + * @tags internals + **/ computeMagnitudes() { var i = 0, j = 0; @@ -656,7 +660,9 @@ class PhaseVocoderProcessor extends OLAProcessor { } } - /** Find peaks in spectrum magnitudes **/ + /** Find peaks in spectrum magnitudes + * @tags internals + **/ findPeaks() { this.nbPeaks = 0; var i = 2; @@ -680,7 +686,9 @@ class PhaseVocoderProcessor extends OLAProcessor { } } - /** Shift peaks and regions of influence by pitchFactor into new specturm */ + /** Shift peaks and regions of influence by pitchFactor into new specturm + * @tags internals + */ shiftPeaks(pitchFactor) { // zero-fill new spectrum this.freqComplexBufferShifted.fill(0); @@ -843,7 +851,9 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); -/** BYTE BEATS */ +/** BYTE BEATS + * @tags internals + */ const chyx = { /*bit*/ bitC: function (x, y, z) { return x & y ? z : 0; diff --git a/packages/tonal/tonal.mjs b/packages/tonal/tonal.mjs index ae75ab69..613ba51e 100644 --- a/packages/tonal/tonal.mjs +++ b/packages/tonal/tonal.mjs @@ -92,6 +92,7 @@ function scaleOffset(scale, offset, note) { * - 5P = perfect fifth * - 5d = diminished fifth * + * @tags music_theory * @param {string | number} amount Either number of semitones or interval string. * @returns Pattern * @memberof Pattern @@ -146,6 +147,7 @@ export const { transpose, trans } = register(['transpose', 'trans'], function tr * * @memberof Pattern * @name scaleTranspose + * @tags music_theory * @param {offset} offset number of steps inside the scale * @returns Pattern * @synonyms scaleTrans, strans @@ -235,6 +237,7 @@ function _getNearestScaleNote(scaleName, note, preferHigher = true) { * The root note defaults to octave 3, if no octave number is given. * * @name scale + * @tags music_theory * @param {string} scale Name of scale * @returns Pattern * @example diff --git a/packages/tonal/voicings.mjs b/packages/tonal/voicings.mjs index d81911e0..6dce6ca8 100644 --- a/packages/tonal/voicings.mjs +++ b/packages/tonal/voicings.mjs @@ -90,6 +90,7 @@ export const setVoicingRange = (name, range) => addVoicings(name, voicingRegistr * Adds a new custom voicing dictionary. * * @name addVoicings + * @tags music_theory * @memberof Pattern * @param {string} name identifier for the voicing dictionary * @param {Object} dictionary maps chord symbol to possible voicings @@ -133,6 +134,7 @@ const getVoicing = (chord, dictionaryName, lastVoicing) => { * Uses [chord-voicings package](https://github.com/felixroos/chord-voicings#chord-voicings). * * @name voicings + * @tags music_theory * @memberof Pattern * @param {string} dictionary which voicing dictionary to use. * @returns Pattern @@ -157,6 +159,7 @@ export const voicings = register('voicings', function (dictionary, pat) { * Maps the chords of the incoming pattern to root notes in the given octave. * * @name rootNotes + * @tags music_theory * @memberof Pattern * @param {octave} octave octave to use * @returns Pattern @@ -189,6 +192,7 @@ export const rootNotes = register('rootNotes', function (octave, pat) { * If you pass a pattern of strings to voicing, they will be interpreted as chords. * * @name voicing + * @tags music_theory * @returns Pattern * @example * n("0 1 2 3").chord("").voicing() diff --git a/packages/webaudio/scope.mjs b/packages/webaudio/scope.mjs index e423b20f..866e0a43 100644 --- a/packages/webaudio/scope.mjs +++ b/packages/webaudio/scope.mjs @@ -98,6 +98,7 @@ function clearScreen(smear = 0, smearRGB = `0,0,0`, ctx = getDrawContext()) { /** * Renders an oscilloscope for the frequency domain of the audio signal. * @name fscope + * @tags visualization * @param {string} color line color as hex or color name. defaults to white. * @param {number} scale scales the y-axis. Defaults to 0.25 * @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen @@ -122,6 +123,7 @@ Pattern.prototype.fscope = function (config = {}) { * Renders an oscilloscope for the time domain of the audio signal. * @name scope * @synonyms tscope + * @tags visualization * @param {object} config optional config with options: * @param {boolean} align if 1, the scope will be aligned to the first zero crossing. defaults to 1 * @param {string} color line color as hex or color name. defaults to white. diff --git a/packages/webaudio/spectrum.mjs b/packages/webaudio/spectrum.mjs index 2ddd214f..c67e321b 100644 --- a/packages/webaudio/spectrum.mjs +++ b/packages/webaudio/spectrum.mjs @@ -5,6 +5,7 @@ import { analysers, getAnalyzerData } from 'superdough'; /** * Renders a spectrum analyzer for the incoming audio signal. * @name spectrum + * @tags visualization * @param {object} config optional config with options: * @param {integer} thickness line thickness in px (default 3) * @param {integer} speed scroll speed (default 1) From ee1a894f3ba2dc468fbf6510afc50c3a7387e2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 12 Oct 2025 16:28:05 +0200 Subject: [PATCH 011/200] Show tags in reference, more compact functions list --- .../src/repl/components/panel/Reference.jsx | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 3faa5c6f..d1157ba2 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -11,6 +11,7 @@ const availableFunctions = (() => { for (const doc of jsdocJson.docs) { if (!isValid(doc)) continue; if (seen.has(doc.name)) continue; + doc.tags = doc.tags?.filter((t) => t && typeof t === 'string') || []; functions.push(doc); const synonyms = doc.synonyms || []; seen.add(doc.name); @@ -113,7 +114,7 @@ export function Reference() { return (
-
+
@@ -134,19 +135,21 @@ export function Reference() { ))}
- @@ -161,8 +164,17 @@ export function Reference() { that you can already make music with a small set of functions!

{visibleFunctions.map((entry, i) => ( -
-

{entry.name}

+
+
+

+ {entry.name} +

+ {entry.tags && ( + + {entry.tags.join(', ')} + + )} +
{!!entry.synonyms_text && (

Synonyms: {entry.synonyms_text} From a4212589e5e0a2d3d6e9a31bf196f30e557b24e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 12 Oct 2025 16:33:07 +0200 Subject: [PATCH 012/200] Recategorize --- packages/core/controls.mjs | 36 ++++++++++++++-------------- packages/core/pattern.mjs | 20 ++++++++-------- packages/core/signal.mjs | 48 +++++++++++++++++++------------------- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 4e60fc48..db689e1a 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1621,7 +1621,7 @@ export const { fadeInTime } = registerControl('fadeInTime'); * Set frequency of sound. * * @name freq - * @tags transforms + * @tags temporal * @param {number | Pattern} frequency in Hz. the audible range is between 20 and 20000 Hz * @example * freq("220 110 440 110").s("superzow").osc() @@ -2205,7 +2205,7 @@ export const { cps } = registerControl('cps'); * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. * * @name clip - * @tags transforms + * @tags temporal * @synonyms legato * @param {number | Pattern} factor >= 0 * @example @@ -2218,7 +2218,7 @@ export const { clip, legato } = registerControl('clip', 'legato'); * Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration. * * @name duration - * @tags transforms + * @tags temporal * @synonyms dur * @param {number | Pattern} seconds >= 0 * @example @@ -2297,7 +2297,7 @@ export const ar = register('ar', (t, pat) => { * MIDI channel: Sets the MIDI channel for the event. * * @name midichan - * @tags examples + * @tags external_io * @param {number | Pattern} channel MIDI channel number (0-15) * @example * note("c4").midichan(1).midi() @@ -2310,7 +2310,7 @@ export const { midimap } = registerControl('midimap'); * MIDI port: Sets the MIDI port for the event. * * @name midiport - * @tags examples + * @tags external_io * @param {number | Pattern} port MIDI port * @example * note("c a f e").midiport("<0 1 2 3>").midi() @@ -2321,7 +2321,7 @@ export const { midiport } = registerControl('midiport'); * MIDI command: Sends a MIDI command message. * * @name midicmd - * @tags examples + * @tags external_io * @param {number | Pattern} command MIDI command * @example * midicmd("clock*48,/2").midi() @@ -2332,7 +2332,7 @@ export const { midicmd } = registerControl('midicmd'); * MIDI control: Sends a MIDI control change message. * * @name control - * @tags examples + * @tags external_io * @param {number | Pattern} MIDI control number (0-127) * @param {number | Pattern} MIDI controller value (0-127) */ @@ -2348,7 +2348,7 @@ export const control = register('control', (args, pat) => { * MIDI control number: Sends a MIDI control change message. * * @name ccn - * @tags examples + * @tags external_io * @param {number | Pattern} MIDI control number (0-127) */ export const { ccn } = registerControl('ccn'); @@ -2356,7 +2356,7 @@ export const { ccn } = registerControl('ccn'); * MIDI control value: Sends a MIDI control change message. * * @name ccv - * @tags examples + * @tags external_io * @param {number | Pattern} MIDI control value (0-127) */ export const { ccv } = registerControl('ccv'); @@ -2366,7 +2366,7 @@ export const { ctlNum } = registerControl('ctlNum'); /** * MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message. * @name nrpnn - * @tags examples + * @tags external_io * @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127) * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() @@ -2375,7 +2375,7 @@ export const { nrpnn } = registerControl('nrpnn'); /** * MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message. * @name nrpv - * @tags examples + * @tags external_io * @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127) * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() @@ -2386,7 +2386,7 @@ export const { nrpv } = registerControl('nrpv'); * MIDI program number: Sends a MIDI program change message. * * @name progNum - * @tags examples + * @tags external_io * @param {number | Pattern} program MIDI program number (0-127) * @example * note("c4").progNum(10).midichan(1).midi() @@ -2396,7 +2396,7 @@ export const { progNum } = registerControl('progNum'); /** * MIDI sysex: Sends a MIDI sysex message. * @name sysex - * @tags examples + * @tags external_io * @param {number | Pattern} id Sysex ID * @param {number | Pattern} data Sysex data * @example @@ -2412,7 +2412,7 @@ export const sysex = register('sysex', (args, pat) => { /** * MIDI sysex ID: Sends a MIDI sysex identifier message. * @name sysexid - * @tags examples + * @tags external_io * @param {number | Pattern} id Sysex ID * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() @@ -2421,7 +2421,7 @@ export const { sysexid } = registerControl('sysexid'); /** * MIDI sysex data: Sends a MIDI sysex message. * @name sysexdata - * @tags examples + * @tags external_io * @param {number | Pattern} data Sysex data * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() @@ -2431,7 +2431,7 @@ export const { sysexdata } = registerControl('sysexdata'); /** * MIDI pitch bend: Sends a MIDI pitch bend message. * @name midibend - * @tags examples + * @tags external_io * @param {number | Pattern} midibend MIDI pitch bend (-1 - 1) * @example * note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi() @@ -2440,7 +2440,7 @@ export const { midibend } = registerControl('midibend'); /** * MIDI key after touch: Sends a MIDI key after touch message. * @name miditouch - * @tags examples + * @tags external_io * @param {number | Pattern} miditouch MIDI key after touch (0-1) * @example * note("c4").miditouch(sine.slow(4).range(0,1)).midi() @@ -2461,7 +2461,7 @@ export const getControlName = (alias) => { * Sets properties in a batch. * * @name as - * @tags transforms + * @tags temporal * @param {String | Array} mapping the control names that are set * @example * "c:.5 a:1 f:.25 e:.8".as("note:clip") diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 2d6459a8..4a0dbe58 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -978,7 +978,7 @@ Pattern.prototype.collect = function () { /** * Selects indices in in stacked notes. - * @tags transforms + * @tags temporal * @example * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") * .arpWith(haps => haps[2]) @@ -993,7 +993,7 @@ export const arpWith = register('arpWith', (func, pat) => { /** * Selects indices in in stacked notes. - * @tags transforms + * @tags temporal * @example * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") * .arp("0 [0,2] 1 [0,2]") @@ -1342,7 +1342,7 @@ export function reify(thing) { /** * Takes a list of patterns, and returns a pattern of lists. * - * @tags transforms + * @tags temporal */ export function sequenceP(pats) { let result = pure([]); @@ -3354,7 +3354,7 @@ Pattern.prototype.steps = Pattern.prototype.pace; * Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'. * It turns a pattern of samples into a pattern of parts of samples. * @name chop - * @tags transforms + * @tags temporal * @memberof Pattern * @returns Pattern * @example @@ -3385,7 +3385,7 @@ export const chop = register('chop', function (n, pat) { /** * Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop. * @name striate - * @tags transforms + * @tags temporal * @memberof Pattern * @returns Pattern * @example @@ -3404,7 +3404,7 @@ export const striate = register('striate', function (n, pat) { /** * Makes the sample fit the given number of cycles by changing the speed. * @name loopAt - * @tags transforms + * @tags temporal * @memberof Pattern * @returns Pattern * @example @@ -3423,7 +3423,7 @@ const _loopAt = function (factor, pat, cps = 0.5) { * Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. * Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points. * @name slice - * @tags transforms + * @tags temporal * @memberof Pattern * @returns Pattern * @example @@ -3477,7 +3477,7 @@ Pattern.prototype.onTriggerTime = function (func) { /** * Works the same as slice, but changes the playback speed of each slice to match the duration of its step. * @name splice - * @tags transforms + * @tags temporal * @example * samples('github:tidalcycles/dirt-samples') * s("breaks165") @@ -3515,7 +3515,7 @@ export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (facto * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. * Similar to `loopAt`. * @name fit - * @tags transforms + * @tags temporal * @example * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) * s("rhodes/2").fit() @@ -3541,7 +3541,7 @@ export const fit = register('fit', (pat) => * given by a global clock and this function will be * deprecated/removed. * @name loopAtCps - * @tags transforms + * @tags temporal * @memberof Pattern * @returns Pattern * @example diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 2c3a6332..ce17fdea 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -300,7 +300,7 @@ const _rearrangeWith = (ipat, n, pat) => { * Slices a pattern into the given number of parts, then plays those parts in random order. * Each part will be played exactly once per cycle. * @name shuffle - * @tags transforms + * @tags temporal * @example * note("c d e f").sound("piano").shuffle(4) * @example @@ -314,7 +314,7 @@ export const shuffle = register('shuffle', (n, pat) => { * Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`, * but parts might be played more than once, or not at all, per cycle. * @name scramble - * @tags transforms + * @tags temporal * @example * note("c d e f").sound("piano").scramble(4) * @example @@ -393,7 +393,7 @@ export const __chooseWith = (pat, xs) => { /** * Choose from the list of values (or patterns of values) using the given * pattern of numbers, which should be in the range of 0..1 - * @tags transforms + * @tags temporal * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -407,7 +407,7 @@ export const chooseWith = (pat, xs) => { /** * As with {chooseWith}, but the structure comes from the chosen values, rather * than the pattern you're using to choose with. - * @tags transforms + * @tags temporal * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -418,7 +418,7 @@ export const chooseInWith = (pat, xs) => { /** * Chooses randomly from the given list of elements. - * @tags transforms + * @tags temporal * @param {...any} xs values / patterns to choose from. * @returns {Pattern} - a continuous pattern. * @example @@ -434,7 +434,7 @@ export const chooseOut = choose; * Chooses from the given list of values (or patterns of values), according * to the pattern that the method is called on. The pattern should be in * the range 0 .. 1. - * @tags transforms + * @tags temporal * @param {...any} xs * @returns {Pattern} */ @@ -445,7 +445,7 @@ Pattern.prototype.choose = function (...xs) { /** * As with choose, but the pattern that this method is called on should be * in the range -1 .. 1 - * @tags transforms + * @tags temporal * @param {...any} xs * @returns {Pattern} */ @@ -455,7 +455,7 @@ Pattern.prototype.choose2 = function (...xs) { /** * Picks one of the elements at random each cycle. - * @tags transforms + * @tags temporal * @synonyms randcat * @returns {Pattern} * @example @@ -498,7 +498,7 @@ const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); /** * Chooses randomly from the given list of elements by giving a probability to each element - * @tags transforms + * @tags temporal * @param {...any} pairs arrays of value and weight * @returns {Pattern} - a continuous pattern. * @example @@ -508,7 +508,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); /** * Picks one of the elements at random each cycle by giving a probability to each element - * @tags transforms + * @tags temporal * @synonyms wrandcat * @returns {Pattern} * @example @@ -590,7 +590,7 @@ export const degradeByWith = register( * 0 = 0% chance of removal * 1 = 100% chance of removal * - * @tags transforms + * @tags temporal * @name degradeBy * @memberof Pattern * @param {number} amount - a number between 0 and 1 @@ -616,7 +616,7 @@ export const degradeBy = register( * * Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)` * - * @tags transforms + * @tags temporal * @name degrade * @memberof Pattern * @returns Pattern @@ -633,7 +633,7 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, t * 1 = 0% chance of removal * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). * - * @tags transforms + * @tags temporal * @name undegradeBy * @memberof Pattern * @param {number} amount - a number between 0 and 1 @@ -662,7 +662,7 @@ export const undegradeBy = register( * Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` * Events that would be removed by degrade are let through by undegrade and vice versa (see second example). * - * @tags transforms + * @tags temporal * @name undegrade * @memberof Pattern * @returns Pattern @@ -681,7 +681,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t * Randomly applies the given function by the given probability. * Similar to `someCyclesBy` * - * @tags transforms + * @tags temporal * @name sometimesBy * @memberof Pattern * @param {number | Pattern} probability - a number between 0 and 1 @@ -701,7 +701,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) { * * Applies the given function with a 50% chance * - * @tags transforms + * @tags temporal * @name sometimes * @memberof Pattern * @param {function} function - the transformation to apply @@ -723,7 +723,7 @@ export const sometimes = register('sometimes', function (func, pat) { * @param {number | Pattern} probability - a number between 0 and 1 * @param {function} function - the transformation to apply * @returns Pattern - * @tags transforms + * @tags temporal * @example * s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5")) */ @@ -746,7 +746,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) * @name someCycles * @memberof Pattern * @returns Pattern - * @tags transforms + * @tags temporal * @example * s("bd,hh*8").someCycles(x=>x.speed("0.5")) */ @@ -761,7 +761,7 @@ export const someCycles = register('someCycles', function (func, pat) { * @name often * @memberof Pattern * @returns Pattern - * @tags transforms + * @tags temporal * @example * s("hh*8").often(x=>x.speed("0.5")) */ @@ -776,7 +776,7 @@ export const often = register('often', function (func, pat) { * @name rarely * @memberof Pattern * @returns Pattern - * @tags transforms + * @tags temporal * @example * s("hh*8").rarely(x=>x.speed("0.5")) */ @@ -788,7 +788,7 @@ export const rarely = register('rarely', function (func, pat) { * * Shorthand for `.sometimesBy(0.1, fn)` * - * @tags transforms + * @tags temporal * @name almostNever * @memberof Pattern * @returns Pattern @@ -803,7 +803,7 @@ export const almostNever = register('almostNever', function (func, pat) { * * Shorthand for `.sometimesBy(0.9, fn)` * - * @tags transforms + * @tags temporal * @name almostAlways * @memberof Pattern * @returns Pattern @@ -818,7 +818,7 @@ export const almostAlways = register('almostAlways', function (func, pat) { * * Shorthand for `.sometimesBy(0, fn)` (never calls fn) * - * @tags transforms + * @tags temporal * @name never * @memberof Pattern * @returns Pattern @@ -833,7 +833,7 @@ export const never = register('never', function (_, pat) { * * Shorthand for `.sometimesBy(1, fn)` (always calls fn) * - * @tags transforms + * @tags temporal * @name always * @memberof Pattern * @returns Pattern From b952e4c735a9f77592824e5542a6e3e93e9530aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 19 Oct 2025 20:54:22 +0200 Subject: [PATCH 013/200] Add superdough tags --- packages/core/controls.mjs | 132 ++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index db689e1a..2add9e41 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -351,7 +351,7 @@ export const { warpsync } = registerControl('warpsync'); * Define a custom webaudio node to use as a sound source. * * @name source - * @tags external_io + * @tags external_io, superdough * @synonyms src * @param {function} getSource * @synonyms src @@ -411,7 +411,7 @@ export const { accelerate } = registerControl('accelerate'); * Sets the velocity from 0 to 1. Is multiplied together with gain. * * @name velocity - * @tags effects + * @tags effects, superdough * @example * s("hh*8") * .gain(".4!2 1 .4!2 1 .4 1") @@ -422,7 +422,7 @@ export const { velocity } = registerControl('velocity'); * Controls the gain by an exponential amount. * * @name gain - * @tags effects + * @tags effects, superdough * @param {number | Pattern} amount gain. * @example * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) @@ -433,7 +433,7 @@ export const { gain } = registerControl('gain'); * Gain applied after all effects have been processed. * * @name postgain - * @tags effects + * @tags effects, superdough * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02").postgain(1.5) @@ -581,7 +581,7 @@ export const { fmvelocity } = registerControl('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 - * @tags samples + * @tags samples, superdough * @param {string | Pattern} bank the name of the bank * @example * s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") @@ -635,7 +635,7 @@ export const { sustain, sus } = registerControl('sustain', 'sus'); * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * * @name release - * @tags effects + * @tags effects, superdough * @param {number | Pattern} time release time in seconds * @synonyms rel * @example @@ -650,7 +650,7 @@ export const { hold } = registerControl('hold'); * can also optionally supply the 'bpq' parameter separated by ':'. * * @name bpf - * @tags effects + * @tags effects, superdough * @param {number | Pattern} frequency center frequency * @synonyms bandf, bp * @example @@ -663,7 +663,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' * Sets the **b**and-**p**ass **q**-factor (resonance). * * @name bpq - * @tags effects + * @tags effects, superdough * @param {number | Pattern} q q factor * @synonyms bandq * @example @@ -743,7 +743,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * Bit crusher effect. * * @name crush - * @tags effects + * @tags effects, superdough * @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>") @@ -755,7 +755,7 @@ export const { crush } = registerControl('crush'); * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * * @name coarse - * @tags effects + * @tags effects, superdough * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @example * s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>") @@ -767,7 +767,7 @@ export const { coarse } = registerControl('coarse'); * Modulate the amplitude of a sound with a continuous waveform * * @name tremolo - * @tags effects + * @tags effects, superdough * @synonyms trem * @param {number | Pattern} speed modulation speed in HZ * @example @@ -780,7 +780,7 @@ export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremolos * Modulate the amplitude of a sound with a continuous waveform * * @name tremolosync - * @tags effects + * @tags effects, superdough * @synonyms tremsync * @param {number | Pattern} cycles modulation speed in cycles * @example @@ -796,7 +796,7 @@ export const { tremolosync } = registerControl( * Depth of amplitude modulation * * @name tremolodepth - * @tags effects + * @tags effects, superdough * @synonyms tremdepth * @param {number | Pattern} depth * @example @@ -808,7 +808,7 @@ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); * Alter the shape of the modulation waveform * * @name tremoloskew - * @tags effects + * @tags effects, superdough * @synonyms tremskew * @param {number | Pattern} amount between 0 & 1, the shape of the waveform * @example @@ -821,7 +821,7 @@ export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); * Alter the phase of the modulation waveform * * @name tremolophase - * @tags effects + * @tags effects, superdough * @synonyms tremphase * @param {number | Pattern} offset the offset in cycles of the modulation * @example @@ -834,7 +834,7 @@ export const { tremolophase } = registerControl('tremolophase', 'tremphase'); * Shape of amplitude modulation * * @name tremoloshape - * @tags effects + * @tags effects, superdough * @synonyms tremshape * @param {number | Pattern} shape tri | square | sine | saw | ramp * @example @@ -846,7 +846,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); * Filter overdrive for supported filter types * * @name drive - * @tags effects + * @tags effects, superdough * @param {number | Pattern} amount * @example * note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>") @@ -860,7 +860,7 @@ export const { drive } = registerControl('drive'); * Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")` * * @name duckorbit - * @tags effects + * @tags effects, superdough * @synonyms duck * @param {number | Pattern} orbit target orbit * @example @@ -881,7 +881,7 @@ export const { duck } = registerControl('duckorbit', 'duck'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckdepth - * @tags effects + * @tags effects, superdough * @param {number | Pattern} depth depth of modulation from 0 to 1 * @example * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>")) @@ -901,7 +901,7 @@ export const { duckdepth } = registerControl('duckdepth'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckonset - * @tags effects + * @tags effects, superdough * @synonyms duckons * * @param {number | Pattern} time The onset time in seconds @@ -929,7 +929,7 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckattack - * @tags effects + * @tags effects, superdough * @synonyms duckatt * * @param {number | Pattern} time The attack time in seconds @@ -1028,7 +1028,7 @@ export const { pwsweep } = registerControl('pwsweep'); * Phaser audio effect that approximates popular guitar pedals. * * @name phaser - * @tags effects + * @tags effects, superdough * @synonyms ph * @param {number | Pattern} speed speed of modulation * @example @@ -1046,7 +1046,7 @@ export const { phaserrate, ph, phaser } = registerControl( * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 * * @name phasersweep - * @tags effects + * @tags effects, superdough * @synonyms phs * @param {number | Pattern} phasersweep most useful values are between 0 and 4000 * @example @@ -1060,7 +1060,7 @@ export const { phasersweep, phs } = registerControl('phasersweep', 'phs'); * The center frequency of the phaser in HZ. Defaults to 1000 * * @name phasercenter - * @tags effects + * @tags effects, superdough * @synonyms phc * @param {number | Pattern} centerfrequency in HZ * @example @@ -1075,7 +1075,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); * The amount the signal is affected by the phaser effect. Defaults to 0.75 * * @name phaserdepth - * @tags effects + * @tags effects, superdough * @synonyms phd, phasdp * @param {number | Pattern} depth number between 0 and 1 * @example @@ -1112,7 +1112,7 @@ export const { cut } = registerControl('cut'); * When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'. * * @name lpf - * @tags effects + * @tags effects, superdough * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms cutoff, ctf, lp * @example @@ -1126,7 +1126,7 @@ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', /** * Sets the lowpass filter envelope modulation depth. * @name lpenv - * @tags effects + * @tags effects, superdough * @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_ * @synonyms lpe * @example @@ -1140,7 +1140,7 @@ export const { lpenv, lpe } = registerControl('lpenv', 'lpe'); /** * Sets the highpass filter envelope modulation depth. * @name hpenv - * @tags effects + * @tags effects, superdough * @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_ * @synonyms hpe * @example @@ -1154,7 +1154,7 @@ export const { hpenv, hpe } = registerControl('hpenv', 'hpe'); /** * Sets the bandpass filter envelope modulation depth. * @name bpenv - * @tags effects + * @tags effects, superdough * @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_ * @synonyms bpe * @example @@ -1168,7 +1168,7 @@ export const { bpenv, bpe } = registerControl('bpenv', 'bpe'); /** * Sets the attack duration for the lowpass filter envelope. * @name lpattack - * @tags effects + * @tags effects, superdough * @param {number | Pattern} attack time of the filter envelope * @synonyms lpa * @example @@ -1182,7 +1182,7 @@ export const { lpattack, lpa } = registerControl('lpattack', 'lpa'); /** * Sets the attack duration for the highpass filter envelope. * @name hpattack - * @tags effects + * @tags effects, superdough * @param {number | Pattern} attack time of the highpass filter envelope * @synonyms hpa * @example @@ -1196,7 +1196,7 @@ export const { hpattack, hpa } = registerControl('hpattack', 'hpa'); /** * Sets the attack duration for the bandpass filter envelope. * @name bpattack - * @tags effects + * @tags effects, superdough * @param {number | Pattern} attack time of the bandpass filter envelope * @synonyms bpa * @example @@ -1210,7 +1210,7 @@ export const { bpattack, bpa } = registerControl('bpattack', 'bpa'); /** * Sets the decay duration for the lowpass filter envelope. * @name lpdecay - * @tags effects + * @tags effects, superdough * @param {number | Pattern} decay time of the filter envelope * @synonyms lpd * @example @@ -1224,7 +1224,7 @@ export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd'); /** * Sets the decay duration for the highpass filter envelope. * @name hpdecay - * @tags effects + * @tags effects, superdough * @param {number | Pattern} decay time of the highpass filter envelope * @synonyms hpd * @example @@ -1239,7 +1239,7 @@ export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd'); /** * Sets the decay duration for the bandpass filter envelope. * @name bpdecay - * @tags effects + * @tags effects, superdough * @param {number | Pattern} decay time of the bandpass filter envelope * @synonyms bpd * @example @@ -1254,7 +1254,7 @@ export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd'); /** * Sets the sustain amplitude for the lowpass filter envelope. * @name lpsustain - * @tags effects + * @tags effects, superdough * @param {number | Pattern} sustain amplitude of the lowpass filter envelope * @synonyms lps * @example @@ -1269,7 +1269,7 @@ export const { lpsustain, lps } = registerControl('lpsustain', 'lps'); /** * Sets the sustain amplitude for the highpass filter envelope. * @name hpsustain - * @tags effects + * @tags effects, superdough * @param {number | Pattern} sustain amplitude of the highpass filter envelope * @synonyms hps * @example @@ -1284,7 +1284,7 @@ export const { hpsustain, hps } = registerControl('hpsustain', 'hps'); /** * Sets the sustain amplitude for the bandpass filter envelope. * @name bpsustain - * @tags effects + * @tags effects, superdough * @param {number | Pattern} sustain amplitude of the bandpass filter envelope * @synonyms bps * @example @@ -1299,7 +1299,7 @@ export const { bpsustain, bps } = registerControl('bpsustain', 'bps'); /** * Sets the release time for the lowpass filter envelope. * @name lprelease - * @tags effects + * @tags effects, superdough * @param {number | Pattern} release time of the filter envelope * @synonyms lpr * @example @@ -1315,7 +1315,7 @@ export const { lprelease, lpr } = registerControl('lprelease', 'lpr'); /** * Sets the release time for the highpass filter envelope. * @name hprelease - * @tags effects + * @tags effects, superdough * @param {number | Pattern} release time of the highpass filter envelope * @synonyms hpr * @example @@ -1331,7 +1331,7 @@ export const { hprelease, hpr } = registerControl('hprelease', 'hpr'); /** * Sets the release time for the bandpass filter envelope. * @name bprelease - * @tags effects + * @tags effects, superdough * @param {number | Pattern} release time of the bandpass filter envelope * @synonyms bpr * @example @@ -1363,7 +1363,7 @@ export const { ftype } = registerControl('ftype'); /** * controls the center of the filter envelope. 0 is unipolar positive, .5 is bipolar, 1 is unipolar negative * @name fanchor - * @tags effects + * @tags effects, superdough * @param {number | Pattern} center 0 to 1 * @example * note("{f g g c d a a#}%8").s("sawtooth").lpf("{1000}%2") @@ -1376,7 +1376,7 @@ export const { fanchor } = registerControl('fanchor'); * When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'. * * @name hpf - * @tags effects + * @tags effects, superdough * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms hp, hcutoff * @example @@ -1438,7 +1438,7 @@ export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'h * Controls the **h**igh-**p**ass **q**-value. * * @name hpq - * @tags effects + * @tags effects, superdough * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms hresonance * @example @@ -1450,7 +1450,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); * Controls the **l**ow-**p**ass **q**-value. * * @name lpq - * @tags effects + * @tags effects, superdough * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms resonance * @example @@ -1463,7 +1463,7 @@ export const { resonance, lpq } = registerControl('resonance', 'lpq'); * DJ filter, below 0.5 is low pass filter, above is high pass filter. * * @name djf - * @tags effects + * @tags effects, superdough * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter * @example * n(irand(16).seg(8)).scale("d:phrygian").s("supersaw").djf("<.5 .3 .2 .75>") @@ -1480,7 +1480,7 @@ export const { djf } = registerControl('djf'); * * * @name delay - * @tags effects + * @tags effects, superdough * @param {number | Pattern} level between 0 and 1 * @example * s("bd bd").delay("<0 .25 .5 1>") @@ -1494,7 +1494,7 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback'] * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @tags effects + * @tags effects, superdough * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1508,7 +1508,7 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @tags effects + * @tags effects, superdough * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1533,7 +1533,7 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', * Sets the time of the delay effect in cycles. * * @name delaysync - * @tags effects + * @tags effects, superdough * @param {number | Pattern} cycles delay length in cycles * @synonyms delayt, dt * @example @@ -1595,7 +1595,7 @@ export const { spread } = registerControl('spread'); * Set dryness of reverb. See `room` and `size` for more information about reverb. * * @name dry - * @tags effects + * @tags effects, superdough * @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() @@ -1814,7 +1814,7 @@ export const { octave } = registerControl('octave'); * An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects. * * @name orbit - * @tags effects + * @tags effects, superdough * @param {number | Pattern} number * @example * stack( @@ -1831,7 +1831,7 @@ export const { overshape } = registerControl('overshape'); * Sets position in stereo. * * @name pan - * @tags effects + * @tags effects, superdough * @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>") @@ -1897,7 +1897,7 @@ export const { mode } = registerControl(['mode', 'anchor']); * When using mininotation, you can also optionally add the 'size' parameter, separated by ':'. * * @name room - * @tags effects + * @tags effects, superdough * @param {number | Pattern} level between 0 and 1 * @example * s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>") @@ -1911,7 +1911,7 @@ export const { room } = registerControl(['room', 'size']); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomlp - * @tags effects + * @tags effects, superdough * @synonyms rlp * @param {number} frequency between 0 and 20000hz * @example @@ -1925,7 +1925,7 @@ export const { roomlp, rlp } = registerControl('roomlp', 'rlp'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomdim - * @tags effects + * @tags effects, superdough * @synonyms rdim * @param {number} frequency between 0 and 20000hz * @example @@ -1940,7 +1940,7 @@ export const { roomdim, rdim } = registerControl('roomdim', 'rdim'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomfade - * @tags effects + * @tags effects, superdough * @synonyms rfade * @param {number} seconds for the reverb to fade * @example @@ -1953,7 +1953,7 @@ export const { roomfade, rfade } = registerControl('roomfade', 'rfade'); /** * Sets the sample to use as an impulse response for the reverb. * @name iresponse - * @tags effects + * @tags effects, superdough * @param {string | Pattern} sample to use as an impulse response * @synonyms ir * @example @@ -1965,7 +1965,7 @@ export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse'); /** * Sets speed of the sample for the impulse response. * @name irspeed - * @tags effects + * @tags effects, superdough * @param {string | Pattern} speed * @example * samples('github:switchangel/pad') @@ -1977,7 +1977,7 @@ export const { irspeed } = registerControl('irspeed'); /** * Sets the beginning of the IR response sample * @name irbegin - * @tags effects + * @tags effects, superdough * @param {string | Pattern} begin between 0 and 1 * @synonyms ir * @example @@ -1991,7 +1991,7 @@ export const { irbegin } = registerControl('irbegin'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomsize - * @tags effects + * @tags effects, superdough * @param {number | Pattern} size between 0 and 10 * @synonyms rsize, sz, size * @example @@ -2015,7 +2015,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', * * * @name shape - * @tags effects + * @tags effects, superdough * @param {number | Pattern} distortion between 0 and 1 * @example * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") @@ -2028,7 +2028,7 @@ export const { shape } = registerControl(['shape', 'shapevol']); * Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;) * * @name distort - * @tags effects + * @tags effects, superdough * @synonyms dist * @param {number | Pattern} distortion * @example @@ -2043,7 +2043,7 @@ export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dis * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) * * @name compressor - * @tags effects + * @tags effects, superdough * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02") @@ -2129,7 +2129,7 @@ export const { squiz } = registerControl('squiz'); * Formant filter to make things sound like vowels. * * @name vowel - * @tags effects + * @tags effects, superdough * @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ. * @example * note("[c2 >]*2").s('sawtooth') @@ -2154,7 +2154,7 @@ export const { waveloss } = registerControl('waveloss'); * Noise crackle density * * @name density - * @tags effects + * @tags effects, superdough * @param {number | Pattern} density between 0 and x * @example * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)) From 96d0f2053e7fc0afbf515668945f3a4f3346fca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 19 Oct 2025 21:13:14 +0200 Subject: [PATCH 014/200] Add supradough controls --- packages/core/controls.mjs | 96 +++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 2add9e41..dda12767 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -411,7 +411,7 @@ export const { accelerate } = registerControl('accelerate'); * Sets the velocity from 0 to 1. Is multiplied together with gain. * * @name velocity - * @tags effects, superdough + * @tags effects, superdough, supradough * @example * s("hh*8") * .gain(".4!2 1 .4!2 1 .4 1") @@ -422,7 +422,7 @@ export const { velocity } = registerControl('velocity'); * Controls the gain by an exponential amount. * * @name gain - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} amount gain. * @example * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) @@ -433,7 +433,7 @@ export const { gain } = registerControl('gain'); * Gain applied after all effects have been processed. * * @name postgain - * @tags effects, superdough + * @tags effects, superdough, supradough * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02").postgain(1.5) @@ -456,7 +456,7 @@ export const { amp } = registerControl('amp'); * Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * * @name attack - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} attack time in seconds. * @synonyms att * @example @@ -472,7 +472,7 @@ export const { attack, att } = registerControl('attack', 'att'); * while decimal numbers and complex ratios sound metallic. * * @name fmh - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} harmonicity * @example * note("c e g b g e") @@ -487,7 +487,7 @@ export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh'); * Controls the modulation index, which defines the brightness of the sound. * * @name fm - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} brightness modulation index * @synonyms fmi * @example @@ -502,7 +502,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); * Ramp type of fm envelope. Exp might be a bit broken.. * * @name fmenv - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} type lin | exp * @example * note("c e g b g e") @@ -518,7 +518,7 @@ export const { fmenv } = registerControl('fmenv'); * Attack time for the FM envelope: time it takes to reach maximum modulation * * @name fmattack - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} time attack time * @example * note("c e g b g e") @@ -533,7 +533,7 @@ export const { fmattack } = registerControl('fmattack'); * Waveform of the fm modulator * * @name fmwave - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} wave waveform * @example * n("0 1 2 3".fast(4)).scale("d:minor").s("sine").fmwave("").fm(4).fmh(2.01) @@ -547,7 +547,7 @@ export const { fmwave } = registerControl('fmwave'); * Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase. * * @name fmdecay - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} time decay time * @example * note("c e g b g e") @@ -562,7 +562,7 @@ export const { fmdecay } = registerControl('fmdecay'); * Sustain level for the FM envelope: how much modulation is applied after the decay phase * * @name fmsustain - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} level sustain level * @example * note("c e g b g e") @@ -611,7 +611,7 @@ export const { fft } = registerControl('fft'); * Note that the decay is only audible if the sustain value is lower than 1. * * @name decay - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} time decay time in seconds * @synonyms dec * @example @@ -623,7 +623,7 @@ export const { decay, dec } = registerControl('decay', 'dec'); * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * * @name sustain - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} gain sustain level between 0 and 1 * @synonyms sus * @example @@ -635,7 +635,7 @@ export const { sustain, sus } = registerControl('sustain', 'sus'); * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * * @name release - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} time release time in seconds * @synonyms rel * @example @@ -650,7 +650,7 @@ export const { hold } = registerControl('hold'); * can also optionally supply the 'bpq' parameter separated by ':'. * * @name bpf - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} frequency center frequency * @synonyms bandf, bp * @example @@ -663,7 +663,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' * Sets the **b**and-**p**ass **q**-factor (resonance). * * @name bpq - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} q q factor * @synonyms bandq * @example @@ -743,7 +743,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * Bit crusher effect. * * @name crush - * @tags effects, superdough + * @tags effects, superdough, supradough * @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>") @@ -755,7 +755,7 @@ export const { crush } = registerControl('crush'); * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * * @name coarse - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @example * s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>") @@ -1112,7 +1112,7 @@ export const { cut } = registerControl('cut'); * When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'. * * @name lpf - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms cutoff, ctf, lp * @example @@ -1126,7 +1126,7 @@ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', /** * Sets the lowpass filter envelope modulation depth. * @name lpenv - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_ * @synonyms lpe * @example @@ -1140,7 +1140,7 @@ export const { lpenv, lpe } = registerControl('lpenv', 'lpe'); /** * Sets the highpass filter envelope modulation depth. * @name hpenv - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_ * @synonyms hpe * @example @@ -1154,7 +1154,7 @@ export const { hpenv, hpe } = registerControl('hpenv', 'hpe'); /** * Sets the bandpass filter envelope modulation depth. * @name bpenv - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_ * @synonyms bpe * @example @@ -1168,7 +1168,7 @@ export const { bpenv, bpe } = registerControl('bpenv', 'bpe'); /** * Sets the attack duration for the lowpass filter envelope. * @name lpattack - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} attack time of the filter envelope * @synonyms lpa * @example @@ -1182,7 +1182,7 @@ export const { lpattack, lpa } = registerControl('lpattack', 'lpa'); /** * Sets the attack duration for the highpass filter envelope. * @name hpattack - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} attack time of the highpass filter envelope * @synonyms hpa * @example @@ -1196,7 +1196,7 @@ export const { hpattack, hpa } = registerControl('hpattack', 'hpa'); /** * Sets the attack duration for the bandpass filter envelope. * @name bpattack - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} attack time of the bandpass filter envelope * @synonyms bpa * @example @@ -1210,7 +1210,7 @@ export const { bpattack, bpa } = registerControl('bpattack', 'bpa'); /** * Sets the decay duration for the lowpass filter envelope. * @name lpdecay - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} decay time of the filter envelope * @synonyms lpd * @example @@ -1224,7 +1224,7 @@ export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd'); /** * Sets the decay duration for the highpass filter envelope. * @name hpdecay - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} decay time of the highpass filter envelope * @synonyms hpd * @example @@ -1239,7 +1239,7 @@ export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd'); /** * Sets the decay duration for the bandpass filter envelope. * @name bpdecay - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} decay time of the bandpass filter envelope * @synonyms bpd * @example @@ -1254,7 +1254,7 @@ export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd'); /** * Sets the sustain amplitude for the lowpass filter envelope. * @name lpsustain - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} sustain amplitude of the lowpass filter envelope * @synonyms lps * @example @@ -1269,7 +1269,7 @@ export const { lpsustain, lps } = registerControl('lpsustain', 'lps'); /** * Sets the sustain amplitude for the highpass filter envelope. * @name hpsustain - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} sustain amplitude of the highpass filter envelope * @synonyms hps * @example @@ -1284,7 +1284,7 @@ export const { hpsustain, hps } = registerControl('hpsustain', 'hps'); /** * Sets the sustain amplitude for the bandpass filter envelope. * @name bpsustain - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} sustain amplitude of the bandpass filter envelope * @synonyms bps * @example @@ -1299,7 +1299,7 @@ export const { bpsustain, bps } = registerControl('bpsustain', 'bps'); /** * Sets the release time for the lowpass filter envelope. * @name lprelease - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} release time of the filter envelope * @synonyms lpr * @example @@ -1315,7 +1315,7 @@ export const { lprelease, lpr } = registerControl('lprelease', 'lpr'); /** * Sets the release time for the highpass filter envelope. * @name hprelease - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} release time of the highpass filter envelope * @synonyms hpr * @example @@ -1331,7 +1331,7 @@ export const { hprelease, hpr } = registerControl('hprelease', 'hpr'); /** * Sets the release time for the bandpass filter envelope. * @name bprelease - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} release time of the bandpass filter envelope * @synonyms bpr * @example @@ -1376,7 +1376,7 @@ export const { fanchor } = registerControl('fanchor'); * When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'. * * @name hpf - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms hp, hcutoff * @example @@ -1391,7 +1391,7 @@ export const { fanchor } = registerControl('fanchor'); * Applies a vibrato to the frequency of the oscillator. * * @name vib - * @tags effects + * @tags effects, superdough, supradough * @synonyms vibrato, v * @param {number | Pattern} frequency of the vibrato in hertz * @example @@ -1419,7 +1419,7 @@ export const { noise } = registerControl('noise'); * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * * @name vibmod - * @tags effects + * @tags effects, superdough, supradough * @synonyms vmod * @param {number | Pattern} depth of vibrato (in semitones) * @example @@ -1438,7 +1438,7 @@ export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'h * Controls the **h**igh-**p**ass **q**-value. * * @name hpq - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms hresonance * @example @@ -1450,7 +1450,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); * Controls the **l**ow-**p**ass **q**-value. * * @name lpq - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms resonance * @example @@ -1480,7 +1480,7 @@ export const { djf } = registerControl('djf'); * * * @name delay - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} level between 0 and 1 * @example * s("bd bd").delay("<0 .25 .5 1>") @@ -1494,7 +1494,7 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback'] * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1508,7 +1508,7 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1635,7 +1635,7 @@ export const { freq } = registerControl('freq'); * Attack time of pitch envelope. * * @name pattack - * @tags effects + * @tags effects, superdough, supradough * @synonyms patt * @param {number | Pattern} time time in seconds * @example @@ -1647,7 +1647,7 @@ export const { pattack, patt } = registerControl('pattack', 'patt'); * Decay time of pitch envelope. * * @name pdecay - * @tags effects + * @tags effects, superdough, supradough * @synonyms pdec * @param {number | Pattern} time time in seconds * @example @@ -1661,7 +1661,7 @@ export const { psustain, psus } = registerControl('psustain', 'psus'); * Release time of pitch envelope * * @name prelease - * @tags effects + * @tags effects, superdough, supradough * @synonyms prel * @param {number | Pattern} time time in seconds * @example @@ -1676,7 +1676,7 @@ export const { prelease, prel } = registerControl('prelease', 'prel'); * If you don't set other pitch envelope controls, `pattack:.2` will be the default. * * @name penv - * @tags effects + * @tags effects, superdough, supradough * @param {number | Pattern} semitones change in semitones * @example * note("c") @@ -1831,7 +1831,7 @@ export const { overshape } = registerControl('overshape'); * Sets position in stereo. * * @name pan - * @tags effects, superdough + * @tags effects, superdough, supradough * @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel) * @example * s("[bd hh]*2").pan("<.5 1 .5 0>") @@ -2028,7 +2028,7 @@ export const { shape } = registerControl(['shape', 'shapevol']); * Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;) * * @name distort - * @tags effects, superdough + * @tags effects, superdough, supradough * @synonyms dist * @param {number | Pattern} distortion * @example From 69360b3ec8731e75045f44b9f08840739fbc7e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 19 Oct 2025 21:17:25 +0200 Subject: [PATCH 015/200] Also tag "noise" --- packages/core/controls.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index dda12767..c6ff8d79 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1409,7 +1409,7 @@ export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', * Adds pink noise to the mix * * @name noise - * @tags generators + * @tags generators, superdough, supradough * @param {number | Pattern} wet wet amount * @example * sound("/2") From 2483c673a4ae08ac33fc60eb2155fdcd4ba091cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 19 Oct 2025 21:19:42 +0200 Subject: [PATCH 016/200] Effects -> fx, untag "crossfade" --- packages/core/controls.mjs | 262 +++++++++++++++--------------- packages/superdough/wavetable.mjs | 2 +- packages/supradough/dough.mjs | 2 +- 3 files changed, 133 insertions(+), 133 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index c6ff8d79..1bfd5a89 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -92,7 +92,7 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); * Position in the wavetable of the wavetable oscillator * * @name wt - * @tags effects + * @tags fx * @param {number | Pattern} position Position in the wavetable from 0 to 1 * @synonyms wavetablePosition * @example @@ -104,7 +104,7 @@ export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePositio * Amount of envelope applied wavetable oscillator's position envelope * * @name wtenv - * @tags effects + * @tags fx * @param {number | Pattern} amount between 0 and 1 */ export const { wtenv } = registerControl('wtenv'); @@ -112,7 +112,7 @@ export const { wtenv } = registerControl('wtenv'); * Attack time of the wavetable oscillator's position envelope * * @name wtattack - * @tags effects + * @tags fx * @synonyms wtatt * @param {number | Pattern} time attack time in seconds */ @@ -122,7 +122,7 @@ export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt'); * Decay time of the wavetable oscillator's position envelope * * @name wtdecay - * @tags effects + * @tags fx * @synonyms wtdec * @param {number | Pattern} time decay time in seconds */ @@ -132,7 +132,7 @@ export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec'); * Sustain time of the wavetable oscillator's position envelope * * @name wtsustain - * @tags effects + * @tags fx * @synonyms wtsus * @param {number | Pattern} gain sustain level (0 to 1) */ @@ -142,7 +142,7 @@ export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus'); * Release time of the wavetable oscillator's position envelope * * @name wtrelease - * @tags effects + * @tags fx * @synonyms wtrel * @param {number | Pattern} time release time in seconds */ @@ -152,7 +152,7 @@ export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel'); * Rate of the LFO for the wavetable oscillator's position * * @name wtrate - * @tags effects + * @tags fx * @param {number | Pattern} rate rate in hertz */ export const { wtrate } = registerControl('wtrate'); @@ -160,7 +160,7 @@ export const { wtrate } = registerControl('wtrate'); * cycle synced rate of the LFO for the wavetable oscillator's position * * @name wtsync - * @tags effects + * @tags fx * @param {number | Pattern} rate rate in cycles */ export const { wtsync } = registerControl('wtsync'); @@ -169,7 +169,7 @@ export const { wtsync } = registerControl('wtsync'); * Depth of the LFO for the wavetable oscillator's position * * @name wtdepth - * @tags effects + * @tags fx * @param {number | Pattern} depth depth of modulation */ export const { wtdepth } = registerControl('wtdepth'); @@ -178,7 +178,7 @@ export const { wtdepth } = registerControl('wtdepth'); * Shape of the LFO for the wavetable oscillator's position * * @name wtshape - * @tags effects + * @tags fx * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { wtshape } = registerControl('wtshape'); @@ -187,7 +187,7 @@ export const { wtshape } = registerControl('wtshape'); * DC offset of the LFO for the wavetable oscillator's position * * @name wtdc - * @tags effects + * @tags fx * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { wtdc } = registerControl('wtdc'); @@ -196,7 +196,7 @@ export const { wtdc } = registerControl('wtdc'); * Skew of the LFO for the wavetable oscillator's position * * @name wtskew - * @tags effects + * @tags fx * @param {number | Pattern} skew How much to bend the LFO shape */ export const { wtskew } = registerControl('wtskew'); @@ -205,7 +205,7 @@ export const { wtskew } = registerControl('wtskew'); * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator * * @name warp - * @tags effects + * @tags fx * @param {number | Pattern} amount Warp of the wavetable from 0 to 1 * @synonyms wavetableWarp * @example @@ -218,7 +218,7 @@ export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp'); * Attack time of the wavetable oscillator's warp envelope * * @name warpattack - * @tags effects + * @tags fx * @synonyms warpatt * @param {number | Pattern} time attack time in seconds */ @@ -228,7 +228,7 @@ export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt'); * Decay time of the wavetable oscillator's warp envelope * * @name warpdecay - * @tags effects + * @tags fx * @synonyms warpdec * @param {number | Pattern} time decay time in seconds */ @@ -238,7 +238,7 @@ export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec'); * Sustain time of the wavetable oscillator's warp envelope * * @name warpsustain - * @tags effects + * @tags fx * @synonyms warpsus * @param {number | Pattern} gain sustain level (0 to 1) */ @@ -248,7 +248,7 @@ export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus' * Release time of the wavetable oscillator's warp envelope * * @name warprelease - * @tags effects + * @tags fx * @synonyms warprel * @param {number | Pattern} time release time in seconds */ @@ -258,7 +258,7 @@ export const { warprelease, warprel } = registerControl('warprelease', 'warprel' * Rate of the LFO for the wavetable oscillator's warp * * @name warprate - * @tags effects + * @tags fx * @param {number | Pattern} rate rate in hertz */ export const { warprate } = registerControl('warprate'); @@ -267,7 +267,7 @@ export const { warprate } = registerControl('warprate'); * Depth of the LFO for the wavetable oscillator's warp * * @name warpdepth - * @tags effects + * @tags fx * @param {number | Pattern} depth depth of modulation */ export const { warpdepth } = registerControl('warpdepth'); @@ -276,7 +276,7 @@ export const { warpdepth } = registerControl('warpdepth'); * Shape of the LFO for the wavetable oscillator's warp * * @name warpshape - * @tags effects + * @tags fx * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { warpshape } = registerControl('warpshape'); @@ -285,7 +285,7 @@ export const { warpshape } = registerControl('warpshape'); * DC offset of the LFO for the wavetable oscillator's warp * * @name warpdc - * @tags effects + * @tags fx * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { warpdc } = registerControl('warpdc'); @@ -294,7 +294,7 @@ export const { warpdc } = registerControl('warpdc'); * Skew of the LFO for the wavetable oscillator's warp * * @name warpskew - * @tags effects + * @tags fx * @param {number | Pattern} skew How much to bend the LFO shape */ export const { warpskew } = registerControl('warpskew'); @@ -306,7 +306,7 @@ export const { warpskew } = registerControl('warpskew'); * spin, chaos, primes, binary, brownian, reciprocal, wormhole, logistic, sigmoid, fractal, flip * * @name warpmode - * @tags effects + * @tags fx * @param {number | string | Pattern} mode Warp mode * @synonyms wavetableWarpMode * @example @@ -320,7 +320,7 @@ export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wave * Amount of randomness of the initial phase of the wavetable oscillator. * * @name wtphaserand - * @tags effects + * @tags fx * @param {number | Pattern} amount Randomness of the initial phase. Between 0 (not random) and 1 (fully random) * @synonyms wavetablePhaseRand * @example @@ -333,7 +333,7 @@ export const { wtphaserand, wavetablePhaseRand } = registerControl('wtphaserand' * Amount of envelope applied wavetable oscillator's position envelope * * @name warpenv - * @tags effects + * @tags fx * @param {number | Pattern} amount between 0 and 1 */ export const { warpenv } = registerControl('warpenv'); @@ -342,7 +342,7 @@ export const { warpenv } = registerControl('warpenv'); * cycle synced rate of the LFO for the wavetable warp position * * @name warpsync - * @tags effects + * @tags fx * @param {number | Pattern} rate rate in cycles */ export const { warpsync } = registerControl('warpsync'); @@ -411,7 +411,7 @@ export const { accelerate } = registerControl('accelerate'); * Sets the velocity from 0 to 1. Is multiplied together with gain. * * @name velocity - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @example * s("hh*8") * .gain(".4!2 1 .4!2 1 .4 1") @@ -422,7 +422,7 @@ export const { velocity } = registerControl('velocity'); * Controls the gain by an exponential amount. * * @name gain - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} amount gain. * @example * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) @@ -433,7 +433,7 @@ export const { gain } = registerControl('gain'); * Gain applied after all effects have been processed. * * @name postgain - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02").postgain(1.5) @@ -444,7 +444,7 @@ export const { postgain } = registerControl('postgain'); * Like `gain`, but linear. * * @name amp - * @tags effects + * @tags fx * @param {number | Pattern} amount gain. * @superdirtOnly * @example @@ -456,7 +456,7 @@ export const { amp } = registerControl('amp'); * Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * * @name attack - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} attack time in seconds. * @synonyms att * @example @@ -472,7 +472,7 @@ export const { attack, att } = registerControl('attack', 'att'); * while decimal numbers and complex ratios sound metallic. * * @name fmh - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} harmonicity * @example * note("c e g b g e") @@ -487,7 +487,7 @@ export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh'); * Controls the modulation index, which defines the brightness of the sound. * * @name fm - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} brightness modulation index * @synonyms fmi * @example @@ -502,7 +502,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); * Ramp type of fm envelope. Exp might be a bit broken.. * * @name fmenv - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} type lin | exp * @example * note("c e g b g e") @@ -518,7 +518,7 @@ export const { fmenv } = registerControl('fmenv'); * Attack time for the FM envelope: time it takes to reach maximum modulation * * @name fmattack - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} time attack time * @example * note("c e g b g e") @@ -533,7 +533,7 @@ export const { fmattack } = registerControl('fmattack'); * Waveform of the fm modulator * * @name fmwave - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} wave waveform * @example * n("0 1 2 3".fast(4)).scale("d:minor").s("sine").fmwave("").fm(4).fmh(2.01) @@ -547,7 +547,7 @@ export const { fmwave } = registerControl('fmwave'); * Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase. * * @name fmdecay - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} time decay time * @example * note("c e g b g e") @@ -562,7 +562,7 @@ export const { fmdecay } = registerControl('fmdecay'); * Sustain level for the FM envelope: how much modulation is applied after the decay phase * * @name fmsustain - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} level sustain level * @example * note("c e g b g e") @@ -593,7 +593,7 @@ export const { bank } = registerControl('bank'); * mix control for the chorus effect * * @name chorus - * @tags effects + * @tags fx * @param {string | Pattern} chorus mix amount between 0 and 1 * @example * note("d d a# a").s("sawtooth").chorus(.5) @@ -611,7 +611,7 @@ export const { fft } = registerControl('fft'); * Note that the decay is only audible if the sustain value is lower than 1. * * @name decay - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} time decay time in seconds * @synonyms dec * @example @@ -623,7 +623,7 @@ export const { decay, dec } = registerControl('decay', 'dec'); * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * * @name sustain - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} gain sustain level between 0 and 1 * @synonyms sus * @example @@ -635,7 +635,7 @@ export const { sustain, sus } = registerControl('sustain', 'sus'); * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * * @name release - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} time release time in seconds * @synonyms rel * @example @@ -650,7 +650,7 @@ export const { hold } = registerControl('hold'); * can also optionally supply the 'bpq' parameter separated by ':'. * * @name bpf - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} frequency center frequency * @synonyms bandf, bp * @example @@ -663,7 +663,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' * Sets the **b**and-**p**ass **q**-factor (resonance). * * @name bpq - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} q q factor * @synonyms bandq * @example @@ -743,7 +743,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * Bit crusher effect. * * @name crush - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @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>") @@ -755,7 +755,7 @@ export const { crush } = registerControl('crush'); * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * * @name coarse - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @example * s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>") @@ -767,7 +767,7 @@ export const { coarse } = registerControl('coarse'); * Modulate the amplitude of a sound with a continuous waveform * * @name tremolo - * @tags effects, superdough + * @tags fx, superdough * @synonyms trem * @param {number | Pattern} speed modulation speed in HZ * @example @@ -780,7 +780,7 @@ export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremolos * Modulate the amplitude of a sound with a continuous waveform * * @name tremolosync - * @tags effects, superdough + * @tags fx, superdough * @synonyms tremsync * @param {number | Pattern} cycles modulation speed in cycles * @example @@ -796,7 +796,7 @@ export const { tremolosync } = registerControl( * Depth of amplitude modulation * * @name tremolodepth - * @tags effects, superdough + * @tags fx, superdough * @synonyms tremdepth * @param {number | Pattern} depth * @example @@ -808,7 +808,7 @@ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); * Alter the shape of the modulation waveform * * @name tremoloskew - * @tags effects, superdough + * @tags fx, superdough * @synonyms tremskew * @param {number | Pattern} amount between 0 & 1, the shape of the waveform * @example @@ -821,7 +821,7 @@ export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); * Alter the phase of the modulation waveform * * @name tremolophase - * @tags effects, superdough + * @tags fx, superdough * @synonyms tremphase * @param {number | Pattern} offset the offset in cycles of the modulation * @example @@ -834,7 +834,7 @@ export const { tremolophase } = registerControl('tremolophase', 'tremphase'); * Shape of amplitude modulation * * @name tremoloshape - * @tags effects, superdough + * @tags fx, superdough * @synonyms tremshape * @param {number | Pattern} shape tri | square | sine | saw | ramp * @example @@ -846,7 +846,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); * Filter overdrive for supported filter types * * @name drive - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} amount * @example * note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>") @@ -860,7 +860,7 @@ export const { drive } = registerControl('drive'); * Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")` * * @name duckorbit - * @tags effects, superdough + * @tags fx, superdough * @synonyms duck * @param {number | Pattern} orbit target orbit * @example @@ -881,7 +881,7 @@ export const { duck } = registerControl('duckorbit', 'duck'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckdepth - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} depth depth of modulation from 0 to 1 * @example * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>")) @@ -901,7 +901,7 @@ export const { duckdepth } = registerControl('duckdepth'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckonset - * @tags effects, superdough + * @tags fx, superdough * @synonyms duckons * * @param {number | Pattern} time The onset time in seconds @@ -929,7 +929,7 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckattack - * @tags effects, superdough + * @tags fx, superdough * @synonyms duckatt * * @param {number | Pattern} time The attack time in seconds @@ -949,7 +949,7 @@ export const { duckattack } = registerControl('duckattack', 'duckatt'); * * @name byteBeatExpression * @synonyms bbexpr - * @tags effects + * @tags fx * * @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat * @example @@ -963,7 +963,7 @@ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpressio * * @name byteBeatStartTime * @synonyms bbst - * @tags effects + * @tags fx * * @param {number | Pattern} byteBeatStartTime in samples (t) * @example @@ -990,7 +990,7 @@ export const { channels, ch } = registerControl('channels', 'ch'); * Controls the pulsewidth of the pulse oscillator * * @name pw - * @tags effects + * @tags fx * @param {number | Pattern} pulsewidth * @example * note("{f a c e}%16").s("pulse").pw(".8:1:.2") @@ -1003,7 +1003,7 @@ export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); * Controls the lfo rate for the pulsewidth of the pulse oscillator * * @name pwrate - * @tags effects + * @tags fx * @param {number | Pattern} rate * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") @@ -1016,7 +1016,7 @@ export const { pwrate } = registerControl('pwrate'); * Controls the lfo sweep for the pulsewidth of the pulse oscillator * * @name pwsweep - * @tags effects + * @tags fx * @param {number | Pattern} sweep * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") @@ -1028,7 +1028,7 @@ export const { pwsweep } = registerControl('pwsweep'); * Phaser audio effect that approximates popular guitar pedals. * * @name phaser - * @tags effects, superdough + * @tags fx, superdough * @synonyms ph * @param {number | Pattern} speed speed of modulation * @example @@ -1046,7 +1046,7 @@ export const { phaserrate, ph, phaser } = registerControl( * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 * * @name phasersweep - * @tags effects, superdough + * @tags fx, superdough * @synonyms phs * @param {number | Pattern} phasersweep most useful values are between 0 and 4000 * @example @@ -1060,7 +1060,7 @@ export const { phasersweep, phs } = registerControl('phasersweep', 'phs'); * The center frequency of the phaser in HZ. Defaults to 1000 * * @name phasercenter - * @tags effects, superdough + * @tags fx, superdough * @synonyms phc * @param {number | Pattern} centerfrequency in HZ * @example @@ -1075,7 +1075,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); * The amount the signal is affected by the phaser effect. Defaults to 0.75 * * @name phaserdepth - * @tags effects, superdough + * @tags fx, superdough * @synonyms phd, phasdp * @param {number | Pattern} depth number between 0 and 1 * @example @@ -1090,7 +1090,7 @@ export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd' * Choose the channel the pattern is sent to in superdirt * * @name channel - * @tags effects + * @tags fx * @param {number | Pattern} channel channel number * */ @@ -1099,7 +1099,7 @@ export const { channel } = registerControl('channel'); * In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open. * * @name cut - * @tags effects + * @tags fx * @param {number | Pattern} group cut group number * @example * s("[oh hh]*4").cut(1) @@ -1112,7 +1112,7 @@ export const { cut } = registerControl('cut'); * When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'. * * @name lpf - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms cutoff, ctf, lp * @example @@ -1126,7 +1126,7 @@ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', /** * Sets the lowpass filter envelope modulation depth. * @name lpenv - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_ * @synonyms lpe * @example @@ -1140,7 +1140,7 @@ export const { lpenv, lpe } = registerControl('lpenv', 'lpe'); /** * Sets the highpass filter envelope modulation depth. * @name hpenv - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_ * @synonyms hpe * @example @@ -1154,7 +1154,7 @@ export const { hpenv, hpe } = registerControl('hpenv', 'hpe'); /** * Sets the bandpass filter envelope modulation depth. * @name bpenv - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_ * @synonyms bpe * @example @@ -1168,7 +1168,7 @@ export const { bpenv, bpe } = registerControl('bpenv', 'bpe'); /** * Sets the attack duration for the lowpass filter envelope. * @name lpattack - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} attack time of the filter envelope * @synonyms lpa * @example @@ -1182,7 +1182,7 @@ export const { lpattack, lpa } = registerControl('lpattack', 'lpa'); /** * Sets the attack duration for the highpass filter envelope. * @name hpattack - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} attack time of the highpass filter envelope * @synonyms hpa * @example @@ -1196,7 +1196,7 @@ export const { hpattack, hpa } = registerControl('hpattack', 'hpa'); /** * Sets the attack duration for the bandpass filter envelope. * @name bpattack - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} attack time of the bandpass filter envelope * @synonyms bpa * @example @@ -1210,7 +1210,7 @@ export const { bpattack, bpa } = registerControl('bpattack', 'bpa'); /** * Sets the decay duration for the lowpass filter envelope. * @name lpdecay - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} decay time of the filter envelope * @synonyms lpd * @example @@ -1224,7 +1224,7 @@ export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd'); /** * Sets the decay duration for the highpass filter envelope. * @name hpdecay - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} decay time of the highpass filter envelope * @synonyms hpd * @example @@ -1239,7 +1239,7 @@ export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd'); /** * Sets the decay duration for the bandpass filter envelope. * @name bpdecay - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} decay time of the bandpass filter envelope * @synonyms bpd * @example @@ -1254,7 +1254,7 @@ export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd'); /** * Sets the sustain amplitude for the lowpass filter envelope. * @name lpsustain - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} sustain amplitude of the lowpass filter envelope * @synonyms lps * @example @@ -1269,7 +1269,7 @@ export const { lpsustain, lps } = registerControl('lpsustain', 'lps'); /** * Sets the sustain amplitude for the highpass filter envelope. * @name hpsustain - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} sustain amplitude of the highpass filter envelope * @synonyms hps * @example @@ -1284,7 +1284,7 @@ export const { hpsustain, hps } = registerControl('hpsustain', 'hps'); /** * Sets the sustain amplitude for the bandpass filter envelope. * @name bpsustain - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} sustain amplitude of the bandpass filter envelope * @synonyms bps * @example @@ -1299,7 +1299,7 @@ export const { bpsustain, bps } = registerControl('bpsustain', 'bps'); /** * Sets the release time for the lowpass filter envelope. * @name lprelease - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} release time of the filter envelope * @synonyms lpr * @example @@ -1315,7 +1315,7 @@ export const { lprelease, lpr } = registerControl('lprelease', 'lpr'); /** * Sets the release time for the highpass filter envelope. * @name hprelease - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} release time of the highpass filter envelope * @synonyms hpr * @example @@ -1331,7 +1331,7 @@ export const { hprelease, hpr } = registerControl('hprelease', 'hpr'); /** * Sets the release time for the bandpass filter envelope. * @name bprelease - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} release time of the bandpass filter envelope * @synonyms bpr * @example @@ -1347,7 +1347,7 @@ export const { bprelease, bpr } = registerControl('bprelease', 'bpr'); /** * Sets the filter type. The ladder filter is more aggressive. More types might be added in the future. * @name ftype - * @tags effects + * @tags fx * @param {number | Pattern} type 12db (0), ladder (1), or 24db (2) * @example * note("{f g g c d a a#}%8").s("sawtooth").lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1) @@ -1363,7 +1363,7 @@ export const { ftype } = registerControl('ftype'); /** * controls the center of the filter envelope. 0 is unipolar positive, .5 is bipolar, 1 is unipolar negative * @name fanchor - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} center 0 to 1 * @example * note("{f g g c d a a#}%8").s("sawtooth").lpf("{1000}%2") @@ -1376,7 +1376,7 @@ export const { fanchor } = registerControl('fanchor'); * When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'. * * @name hpf - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} frequency audible between 0 and 20000 * @synonyms hp, hcutoff * @example @@ -1391,7 +1391,7 @@ export const { fanchor } = registerControl('fanchor'); * Applies a vibrato to the frequency of the oscillator. * * @name vib - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @synonyms vibrato, v * @param {number | Pattern} frequency of the vibrato in hertz * @example @@ -1419,7 +1419,7 @@ export const { noise } = registerControl('noise'); * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * * @name vibmod - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @synonyms vmod * @param {number | Pattern} depth of vibrato (in semitones) * @example @@ -1438,7 +1438,7 @@ export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'h * Controls the **h**igh-**p**ass **q**-value. * * @name hpq - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms hresonance * @example @@ -1450,7 +1450,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); * Controls the **l**ow-**p**ass **q**-value. * * @name lpq - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} q resonance factor between 0 and 50 * @synonyms resonance * @example @@ -1463,7 +1463,7 @@ export const { resonance, lpq } = registerControl('resonance', 'lpq'); * DJ filter, below 0.5 is low pass filter, above is high pass filter. * * @name djf - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter * @example * n(irand(16).seg(8)).scale("d:phrygian").s("supersaw").djf("<.5 .3 .2 .75>") @@ -1480,7 +1480,7 @@ export const { djf } = registerControl('djf'); * * * @name delay - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} level between 0 and 1 * @example * s("bd bd").delay("<0 .25 .5 1>") @@ -1494,7 +1494,7 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback'] * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1508,7 +1508,7 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it * * @name delayfeedback - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} feedback between 0 and 1 * @synonyms delayfb, dfb * @example @@ -1520,7 +1520,7 @@ export const { delayspeed } = registerControl('delayspeed'); * Sets the time of the delay effect. * * @name delayspeed - * @tags effects + * @tags fx * @param {number | Pattern} delayspeed controls the pitch of the delay feedback * @synonyms delayt, dt * @example @@ -1533,7 +1533,7 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', * Sets the time of the delay effect in cycles. * * @name delaysync - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} cycles delay length in cycles * @synonyms delayt, dt * @example @@ -1546,7 +1546,7 @@ export const { delaysync } = registerControl('delaysync'); * Specifies whether delaytime is calculated relative to cps. * * @name lock - * @tags effects + * @tags fx * @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle. * @superdirtOnly * @example @@ -1560,7 +1560,7 @@ export const { lock } = registerControl('lock'); * Set detune for stacked voices of supported oscillators * * @name detune - * @tags effects + * @tags fx * @param {number | Pattern} amount * @synonyms det * @example @@ -1572,7 +1572,7 @@ export const { detune, det } = registerControl('detune', 'det'); * Set number of stacked voices for supported oscillators * * @name unison - * @tags effects + * @tags fx * @param {number | Pattern} numvoices * @example * note("d f a a# a d3").fast(2).s("supersaw").unison("<1 2 7>") @@ -1584,7 +1584,7 @@ export const { unison } = registerControl('unison'); * Set the stereo pan spread for supported oscillators * * @name spread - * @tags effects + * @tags fx * @param {number | Pattern} spread between 0 and 1 * @example * note("d f a a# a d3").fast(2).s("supersaw").spread("<0 .3 1>") @@ -1595,7 +1595,7 @@ export const { spread } = registerControl('spread'); * Set dryness of reverb. See `room` and `size` for more information about reverb. * * @name dry - * @tags effects, superdough + * @tags fx, superdough * @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() @@ -1635,7 +1635,7 @@ export const { freq } = registerControl('freq'); * Attack time of pitch envelope. * * @name pattack - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @synonyms patt * @param {number | Pattern} time time in seconds * @example @@ -1647,7 +1647,7 @@ export const { pattack, patt } = registerControl('pattack', 'patt'); * Decay time of pitch envelope. * * @name pdecay - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @synonyms pdec * @param {number | Pattern} time time in seconds * @example @@ -1661,7 +1661,7 @@ export const { psustain, psus } = registerControl('psustain', 'psus'); * Release time of pitch envelope * * @name prelease - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @synonyms prel * @param {number | Pattern} time time in seconds * @example @@ -1676,7 +1676,7 @@ export const { prelease, prel } = registerControl('prelease', 'prel'); * If you don't set other pitch envelope controls, `pattack:.2` will be the default. * * @name penv - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} semitones change in semitones * @example * note("c") @@ -1688,7 +1688,7 @@ export const { penv } = registerControl('penv'); * Curve of envelope. Defaults to linear. exponential is good for kicks * * @name pcurve - * @tags effects + * @tags fx * @param {number | Pattern} type 0 = linear, 1 = exponential * @example * note("g1*4") @@ -1705,7 +1705,7 @@ export const { pcurve } = registerControl('pcurve'); * If you don't set an anchor, the value will default to the psustain value. * * @name panchor - * @tags effects + * @tags fx * @param {number | Pattern} anchor anchor offset * @example * note("c c4").penv(12).panchor("<0 .5 1 .5>") @@ -1727,7 +1727,7 @@ export const { gate, gat } = registerControl('gate', 'gat'); * Emulation of a Leslie speaker: speakers rotating in a wooden amplified cabinet. * * @name leslie - * @tags effects + * @tags fx * @param {number | Pattern} wet between 0 and 1 * @example * n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc() @@ -1739,7 +1739,7 @@ export const { leslie } = registerControl('leslie'); * Rate of modulation / rotation for leslie effect * * @name lrate - * @tags effects + * @tags fx * @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() @@ -1752,7 +1752,7 @@ export const { lrate } = registerControl('lrate'); * Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble) * * @name lsize - * @tags effects + * @tags fx * @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() @@ -1814,7 +1814,7 @@ export const { octave } = registerControl('octave'); * An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects. * * @name orbit - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} number * @example * stack( @@ -1831,7 +1831,7 @@ export const { overshape } = registerControl('overshape'); * Sets position in stereo. * * @name pan - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel) * @example * s("[bd hh]*2").pan("<.5 1 .5 0>") @@ -1897,7 +1897,7 @@ export const { mode } = registerControl(['mode', 'anchor']); * When using mininotation, you can also optionally add the 'size' parameter, separated by ':'. * * @name room - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} level between 0 and 1 * @example * s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>") @@ -1911,7 +1911,7 @@ export const { room } = registerControl(['room', 'size']); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomlp - * @tags effects, superdough + * @tags fx, superdough * @synonyms rlp * @param {number} frequency between 0 and 20000hz * @example @@ -1925,7 +1925,7 @@ export const { roomlp, rlp } = registerControl('roomlp', 'rlp'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomdim - * @tags effects, superdough + * @tags fx, superdough * @synonyms rdim * @param {number} frequency between 0 and 20000hz * @example @@ -1940,7 +1940,7 @@ export const { roomdim, rdim } = registerControl('roomdim', 'rdim'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomfade - * @tags effects, superdough + * @tags fx, superdough * @synonyms rfade * @param {number} seconds for the reverb to fade * @example @@ -1953,7 +1953,7 @@ export const { roomfade, rfade } = registerControl('roomfade', 'rfade'); /** * Sets the sample to use as an impulse response for the reverb. * @name iresponse - * @tags effects, superdough + * @tags fx, superdough * @param {string | Pattern} sample to use as an impulse response * @synonyms ir * @example @@ -1965,7 +1965,7 @@ export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse'); /** * Sets speed of the sample for the impulse response. * @name irspeed - * @tags effects, superdough + * @tags fx, superdough * @param {string | Pattern} speed * @example * samples('github:switchangel/pad') @@ -1977,7 +1977,7 @@ export const { irspeed } = registerControl('irspeed'); /** * Sets the beginning of the IR response sample * @name irbegin - * @tags effects, superdough + * @tags fx, superdough * @param {string | Pattern} begin between 0 and 1 * @synonyms ir * @example @@ -1991,7 +1991,7 @@ export const { irbegin } = registerControl('irbegin'); * When this property is changed, the reverb will be recaculated, so only change this sparsely.. * * @name roomsize - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} size between 0 and 10 * @synonyms rsize, sz, size * @example @@ -2015,7 +2015,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', * * * @name shape - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} distortion between 0 and 1 * @example * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") @@ -2028,7 +2028,7 @@ export const { shape } = registerControl(['shape', 'shapevol']); * Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;) * * @name distort - * @tags effects, superdough, supradough + * @tags fx, superdough, supradough * @synonyms dist * @param {number | Pattern} distortion * @example @@ -2043,7 +2043,7 @@ export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dis * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) * * @name compressor - * @tags effects, superdough + * @tags fx, superdough * @example * s("bd sd [~ bd] sd,hh*8") * .compressor("-20:20:10:.002:.02") @@ -2064,7 +2064,7 @@ export const { compressorRelease } = registerControl('compressorRelease'); * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * * @name speed - * @tags effects + * @tags fx * @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards. * @example * s("bd*6").speed("1 2 4 1 -2 -4") @@ -2078,7 +2078,7 @@ export const { speed } = registerControl('speed'); * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * * @name stretch - * @tags effects + * @tags fx * @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards. * @example * s("gm_flute").stretch("1 2 .5") @@ -2089,7 +2089,7 @@ export const { stretch } = registerControl('stretch'); * Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * * @name unit - * @tags effects + * @tags fx * @param {number | string | Pattern} unit see description above * @example * speed("1 2 .5 3").s("bd").unit("c").osc() @@ -2104,7 +2104,7 @@ export const { unit } = registerControl('unit'); * "A simplistic pitch-raising algorithm. It's not meant to sound natural; its sound is reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter, depending on the input. The algorithm works by cutting the signal into fragments (delimited by upwards-going zero-crossings) and squeezing those fragments in the time domain (i.e. simply playing them back faster than they came in), leaving silences inbetween. All the parameters apart from memlen can be modulated." * * @name squiz - * @tags effects + * @tags fx * @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() @@ -2129,7 +2129,7 @@ export const { squiz } = registerControl('squiz'); * Formant filter to make things sound like vowels. * * @name vowel - * @tags effects, superdough + * @tags fx, superdough * @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ. * @example * note("[c2 >]*2").s('sawtooth') @@ -2154,7 +2154,7 @@ export const { waveloss } = registerControl('waveloss'); * Noise crackle density * * @name density - * @tags effects, superdough + * @tags fx, superdough * @param {number | Pattern} density between 0 and x * @example * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)) @@ -2262,7 +2262,7 @@ export let createParams = (...names) => * ADSR envelope: Combination of Attack, Decay, Sustain, and Release. * * @name adsr - * @tags effects + * @tags fx * @param {number | Pattern} time attack time in seconds * @param {number | Pattern} time decay time in seconds * @param {number | Pattern} gain sustain level (0 to 1) diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index cfc669c8..677d8362 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -180,7 +180,7 @@ export function registerWaveTable(key, tables, params) { * Loads a collection of wavetables to use with `s` * * @name tables - * @tags effects + * @tags fx */ export const tables = async (url, frameLen, json, options = {}) => { if (json !== undefined) return _processTables(json, url, frameLen); diff --git a/packages/supradough/dough.mjs b/packages/supradough/dough.mjs index 23dfbee0..9b5bf189 100644 --- a/packages/supradough/dough.mjs +++ b/packages/supradough/dough.mjs @@ -18,7 +18,7 @@ function applyGainCurve(val) { * @param {number} a - Signal A (can be a single value or an array value in buffer processing). * @param {number} b - Signal B (can be a single value or an array value in buffer processing). * @param {number} m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix). - * @tags effects + * @noAutocomplete * @returns {number} Crossfaded output value. */ function crossfade(a, b, m) { From 7e4cbefc8fa812769f35eb9da29d3f50839518e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 19 Oct 2025 21:22:57 +0200 Subject: [PATCH 017/200] Tag crossfade as internals --- packages/supradough/dough.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supradough/dough.mjs b/packages/supradough/dough.mjs index 9b5bf189..a3109733 100644 --- a/packages/supradough/dough.mjs +++ b/packages/supradough/dough.mjs @@ -18,7 +18,7 @@ function applyGainCurve(val) { * @param {number} a - Signal A (can be a single value or an array value in buffer processing). * @param {number} b - Signal B (can be a single value or an array value in buffer processing). * @param {number} m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix). - * @noAutocomplete + * @tags internals * @returns {number} Crossfaded output value. */ function crossfade(a, b, m) { From 466bf819d883b7f2e35a5dc617727c53765d71cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 19 Oct 2025 21:38:46 +0200 Subject: [PATCH 018/200] Fix formatting (trailing space) --- packages/superdough/worklets.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 3e827622..b99b9caf 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -852,7 +852,7 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); /** BYTE BEATS - * @tags internals + * @tags internals */ const chyx = { /*bit*/ bitC: function (x, y, z) { From 0830a9df69ddd49bcec08f25b58b390a103bbb74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 1 Nov 2025 20:19:57 +0100 Subject: [PATCH 019/200] Add superdirt tags --- packages/core/controls.mjs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 1bfd5a89..da5337ec 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -399,7 +399,7 @@ export const { note } = registerControl(['note', 'n']); * A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt. * * @name accelerate - * @tags samples + * @tags samples, superdirt * @param {number | Pattern} amount acceleration. * @superdirtOnly * @example @@ -444,7 +444,7 @@ export const { postgain } = registerControl('postgain'); * Like `gain`, but linear. * * @name amp - * @tags fx + * @tags fx, superdirt * @param {number | Pattern} amount gain. * @superdirtOnly * @example @@ -1075,7 +1075,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); * The amount the signal is affected by the phaser effect. Defaults to 0.75 * * @name phaserdepth - * @tags fx, superdough + * @tags fx, superdough, superdirt * @synonyms phd, phasdp * @param {number | Pattern} depth number between 0 and 1 * @example @@ -1546,7 +1546,7 @@ export const { delaysync } = registerControl('delaysync'); * Specifies whether delaytime is calculated relative to cps. * * @name lock - * @tags fx + * @tags fx, superdirt * @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle. * @superdirtOnly * @example @@ -1595,7 +1595,7 @@ export const { spread } = registerControl('spread'); * Set dryness of reverb. See `room` and `size` for more information about reverb. * * @name dry - * @tags fx, superdough + * @tags fx, superdough, superdirt * @param {number | Pattern} dry 0 = wet, 1 = dry * @example * n("[0,3,7](3,8)").s("superpiano").room(.7).dry("<0 .5 .75 1>").osc() @@ -1727,7 +1727,7 @@ export const { gate, gat } = registerControl('gate', 'gat'); * Emulation of a Leslie speaker: speakers rotating in a wooden amplified cabinet. * * @name leslie - * @tags fx + * @tags fx, superdirt * @param {number | Pattern} wet between 0 and 1 * @example * n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc() @@ -1739,7 +1739,7 @@ export const { leslie } = registerControl('leslie'); * Rate of modulation / rotation for leslie effect * * @name lrate - * @tags fx + * @tags fx, superdirt * @param {number | Pattern} rate 6.7 for fast, 0.7 for slow * @example * n("0,4,7").s("supersquare").leslie(1).lrate("<1 2 4 8>").osc() @@ -1752,7 +1752,7 @@ export const { lrate } = registerControl('lrate'); * Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble) * * @name lsize - * @tags fx + * @tags fx, superdirt * @param {number | Pattern} meters somewhere between 0 and 1 * @example * n("0,4,7").s("supersquare").leslie(1).lrate(2).lsize("<.1 .5 1>").osc() @@ -1801,6 +1801,7 @@ export const { nudge } = registerControl('nudge'); * Sets the default octave of a synth. * * @name octave + * @tags fx, superdirt * @param {number | Pattern} octave octave number * @example * n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc() @@ -2089,7 +2090,7 @@ export const { stretch } = registerControl('stretch'); * Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * * @name unit - * @tags fx + * @tags fx, superdirt * @param {number | string | Pattern} unit see description above * @example * speed("1 2 .5 3").s("bd").unit("c").osc() @@ -2104,7 +2105,7 @@ export const { unit } = registerControl('unit'); * "A simplistic pitch-raising algorithm. It's not meant to sound natural; its sound is reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter, depending on the input. The algorithm works by cutting the signal into fragments (delimited by upwards-going zero-crossings) and squeezing those fragments in the time domain (i.e. simply playing them back faster than they came in), leaving silences inbetween. All the parameters apart from memlen can be modulated." * * @name squiz - * @tags fx + * @tags fx, superdirt * @param {number | Pattern} squiz Try passing multiples of 2 to it - 2, 4, 8 etc. * @example * squiz("2 4/2 6 [8 16]").s("bd").osc() From b7827cb89f16624773f9c5717d64419d1a729154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 1 Nov 2025 21:11:57 +0100 Subject: [PATCH 020/200] Tag new functions --- packages/core/controls.mjs | 4 ++ packages/core/pattern.mjs | 9 ++++ .../src/repl/components/panel/Reference.jsx | 42 ++++--------------- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 50a49486..7c66b400 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -2049,6 +2049,7 @@ export const { distort, dist } = registerControl(['distort', 'distortvol', 'dist * * @name distortvol * @synonyms distvol + * @tags fx, superdough, supradough * @param {number | Pattern} volume linear postgain of the distortion * @example * s("bd*4").bank("tr909").distort(2).distortvol(0.8) @@ -2059,6 +2060,7 @@ export const { distortvol } = registerControl('distortvol', 'distvol'); * Type of waveshaping distortion to apply. * * @name distorttype + * @tags fx, superdough, supradough * @synonyms disttype * @param {number | string | Pattern} type type of distortion to apply * @example @@ -2487,6 +2489,7 @@ export const { polyTouch } = registerControl('polyTouch'); /** * The host to send open sound control messages to. Requires running the OSC bridge. * @name oschost + * @tags external_io * @param {string | Pattern} oschost e.g. 'localhost' * @example * note("c4").oschost('127.0.0.1').oscport(57120).osc(); @@ -2496,6 +2499,7 @@ export const { oschost } = registerControl('oschost'); /** * The port to send open sound control messages to. Requires running the OSC bridge. * @name oscport + * @tags external_io * @param {number | Pattern} oscport e.g. 57120 * @example * note("c4").oschost('127.0.0.1').oscport(57120).osc(); diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 8cb30e15..e8cd3d23 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3128,6 +3128,7 @@ export const extend = stepRegister('extend', function (factor, pat) { * `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`. * * TODO: find out how this function differs from extend + * @tags temporal * @example * stepcat( * sound("bd bd - cp").replicate(2), @@ -3697,6 +3698,7 @@ export const morph = (frompat, topat, bypat) => { * Soft-clipping distortion * * @name soft + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * @@ -3705,6 +3707,7 @@ export const morph = (frompat, topat, bypat) => { * Hard-clipping distortion * * @name hard + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * @@ -3713,6 +3716,7 @@ export const morph = (frompat, topat, bypat) => { * Cubic polynomial distortion * * @name cubic + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * @@ -3721,6 +3725,7 @@ export const morph = (frompat, topat, bypat) => { * Diode-emulating distortion * * @name diode + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * @@ -3729,6 +3734,7 @@ export const morph = (frompat, topat, bypat) => { * Asymmetrical diode distortion * * @name asym + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * @@ -3737,6 +3743,7 @@ export const morph = (frompat, topat, bypat) => { * Wavefolding distortion * * @name fold + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * @@ -3745,6 +3752,7 @@ export const morph = (frompat, topat, bypat) => { * Wavefolding distortion composed with sinusoid * * @name sinefold + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * @@ -3753,6 +3761,7 @@ export const morph = (frompat, topat, bypat) => { * Distortion via Chebyshev polynomials * * @name chebyshev + * @tags fx * @param {number | Pattern} distortion amount of distortion to apply * @param {number | Pattern} volume linear postgain of the distortion * diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 2a7dcc2a..3053f03d 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -11,8 +11,13 @@ const availableFunctions = (() => { for (const doc of jsdocJson.docs) { if (!isValid(doc)) continue; if (seen.has(doc.name)) continue; - doc.tags = doc.tags?.filter((t) => t && typeof t === 'string') || []; + + // jsdoc also uses "tags" for when you use @something in the comments and it doesn't know what + // @something is. We only want data from comments like `@tags fx, superdough` here. + // If nothing is specified, we default to "untagged" for debugging + doc.tags = doc.tags?.filter((t) => t && typeof t === 'string') || ['untagged']; functions.push(doc); + const synonyms = doc.synonyms || []; seen.add(doc.name); for (const s of synonyms) { @@ -38,16 +43,6 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; -const GROUP_DISPLAY_NAMES = { - external_io: 'External I/O', - effects: 'Effects', - untagged: 'Untagged', - structure: 'Structure', - transforms: 'Transforms', -}; - -const GROUP_ORDER = ['effects', 'transforms', 'structure', 'untagged', 'external_io']; - export function Reference() { const [search, setSearch] = useState(''); const [selectedTag, setSelectedTag] = useState(null); @@ -80,29 +75,6 @@ export function Reference() { }); }, [search, selectedTag]); - const visibleFunctionsByGroup = (() => { - const groups = {}; - for (const doc of visibleFunctions) { - const group = (doc.tags || ['untagged'])[0]; - if (!groups[group]) { - groups[group] = []; - } - groups[group].push(doc); - } - return groups; - })(); - // console.log(visibleFunctionsByGroup); - - // Sort and map group entries - const sortedGroups = Object.entries(visibleFunctionsByGroup).sort(([a], [b]) => { - const ai = GROUP_ORDER.indexOf(a); - const bi = GROUP_ORDER.indexOf(b); - if (ai === -1 && bi === -1) return a.localeCompare(b); - if (ai === -1) return 1; - if (bi === -1) return -1; - return ai - bi; - }); - const tagCounts = {}; for (const doc of availableFunctions) { (doc.tags || ['untagged']).forEach((t) => { @@ -114,7 +86,7 @@ export function Reference() { return (

-
+
From 2fa8ed14245b45cce91708495befa82afe50745b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 9 Nov 2025 20:21:19 +0100 Subject: [PATCH 021/200] Revert seq synonym refactor --- packages/core/pattern.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index e8cd3d23..b8376c8f 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1566,6 +1566,11 @@ export function fastcat(...pats) { return result; } +/** See `fastcat` */ +export function sequence(...pats) { + return fastcat(...pats); +} + /** Like **cat**, but the items are crammed into one cycle. * @tags combiners * @synonyms seq, fastcat @@ -1579,9 +1584,6 @@ export function fastcat(...pats) { * note("c4(5,8)") * ) */ -export function sequence(...pats) { - return fastcat(...pats); -} export function seq(...pats) { return fastcat(...pats); From 65dd79e374ce791bf67c7de217b45c10de20f7ff Mon Sep 17 00:00:00 2001 From: "Lu[ke] Wilson" Date: Sat, 29 Nov 2025 17:44:33 +0000 Subject: [PATCH 022/200] add lots of synonyms --- packages/core/controls.mjs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 21a6d962..0f9ba4d8 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -465,6 +465,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); * * @name fmenv * @param {number | Pattern} type lin | exp + * @synonyms fme * @example * note("c e g b g e") * .fm(4) @@ -474,12 +475,13 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); * ._scope() * */ -export const { fmenv } = registerControl('fmenv'); +export const { fmenv } = registerControl('fmenv', 'fme'); /** * Attack time for the FM envelope: time it takes to reach maximum modulation * * @name fmattack * @param {number | Pattern} time attack time + * @synonyms fmatt * @example * note("c e g b g e") * .fm(4) @@ -487,7 +489,7 @@ export const { fmenv } = registerControl('fmenv'); * ._scope() * */ -export const { fmattack } = registerControl('fmattack'); +export const { fmattack } = registerControl('fmattack', 'fmatt'); /** * Waveform of the fm modulator @@ -507,6 +509,7 @@ export const { fmwave } = registerControl('fmwave'); * * @name fmdecay * @param {number | Pattern} time decay time + * @synonyms fmdec * @example * note("c e g b g e") * .fm(4) @@ -515,12 +518,13 @@ export const { fmwave } = registerControl('fmwave'); * ._scope() * */ -export const { fmdecay } = registerControl('fmdecay'); +export const { fmdecay } = registerControl('fmdecay', 'fmdec'); /** * Sustain level for the FM envelope: how much modulation is applied after the decay phase * * @name fmsustain * @param {number | Pattern} level sustain level + * @synonyms fmsus * @example * note("c e g b g e") * .fm(4) @@ -529,10 +533,10 @@ export const { fmdecay } = registerControl('fmdecay'); * ._scope() * */ -export const { fmsustain } = registerControl('fmsustain'); +export const { fmsustain } = registerControl('fmsustain', 'fmsus'); // these are not really useful... skipping for now -export const { fmrelease } = registerControl('fmrelease'); -export const { fmvelocity } = registerControl('fmvelocity'); +export const { fmrelease } = registerControl('fmrelease', 'fmrel'); +export const { fmvelocity } = registerControl('fmvelocity', 'fmvel'); /** * Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`. @@ -862,7 +866,7 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckattack - * @synonyms duckatt + * @synonyms duckatt, datt * * @param {number | Pattern} time The attack time in seconds * @example @@ -874,20 +878,20 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:5").duckattack("0.4:0.1") * */ -export const { duckattack } = registerControl('duckattack', 'duckatt'); +export const { duckattack } = registerControl('duckattack', 'duckatt', 'datt'); /** * Create byte beats with custom expressions * * @name byteBeatExpression - * @synonyms bbexpr + * @synonyms bbexpr, bb * * @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat * @example * s("bytebeat").bbexpr('t*(t>>15^t>>66)') * */ -export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); +export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr', 'bb'); /** * Create byte beats with custom expressions @@ -931,24 +935,26 @@ export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); * Controls the lfo rate for the pulsewidth of the pulse oscillator * * @name pwrate + * @synonyms pwr * @param {number | Pattern} rate * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") * */ -export const { pwrate } = registerControl('pwrate'); +export const { pwrate } = registerControl('pwrate', 'pwr'); /** * Controls the lfo sweep for the pulsewidth of the pulse oscillator * * @name pwsweep + * @synonyms pws * @param {number | Pattern} sweep * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") * */ -export const { pwsweep } = registerControl('pwsweep'); +export const { pwsweep } = registerControl('pwsweep', 'pws'); /** * Phaser audio effect that approximates popular guitar pedals. @@ -1615,12 +1621,12 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', * * @name delaysync * @param {number | Pattern} cycles delay length in cycles - * @synonyms delayt, dt + * @synonyms delays, ds * @example * s("bd bd").delay(.25).delaysync("<1 2 3 5>".div(8)) * */ -export const { delaysync } = registerControl('delaysync'); +export const { delaysync } = registerControl('delaysync', 'delays', 'ds'); /** * Specifies whether delaytime is calculated relative to cps. From ec73c48e2e3b2de112e45b0707be1738a3f6c3cd Mon Sep 17 00:00:00 2001 From: space-shell Date: Mon, 8 Dec 2025 09:44:02 +0100 Subject: [PATCH 023/200] feat: Add Helix keybindings and Nix development environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for Helix editor keybindings in the REPL, providing users with a modern, selection-first modal editing experience as an alternative to Vim, Emacs, and VSCode keybindings. Helix Keybindings Integration: - Add codemirror-helix@^0.5.0 dependency to @strudel/codemirror - Import and register helix keybindings in keybindings.mjs - Add 'Helix' option to keybindings selector in Settings UI - Create comprehensive user documentation at technical-manual/helix.mdx - Update developer documentation with keybinding integration pattern The Helix mode provides selection-first editing (select → action) which differs from Vim's action-motion paradigm, offering an alternative workflow for modal editing enthusiasts. Documentation: - Update CLAUDE.md with Helix support and keybinding addition process - Create helix.mdx with user-facing documentation and command reference - Update CHANGELOG.md with December 2025 entry Testing: Run `pnpm i` to install new dependencies, then `pnpm dev` to test. Verify Helix mode works in Settings → Keybindings → Helix. --- CHANGELOG.md | 4 + packages/codemirror/keybindings.mjs | 2 + packages/codemirror/package.json | 1 + pnpm-lock.yaml | 24 +++++- website/src/pages/technical-manual/helix.mdx | 84 +++++++++++++++++++ .../src/repl/components/panel/SettingsTab.jsx | 2 +- 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 website/src/pages/technical-manual/helix.mdx diff --git a/CHANGELOG.md b/CHANGELOG.md index a13e379d..7c73c39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ NOTE: you can generate this with `node warm.js`. it might still not be perfectly sorted... +## december 2025 + +- 2025-12-08 Add Helix keybindings support to REPL editor - Added `codemirror-helix` package integration, enabling Helix editor keybindings as a new option in the REPL settings alongside Vim, Emacs, and VSCode modes + ## november 2025 - 2025-11-27T22:03:53+01:00 [perf] in `noise`, let noiseMix do the disconnect when it exists by jeromew in: [#1783](https://codeberg.org/uzu/strudel/pulls/1783) diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs index 81dc87a5..bc13f323 100644 --- a/packages/codemirror/keybindings.mjs +++ b/packages/codemirror/keybindings.mjs @@ -6,6 +6,7 @@ import { emacs } from '@replit/codemirror-emacs'; import { vim, Vim } from '@replit/codemirror-vim'; // import { vim } from './vim_test.mjs'; import { vscodeKeymap } from '@replit/codemirror-vscode-keymap'; +import { helix } from 'codemirror-helix'; import { logger } from '@strudel/core'; const vscodePlugin = ViewPlugin.fromClass( @@ -127,6 +128,7 @@ try { const keymaps = { vim, emacs, + helix, codemirror: () => keymap.of(defaultKeymap), vscode: vscodeExtension, }; diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index a3735491..41946444 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -44,6 +44,7 @@ "@replit/codemirror-emacs": "^6.1.0", "@replit/codemirror-vim": "^6.3.0", "@replit/codemirror-vscode-keymap": "^6.0.2", + "codemirror-helix": "^0.5.0", "@strudel/core": "workspace:*", "@strudel/draw": "workspace:*", "@strudel/tonal": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23903fca..709a5b66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,7 +41,7 @@ importers: version: 2.2.7 '@vitest/coverage-v8': specifier: 3.0.4 - version: 3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)) + version: 3.0.4(vitest@3.0.4) '@vitest/ui': specifier: ^3.0.4 version: 3.0.4(vitest@3.0.4) @@ -221,6 +221,9 @@ importers: '@tonaljs/tonal': specifier: ^4.10.0 version: 4.10.0 + codemirror-helix: + specifier: ^0.5.0 + version: 0.5.0(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2) nanostores: specifier: ^0.11.3 version: 0.11.3 @@ -3536,6 +3539,15 @@ packages: resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + codemirror-helix@0.5.0: + resolution: {integrity: sha512-hI56hf9VGz53H1YvL6H1GC7HtP6te8vX+MsIHaE9J7Q3PQ6KFapKtIRg6lqSH898ikHWpMCPu42r6HJN0IfVLA==} + peerDependencies: + '@codemirror/commands': ^6.0.0 + '@codemirror/language': ^6.0.0 + '@codemirror/search': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -10554,7 +10566,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0))': + '@vitest/coverage-v8@3.0.4(vitest@3.0.4)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -11254,6 +11266,14 @@ snapshots: cmd-shim@6.0.3: {} + codemirror-helix@0.5.0(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2): + dependencies: + '@codemirror/commands': 6.8.0 + '@codemirror/language': 6.10.8 + '@codemirror/search': 6.5.8 + '@codemirror/state': 6.5.1 + '@codemirror/view': 6.36.2 + collapse-white-space@2.1.0: {} color-convert@2.0.1: diff --git a/website/src/pages/technical-manual/helix.mdx b/website/src/pages/technical-manual/helix.mdx new file mode 100644 index 00000000..1de42d1e --- /dev/null +++ b/website/src/pages/technical-manual/helix.mdx @@ -0,0 +1,84 @@ +--- +title: Helix Keybindings +layout: ../../layouts/MainLayout.astro +--- + +# Helix Keybindings in the REPL + +The Strudel REPL supports [Helix editor](https://helix-editor.com/) keybindings through the `codemirror-helix` extension. Helix is a post-modern modal text editor with a focus on selection-first editing and multiple cursors. + +## Enabling Helix Mode + +1. Open the Settings panel in the REPL (click the settings icon or use the keyboard shortcut) +2. Find the "Keybindings" section +3. Select "Helix" from the available options + +## Key Differences from Vim + +Helix uses a selection-first approach, which means: + +- **Selection → Action** (Helix) vs **Action → Motion** (Vim) +- For example, to delete a word in Helix: `w` (select word) → `d` (delete) +- In Vim, it would be: `dw` (delete word) + +## Common Helix Commands + +### Normal Mode + +- `i` — Enter insert mode before selection +- `a` — Enter insert mode after selection +- `v` — Enter visual/select mode +- `w` — Select next word +- `e` — Select to end of word +- `b` — Select previous word +- `x` — Select/extend line +- `d` — Delete selection +- `c` — Change selection (delete and enter insert mode) +- `y` — Yank (copy) selection +- `p` — Paste after selection +- `u` — Undo +- `U` — Redo +- `/` — Search +- `n` — Select next search match +- `N` — Select previous search match + +### Movement + +- `h, j, k, l` — Move left, down, up, right +- `gg` — Go to start of document +- `ge` — Go to end of document +- `{` / `}` — Move to previous/next paragraph +- `%` — Match bracket + +### Multi-cursor + +- `C` — Duplicate cursor to line below +- `Alt-C` — Duplicate cursor to line above +- `,` — Remove primary cursor +- `Alt-,` — Remove all secondary cursors + +## Strudel-Specific Features + +Currently, Helix mode in Strudel provides standard Helix keybindings. Unlike Vim mode, there are no custom Strudel-specific commands (like `:w` to evaluate) yet. + +To evaluate code while in Helix mode, use the standard shortcuts: +- `Ctrl+Enter` or `Alt+Enter` — Evaluate code +- `Alt+.` — Stop playback + +## Resources + +- [Helix Documentation](https://docs.helix-editor.com/) +- [Helix Keymap](https://docs.helix-editor.com/keymap.html) +- [codemirror-helix on npm](https://www.npmjs.com/package/codemirror-helix) +- [codemirror-helix on GitLab](https://gitlab.com/_rvidal/codemirror-helix) + +## Notes + +- The `codemirror-helix` extension is described as an "initial version" and may not implement all Helix features +- Behavior may differ slightly from the native Helix editor +- If you encounter issues, you can switch back to other keybinding modes in Settings +- The keybinding preference is saved in your browser's local storage + +## Contributing + +If you'd like to add Strudel-specific Helix commands (similar to Vim's `:w` for evaluate), contributions are welcome! See the implementation in `/packages/codemirror/keybindings.mjs` for reference. diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index f46fa661..3cc8d639 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -220,7 +220,7 @@ export function SettingsTab({ started }) { settingsMap.setKey('keybindings', keybindings)} - items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs', vscode: 'VSCode' }} + items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs', helix: 'Helix', vscode: 'VSCode' }} > From a6b3b9ef5e7ca385ea39722fe861a8ed469113ac Mon Sep 17 00:00:00 2001 From: space-shell Date: Mon, 15 Dec 2025 13:22:13 +0100 Subject: [PATCH 024/200] updates formatting --- website/src/pages/technical-manual/helix.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/pages/technical-manual/helix.mdx b/website/src/pages/technical-manual/helix.mdx index 1de42d1e..36f05898 100644 --- a/website/src/pages/technical-manual/helix.mdx +++ b/website/src/pages/technical-manual/helix.mdx @@ -62,6 +62,7 @@ Helix uses a selection-first approach, which means: Currently, Helix mode in Strudel provides standard Helix keybindings. Unlike Vim mode, there are no custom Strudel-specific commands (like `:w` to evaluate) yet. To evaluate code while in Helix mode, use the standard shortcuts: + - `Ctrl+Enter` or `Alt+Enter` — Evaluate code - `Alt+.` — Stop playback From fd1e3d248aa3b2d429456c73a1de17cd19253e5c Mon Sep 17 00:00:00 2001 From: Dsm0 Date: Tue, 23 Dec 2025 01:47:06 -0800 Subject: [PATCH 025/200] block based eval --- packages/codemirror/block_utilities.mjs | 51 +++ packages/codemirror/codemirror.mjs | 85 ++++- packages/codemirror/flash.mjs | 8 +- packages/codemirror/highlight.mjs | 229 ++++++++++---- packages/codemirror/slider.mjs | 123 +++++++- packages/codemirror/widget.mjs | 240 +++++++++++--- packages/core/evaluate.mjs | 28 ++ packages/core/repl.mjs | 296 ++++++++++++++++-- packages/draw/draw.mjs | 10 + packages/transpiler/transpiler.mjs | 209 +++++++++++-- .../src/repl/components/panel/SettingsTab.jsx | 6 + website/src/repl/useReplContext.jsx | 19 +- website/src/settings.mjs | 2 + 13 files changed, 1106 insertions(+), 200 deletions(-) create mode 100644 packages/codemirror/block_utilities.mjs diff --git a/packages/codemirror/block_utilities.mjs b/packages/codemirror/block_utilities.mjs new file mode 100644 index 00000000..4b53755d --- /dev/null +++ b/packages/codemirror/block_utilities.mjs @@ -0,0 +1,51 @@ +// Block-based evaluation utilities + +export function getBlockRegions(code) { + const chars = code.split(''); + let i = 0, + blanks = [], + blockStart = 0, + regions = []; + while (i < chars.length) { + const isBlank = chars[i] === '\n'; + if (isBlank) { + blanks.push(i); + } else if (chars[i].trim() !== '') { + if (blanks.length > 1) { + regions.push([blockStart, blanks[0]]); + blockStart = i; + } + blanks = []; + } + i++; + } + regions.push([blockStart, blanks.length ? blanks[0] : i]); + return regions; +} + +export function getBlockAt(code, cursor) { + const regions = getBlockRegions(code); + for (const [start, end] of regions) { + if (cursor >= start && cursor <= end) { + return [start, end]; + } + } + return null; +} + +export const evalBlock = (strudelMirror) => { + const { state } = strudelMirror.editor; + const code = state.doc.toString(); + const cursor = state.selection.main.head; + const range = getBlockAt(code, cursor); + if (range) { + const [a, b] = range; + const block = code.slice(a, b); + if (block) { + // Flash the block being evaluated + strudelMirror.flash(200, { from: a, to: b }); + strudelMirror.repl.evaluate(block, true, true, true, { range }); + } + } + return true; +}; diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index a612ab61..71986ba4 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -13,7 +13,7 @@ import { } from '@codemirror/view'; import { persistentAtom } from '@nanostores/persistent'; import { logger, registerControl, repl } from '@strudel/core'; -import { cleanupDraw, Drawer } from '@strudel/draw'; +import { cleanupDraw, Drawer, cleanupDrawContext } from '@strudel/draw'; import { isAutoCompletionEnabled } from './autocomplete.mjs'; import { basicSetup } from './basicSetup.mjs'; @@ -21,9 +21,13 @@ import { flash, isFlashEnabled } from './flash.mjs'; import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; import { keybindings } from './keybindings.mjs'; import { sliderPlugin, updateSliderWidgets } from './slider.mjs'; +import { widgetPlugin, updateWidgets } from './widget.mjs'; import { activateTheme, initTheme, theme } from './themes.mjs'; import { isTooltipEnabled } from './tooltip.mjs'; -import { updateWidgets, widgetPlugin } from './widget.mjs'; +import { getActiveWidgets } from './widget.mjs'; +import { getSliderWidgets } from './slider.mjs'; + +import { evalBlock } from './block_utilities.mjs'; export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands'; @@ -63,6 +67,7 @@ export const defaultSettings = { isLineWrappingEnabled: false, isTabIndentationEnabled: false, isMultiCursorEnabled: false, + isBlockBasedEvalEnabled: false, theme: 'strudelTheme', fontFamily: 'monospace', fontSize: 18, @@ -74,7 +79,7 @@ export const codemirrorSettings = persistentAtom('codemirror-settings', defaultS }); // https://codemirror.net/docs/guide/ -export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo }) { +export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo, strudelMirror }) { const settings = codemirrorSettings.get(); const initialSettings = Object.keys(compartments).map((key) => compartments[key].of(extensions[key](parseBooleans(settings[key]))), @@ -104,11 +109,26 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo keymap.of([ { key: 'Ctrl-Enter', - run: () => onEvaluate?.(), + run: () => { + // issue with referencing settings, this works more reliably + if (strudelMirror?.isBlockBasedEvalEnabled) { + evalBlock(strudelMirror); + return true; + } else { + return onEvaluate?.(); + } + }, }, { key: 'Alt-Enter', - run: () => onEvaluate?.(), + run: () => { + if (strudelMirror?.isBlockBasedEvalEnabled) { + evalBlock(strudelMirror); + return true; + } else { + return onEvaluate?.(); + } + }, }, { key: 'Ctrl-.', @@ -162,6 +182,7 @@ export class StrudelMirror { this.onDraw = onDraw || this.draw; this.id = id || s4(); this.solo = solo; + this.isBlockBasedEvalEnabled = false; // Will be updated via updateSettings() this.drawer = new Drawer((haps, time, _, painters) => { const currentFrame = haps.filter((hap) => hap.isActive(time)); @@ -169,6 +190,7 @@ export class StrudelMirror { this.onDraw(haps, time, painters); }, drawTime); + this.prebaked = prebake(); autodraw && this.drawFirstFrame(); this.repl = repl({ @@ -192,20 +214,28 @@ export class StrudelMirror { cleanupDraw(true, id); } }, - beforeEval: async () => { - cleanupDraw(true, id); + beforeEval: async ({ blockBased } = {}) => { + // Only clean up all drawings for full evaluation + // Block-based eval should preserve animations (like .scope()) from other blocks + if (!blockBased) { + cleanupDraw(true, id); + } await this.prebaked; await replOptions?.beforeEval?.(); }, afterEval: (options) => { // remember for when highlighting is toggled on - this.miniLocations = options.meta?.miniLocations; + this.miniLocations = options.meta?.miniLocations || []; this.widgets = options.meta?.widgets; + const sliders = this.widgets.filter((w) => w.type === 'slider'); - updateSliderWidgets(this.editor, sliders); const widgets = this.widgets.filter((w) => w.type !== 'slider'); - updateWidgets(this.editor, widgets); - updateMiniLocations(this.editor, this.miniLocations); + // range-aware update for block-based evaluation + const range = options.range && options.range.length >= 2 ? options.range : null; + + updateSliderWidgets(this.editor, sliders, range); + updateWidgets(this.editor, widgets, range); + updateMiniLocations(this.editor, this.miniLocations, range); replOptions?.afterEval?.(options); // if no painters are set (.onPaint was not called), then we only need // the present moment (for highlighting) @@ -213,8 +243,14 @@ export class StrudelMirror { this.drawer.setDrawTime(drawTime); // invalidate drawer after we've set the appropriate drawTime this.drawer.invalidate(this.repl.scheduler); + + // Clean up draw context if a non-inline widget was removed + if (options.widgetRemoved) { + cleanupDrawContext(id); + } }, }); + this.cleanupDrawContext = () => cleanupDrawContext(id); this.editor = initEditor({ root, initialCode, @@ -227,7 +263,9 @@ export class StrudelMirror { onEvaluate: () => this.evaluate(), onStop: () => this.stop(), mondo: replOptions.mondo, + strudelMirror: this, }); + const cmEditor = this.root.querySelector('.cm-editor'); if (cmEditor) { this.root.style.display = 'block'; @@ -297,8 +335,9 @@ export class StrudelMirror { this.flash(); await this.repl.evaluate(this.code, autostart); } + async stop() { - this.repl.scheduler.stop(); + this.repl.stop(); } // Listen for global stop requests (e.g., from Vim :q) @@ -317,8 +356,8 @@ export class StrudelMirror { this.evaluate(); } } - flash(ms) { - flash(this.editor, ms); + flash(ms, range) { + flash(this.editor, ms, range); } highlight(haps, time) { highlightMiniLocations(this.editor, time, haps); @@ -350,6 +389,10 @@ export class StrudelMirror { setLineWrappingEnabled(enabled) { this.reconfigureExtension('isLineWrappingEnabled', enabled); } + + setBlockBasedEvalEnabled(enabled) { + this.reconfigureExtension('isBlockBasedEvalEnabled', enabled); + } setBracketMatchingEnabled(enabled) { this.reconfigureExtension('isBracketMatchingEnabled', enabled); } @@ -371,6 +414,10 @@ export class StrudelMirror { for (let key in extensions) { this.reconfigureExtension(key, settings[key]); } + // Update block-based eval setting on the instance + if (settings.isBlockBasedEvalEnabled !== undefined) { + this.isBlockBasedEvalEnabled = parseBooleans(settings.isBlockBasedEvalEnabled); + } const updated = { ...codemirrorSettings.get(), ...settings }; codemirrorSettings.set(updated); } @@ -392,6 +439,16 @@ export class StrudelMirror { }; this.editor.dispatch({ changes }); } + // used for debugging but could serve other purposes + getActiveWidgets() { + return getActiveWidgets(this.editor); + } + getSliderWidgets() { + return getSliderWidgets(this.editor); + } + getMiniLocations() { + return this.miniLocations; + } clear() { this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl); this.onEvaluateRequest && document.removeEventListener('repl-evaluate', this.onEvaluateRequest); diff --git a/packages/codemirror/flash.mjs b/packages/codemirror/flash.mjs index 243500bf..7b247350 100644 --- a/packages/codemirror/flash.mjs +++ b/packages/codemirror/flash.mjs @@ -14,7 +14,8 @@ export const flashField = StateField.define({ const mark = Decoration.mark({ attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` }, }); - flash = Decoration.set([mark.range(0, tr.newDoc.length)]); + const range = e.value.range || { from: 0, to: tr.newDoc.length }; + flash = Decoration.set([mark.range(range.from, range.to)]); } else { flash = Decoration.set([]); } @@ -29,8 +30,9 @@ export const flashField = StateField.define({ provide: (f) => EditorView.decorations.from(f), }); -export const flash = (view, ms = 200) => { - view.dispatch({ effects: setFlash.of(true) }); +export const flash = (view, ms = 200, range) => { + const flashData = range ? { range } : true; + view.dispatch({ effects: setFlash.of(flashData) }); setTimeout(() => { view.dispatch({ effects: setFlash.of(false) }); }, ms); diff --git a/packages/codemirror/highlight.mjs b/packages/codemirror/highlight.mjs index f9f977c6..5cc6d3a1 100644 --- a/packages/codemirror/highlight.mjs +++ b/packages/codemirror/highlight.mjs @@ -3,8 +3,9 @@ import { Decoration, EditorView } from '@codemirror/view'; export const setMiniLocations = StateEffect.define(); export const showMiniLocations = StateEffect.define(); -export const updateMiniLocations = (view, locations) => { - view.dispatch({ effects: setMiniLocations.of(locations) }); +export const displayMiniLocations = StateEffect.define(); +export const updateMiniLocations = (view, locations, range = null) => { + view.dispatch({ effects: setMiniLocations.of({ locations, range }) }); }; export const highlightMiniLocations = (view, atTime, haps) => { view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) }); @@ -21,23 +22,56 @@ const miniLocations = StateField.define({ for (let e of tr.effects) { if (e.is(setMiniLocations)) { - // this is called on eval, with the mini locations obtained from the transpiler - // codemirror will automatically remap the marks when the document is edited - // create a mark for each mini location, adding the range to the spec to find it later - const marks = e.value - .filter(([from]) => from < tr.newDoc.length) - .map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]) - .map( - (range) => - Decoration.mark({ - id: range.join(':'), - // this green is only to verify that the decoration moves when the document is edited - // it will be removed later, so the mark is not visible by default - attributes: { style: `background-color: #00CA2880` }, - }).range(...range), // -> Decoration - ); + //block-based eval case + if (e.value.range) { + const stateMiniLocations = getMiniLocationsFromDecorations(locations); + const existingById = new Map(stateMiniLocations.map(({ id, from, to }) => [id, [from, to]])); - locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet + const normalized = e.value.locations + .filter(([from]) => from < tr.newDoc.length) + .map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]); + + const newIds = new Set(normalized.map((r) => r.join(':'))); + + const marks = normalized.map((range) => { + const id = range.join(':'); + const useRange = existingById.get(id) || range; + return Decoration.mark({ + id, + // this green is only to verify that the decoration moves when the document is edited + // it will be removed later, so the mark is not visible by default + attributes: { style: `background-color: #00CA2880` }, + }).range(...useRange); // -> Decoration + }); + + const previousMarks = stateMiniLocations + .filter(({ id }) => !newIds.has(id)) + .map(({ from, to, id }) => + Decoration.mark({ + id, + attributes: { style: `background-color: #00CA2880` }, + }).range(from, to), + ); + + locations = Decoration.set(previousMarks.concat(marks), true); // -> DecorationSet === RangeSet + } else { + // this is called on eval, with the mini locations obtained from the transpiler + // codemirror will automatically remap the marks when the document is edited + // create a mark for each mini location, adding the range to the spec to find it later + const marks = e.value.locations + .filter(([from]) => from < tr.newDoc.length) + .map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]) + .map( + (range) => + Decoration.mark({ + id: range.join(':'), + // this green is only to verify that the decoration moves when the document is edited + // it will be removed later, so the mark is not visible by default + attributes: { style: `background-color: #00CA2880` }, + }).range(...range), // -> Decoration + ); + locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet + } } } @@ -75,12 +109,83 @@ const visibleMiniLocations = StateField.define({ }, }); -// // Derive the set of decorations from the miniLocations and visibleLocations -const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => { - const iterator = state.field(miniLocations).iter(); - const { haps } = state.field(visibleMiniLocations); - const builder = new RangeSetBuilder(); +const displayMiniLocationsState = StateField.define({ + create() { + return true; // default to showing miniLocations + }, + update(display, tr) { + for (let e of tr.effects) { + if (e.is(displayMiniLocations)) { + display = e.value; + } + } + return display; + }, +}); +// // Derive the set of decorations from the miniLocations and visibleLocations +const miniLocationHighlights = EditorView.decorations.compute( + [miniLocations, visibleMiniLocations, displayMiniLocationsState], + (state) => { + // Check if miniLocations display is disabled + const shouldDisplay = state.field(displayMiniLocationsState); + if (!shouldDisplay) { + return Decoration.none; // Return empty decorations if display is disabled + } + + const iterator = state.field(miniLocations).iter(); + const { haps } = state.field(visibleMiniLocations); + const builder = new RangeSetBuilder(); + + while (iterator.value) { + const { + from, + to, + value: { + spec: { id }, + }, + } = iterator; + + if (haps.has(id)) { + const hap = haps.get(id); + const color = hap.value?.color ?? 'var(--foreground)'; + const style = hap.value?.markcss || `outline: solid 2px ${color}`; + // Get explicit channels for color values + /* + const swatch = document.createElement('div'); + swatch.style.color = color; + document.body.appendChild(swatch); + let channels = getComputedStyle(swatch) + .color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/) + .slice(1) + .map((c) => parseFloat(c || 1)); + document.body.removeChild(swatch); + + // Get percentage of event + const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration; + channels[3] *= percent; + */ + + builder.add( + from, + to, + Decoration.mark({ + // attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, + attributes: { style }, + }), + ); + } + + iterator.next(); + } + + return builder.finish(); + }, +); + +const getMiniLocationsFromDecorations = (decorations) => { + const iterator = decorations.iter(); + const miniLocationsArray = []; while (iterator.value) { const { from, @@ -89,50 +194,48 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi spec: { id }, }, } = iterator; - - if (haps.has(id)) { - const hap = haps.get(id); - const color = hap.value?.color ?? 'var(--foreground)'; - const style = hap.value?.markcss || `outline: solid 2px ${color}`; - // Get explicit channels for color values - /* - const swatch = document.createElement('div'); - swatch.style.color = color; - document.body.appendChild(swatch); - let channels = getComputedStyle(swatch) - .color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/) - .slice(1) - .map((c) => parseFloat(c || 1)); - document.body.removeChild(swatch); - - // Get percentage of event - const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration; - channels[3] *= percent; - */ - - builder.add( - from, - to, - Decoration.mark({ - // attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, - attributes: { style }, - }), - ); - } - + miniLocationsArray.push({ + from, + to, + id, + }); iterator.next(); } + return miniLocationsArray; +}; - return builder.finish(); -}); +export const getMiniLocations = (state) => { + const decorations = state.field(miniLocations); + return getMiniLocationsFromDecorations(decorations); +}; -export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]; +export const getActiveMiniLocations = (state) => { + const miniLocations = getMiniLocations(state); + const { haps } = state.field(visibleMiniLocations); + + const activeMiniLocations = miniLocations.filter((location) => haps.has(location.id)); + return activeMiniLocations; +}; + +export const highlightExtension = [ + miniLocations, + visibleMiniLocations, + displayMiniLocationsState, + miniLocationHighlights, +]; export const isPatternHighlightingEnabled = (on, config) => { - on && - config && - setTimeout(() => { - updateMiniLocations(config.editor, config.miniLocations); - }, 100); - return on ? Prec.highest(highlightExtension) : []; + // NOTE: + // Modified this function to always return the highlightExtension, and instead just toggle whether or not the miniLocations are displayed. + // This is because block based evaluation only updates regions of miniLocations, and those updates need to be kept track of constantly. + // The setTimeout was also removed because it conflicted with the range-specific updates required by + // block based evaluation. + // Not sure if this is the best approach, but for block based eval I can't think of a better way to do it. + + if (config) { + // Toggle the display state for miniLocations + config.editor.dispatch({ effects: displayMiniLocations.of(on) }); + } + + return Prec.highest(highlightExtension); }; diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs index 72f95125..647779ba 100644 --- a/packages/codemirror/slider.mjs +++ b/packages/codemirror/slider.mjs @@ -2,11 +2,11 @@ import { ref, pure } from '@strudel/core'; import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; import { StateEffect } from '@codemirror/state'; +// Global state storage for all widget types export let sliderValues = {}; -const getSliderID = (from) => `slider_${from}`; export class SliderWidget extends WidgetType { - constructor(value, min, max, from, to, step, view) { + constructor(value, min, max, from, to, step, view, id) { super(); this.value = value; this.min = min; @@ -16,10 +16,21 @@ export class SliderWidget extends WidgetType { this.to = to; this.step = step; this.view = view; + this.id = id || `${from}:${to}`; // Range-based ID for stability } - eq() { - return false; + eq(other) { + if (!(other instanceof SliderWidget)) { + return false; + } + return ( + this.id === other.id && + this.from === other.from && + this.to === other.to && + this.value === other.value && + this.min === other.min && + this.max === other.max + ); } toDOM() { @@ -38,6 +49,7 @@ export class SliderWidget extends WidgetType { slider.from = this.from; slider.originalFrom = this.originalFrom; slider.to = this.to; + slider.id = this.id; // Store range-based ID in DOM element slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)'; this.slider = slider; slider.addEventListener('input', (e) => { @@ -49,7 +61,7 @@ export class SliderWidget extends WidgetType { slider.originalValue = insert; slider.value = insert; this.view.dispatch({ changes: change }); - const id = getSliderID(slider.originalFrom); // matches id generated in transpiler + const id = slider.id; // Use range-based ID window.postMessage({ type: 'cm-slider', value: Number(next), id }); }); return wrap; @@ -62,19 +74,60 @@ export class SliderWidget extends WidgetType { export const setSliderWidgets = StateEffect.define(); -export const updateSliderWidgets = (view, widgets) => { - view.dispatch({ effects: setSliderWidgets.of(widgets) }); +export const setSliderWidgetsInRange = StateEffect.define(); + +export const updateSliderWidgets = (view, widgets, range = null) => { + if (range) { + // range argument passed for block-based evaluation + view.dispatch({ effects: setSliderWidgetsInRange.of({ widgets, range }) }); + } else { + view.dispatch({ effects: setSliderWidgets.of(widgets) }); + } }; function getSliders(widgetConfigs, view) { - return widgetConfigs - .filter((w) => w.type === 'slider') - .map(({ from, to, value, min, max, step }) => { - return Decoration.widget({ - widget: new SliderWidget(value, min, max, from, to, step, view), - side: 0, - }).range(from /* , to */); - }); + return ( + widgetConfigs + .filter((w) => w.type === 'slider') + // Deduplicate sliders that might appear multiple times (e.g., during paste operations) + .filter((slider, index, self) => index === self.findIndex((s) => s.from === slider.from && s.to === slider.to)) + .sort((a, b) => a.from - b.from) + .map(({ from, to, value, min, max, step, id }) => { + return Decoration.widget({ + widget: new SliderWidget(value, min, max, from, to, step, view, id), + side: 0, + }).range(from /* , to */); + }) + ); +} + +export function getSliderWidgets(view) { + if (!view || !view.state) { + return []; + } + + const sliderPluginInstance = view.plugin(sliderPlugin); + if (!sliderPluginInstance || !sliderPluginInstance.decorations) { + return []; + } + + const sliderWidgets = []; + + sliderPluginInstance.decorations.between(0, view.state.doc.length, (from, to, decoration) => { + if (decoration.widget instanceof SliderWidget) { + sliderWidgets.push({ + type: 'slider', + from: decoration.widget.from, + to: decoration.widget.to, + value: decoration.widget.value, + min: decoration.widget.min, + max: decoration.widget.max, + step: decoration.widget.step, + }); + } + }); + + return sliderWidgets; } export const sliderPlugin = ViewPlugin.fromClass( @@ -101,7 +154,42 @@ export const sliderPlugin = ViewPlugin.fromClass( } } for (let e of tr.effects) { - if (e.is(setSliderWidgets)) { + if (e.is(setSliderWidgetsInRange)) { + // Block-aware slider update logic + const { widgets, range } = e.value; + const [rangeStart, rangeEnd] = range; + + // Get existing slider widgets that should be preserved + const existingSliders = []; + this.decorations.between(0, update.view.state.doc.length, (from, to, decoration) => { + if (decoration.widget instanceof SliderWidget) { + // Preserve sliders outside the evaluation range + // Use strict > for rangeEnd because when code is deleted, slider positions + // map to the deletion boundary (rangeEnd), and those should be removed, not preserved + if (from < rangeStart || from > rangeEnd) { + existingSliders.push({ + from, + to, + value: decoration.widget.value, + min: decoration.widget.min, + max: decoration.widget.max, + step: decoration.widget.step, + id: decoration.widget.id || `${from}:${to}`, + type: 'slider', + }); + } + } + }); + + // Merge preserved sliders with new widgets + const mergedWidgets = [...existingSliders, ...widgets] + .filter( + (slider, index, self) => index === self.findIndex((s) => s.type === 'slider' && s.id === slider.id), + ) + .sort((a, b) => a.from - b.from); + + this.decorations = Decoration.set(getSliders(mergedWidgets, update.view)); + } else if (e.is(setSliderWidgets)) { this.decorations = Decoration.set(getSliders(e.value, update.view)); } } @@ -131,6 +219,7 @@ export let sliderWithID = (id, value, min, max) => { sliderValues[id] = value; // sync state at eval time (code -> state) return ref(() => sliderValues[id]); // use state at query time }; + // update state when sliders are moved if (typeof window !== 'undefined') { window.addEventListener('message', (e) => { @@ -139,7 +228,7 @@ if (typeof window !== 'undefined') { // update state when slider is moved sliderValues[e.data.id] = e.data.value; } else { - console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`); + console.error(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`); } } }); diff --git a/packages/codemirror/widget.mjs b/packages/codemirror/widget.mjs index 42d3b151..6c40d673 100644 --- a/packages/codemirror/widget.mjs +++ b/packages/codemirror/widget.mjs @@ -1,55 +1,111 @@ import { StateEffect, StateField } from '@codemirror/state'; -import { Decoration, EditorView, WidgetType } from '@codemirror/view'; +import { Decoration, EditorView, WidgetType, ViewPlugin } from '@codemirror/view'; import { getWidgetID, registerWidgetType } from '@strudel/transpiler'; import { Pattern } from '@strudel/core'; -export const addWidget = StateEffect.define({ - map: ({ from, to }, change) => { - return { from: change.mapPos(from), to: change.mapPos(to) }; - }, -}); +export const setWidgets = StateEffect.define(); -export const updateWidgets = (view, widgets) => { - view.dispatch({ effects: addWidget.of(widgets) }); +export const setWidgetsInRange = StateEffect.define(); + +export const updateWidgets = (view, widgets, range = null) => { + if (range) { + // range argument passed for block-based evaluation + view.dispatch({ effects: setWidgetsInRange.of({ widgets, range }) }); + } else { + view.dispatch({ effects: setWidgets.of(widgets) }); + } }; -function getWidgets(widgetConfigs) { - return ( - widgetConfigs - // codemirror throws an error if we don't sort - .sort((a, b) => a.to - b.to) - .map((widgetConfig) => { +function getWidgets(widgetConfigs, view) { + const filtered = widgetConfigs + // Filter to widget configs only (exclude sliders) + .filter((w) => w && w.type && w.type !== 'slider') + // Deduplicate widgets by ID, matching slider behavior for stable widget identity + .filter((widget, index, self) => index === self.findIndex((w) => w.type === widget.type && w.id === widget.id)); + + // Filter out widgets whose range is encompassed by another widget + // const nonEncompassed = filterEncompassedWidgets(filtered); + + return filtered + .sort((a, b) => (a.to || 0) - (b.to || 0)) + .map((widgetConfig) => { + try { return Decoration.widget({ - widget: new BlockWidget(widgetConfig), + widget: new BlockWidget(widgetConfig, view), side: 0, - block: true, - }).range(widgetConfig.to); - }) - ); + }).range(widgetConfig.to || widgetConfig.from || 0); + } catch (error) { + console.error('error creating widget', error); + return null; + } + }) + .filter(Boolean); // Remove any null results from failed creations } -const widgetField = StateField.define( - /* */ { - create() { - return Decoration.none; - }, - update(widgets, tr) { - widgets = widgets.map(tr.changes); - for (let e of tr.effects) { - if (e.is(addWidget)) { - try { - widgets = widgets.update({ - filter: () => false, - add: getWidgets(e.value), - }); - } catch (error) { - console.log('err', error); +export const widgetPlugin = ViewPlugin.fromClass( + class { + decorations; //: DecorationSet + + constructor(view /* : EditorView */) { + this.decorations = Decoration.set([]); + } + + update(update /* : ViewUpdate */) { + update.transactions.forEach((tr) => { + if (tr.docChanged) { + this.decorations = this.decorations.map(tr.changes); + const iterator = this.decorations.iter(); + // Apply changes to iterator.from and iterator.to if docChanged + while (iterator.value) { + // when the widgets are moved, we need to tell the dom node the current position + // this is important because the widget functions have to work with the dom node + if (iterator.value?.widget instanceof BlockWidget) { + iterator.value.widget.from = iterator.from; + iterator.value.widget.to = iterator.to; + } + iterator.next(); } } - } - return widgets; - }, - provide: (f) => EditorView.decorations.from(f), + for (let e of tr.effects) { + if (e.is(setWidgetsInRange)) { + // Block-aware widget update logic + const { widgets, range } = e.value; + const [rangeStart, rangeEnd] = range; + + // Get existing widget widgets that should be preserved + const existingWidgets = []; + this.decorations.between(0, update.view.state.doc.length, (from, to, decoration) => { + if (decoration.widget instanceof BlockWidget) { + // Preserve widgets outside the evaluation range + // Use strict > for rangeEnd because when code is deleted, widget positions + // map to the deletion boundary (rangeEnd), and those should be removed, not preserved + if (from < rangeStart || from > rangeEnd) { + existingWidgets.push({ + from: decoration.widget.from, + to: decoration.widget.to, + type: decoration.widget.type, + index: decoration.widget.index, + id: decoration.widget.id, + }); + } + } + }); + + // Merge preserved widgets with new widgets, deduplicating by ID + const mergedWidgets = [...existingWidgets, ...widgets].filter( + (widget, index, self) => index === self.findIndex((w) => w.type === widget.type && w.id === widget.id), + ); + + this.decorations = Decoration.set(getWidgets(mergedWidgets, update.view)); + } else if (e.is(setWidgets)) { + this.decorations = Decoration.set(getWidgets(e.value, update.view)); + } + } + }); + } + }, + { + decorations: (v) => v.decorations, }, ); @@ -60,24 +116,116 @@ export function setWidget(id, el) { } export class BlockWidget extends WidgetType { - constructor(widgetConfig) { + constructor(widgetConfig, view) { super(); + + // Graceful handling of invalid configs like sliders + if (!widgetConfig || typeof widgetConfig !== 'object') { + widgetConfig = { type: 'unknown', from: 0, to: 0 }; + } + + this.from = widgetConfig.from || 0; + this.originalFrom = widgetConfig.from || 0; + this.to = widgetConfig.to || this.from; + this.originalTo = widgetConfig.to || this.from; + this.type = widgetConfig.type || 'unknown'; + this.index = widgetConfig.index || 0; + this.view = view; + + // Use range-based ID for stability, similar to sliders + this.id = widgetConfig.id || getWidgetID?.(widgetConfig); this.widgetConfig = widgetConfig; } - eq() { - return true; + + eq(other) { + if (!(other instanceof BlockWidget)) { + return false; + } + return ( + this.id === other.id && + this.from === other.from && + this.to === other.to && + this.type === other.type && + this.index === other.index + ); } + toDOM() { - const id = getWidgetID(this.widgetConfig); - const el = widgetElements[id]; - return el; + let wrap = document.createElement('span'); + wrap.setAttribute('aria-hidden', 'true'); + wrap.className = 'cm-widget-container'; + + let el = widgetElements[this.id]; + if (el) { + // Ensure the element has the correct ID + el.id = this.id; + wrap.appendChild(el); + } else { + // Create a placeholder element if the widget element doesn't exist + // This prevents CodeMirror errors when widget is missing + const placeholder = document.createElement('span'); + placeholder.setAttribute('aria-hidden', 'true'); + placeholder.className = 'cm-widget-placeholder'; + placeholder.style.cssText = 'display: none;'; // Hide placeholder + placeholder.id = this.id; + wrap.appendChild(placeholder); + } + + return wrap; } + ignoreEvent(e) { return true; } } -export const widgetPlugin = [widgetField]; +export function getActiveWidgets(view) { + if (!view || !view.state) { + return []; + } + + const widgetPluginInstance = view.plugin(widgetPlugin); + if (!widgetPluginInstance || !widgetPluginInstance.decorations) { + return []; + } + + const widgets = []; + + widgetPluginInstance.decorations.between(0, view.state.doc.length, (from, to, decoration) => { + if (decoration.widget instanceof BlockWidget) { + widgets.push({ + type: decoration.widget.type, + from: decoration.widget.from, + to: decoration.widget.to, + index: decoration.widget.index, + id: decoration.widget.id, + }); + } + }); + + return widgets; +} + +export function getAllWidgetIds(view) { + if (!view || !view.state) { + return []; + } + + const widgetPluginInstance = view.plugin(widgetPlugin); + if (!widgetPluginInstance || !widgetPluginInstance.decorations) { + return []; + } + + const widgetIds = []; + + widgetPluginInstance.decorations.between(0, view.state.doc.length, (from, to, decoration) => { + if (decoration.widget instanceof BlockWidget) { + widgetIds.push(decoration.widget.id); + } + }); + + return widgetIds; +} // widget implementer API to create a new widget type export function registerWidget(type, fn) { diff --git a/packages/core/evaluate.mjs b/packages/core/evaluate.mjs index 0599d866..bb7b4664 100644 --- a/packages/core/evaluate.mjs +++ b/packages/core/evaluate.mjs @@ -5,6 +5,34 @@ This program is free software: you can redistribute it and/or modify it under th */ export const strudelScope = {}; +// Make strudelScope available globally so transpiled code can access it +globalThis.strudelScope = strudelScope; + +// Track user-defined keys (from block-based eval) so we can clear them without removing strudel functions +export const userDefinedKeys = new Set(); +globalThis.userDefinedKeys = userDefinedKeys; + +/** + * Clears all user-defined variables and functions from the scope. + * This removes variables created during block-based evaluation. + * @name clearScope + * @example + * // After defining variables in blocks: + * // let myVar = 5 + * // function myFunc() { return 10; } + * clearScope() // removes myVar and myFunc from scope + */ +export const clearScope = () => { + for (const key of userDefinedKeys) { + delete strudelScope[key]; + delete globalThis[key]; + } + userDefinedKeys.clear(); + // Return silence if available (for use in pattern expressions), otherwise undefined + return globalThis.silence; +}; +// Make clearScope available globally +globalThis.clearScope = clearScope; export const evalScope = async (...args) => { const results = await Promise.allSettled(args); diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 470be47b..4e0af7ab 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -32,6 +32,7 @@ export function repl({ pattern: undefined, miniLocations: [], widgets: [], + sliders: [], pending: false, started: false, }; @@ -70,11 +71,172 @@ export function repl({ let allTransform; let eachTransform; + // Block-based evaluation state + let codeBlocks = {}; + let lastActiveVisualizerLabel = null; + // Track which patterns belong to which blocks: { blockRange: [patternKeys] } + let blockPatterns = new Map(); + + // Helper function to collect properties from all code blocks (handles both labeled and anonymous blocks) + function collectFromBlocks(property) { + return Object.entries(codeBlocks).flatMap(([key, block]) => { + if (key === '$') { + // Anonymous blocks are stored as an array of block objects + return Array.isArray(block) ? block.flatMap((b) => b[property] || []) : []; + } + // Labeled blocks are stored as single block objects + return block[property] || []; + }); + } + + // Helper function to process a single labeled block + function processLabeledBlock(labels, i, code, options, meta) { + const label = labels[i]; + const nextLabel = labels[i + 1] || { index: code.length, end: code.length }; + + const labelCode = code.slice(label.index, nextLabel.index); + const labelRange = [label.index + options.range[0], label.end + options.range[0]]; + + // Calculate the full block range (from label start to next label start) + const blockStart = label.index + options.range[0]; + const blockEnd = nextLabel.index + options.range[0]; + + const blockWidgets = (meta?.widgets || []).filter((widget) => { + const widgetPos = widget.from ?? widget.index ?? 0; + return widgetPos >= blockStart && widgetPos < blockEnd; + }); + + const blockSliders = (meta?.sliders || []).filter((slider) => { + const sliderPos = slider.from ?? slider.index ?? 0; + return sliderPos >= blockStart && sliderPos < blockEnd; + }); + + const blockMiniLocations = (meta?.miniLocations || []).filter((loc) => { + // const locStart = loc.start ?? loc.from ?? 0; + // mini locations can be either [start, end] arrays or objects with start/from + const locStart = Array.isArray(loc) ? loc[0] : (loc.start ?? loc.from ?? 0); + return locStart >= blockStart && locStart < blockEnd; + }); + + handleSingleLabelBlock( + label, + labelCode, + { ...options, range: labelRange }, + { widgets: blockWidgets, sliders: blockSliders, miniLocations: blockMiniLocations }, + ); + } + + // // Helper function to filter meta (widgets, sliders, miniLocations) by block range + // function splitCodeByRange(meta, blockStart, blockEnd) { + // } + + // Helper function to extract labels from code with their positions + function extractLabelsFromCode(code) { + const labels = []; + // Regex to find label patterns like "d1:" or "myLabel:" + const labelRegex = /^(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:)/gm; + let match; + while ((match = labelRegex.exec(code)) !== null) { + labels.push({ + name: match[2], + index: match.index, + end: match.index + match[1].length, + fullMatch: match[1], + }); + } + + // Also check for 'all()' and treat it as a special label + // just for management purposes + const allRegex = /all\s*\(\s*([^)]+)\s*\)/; + const allMatch = allRegex.exec(code); + if (allMatch) { + // Check if the argument contains a widget call + const argCode = allMatch[1]; + const activeVisualizer = detectActiveVisualizer(argCode); + + labels.push({ + name: 'all', + index: allMatch.index, + end: allMatch.index + allMatch[0].length, + fullMatch: allMatch[0], + activeVisualizer: activeVisualizer, + }); + } + + return labels; + } + + // Helper function to detect non-inline widget calls in code + function detectActiveVisualizer(code) { + // List of non-inline widgets that need cleanup + // These are Pattern.prototype methods that create persistent visualizations + // I don't like this approach, feels hacky, but since these methods don't create ids that + // can be read through the transpiler, I'm not sure how best to detect them. + // Would probably be better to register these methods through some function that would + // tag them better + const nonInlineWidgets = ['punchcard', 'spiral', 'scope', 'pitchwheel', 'spectrum', 'pianoroll', 'wordfall',]; + + for (const widget of nonInlineWidgets) { + const widgetRegex = new RegExp(`\\.${widget}\\s*\\(`); + if (widgetRegex.test(code)) { + return widget; + } + } + return null; + } + + // Helper function to handle single label code block storage + function handleSingleLabelBlock(label, code, options, meta) { + + // Detect if this block contains a non-inline widget + // For 'all' label, widget info is already on the label object from extractLabelsFromCode + + // As mentioned in detectActiveVisualizer, this is a bad approach to + // managing non-inline widgets (or widgets that don't have ids) + // and a proper solution would give them widgets in the transpiler, or at least + // track where they are so we don't have to futz around with regexes + const activeVisualizer = label.activeVisualizer !== undefined + ? label.activeVisualizer + : detectActiveVisualizer(code); + + if (activeVisualizer !== null) { + lastActiveVisualizerLabel = label.name; + } + + // Store the entire code block under the label name + codeBlocks[label.name] = { + code: code, + range: options.range, + labels: [label.name], + miniLocations: meta?.miniLocations || [], + widgets: meta?.widgets || [], + sliders: meta?.sliders || [], + activeVisualizer: activeVisualizer, // Store the widget type if present, null otherwise + }; + + + // Clean up any blocks with conflicting ranges + for (const [existingKey, existingBlock] of Object.entries(codeBlocks)) { + if (existingKey !== label.name && existingBlock.range && options.range) { + const [existingStart, existingEnd] = existingBlock.range; + const [newStart, newEnd] = options.range; + + // If ranges overlap (not just touch), remove the stale block + if (!(newEnd <= existingStart || newStart >= existingEnd)) { + delete codeBlocks[existingKey]; + } + } + } + } + const hush = function () { pPatterns = {}; anonymousIndex = 0; allTransform = undefined; eachTransform = undefined; + codeBlocks = {}; + blockPatterns.clear(); + lastActiveVisualizerLabel = null; // Reset 'all' visualizer tracking return silence; }; @@ -93,7 +255,18 @@ export function repl({ }; setTime(() => scheduler.now()); // TODO: refactor? - const stop = () => scheduler.stop(); + const stop = () => { + codeBlocks = {}; + blockPatterns.clear(); + pPatterns = {}; + lastActiveVisualizerLabel = null; // Reset 'all' visualizer tracking + updateState({ + miniLocations: [], + widgets: [], + sliders: [], + }); + scheduler.stop(); + }; const start = () => scheduler.start(); const pause = () => scheduler.pause(); const toggle = () => scheduler.toggle(); @@ -201,7 +374,7 @@ export function repl({ }); }; - const evaluate = async (code, autostart = true, shouldHush = true) => { + const evaluate = async (code, autostart = true, shouldHush = true, blockBased = false, options = {}) => { if (!code) { throw new Error('no code to evaluate'); } @@ -209,15 +382,92 @@ export function repl({ updateState({ code, pending: true }); await injectPatternMethods(); setTime(() => scheduler.now()); // TODO: refactor? - await beforeEval?.({ code }); + await beforeEval?.({ code, blockBased }); allTransforms = []; // reset all transforms - shouldHush && hush(); + + const transpilerOptionsWithBlock = { + ...transpilerOptions, + blockBased, + range: options.range || [], + }; + + if (!blockBased) { + codeBlocks = {}; + shouldHush && hush(); + } if (mondo) { code = `mondolang\`${code}\``; } - let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions); - if (Object.keys(pPatterns).length) { + + let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptionsWithBlock); + + // Track activeVisualizer cleanup: check if any block's visualizer was removed + let widgetRemoved = false; + + if (blockBased) { + const labels = extractLabelsFromCode(code); + + // Store code blocks in dictionary using labels as keys + if (labels.length > 0) { + for (let i = 0; i < labels.length; i++) { + // processLabeledBlock(labels, i, code, options, meta); + // processing transpiler output instead of code is simply to avoid + // extra regex in detecting whether or not an inline widget has been commented out + processLabeledBlock(labels, i, meta.output, options, meta); + } + } else if (pattern !== silence) { + // variable/function declarations that return silence are allowed, + // but anonymous pattern blocks pose an issue for block-based evaluation + // if an anonymous pattern is evaluated multiple times it will just stack and get louder and louder + + // it's very common for users to write code prefixed with '$' + // but to modify and override existing patterns, the patterns must be labeled, + // otherwise we'll have no idea of which pattern is being overridden + + // (we probably need to update the docs on this) + + // we could easily enable it, but it would confuse a lot of people + throw new Error( + 'anonymous labels disabled for block based evaluation (see https://strudel.cc/blog/#label-notation)', + ); + } + + // For block-based evaluation, collect from all blocks + // The highlight/widget/slider systems will handle selective updates based on the range + meta.miniLocations = collectFromBlocks('miniLocations'); + meta.widgets = collectFromBlocks('widgets'); + meta.sliders = collectFromBlocks('sliders'); + + // Track activeVisualizer cleanup: check if any block's visualizer was removed + const blocksToUpdate = labels.map((label) => label.name); + + for (const [key, block] of Object.entries(codeBlocks)) { + if (blocksToUpdate.includes(key)) { + // This block was just updated + if (block.activeVisualizer !== null) { + // Block now has a visualizer, update tracking + lastActiveVisualizerLabel = key; + } else if (lastActiveVisualizerLabel === key) { + // This block lost its visualizer, trigger cleanup + widgetRemoved = true; + lastActiveVisualizerLabel = null; + } + } + } + } + + const allPatterns = Object.values(pPatterns); + + if (blockBased) { + pPatterns = Object.fromEntries( + Object.entries(pPatterns).filter(([key]) => { + return Object.keys(codeBlocks).includes(key); + }), + ); + } + + if (allPatterns.length) { let patterns = []; let soloActive = false; for (const [key, value] of Object.entries(pPatterns)) { @@ -250,18 +500,22 @@ export function repl({ if (!isPattern(pattern)) { pattern = silence; } + logger(`[eval] code updated`); pattern = await setPattern(pattern, autostart); updateState({ miniLocations: meta?.miniLocations || [], widgets: meta?.widgets || [], + sliders: meta?.sliders || [], activeCode: code, pattern, evalError: undefined, schedulerError: undefined, pending: false, }); - afterEval?.({ code, pattern, meta }); + + + afterEval?.({ code, pattern, meta, range: options.range, widgetRemoved }); return pattern; } catch (err) { logger(`[eval] error: ${err.message}`, 'error'); @@ -276,18 +530,18 @@ export function repl({ export const getTrigger = ({ getTime, defaultOutput }) => - async (hap, deadline, duration, cps, t) => { - // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) - // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 - try { - if (!hap.context.onTrigger || !hap.context.dominantTrigger) { - await defaultOutput(hap, deadline, duration, cps, t); + async (hap, deadline, duration, cps, t) => { + // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) + // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 + try { + if (!hap.context.onTrigger || !hap.context.dominantTrigger) { + await defaultOutput(hap, deadline, duration, cps, t); + } + if (hap.context.onTrigger) { + // call signature of output / onTrigger is different... + await hap.context.onTrigger(hap, getTime(), cps, t); + } + } catch (err) { + errorLogger(err, 'getTrigger'); } - if (hap.context.onTrigger) { - // call signature of output / onTrigger is different... - await hap.context.onTrigger(hap, getTime(), cps, t); - } - } catch (err) { - errorLogger(err, 'getTrigger'); - } - }; + }; diff --git a/packages/draw/draw.mjs b/packages/draw/draw.mjs index 95f3aed5..514215f1 100644 --- a/packages/draw/draw.mjs +++ b/packages/draw/draw.mjs @@ -78,6 +78,16 @@ export const cleanupDraw = (clearScreen = true, id) => { stopAllAnimations(id); }; +export const cleanupDrawContext = (replID) => { + const ctx = getDrawContext(); + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + + // clear the big canvas context, ignore inline widgets + Object.keys(animationFrames).forEach((id) => + (!replID || id.startsWith(replID)) && !id.startsWith('_') && stopAnimationFrame(id) + ); +}; + Pattern.prototype.onPaint = function (painter) { return this.withState((state) => { if (!state.controls.painters) { diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index fea6bad4..467e6c53 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -19,7 +19,14 @@ export function registerLanguage(type, config) { } export function transpiler(input, options = {}) { - const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options; + const { + wrapAsync = false, + addReturn = true, + emitMiniLocations = true, + emitWidgets = true, + blockBased = false, + range = [] + } = options; let ast = parse(input, { ecmaVersion: 2022, @@ -28,6 +35,13 @@ export function transpiler(input, options = {}) { }); let miniLocations = []; + + // Position offset for block-based evaluation + let nodeOffset = range && range.length > 0 ? range[0] : 0; + + // Track declarations to add to strudelScope for block-based eval + let scopeDeclarations = []; + const collectMiniLocations = (value, node) => { const minilang = languages.get('minilang'); if (minilang) { @@ -40,9 +54,27 @@ export function transpiler(input, options = {}) { } }; let widgets = []; + let sliders = []; walk(ast, { enter(node, parent /* , prop, index */) { + // Apply position offset for block-based evaluation + if (blockBased && node.start !== undefined) { + node.start = node.start + nodeOffset; + node.end = node.end + nodeOffset; + } + // Collect variable and function declarations for strudelScope (block-based eval) + if (blockBased && parent?.type === 'Program') { + if (node.type === 'VariableDeclaration') { + for (const declarator of node.declarations) { + if (declarator.id?.name) { + scopeDeclarations.push(declarator.id.name); + } + } + } else if (node.type === 'FunctionDeclaration' && node.id?.name) { + scopeDeclarations.push(node.id.name); + } + } if (isLanguageLiteral(node)) { const { name } = node.tag; const language = languages.get(name); @@ -79,22 +111,29 @@ export function transpiler(input, options = {}) { return this.replace(miniWithLocation(value, node)); } if (isSliderFunction(node)) { - emitWidgets && - widgets.push({ - from: node.arguments[0].start, - to: node.arguments[0].end, - value: node.arguments[0].raw, // don't use value! - min: node.arguments[1]?.value ?? 0, - max: node.arguments[2]?.value ?? 1, - step: node.arguments[3]?.value, - type: 'slider', - }); - return this.replace(sliderWithLocation(node)); + const from = node.arguments[0].start + nodeOffset; + const to = node.arguments[0].end + nodeOffset; + const id = `${from}:${to}`; // Range-based ID for stability + + const sliderConfig = { + from, + to, + id, + value: node.arguments[0].raw, // don't use value! + min: node.arguments[1]?.value ?? 0, + max: node.arguments[2]?.value ?? 1, + step: node.arguments[3]?.value, + type: 'slider', + }; + emitWidgets && widgets.push(sliderConfig); + sliders.push(sliderConfig); + return this.replace(sliderWithLocation(node, nodeOffset)); } if (isWidgetMethod(node)) { const type = node.callee.property.name; const index = widgets.filter((w) => w.type === type).length; const widgetConfig = { + from: node.start, to: node.end, index, type, @@ -115,17 +154,33 @@ export function transpiler(input, options = {}) { let { body } = ast; + const silenceExpression = { + type: 'ExpressionStatement', + expression: { + type: 'Identifier', + name: 'silence', + }, + }; + if (!body.length) { console.warn('empty body -> fallback to silence'); - body.push({ - type: 'ExpressionStatement', - expression: { - type: 'Identifier', - name: 'silence', - }, - }); + body.push(silenceExpression); } else if (!body?.[body.length - 1]?.expression) { - throw new Error('unexpected ast format without body expression'); + // Last statement is not an expression (e.g., VariableDeclaration, FunctionDeclaration) + if (blockBased) { + // For block-based eval, add silence as the return value when block ends with declaration + body.push(silenceExpression); + } else { + throw new Error('unexpected ast format without body expression'); + } + } + + // For block-based eval, add scope assignments before the return statement + // This allows variables/functions defined in one block to be used in other blocks + if (blockBased && scopeDeclarations.length > 0) { + const scopeAssignments = scopeDeclarations.flatMap((name) => createScopeAssignment(name)); + // Insert scope assignments before the last statement (which will become the return) + body.splice(body.length - 1, 0, ...scopeAssignments); } // add return to last statement @@ -143,7 +198,7 @@ export function transpiler(input, options = {}) { if (!emitMiniLocations) { return { output }; } - return { output, miniLocations, widgets }; + return { output, miniLocations, widgets, sliders }; } function isStringWithDoubleQuotes(node, locations, code) { @@ -190,8 +245,14 @@ function isWidgetMethod(node) { return node.type === 'CallExpression' && widgetMethods.includes(node.callee.property?.name); } -function sliderWithLocation(node) { - const id = 'slider_' + node.arguments[0].start; // use loc of first arg for id +function sliderWithLocation(node, nodeOffset = 0) { + // Apply nodeOffset for block-based evaluation to generate correct range + const from = node.arguments[0].start + nodeOffset; + const to = node.arguments[0].end + nodeOffset; + + // Use range-based ID for stability during block evaluation + const id = `${from}:${to}`; + // add loc as identifier to first argument // the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?) node.arguments.unshift({ @@ -206,14 +267,26 @@ function sliderWithLocation(node) { export function getWidgetID(widgetConfig) { // the widget id is used as id for the dom element + as key for eventual resources // for example, for each scope widget, a new analyser + buffer (large) is created - // that means, if we use the index index of line position as id, less garbage is generated - // return `widget_${widgetConfig.to}`; // more gargabe - //return `widget_${widgetConfig.index}_${widgetConfig.to}`; // also more garbage - return `${widgetConfig.id || ''}_widget_${widgetConfig.type}_${widgetConfig.index}`; // less garbage + // Update: use range-based ID generation for better stability during block evaluation + // When we have both from and to, use them together for stability + // Otherwise fall back to position-based ID for backward compatibility + let uniqueIdentifier; + if (widgetConfig.from !== undefined && widgetConfig.to !== undefined) { + // Use range for more stable identification + uniqueIdentifier = `${widgetConfig.from}-${widgetConfig.to}`; + } else { + // Fallback to single position (for backward compatibility) + uniqueIdentifier = widgetConfig.to || widgetConfig.from || 0; + } + const baseId = `${widgetConfig.id || ''}_widget_${widgetConfig.type}`; + return `${baseId}_${widgetConfig.index}_${uniqueIdentifier}`; } function widgetWithLocation(node, widgetConfig) { const id = getWidgetID(widgetConfig); + // Store the unique ID back into the config so it's available for widget management + // This is critical for block-based evaluation to match existing widgets with new ones + widgetConfig.id = id; // add loc as identifier to first argument // the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?) node.arguments.unshift({ @@ -327,3 +400,85 @@ function languageWithLocation(name, value, offset) { optional: false, }; } + +// Creates AST nodes for: userDefinedKeys.add('name'); strudelScope.name = name; globalThis.name = name; +// Used in block-based evaluation to persist variables/functions across blocks +// We add to both strudelScope (for internal lookups) and globalThis (for direct access) +// We also track the key in userDefinedKeys so clearScope() can remove it later +function createScopeAssignment(name) { + return [ + // userDefinedKeys.add('name'); + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'userDefinedKeys', + }, + property: { + type: 'Identifier', + name: 'add', + }, + computed: false, + }, + arguments: [ + { + type: 'Literal', + value: name, + }, + ], + }, + }, + // strudelScope.name = name; + { + type: 'ExpressionStatement', + expression: { + type: 'AssignmentExpression', + operator: '=', + left: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'strudelScope', + }, + property: { + type: 'Identifier', + name: name, + }, + computed: false, + }, + right: { + type: 'Identifier', + name: name, + }, + }, + }, + // globalThis.name = name; + { + type: 'ExpressionStatement', + expression: { + type: 'AssignmentExpression', + operator: '=', + left: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'globalThis', + }, + property: { + type: 'Identifier', + name: name, + }, + computed: false, + }, + right: { + type: 'Identifier', + name: name, + }, + }, + }, + ]; +} diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index f46fa661..4e81aa00 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -116,6 +116,7 @@ export function SettingsTab({ started }) { isMultiCursorEnabled, patternAutoStart, includePrebakeScriptInShare, + isBlockBasedEvalEnabled, } = useSettings(); const shouldAlwaysSync = isUdels(); const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; @@ -288,6 +289,11 @@ export function SettingsTab({ started }) { onChange={(cbEvent) => settingsMap.setKey('isMultiCursorEnabled', cbEvent.target.checked)} value={isMultiCursorEnabled} /> + settingsMap.setKey('isBlockBasedEvalEnabled', cbEvent.target.checked)} + value={isBlockBasedEvalEnabled} + /> settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)} diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index 0fbd89e3..0d44eedf 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -106,17 +106,19 @@ export function useReplContext() { //post to iframe parent (like Udels) if it exists... window.parent?.postMessage(code); - setLatestCode(code); - window.location.hash = '#' + code2hash(code); - setDocumentTitle(code); + // Get the full buffer content from the editor instead of just the evaluated block + const fullBufferCode = editorRef.current?.code || code; + setLatestCode(fullBufferCode); + window.location.hash = '#' + code2hash(fullBufferCode); + setDocumentTitle(fullBufferCode); const viewingPatternData = getViewingPatternData(); - setVersionDefaultsFrom(code); - const data = { ...viewingPatternData, code }; + setVersionDefaultsFrom(fullBufferCode); + const data = { ...viewingPatternData, code: fullBufferCode }; let id = data.id; const isExamplePattern = viewingPatternData.collection !== userPattern.collection; if (isExamplePattern) { - const codeHasChanged = code !== viewingPatternData.code; + const codeHasChanged = fullBufferCode !== viewingPatternData.code; if (codeHasChanged) { // fork example const newPattern = userPattern.duplicate(data); @@ -168,9 +170,8 @@ export function useReplContext() { useEffect(() => { let editorSettings = {}; Object.keys(defaultSettings).forEach((key) => { - if (Object.prototype.hasOwnProperty.call(_settings, key)) { - editorSettings[key] = _settings[key]; - } + // Don't use hasOwnProperty - nanostore uses proxies so values may not be own properties + editorSettings[key] = _settings[key]; }); editorRef.current?.updateSettings(editorSettings); }, [_settings]); diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 8ec7d455..9094cba9 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -32,6 +32,7 @@ export const defaultSettings = { isPatternHighlightingEnabled: true, isTabIndentationEnabled: false, isMultiCursorEnabled: false, + isBlockBasedEvalEnabled: false, theme: 'strudelTheme', fontFamily: 'monospace', fontSize: 18, @@ -92,6 +93,7 @@ export function useSettings() { isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled), isTabIndentationEnabled: parseBoolean(state.isTabIndentationEnabled), isMultiCursorEnabled: parseBoolean(state.isMultiCursorEnabled), + isBlockBasedEvalEnabled: parseBoolean(state.isBlockBasedEvalEnabled), fontSize: Number(state.fontSize), panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is! isPanelPinned: parseBoolean(state.isPanelPinned), From ba7721f53c47644368fc7d17801cd60fa817c190 Mon Sep 17 00:00:00 2001 From: Dsm0 Date: Tue, 23 Dec 2025 02:25:18 -0800 Subject: [PATCH 026/200] code formatting --- packages/codemirror/codemirror.mjs | 1 - packages/core/repl.mjs | 44 ++++++++++++++---------------- packages/draw/draw.mjs | 4 +-- packages/transpiler/transpiler.mjs | 2 +- website/astro.config.mjs | 9 ++++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 71986ba4..d0115e13 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -190,7 +190,6 @@ export class StrudelMirror { this.onDraw(haps, time, painters); }, drawTime); - this.prebaked = prebake(); autodraw && this.drawFirstFrame(); this.repl = repl({ diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 4e0af7ab..4c730515 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -172,9 +172,9 @@ export function repl({ // These are Pattern.prototype methods that create persistent visualizations // I don't like this approach, feels hacky, but since these methods don't create ids that // can be read through the transpiler, I'm not sure how best to detect them. - // Would probably be better to register these methods through some function that would + // Would probably be better to register these methods through some function that would // tag them better - const nonInlineWidgets = ['punchcard', 'spiral', 'scope', 'pitchwheel', 'spectrum', 'pianoroll', 'wordfall',]; + const nonInlineWidgets = ['punchcard', 'spiral', 'scope', 'pitchwheel', 'spectrum', 'pianoroll', 'wordfall']; for (const widget of nonInlineWidgets) { const widgetRegex = new RegExp(`\\.${widget}\\s*\\(`); @@ -187,17 +187,15 @@ export function repl({ // Helper function to handle single label code block storage function handleSingleLabelBlock(label, code, options, meta) { - // Detect if this block contains a non-inline widget // For 'all' label, widget info is already on the label object from extractLabelsFromCode - // As mentioned in detectActiveVisualizer, this is a bad approach to + // As mentioned in detectActiveVisualizer, this is a bad approach to // managing non-inline widgets (or widgets that don't have ids) // and a proper solution would give them widgets in the transpiler, or at least // track where they are so we don't have to futz around with regexes - const activeVisualizer = label.activeVisualizer !== undefined - ? label.activeVisualizer - : detectActiveVisualizer(code); + const activeVisualizer = + label.activeVisualizer !== undefined ? label.activeVisualizer : detectActiveVisualizer(code); if (activeVisualizer !== null) { lastActiveVisualizerLabel = label.name; @@ -214,7 +212,6 @@ export function repl({ activeVisualizer: activeVisualizer, // Store the widget type if present, null otherwise }; - // Clean up any blocks with conflicting ranges for (const [existingKey, existingBlock] of Object.entries(codeBlocks)) { if (existingKey !== label.name && existingBlock.range && options.range) { @@ -412,7 +409,7 @@ export function repl({ if (labels.length > 0) { for (let i = 0; i < labels.length; i++) { // processLabeledBlock(labels, i, code, options, meta); - // processing transpiler output instead of code is simply to avoid + // processing transpiler output instead of code is simply to avoid // extra regex in detecting whether or not an inline widget has been commented out processLabeledBlock(labels, i, meta.output, options, meta); } @@ -514,7 +511,6 @@ export function repl({ pending: false, }); - afterEval?.({ code, pattern, meta, range: options.range, widgetRemoved }); return pattern; } catch (err) { @@ -530,18 +526,18 @@ export function repl({ export const getTrigger = ({ getTime, defaultOutput }) => - async (hap, deadline, duration, cps, t) => { - // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) - // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 - try { - if (!hap.context.onTrigger || !hap.context.dominantTrigger) { - await defaultOutput(hap, deadline, duration, cps, t); - } - if (hap.context.onTrigger) { - // call signature of output / onTrigger is different... - await hap.context.onTrigger(hap, getTime(), cps, t); - } - } catch (err) { - errorLogger(err, 'getTrigger'); + async (hap, deadline, duration, cps, t) => { + // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) + // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 + try { + if (!hap.context.onTrigger || !hap.context.dominantTrigger) { + await defaultOutput(hap, deadline, duration, cps, t); } - }; + if (hap.context.onTrigger) { + // call signature of output / onTrigger is different... + await hap.context.onTrigger(hap, getTime(), cps, t); + } + } catch (err) { + errorLogger(err, 'getTrigger'); + } + }; diff --git a/packages/draw/draw.mjs b/packages/draw/draw.mjs index 514215f1..f915e911 100644 --- a/packages/draw/draw.mjs +++ b/packages/draw/draw.mjs @@ -83,8 +83,8 @@ export const cleanupDrawContext = (replID) => { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clear the big canvas context, ignore inline widgets - Object.keys(animationFrames).forEach((id) => - (!replID || id.startsWith(replID)) && !id.startsWith('_') && stopAnimationFrame(id) + Object.keys(animationFrames).forEach( + (id) => (!replID || id.startsWith(replID)) && !id.startsWith('_') && stopAnimationFrame(id), ); }; diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 467e6c53..24dd3f1f 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -25,7 +25,7 @@ export function transpiler(input, options = {}) { emitMiniLocations = true, emitWidgets = true, blockBased = false, - range = [] + range = [], } = options; let ast = parse(input, { diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 151c48fc..b5921d55 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -10,8 +10,13 @@ import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet'; import tailwind from '@astrojs/tailwind'; import AstroPWA from '@vite-pwa/astro'; -const site = `https://strudel.cc/`; // root url without a path -const base = '/'; // base path of the strudel site +import process from 'node:process'; + +const site = process.env.SITE_URL || `https://strudel.cc/`; // root url without a path +const base = process.env.BASE_PATH || ''; // base path of the strudel site + +// const site = `https://strudel.cc/`; // root url without a path +// const base = '/'; // base path of the strudel site const baseNoTrailing = base.endsWith('/') ? base.slice(0, -1) : base; // this rehype plugin fixes relative links From 4df19f9ff846c9bd62e640688cd51b71eb50632b Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Wed, 24 Dec 2025 16:05:46 -0500 Subject: [PATCH 027/200] Save MIDI-in CC state --- packages/midi/midi.mjs | 47 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index cd38d358..8abfe6cd 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -481,6 +481,43 @@ let listeners = {}; const refs = {}; const refsByChan = {}; +/** + * + * @param {string} input + * @param {number|undefined} chan + * @returns {Object} + */ +function getMidinState(input, chan) { + const initialDataRaw = localStorage.getItem(`strudel-midin-${input}-chan${chan !== undefined ? chan : 'all'}`); + if (!initialDataRaw) { + return {}; + } + + try { + return JSON.parse(initialDataRaw); + } catch (err) { + console.warn( + `Failed to parse MIDI state from localStorage for input "${input}" and channel "${chan}"`, + initialDataRaw, + err, + ); + return {}; + } +} + +/** + * + * @param {string} input + * @param {number|undefined} chan + * @param {number} cc + * @param {number} value + */ +function saveMidinState(input, chan, cc, value) { + const state = getMidinState(input, chan); + state[cc] = value; + localStorage.setItem(`strudel-midin-${input}-chan${chan !== undefined ? chan : 'all'}`, JSON.stringify(state)); +} + /** * MIDI input: Opens a MIDI input port to receive MIDI control change messages. * @@ -524,10 +561,13 @@ export async function midin(input) { refs[input] ??= {}; refsByChan[input] ??= {}; const cc = (cc, chan) => { + const initialState = getMidinState(input, chan); + const initialValue = initialState[cc] || 0; + if (chan !== undefined) { - return ref(() => refsByChan[input][cc]?.[chan] || 0); + return ref(() => refsByChan[input][cc]?.[chan] || initialValue); } - return ref(() => refs[input][cc] || 0); + return ref(() => refs[input][cc] || initialValue); }; listeners[input] && device.removeListener('midimessage', listeners[input]); @@ -539,6 +579,9 @@ export async function midin(input) { refsByChan[input][ccNum] ??= {}; refsByChan[input][ccNum][chan] = scaled; refs[input][ccNum] = scaled; + + saveMidinState(input, undefined, ccNum, scaled); + saveMidinState(input, chan, ccNum, scaled); }; device.addListener('midimessage', listeners[input]); return cc; From 84e59645fd630f13ccb478c24876becd5e7257dd Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 18:27:28 -0500 Subject: [PATCH 028/200] Encapsulate MIDI input handling --- packages/midi/midi.mjs | 80 ++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 8abfe6cd..8d54d768 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -477,10 +477,6 @@ Pattern.prototype.midi = function (midiport, options = {}) { }); }; -let listeners = {}; -const refs = {}; -const refsByChan = {}; - /** * * @param {string} input @@ -518,6 +514,51 @@ function saveMidinState(input, chan, cc, value) { localStorage.setItem(`strudel-midin-${input}-chan${chan !== undefined ? chan : 'all'}`, JSON.stringify(state)); } +class MidiInput { + /** + * + * @param {string | number} input MIDI device name or index defaulting to 0 + * @param {WebMidi.MIDIInput | undefined} device MIDI input device instance (or empty to use as placeholder) + */ + constructor(device) { + this.id = device.id; + this.name = device.name; + + this._refs = {}; + this._refsByChan = {}; + + this.cc = (cc, chan) => { + const initialState = getMidinState(this.name, chan); + const initialValue = initialState[cc] || 0; + + if (chan !== undefined) { + return ref(() => this._refsByChan[cc]?.[chan] || initialValue); + } + return ref(() => this._refs[cc] || initialValue); + }; + + const midiListener = this._onMidiMessage.bind(this); + device.addListener('midimessage', midiListener); + } + + _onMidiMessage(e) { + const ccNum = e.dataBytes[0]; + const v = e.dataBytes[1]; + const chan = e.message.channel; + const scaled = v / 127; + + this._refs[ccNum] = scaled; + this._refsByChan[ccNum] ??= {}; + this._refsByChan[ccNum][chan] = scaled; + + saveMidinState(this.name, undefined, ccNum, scaled); + saveMidinState(this.name, chan, ccNum, scaled); + } +} + +// MIDI input wrappers, by device ID +const midiInputs = {}; + /** * MIDI input: Opens a MIDI input port to receive MIDI control change messages. * @@ -550,6 +591,10 @@ export async function midin(input) { `midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`, ); } + + const instance = midiInputs[device.id] || new MidiInput(device); + midiInputs[device.id] = instance; + if (initial) { const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); logger( @@ -558,31 +603,6 @@ export async function midin(input) { }`, ); } - refs[input] ??= {}; - refsByChan[input] ??= {}; - const cc = (cc, chan) => { - const initialState = getMidinState(input, chan); - const initialValue = initialState[cc] || 0; - if (chan !== undefined) { - return ref(() => refsByChan[input][cc]?.[chan] || initialValue); - } - return ref(() => refs[input][cc] || initialValue); - }; - - listeners[input] && device.removeListener('midimessage', listeners[input]); - listeners[input] = (e) => { - const ccNum = e.dataBytes[0]; - const v = e.dataBytes[1]; - const chan = e.message.channel; - const scaled = v / 127; - refsByChan[input][ccNum] ??= {}; - refsByChan[input][ccNum][chan] = scaled; - refs[input][ccNum] = scaled; - - saveMidinState(input, undefined, ccNum, scaled); - saveMidinState(input, chan, ccNum, scaled); - }; - device.addListener('midimessage', listeners[input]); - return cc; + return instance.cc; } From 213edfb211e08cc7ea9fd2649b50b875ad61fc5c Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 18:32:53 -0500 Subject: [PATCH 029/200] Roll up save/load state into MIDI wrapper --- packages/midi/midi.mjs | 67 +++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 8d54d768..27c865e7 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -477,43 +477,6 @@ Pattern.prototype.midi = function (midiport, options = {}) { }); }; -/** - * - * @param {string} input - * @param {number|undefined} chan - * @returns {Object} - */ -function getMidinState(input, chan) { - const initialDataRaw = localStorage.getItem(`strudel-midin-${input}-chan${chan !== undefined ? chan : 'all'}`); - if (!initialDataRaw) { - return {}; - } - - try { - return JSON.parse(initialDataRaw); - } catch (err) { - console.warn( - `Failed to parse MIDI state from localStorage for input "${input}" and channel "${chan}"`, - initialDataRaw, - err, - ); - return {}; - } -} - -/** - * - * @param {string} input - * @param {number|undefined} chan - * @param {number} cc - * @param {number} value - */ -function saveMidinState(input, chan, cc, value) { - const state = getMidinState(input, chan); - state[cc] = value; - localStorage.setItem(`strudel-midin-${input}-chan${chan !== undefined ? chan : 'all'}`, JSON.stringify(state)); -} - class MidiInput { /** * @@ -528,7 +491,7 @@ class MidiInput { this._refsByChan = {}; this.cc = (cc, chan) => { - const initialState = getMidinState(this.name, chan); + const initialState = this._loadState(chan); const initialValue = initialState[cc] || 0; if (chan !== undefined) { @@ -551,8 +514,32 @@ class MidiInput { this._refsByChan[ccNum] ??= {}; this._refsByChan[ccNum][chan] = scaled; - saveMidinState(this.name, undefined, ccNum, scaled); - saveMidinState(this.name, chan, ccNum, scaled); + this._saveState(undefined, ccNum, scaled); + this._saveState(chan, ccNum, scaled); + } + + _loadState(chan) { + const initialDataRaw = localStorage.getItem(`strudel-midin-${this.name}-chan${chan !== undefined ? chan : 'all'}`); + if (!initialDataRaw) { + return {}; + } + + try { + return JSON.parse(initialDataRaw); + } catch (err) { + console.warn( + `Failed to parse MIDI state from localStorage for input "${this.name}" and channel "${chan}"`, + initialDataRaw, + err, + ); + return {}; + } + } + + _saveState(chan, cc, value) { + const state = this._loadState(chan); + state[cc] = value; + localStorage.setItem(`strudel-midin-${this.name}-chan${chan !== undefined ? chan : 'all'}`, JSON.stringify(state)); } } From 47557cf06ff06e94e6407ba62955a7c5768c71ec Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 18:38:55 -0500 Subject: [PATCH 030/200] Move device lookup inside input wrapper --- packages/midi/midi.mjs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 27c865e7..97226dc5 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -481,11 +481,18 @@ class MidiInput { /** * * @param {string | number} input MIDI device name or index defaulting to 0 - * @param {WebMidi.MIDIInput | undefined} device MIDI input device instance (or empty to use as placeholder) */ - constructor(device) { + constructor(input) { + const device = getDevice(input, WebMidi.inputs); + if (!device) { + throw new Error( + `midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`, + ); + } + this.id = device.id; this.name = device.name; + this.device = device; this._refs = {}; this._refsByChan = {}; @@ -572,15 +579,10 @@ export async function midin(input) { ); } const initial = await enableWebMidi(); // only returns on first init - const device = getDevice(input, WebMidi.inputs); - if (!device) { - throw new Error( - `midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`, - ); - } - const instance = midiInputs[device.id] || new MidiInput(device); - midiInputs[device.id] = instance; + const instance = midiInputs[input] || new MidiInput(input); + midiInputs[input] = instance; + const device = instance.device; if (initial) { const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); From 1860d31624d0cb1e7186bf273acb09cedb5ee184 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 18:46:39 -0500 Subject: [PATCH 031/200] Tweak cc init --- packages/midi/midi.mjs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 97226dc5..c2c08c07 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -497,20 +497,24 @@ class MidiInput { this._refs = {}; this._refsByChan = {}; - this.cc = (cc, chan) => { - const initialState = this._loadState(chan); - const initialValue = initialState[cc] || 0; - - if (chan !== undefined) { - return ref(() => this._refsByChan[cc]?.[chan] || initialValue); - } - return ref(() => this._refs[cc] || initialValue); - }; - const midiListener = this._onMidiMessage.bind(this); device.addListener('midimessage', midiListener); } + createCC(cc, chan) { + if (chan !== undefined && !(chan in this._refsByChan)) { + this._refsByChan[chan] = {}; + } + + const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan]; + if (!(cc in lookupMap)) { + const initialState = this._loadState(chan); + lookupMap[cc] = initialState[cc] || 0; + } + + return ref(() => lookupMap[cc]); + } + _onMidiMessage(e) { const ccNum = e.dataBytes[0]; const v = e.dataBytes[1]; @@ -593,5 +597,5 @@ export async function midin(input) { ); } - return instance.cc; + return instance.createCC.bind(instance); } From 1aaa04ab50af665a612ca0f295a4153a2054724d Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 18:58:13 -0500 Subject: [PATCH 032/200] Send stored CC values to controller --- packages/midi/midi.mjs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index c2c08c07..859c54f9 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -499,13 +499,16 @@ class MidiInput { const midiListener = this._onMidiMessage.bind(this); device.addListener('midimessage', midiListener); + + this._loadAllStates(); + + const output = WebMidi.outputs.find((o) => o.name === this.name); + if (output) { + this._sendAllStates(output); + } } createCC(cc, chan) { - if (chan !== undefined && !(chan in this._refsByChan)) { - this._refsByChan[chan] = {}; - } - const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan]; if (!(cc in lookupMap)) { const initialState = this._loadState(chan); @@ -529,6 +532,15 @@ class MidiInput { this._saveState(chan, ccNum, scaled); } + _loadAllStates() { + Object.assign(this._refs, this._loadState(undefined)); + + for (let chan = 1; chan <= 16; chan++) { + this._refsByChan[chan] ??= {}; + Object.assign(this._refsByChan[chan], this._loadState(chan)); + } + } + _loadState(chan) { const initialDataRaw = localStorage.getItem(`strudel-midin-${this.name}-chan${chan !== undefined ? chan : 'all'}`); if (!initialDataRaw) { @@ -552,6 +564,17 @@ class MidiInput { state[cc] = value; localStorage.setItem(`strudel-midin-${this.name}-chan${chan !== undefined ? chan : 'all'}`, JSON.stringify(state)); } + + _sendAllStates(output) { + for (const [chan, refs] of Object.entries(this._refsByChan)) { + const channel = Number(chan); + for (const [cc, value] of Object.entries(refs)) { + const ccn = Number(cc); + const scaled = Math.round(value * 127); + output.sendControlChange(ccn, scaled, channel); + } + } + } } // MIDI input wrappers, by device ID From b8dc50267e44ccaa39ddd2b638ccc34420476450 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 19:02:06 -0500 Subject: [PATCH 033/200] Clean up unused state fields --- packages/midi/midi.mjs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 859c54f9..49a87ac1 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -490,9 +490,7 @@ class MidiInput { ); } - this.id = device.id; this.name = device.name; - this.device = device; this._refs = {}; this._refsByChan = {}; @@ -502,7 +500,7 @@ class MidiInput { this._loadAllStates(); - const output = WebMidi.outputs.find((o) => o.name === this.name); + const output = WebMidi.outputs.find((o) => o.name === device.name); if (output) { this._sendAllStates(output); } @@ -577,7 +575,7 @@ class MidiInput { } } -// MIDI input wrappers, by device ID +// MIDI input wrappers, by specified input string/index const midiInputs = {}; /** @@ -609,12 +607,11 @@ export async function midin(input) { const instance = midiInputs[input] || new MidiInput(input); midiInputs[input] = instance; - const device = instance.device; if (initial) { - const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); + const otherInputs = WebMidi.inputs.filter((o) => o.name !== instance.name); logger( - `Midi enabled! Using "${device.name}". ${ + `Midi enabled! Using "${instance.name}". ${ otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' }`, ); From 0c01a997ac3e21227ac092248c4d20e06e422017 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 19:06:19 -0500 Subject: [PATCH 034/200] Avoid referencing found MIDI device name --- packages/midi/midi.mjs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 49a87ac1..eb6ca12e 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -483,6 +483,8 @@ class MidiInput { * @param {string | number} input MIDI device name or index defaulting to 0 */ constructor(input) { + this.stateKey = typeof input === 'string' ? input : undefined; + const device = getDevice(input, WebMidi.inputs); if (!device) { throw new Error( @@ -490,8 +492,6 @@ class MidiInput { ); } - this.name = device.name; - this._refs = {}; this._refsByChan = {}; @@ -540,7 +540,13 @@ class MidiInput { } _loadState(chan) { - const initialDataRaw = localStorage.getItem(`strudel-midin-${this.name}-chan${chan !== undefined ? chan : 'all'}`); + if (!this.stateKey) { + return {}; + } + + const initialDataRaw = localStorage.getItem( + `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, + ); if (!initialDataRaw) { return {}; } @@ -549,7 +555,7 @@ class MidiInput { return JSON.parse(initialDataRaw); } catch (err) { console.warn( - `Failed to parse MIDI state from localStorage for input "${this.name}" and channel "${chan}"`, + `Failed to parse MIDI state from localStorage for input "${this.stateKey}" and channel "${chan}"`, initialDataRaw, err, ); @@ -558,9 +564,16 @@ class MidiInput { } _saveState(chan, cc, value) { + if (!this.stateKey) { + return; + } + const state = this._loadState(chan); state[cc] = value; - localStorage.setItem(`strudel-midin-${this.name}-chan${chan !== undefined ? chan : 'all'}`, JSON.stringify(state)); + localStorage.setItem( + `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, + JSON.stringify(state), + ); } _sendAllStates(output) { From dab1a018e4cf0e7c35ea3c1317c303df1d4c1b5e Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 19:52:07 -0500 Subject: [PATCH 035/200] Support delayed connection and reconnection of MIDI inputs --- packages/midi/midi.mjs | 105 +++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index eb6ca12e..56e7f005 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -59,9 +59,6 @@ export function enableWebMidi(options = {}) { } function getDevice(indexOrName, devices) { - if (!devices.length) { - throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); - } if (typeof indexOrName === 'number') { return devices[indexOrName]; } @@ -73,12 +70,13 @@ function getDevice(indexOrName, devices) { const IACOutput = byName('IAC'); const device = IACOutput ?? devices[0]; if (!device) { - throw new Error( - `🔌 MIDI device '${device ? device : ''}' not found. Use one of ${getMidiDeviceNamesString(devices)}`, - ); + if (!devices.length) { + throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); + } + throw new Error(`🔌 Default MIDI device not found. Use one of ${getMidiDeviceNamesString(devices)}`); } - return IACOutput ?? devices[0]; + return device; } // send start/stop messages to outputs when repl starts/stops @@ -483,27 +481,15 @@ class MidiInput { * @param {string | number} input MIDI device name or index defaulting to 0 */ constructor(input) { + this.input = input; this.stateKey = typeof input === 'string' ? input : undefined; - const device = getDevice(input, WebMidi.inputs); - if (!device) { - throw new Error( - `midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`, - ); - } - this._refs = {}; this._refsByChan = {}; - const midiListener = this._onMidiMessage.bind(this); - device.addListener('midimessage', midiListener); - this._loadAllStates(); - const output = WebMidi.outputs.find((o) => o.name === device.name); - if (output) { - this._sendAllStates(output); - } + this.initialDevice = this._startDeviceListener(); } createCC(cc, chan) { @@ -516,6 +502,71 @@ class MidiInput { return ref(() => lookupMap[cc]); } + _startDeviceListener() { + const initialDevice = getDevice(this.input, WebMidi.inputs); + if (!initialDevice) { + console.warn(`midiin: device "${this.input}" not found`); + } + + // Background connection loop + (async () => { + const midiListener = this._onMidiMessage.bind(this); + let device = initialDevice; + + while (true) { + if (!device) { + device = await this._waitForDevice(); + } + + console.log('midiin: device connected:', device.name); + + // Wait a bit for device to be ready to receive last state + await new Promise((resolve) => setTimeout(resolve, 2000)); + + try { + const output = WebMidi.outputs.find((o) => o.name === device.name); + if (output) { + this._sendAllStates(output); + } + } catch (err) { + console.error('midiin: failed to send last state on connect:', device.name, err); + } + + // Listen for incoming MIDI messages and for disconnection + device.addListener('midimessage', midiListener); + + await new Promise((resolve) => { + const disconnectListener = (e) => { + if (e.port.name === device.name) { + console.warn('midiin: device disconnected:', device.name); + WebMidi.removeListener('disconnected', disconnectListener); + resolve(); + } + }; + WebMidi.addListener('disconnected', disconnectListener); + }); + + device = null; // Clear var to trigger wait for connection + } + })(); + + return initialDevice; + } + + _waitForDevice() { + return new Promise((resolve) => { + const connListener = () => { + const device = getDevice(this.input, WebMidi.inputs); + if (device) { + WebMidi.removeListener('connected', connListener); + resolve(device); + } + }; + + WebMidi.addListener('connected', connListener); + }); + } + _onMidiMessage(e) { const ccNum = e.dataBytes[0]; const v = e.dataBytes[1]; @@ -622,11 +673,15 @@ export async function midin(input) { midiInputs[input] = instance; if (initial) { - const otherInputs = WebMidi.inputs.filter((o) => o.name !== instance.name); + const device = instance.initialDevice; + + const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); logger( - `Midi enabled! Using "${instance.name}". ${ - otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' - }`, + device + ? `Midi enabled! Using "${device.name}". ${ + otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' + }` + : `Midi enabled! Waiting for device "${input}"... Currently connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`, ); } From bb7f587025c071bc13e13904eb0084345d807b77 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 19:56:15 -0500 Subject: [PATCH 036/200] Encapsulate more logic --- packages/midi/midi.mjs | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 56e7f005..08defe7e 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -524,10 +524,8 @@ class MidiInput { await new Promise((resolve) => setTimeout(resolve, 2000)); try { - const output = WebMidi.outputs.find((o) => o.name === device.name); - if (output) { - this._sendAllStates(output); - } + // Still continue if sending did not work + this._sendAllStates(device); } catch (err) { console.error('midiin: failed to send last state on connect:', device.name, err); } @@ -535,16 +533,10 @@ class MidiInput { // Listen for incoming MIDI messages and for disconnection device.addListener('midimessage', midiListener); - await new Promise((resolve) => { - const disconnectListener = (e) => { - if (e.port.name === device.name) { - console.warn('midiin: device disconnected:', device.name); - WebMidi.removeListener('disconnected', disconnectListener); - resolve(); - } - }; - WebMidi.addListener('disconnected', disconnectListener); - }); + await this._waitForDeviceDisconnect(device); + + console.warn('midiin: device disconnected:', device.name); + device.removeListener('midimessage', midiListener); device = null; // Clear var to trigger wait for connection } @@ -567,6 +559,19 @@ class MidiInput { }); } + _waitForDeviceDisconnect(device) { + return new Promise((resolve) => { + const disconnListener = (e) => { + if (e.port.name === device.name) { + WebMidi.removeListener('disconnected', disconnListener); + resolve(); + } + }; + + WebMidi.addListener('disconnected', disconnListener); + }); + } + _onMidiMessage(e) { const ccNum = e.dataBytes[0]; const v = e.dataBytes[1]; @@ -627,7 +632,12 @@ class MidiInput { ); } - _sendAllStates(output) { + _sendAllStates(device) { + const output = WebMidi.outputs.find((o) => o.name === device.name); + if (!output) { + return; + } + for (const [chan, refs] of Object.entries(this._refsByChan)) { const channel = Number(chan); for (const [cc, value] of Object.entries(refs)) { From ecada58edc9999f3dce3f23c5b8ec445243866fd Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 20:59:50 -0500 Subject: [PATCH 037/200] Carry out MIDI input wrapper into dedicated file --- packages/midi/input.mjs | 183 ++++++++++++++++++++++++++++++++++++ packages/midi/midi.mjs | 203 +--------------------------------------- packages/midi/util.mjs | 38 ++++++++ 3 files changed, 224 insertions(+), 200 deletions(-) create mode 100644 packages/midi/input.mjs create mode 100644 packages/midi/util.mjs diff --git a/packages/midi/input.mjs b/packages/midi/input.mjs new file mode 100644 index 00000000..aa16cfea --- /dev/null +++ b/packages/midi/input.mjs @@ -0,0 +1,183 @@ +/* +midi.mjs - +Copyright (C) 2022 Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ + +import { WebMidi } from 'webmidi'; +import { ref } from '@strudel/core'; +import { getDevice } from './util.mjs'; + +export class MidiInput { + /** + * + * @param {string | number} input MIDI device name or index defaulting to 0 + */ + constructor(input) { + this.input = input; + this.stateKey = typeof input === 'string' ? input : undefined; + + this._refs = {}; + this._refsByChan = {}; + + this._loadAllStates(); + + this.initialDevice = this._startDeviceListener(); + } + + createCC(cc, chan) { + const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan]; + if (!(cc in lookupMap)) { + const initialState = this._loadState(chan); + lookupMap[cc] = initialState[cc] || 0; + } + + return ref(() => lookupMap[cc]); + } + + _startDeviceListener() { + const initialDevice = getDevice(this.input, WebMidi.inputs); + if (!initialDevice) { + console.warn(`midiin: device "${this.input}" not found`); + } + + // Background connection loop + (async () => { + const midiListener = this._onMidiMessage.bind(this); + let device = initialDevice; + + while (true) { + if (!device) { + device = await this._waitForDevice(); + } + + console.log('midiin: device connected:', device.name); + + // Wait a bit for device to be ready to receive last state + await new Promise((resolve) => setTimeout(resolve, 2000)); + + try { + // Still continue if sending did not work + this._sendAllStates(device); + } catch (err) { + console.error('midiin: failed to send last state on connect:', device.name, err); + } + + // Listen for incoming MIDI messages and for disconnection + device.addListener('midimessage', midiListener); + + await this._waitForDeviceDisconnect(device); + + console.warn('midiin: device disconnected:', device.name); + device.removeListener('midimessage', midiListener); + + device = null; // Clear var to trigger wait for connection + } + })(); + + return initialDevice; + } + + _waitForDevice() { + return new Promise((resolve) => { + const connListener = () => { + const device = getDevice(this.input, WebMidi.inputs); + if (device) { + WebMidi.removeListener('connected', connListener); + resolve(device); + } + }; + + WebMidi.addListener('connected', connListener); + }); + } + + _waitForDeviceDisconnect(device) { + return new Promise((resolve) => { + const disconnListener = (e) => { + if (e.port.name === device.name) { + WebMidi.removeListener('disconnected', disconnListener); + resolve(); + } + }; + + WebMidi.addListener('disconnected', disconnListener); + }); + } + + _onMidiMessage(e) { + const ccNum = e.dataBytes[0]; + const v = e.dataBytes[1]; + const chan = e.message.channel; + const scaled = v / 127; + + this._refs[ccNum] = scaled; + this._refsByChan[ccNum] ??= {}; + this._refsByChan[ccNum][chan] = scaled; + + this._saveState(undefined, ccNum, scaled); + this._saveState(chan, ccNum, scaled); + } + + _loadAllStates() { + Object.assign(this._refs, this._loadState(undefined)); + + for (let chan = 1; chan <= 16; chan++) { + this._refsByChan[chan] ??= {}; + Object.assign(this._refsByChan[chan], this._loadState(chan)); + } + } + + _loadState(chan) { + if (!this.stateKey) { + return {}; + } + + const initialDataRaw = localStorage.getItem( + `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, + ); + if (!initialDataRaw) { + return {}; + } + + try { + return JSON.parse(initialDataRaw); + } catch (err) { + console.warn( + `Failed to parse MIDI state from localStorage for input "${this.stateKey}" and channel "${chan}"`, + initialDataRaw, + err, + ); + return {}; + } + } + + _saveState(chan, cc, value) { + if (!this.stateKey) { + return; + } + + const state = this._loadState(chan); + state[cc] = value; + localStorage.setItem( + `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, + JSON.stringify(state), + ); + } + + _sendAllStates(device) { + const output = WebMidi.outputs.find((o) => o.name === device.name); + if (!output) { + return; + } + + for (const [chan, refs] of Object.entries(this._refsByChan)) { + const channel = Number(chan); + for (const [cc, value] of Object.entries(refs)) { + const ccn = Number(cc); + const scaled = Math.round(value * 127); + output.sendControlChange(ccn, scaled, channel); + } + } + } +} diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 08defe7e..1e106a89 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -5,10 +5,12 @@ This program is free software: you can redistribute it and/or modify it under th */ import * as _WebMidi from 'webmidi'; -import { Pattern, isPattern, logger, ref } from '@strudel/core'; +import { Pattern, isPattern, logger } from '@strudel/core'; import { noteToMidi, getControlName } from '@strudel/core'; import { Note } from 'webmidi'; import { scheduleAtTime } from '../superdough/helpers.mjs'; +import { getMidiDeviceNamesString, getDevice } from './util.mjs'; +import { MidiInput } from './input.mjs'; // if you use WebMidi from outside of this package, make sure to import that instance: export const { WebMidi } = _WebMidi; @@ -17,10 +19,6 @@ function supportsMidi() { return typeof navigator.requestMIDIAccess === 'function'; } -function getMidiDeviceNamesString(devices) { - return devices.map((o) => `'${o.name}'`).join(' | '); -} - export function enableWebMidi(options = {}) { const { onReady, onConnected, onDisconnected, onEnabled } = options; if (WebMidi.enabled) { @@ -58,27 +56,6 @@ export function enableWebMidi(options = {}) { }); } -function getDevice(indexOrName, devices) { - if (typeof indexOrName === 'number') { - return devices[indexOrName]; - } - const byName = (name) => devices.find((output) => output.name.includes(name)); - if (typeof indexOrName === 'string') { - return byName(indexOrName); - } - // attempt to default to first IAC device if none is specified - const IACOutput = byName('IAC'); - const device = IACOutput ?? devices[0]; - if (!device) { - if (!devices.length) { - throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); - } - throw new Error(`🔌 Default MIDI device not found. Use one of ${getMidiDeviceNamesString(devices)}`); - } - - return device; -} - // send start/stop messages to outputs when repl starts/stops if (typeof window !== 'undefined') { window.addEventListener('message', (e) => { @@ -475,180 +452,6 @@ Pattern.prototype.midi = function (midiport, options = {}) { }); }; -class MidiInput { - /** - * - * @param {string | number} input MIDI device name or index defaulting to 0 - */ - constructor(input) { - this.input = input; - this.stateKey = typeof input === 'string' ? input : undefined; - - this._refs = {}; - this._refsByChan = {}; - - this._loadAllStates(); - - this.initialDevice = this._startDeviceListener(); - } - - createCC(cc, chan) { - const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan]; - if (!(cc in lookupMap)) { - const initialState = this._loadState(chan); - lookupMap[cc] = initialState[cc] || 0; - } - - return ref(() => lookupMap[cc]); - } - - _startDeviceListener() { - const initialDevice = getDevice(this.input, WebMidi.inputs); - if (!initialDevice) { - console.warn(`midiin: device "${this.input}" not found`); - } - - // Background connection loop - (async () => { - const midiListener = this._onMidiMessage.bind(this); - let device = initialDevice; - - while (true) { - if (!device) { - device = await this._waitForDevice(); - } - - console.log('midiin: device connected:', device.name); - - // Wait a bit for device to be ready to receive last state - await new Promise((resolve) => setTimeout(resolve, 2000)); - - try { - // Still continue if sending did not work - this._sendAllStates(device); - } catch (err) { - console.error('midiin: failed to send last state on connect:', device.name, err); - } - - // Listen for incoming MIDI messages and for disconnection - device.addListener('midimessage', midiListener); - - await this._waitForDeviceDisconnect(device); - - console.warn('midiin: device disconnected:', device.name); - device.removeListener('midimessage', midiListener); - - device = null; // Clear var to trigger wait for connection - } - })(); - - return initialDevice; - } - - _waitForDevice() { - return new Promise((resolve) => { - const connListener = () => { - const device = getDevice(this.input, WebMidi.inputs); - if (device) { - WebMidi.removeListener('connected', connListener); - resolve(device); - } - }; - - WebMidi.addListener('connected', connListener); - }); - } - - _waitForDeviceDisconnect(device) { - return new Promise((resolve) => { - const disconnListener = (e) => { - if (e.port.name === device.name) { - WebMidi.removeListener('disconnected', disconnListener); - resolve(); - } - }; - - WebMidi.addListener('disconnected', disconnListener); - }); - } - - _onMidiMessage(e) { - const ccNum = e.dataBytes[0]; - const v = e.dataBytes[1]; - const chan = e.message.channel; - const scaled = v / 127; - - this._refs[ccNum] = scaled; - this._refsByChan[ccNum] ??= {}; - this._refsByChan[ccNum][chan] = scaled; - - this._saveState(undefined, ccNum, scaled); - this._saveState(chan, ccNum, scaled); - } - - _loadAllStates() { - Object.assign(this._refs, this._loadState(undefined)); - - for (let chan = 1; chan <= 16; chan++) { - this._refsByChan[chan] ??= {}; - Object.assign(this._refsByChan[chan], this._loadState(chan)); - } - } - - _loadState(chan) { - if (!this.stateKey) { - return {}; - } - - const initialDataRaw = localStorage.getItem( - `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, - ); - if (!initialDataRaw) { - return {}; - } - - try { - return JSON.parse(initialDataRaw); - } catch (err) { - console.warn( - `Failed to parse MIDI state from localStorage for input "${this.stateKey}" and channel "${chan}"`, - initialDataRaw, - err, - ); - return {}; - } - } - - _saveState(chan, cc, value) { - if (!this.stateKey) { - return; - } - - const state = this._loadState(chan); - state[cc] = value; - localStorage.setItem( - `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, - JSON.stringify(state), - ); - } - - _sendAllStates(device) { - const output = WebMidi.outputs.find((o) => o.name === device.name); - if (!output) { - return; - } - - for (const [chan, refs] of Object.entries(this._refsByChan)) { - const channel = Number(chan); - for (const [cc, value] of Object.entries(refs)) { - const ccn = Number(cc); - const scaled = Math.round(value * 127); - output.sendControlChange(ccn, scaled, channel); - } - } - } -} - // MIDI input wrappers, by specified input string/index const midiInputs = {}; diff --git a/packages/midi/util.mjs b/packages/midi/util.mjs new file mode 100644 index 00000000..f2e7e822 --- /dev/null +++ b/packages/midi/util.mjs @@ -0,0 +1,38 @@ +import { Input, Output } from 'webmidi'; + +/** + * Get a string listing device names for error messages. + * @param {Input[] | Output[]} devices + * @returns {string} + */ +export function getMidiDeviceNamesString(devices) { + return devices.map((o) => `'${o.name}'`).join(' | '); +} + +/** + * Look up a device by index or name. Otherwise return a default device, or fail if none are connected. + * + * @param {string | number} indexOrName + * @param {Input[] | Output[]} devices + * @returns {Input | Output | undefined} + */ +export function getDevice(indexOrName, devices) { + if (typeof indexOrName === 'number') { + return devices[indexOrName]; + } + const byName = (name) => devices.find((output) => output.name.includes(name)); + if (typeof indexOrName === 'string') { + return byName(indexOrName); + } + // attempt to default to first IAC device if none is specified + const IACOutput = byName('IAC'); + const device = IACOutput ?? devices[0]; + if (!device) { + if (!devices.length) { + throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); + } + throw new Error(`🔌 Default MIDI device not found. Use one of ${getMidiDeviceNamesString(devices)}`); + } + + return device; +} From a0291e8a428d6dbf96f6e1c93b1a31423f1172f6 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 21:07:39 -0500 Subject: [PATCH 038/200] Fix comments --- packages/midi/input.mjs | 2 +- packages/midi/util.mjs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/midi/input.mjs b/packages/midi/input.mjs index aa16cfea..943916e5 100644 --- a/packages/midi/input.mjs +++ b/packages/midi/input.mjs @@ -1,5 +1,5 @@ /* -midi.mjs - +input.mjs - MIDI input wrapper Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ diff --git a/packages/midi/util.mjs b/packages/midi/util.mjs index f2e7e822..99158e68 100644 --- a/packages/midi/util.mjs +++ b/packages/midi/util.mjs @@ -1,3 +1,9 @@ +/* +util.mjs - MIDI utility functions +Copyright (C) 2022 Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ + import { Input, Output } from 'webmidi'; /** From ee326c709e201c55f0d61b421f9cf1e90565f9d8 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 25 Dec 2025 21:42:58 -0500 Subject: [PATCH 039/200] Fix MIDI input logging --- packages/midi/input.mjs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/midi/input.mjs b/packages/midi/input.mjs index 943916e5..8e488942 100644 --- a/packages/midi/input.mjs +++ b/packages/midi/input.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import { WebMidi } from 'webmidi'; -import { ref } from '@strudel/core'; +import { logger, ref } from '@strudel/core'; import { getDevice } from './util.mjs'; export class MidiInput { @@ -15,7 +15,7 @@ export class MidiInput { */ constructor(input) { this.input = input; - this.stateKey = typeof input === 'string' ? input : undefined; + this.stateKey = typeof input === 'string' ? input : undefined; // Saved state is not tracked for numeric index inputs this._refs = {}; this._refsByChan = {}; @@ -37,9 +37,6 @@ export class MidiInput { _startDeviceListener() { const initialDevice = getDevice(this.input, WebMidi.inputs); - if (!initialDevice) { - console.warn(`midiin: device "${this.input}" not found`); - } // Background connection loop (async () => { @@ -51,8 +48,6 @@ export class MidiInput { device = await this._waitForDevice(); } - console.log('midiin: device connected:', device.name); - // Wait a bit for device to be ready to receive last state await new Promise((resolve) => setTimeout(resolve, 2000)); @@ -68,9 +63,7 @@ export class MidiInput { await this._waitForDeviceDisconnect(device); - console.warn('midiin: device disconnected:', device.name); device.removeListener('midimessage', midiListener); - device = null; // Clear var to trigger wait for connection } })(); @@ -83,6 +76,8 @@ export class MidiInput { const connListener = () => { const device = getDevice(this.input, WebMidi.inputs); if (device) { + logger(`[midi] device reconnected: ${device.name}`); + WebMidi.removeListener('connected', connListener); resolve(device); } @@ -96,6 +91,8 @@ export class MidiInput { return new Promise((resolve) => { const disconnListener = (e) => { if (e.port.name === device.name) { + logger(`[midi] device disconnected: ${device.name}`); + WebMidi.removeListener('disconnected', disconnListener); resolve(); } From 1a7f464998e3dd797ff821c89a5754133f4c93b1 Mon Sep 17 00:00:00 2001 From: Dsm0 Date: Tue, 30 Dec 2025 18:34:26 -0600 Subject: [PATCH 040/200] requested revisions 1 (and fix failing test) --- packages/codemirror/block_utilities.mjs | 2 +- packages/core/repl.mjs | 103 +++++------------------- packages/transpiler/transpiler.mjs | 71 +++++++++++++++- test/examples.test.mjs | 1 + 4 files changed, 90 insertions(+), 87 deletions(-) diff --git a/packages/codemirror/block_utilities.mjs b/packages/codemirror/block_utilities.mjs index 4b53755d..0d13259a 100644 --- a/packages/codemirror/block_utilities.mjs +++ b/packages/codemirror/block_utilities.mjs @@ -44,7 +44,7 @@ export const evalBlock = (strudelMirror) => { if (block) { // Flash the block being evaluated strudelMirror.flash(200, { from: a, to: b }); - strudelMirror.repl.evaluate(block, true, true, true, { range }); + strudelMirror.repl.evaluate(block, true, true, { range }); } } return true; diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 4c730515..d1bb1b1a 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -126,76 +126,11 @@ export function repl({ ); } - // // Helper function to filter meta (widgets, sliders, miniLocations) by block range - // function splitCodeByRange(meta, blockStart, blockEnd) { - // } - - // Helper function to extract labels from code with their positions - function extractLabelsFromCode(code) { - const labels = []; - // Regex to find label patterns like "d1:" or "myLabel:" - const labelRegex = /^(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:)/gm; - let match; - while ((match = labelRegex.exec(code)) !== null) { - labels.push({ - name: match[2], - index: match.index, - end: match.index + match[1].length, - fullMatch: match[1], - }); - } - - // Also check for 'all()' and treat it as a special label - // just for management purposes - const allRegex = /all\s*\(\s*([^)]+)\s*\)/; - const allMatch = allRegex.exec(code); - if (allMatch) { - // Check if the argument contains a widget call - const argCode = allMatch[1]; - const activeVisualizer = detectActiveVisualizer(argCode); - - labels.push({ - name: 'all', - index: allMatch.index, - end: allMatch.index + allMatch[0].length, - fullMatch: allMatch[0], - activeVisualizer: activeVisualizer, - }); - } - - return labels; - } - - // Helper function to detect non-inline widget calls in code - function detectActiveVisualizer(code) { - // List of non-inline widgets that need cleanup - // These are Pattern.prototype methods that create persistent visualizations - // I don't like this approach, feels hacky, but since these methods don't create ids that - // can be read through the transpiler, I'm not sure how best to detect them. - // Would probably be better to register these methods through some function that would - // tag them better - const nonInlineWidgets = ['punchcard', 'spiral', 'scope', 'pitchwheel', 'spectrum', 'pianoroll', 'wordfall']; - - for (const widget of nonInlineWidgets) { - const widgetRegex = new RegExp(`\\.${widget}\\s*\\(`); - if (widgetRegex.test(code)) { - return widget; - } - } - return null; - } - // Helper function to handle single label code block storage function handleSingleLabelBlock(label, code, options, meta) { // Detect if this block contains a non-inline widget - // For 'all' label, widget info is already on the label object from extractLabelsFromCode - - // As mentioned in detectActiveVisualizer, this is a bad approach to - // managing non-inline widgets (or widgets that don't have ids) - // and a proper solution would give them widgets in the transpiler, or at least - // track where they are so we don't have to futz around with regexes - const activeVisualizer = - label.activeVisualizer !== undefined ? label.activeVisualizer : detectActiveVisualizer(code); + // The activeVisualizer is now provided by the transpiler for all labels + const activeVisualizer = label.activeVisualizer || null; if (activeVisualizer !== null) { lastActiveVisualizerLabel = label.name; @@ -371,7 +306,7 @@ export function repl({ }); }; - const evaluate = async (code, autostart = true, shouldHush = true, blockBased = false, options = {}) => { + const evaluate = async (code, autostart = true, blockBased = false, options = {}) => { if (!code) { throw new Error('no code to evaluate'); } @@ -390,7 +325,7 @@ export function repl({ if (!blockBased) { codeBlocks = {}; - shouldHush && hush(); + hush(); } if (mondo) { @@ -403,7 +338,7 @@ export function repl({ let widgetRemoved = false; if (blockBased) { - const labels = extractLabelsFromCode(code); + const labels = meta.labels || []; // Store code blocks in dictionary using labels as keys if (labels.length > 0) { @@ -526,18 +461,18 @@ export function repl({ export const getTrigger = ({ getTime, defaultOutput }) => - async (hap, deadline, duration, cps, t) => { - // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) - // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 - try { - if (!hap.context.onTrigger || !hap.context.dominantTrigger) { - await defaultOutput(hap, deadline, duration, cps, t); + async (hap, deadline, duration, cps, t) => { + // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) + // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 + try { + if (!hap.context.onTrigger || !hap.context.dominantTrigger) { + await defaultOutput(hap, deadline, duration, cps, t); + } + if (hap.context.onTrigger) { + // call signature of output / onTrigger is different... + await hap.context.onTrigger(hap, getTime(), cps, t); + } + } catch (err) { + errorLogger(err, 'getTrigger'); } - if (hap.context.onTrigger) { - // call signature of output / onTrigger is different... - await hap.context.onTrigger(hap, getTime(), cps, t); - } - } catch (err) { - errorLogger(err, 'getTrigger'); - } - }; + }; diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index 24dd3f1f..e7d4acfa 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -55,6 +55,7 @@ export function transpiler(input, options = {}) { }; let widgets = []; let sliders = []; + let labels = []; walk(ast, { enter(node, parent /* , prop, index */) { @@ -146,10 +147,32 @@ export function transpiler(input, options = {}) { return this.replace(withAwait(node)); } if (isLabelStatement(node)) { + // Collect label info for block-based evaluation + // Store positions WITHOUT offset so repl can slice the transpiler output correctly + if (blockBased) { + labels.push({ + name: node.label.name, + index: node.start - nodeOffset, + end: node.label.end - nodeOffset, + fullMatch: input.slice(node.start - nodeOffset, node.label.end - nodeOffset), + activeVisualizer: findVisualizerInSubtree(node.body), + }); + } return this.replace(labelToP(node)); } + // Detect all() calls as special labels for block management + // Store positions WITHOUT offset so repl can slice the transpiler output correctly + if (blockBased && isAllCall(node)) { + labels.push({ + name: 'all', + index: node.start - nodeOffset, + end: node.end - nodeOffset, + fullMatch: input.slice(node.start - nodeOffset, node.end - nodeOffset), + activeVisualizer: node.arguments[0] ? findVisualizerInSubtree(node.arguments[0]) : null, + }); + } }, - leave(node, parent, prop, index) {}, + leave(node, parent, prop, index) { }, }); let { body } = ast; @@ -198,7 +221,7 @@ export function transpiler(input, options = {}) { if (!emitMiniLocations) { return { output }; } - return { output, miniLocations, widgets, sliders }; + return { output, miniLocations, widgets, sliders, labels }; } function isStringWithDoubleQuotes(node, locations, code) { @@ -301,6 +324,10 @@ function isBareSamplesCall(node, parent) { return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression'; } +function isAllCall(node) { + return node.type === 'CallExpression' && node.callee.name === 'all'; +} + function withAwait(node) { return { type: 'AwaitExpression', @@ -401,6 +428,46 @@ function languageWithLocation(name, value, offset) { }; } +// List of non-inline widgets that need cleanup +// These are Pattern.prototype methods that create persistent visualizations +// (should be repalced by a function call producing an actual list of registered widgets) +const nonInlineWidgets = ['punchcard', 'spiral', 'scope', 'pitchwheel', 'spectrum', 'pianoroll', 'wordfall']; + +function isVisualizerCall(node) { + if ( + node.type === 'CallExpression' && + node.callee.type === 'MemberExpression' && + nonInlineWidgets.includes(node.callee.property?.name) + ) { + return node.callee.property.name; + } + return null; +} + +function findVisualizerInSubtree(node) { + if (!node || typeof node !== 'object') return null; + + // Check if this node is a visualizer call + const viz = isVisualizerCall(node); + if (viz) return viz; + + // Recursively search children + for (const key of Object.keys(node)) { + if (key === 'parent') continue; // Skip parent references to avoid cycles + const child = node[key]; + if (Array.isArray(child)) { + for (const item of child) { + const found = findVisualizerInSubtree(item); + if (found) return found; + } + } else if (child && typeof child === 'object' && child.type) { + const found = findVisualizerInSubtree(child); + if (found) return found; + } + } + return null; +} + // Creates AST nodes for: userDefinedKeys.add('name'); strudelScope.name = name; globalThis.name = name; // Used in block-based evaluation to persist variables/functions across blocks // We add to both strudelScope (for internal lookups) and globalThis (for direct access) diff --git a/test/examples.test.mjs b/test/examples.test.mjs index 896a02f8..18f12bb4 100644 --- a/test/examples.test.mjs +++ b/test/examples.test.mjs @@ -20,6 +20,7 @@ const skippedExamples = [ 'accelerationX', 'defaultmidimap', 'midimaps', + 'clearScope' ]; describe('runs examples', () => { From 160ef84b47be62bb8181cd63858c2d83ff451cb4 Mon Sep 17 00:00:00 2001 From: Dsm0 Date: Tue, 30 Dec 2025 20:14:32 -0600 Subject: [PATCH 041/200] move evaluate/evaluateBlock to different functions --- packages/codemirror/block_utilities.mjs | 2 +- packages/core/repl.mjs | 255 ++++++++++++++---------- packages/transpiler/transpiler.mjs | 2 +- test/examples.test.mjs | 2 +- 4 files changed, 152 insertions(+), 109 deletions(-) diff --git a/packages/codemirror/block_utilities.mjs b/packages/codemirror/block_utilities.mjs index 0d13259a..fd3dbedc 100644 --- a/packages/codemirror/block_utilities.mjs +++ b/packages/codemirror/block_utilities.mjs @@ -44,7 +44,7 @@ export const evalBlock = (strudelMirror) => { if (block) { // Flash the block being evaluated strudelMirror.flash(200, { from: a, to: b }); - strudelMirror.repl.evaluate(block, true, true, { range }); + strudelMirror.repl.evaluateBlock(block, true, { range }); } } return true; diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index d1bb1b1a..e3efdd3e 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -187,6 +187,48 @@ export function repl({ }; setTime(() => scheduler.now()); // TODO: refactor? + // Helper function to apply pattern transformations (solo, each, all) + // this should be abstracted more + function applyPatternTransforms(pattern) { + const allPatterns = Object.values(pPatterns); + + if (allPatterns.length) { + let patterns = []; + let soloActive = false; + for (const [key, value] of Object.entries(pPatterns)) { + // handle soloed patterns ex: S$: s("bd!4") + const isSolod = key.length > 1 && key.startsWith('S'); + if (isSolod && soloActive === false) { + // first time we see a soloed pattern, clear existing patterns + patterns = []; + soloActive = true; + } + if (!soloActive || (soloActive && isSolod)) { + const valWithState = value.withState((state) => state.setControls({ id: key })); + patterns.push(valWithState); + } + } + if (eachTransform) { + // Explicit lambda so only element (not index and array) are passed + patterns = patterns.map((x) => eachTransform(x)); + } + pattern = stack(...patterns); + } else if (eachTransform) { + pattern = eachTransform(pattern); + } + if (allTransforms.length) { + for (const transform of allTransforms) { + pattern = transform(pattern); + } + } + + if (!isPattern(pattern)) { + pattern = silence; + } + + return pattern; + } + const stop = () => { codeBlocks = {}; blockPatterns.clear(); @@ -306,7 +348,7 @@ export function repl({ }); }; - const evaluate = async (code, autostart = true, blockBased = false, options = {}) => { + const evaluate = async (code, autostart = true) => { if (!code) { throw new Error('no code to evaluate'); } @@ -314,20 +356,60 @@ export function repl({ updateState({ code, pending: true }); await injectPatternMethods(); setTime(() => scheduler.now()); // TODO: refactor? - await beforeEval?.({ code, blockBased }); + await beforeEval?.({ code, blockBased: false }); + allTransforms = []; // reset all transforms + + codeBlocks = {}; + hush(); + + if (mondo) { + code = `mondolang\`${code}\``; + } + + let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions); + + pattern = applyPatternTransforms(pattern); + + logger(`[eval] code updated`); + pattern = await setPattern(pattern, autostart); + updateState({ + miniLocations: meta?.miniLocations || [], + widgets: meta?.widgets || [], + sliders: meta?.sliders || [], + activeCode: code, + pattern, + evalError: undefined, + schedulerError: undefined, + pending: false, + }); + + afterEval?.({ code, pattern, meta, range: undefined, widgetRemoved: false }); + return pattern; + } catch (err) { + logger(`[eval] error: ${err.message}`, 'error'); + console.error(err); + updateState({ evalError: err, pending: false }); + onEvalError?.(err); + } + }; + + const evaluateBlock = async (code, autostart = true, options = {}) => { + if (!code) { + throw new Error('no code to evaluate'); + } + try { + updateState({ code, pending: true }); + await injectPatternMethods(); + setTime(() => scheduler.now()); // TODO: refactor? + await beforeEval?.({ code, blockBased: true }); allTransforms = []; // reset all transforms const transpilerOptionsWithBlock = { ...transpilerOptions, - blockBased, + blockBased: true, range: options.range || [], }; - if (!blockBased) { - codeBlocks = {}; - hush(); - } - if (mondo) { code = `mondolang\`${code}\``; } @@ -337,101 +419,61 @@ export function repl({ // Track activeVisualizer cleanup: check if any block's visualizer was removed let widgetRemoved = false; - if (blockBased) { - const labels = meta.labels || []; + const labels = meta.labels || []; - // Store code blocks in dictionary using labels as keys - if (labels.length > 0) { - for (let i = 0; i < labels.length; i++) { - // processLabeledBlock(labels, i, code, options, meta); - // processing transpiler output instead of code is simply to avoid - // extra regex in detecting whether or not an inline widget has been commented out - processLabeledBlock(labels, i, meta.output, options, meta); - } - } else if (pattern !== silence) { - // variable/function declarations that return silence are allowed, - // but anonymous pattern blocks pose an issue for block-based evaluation - // if an anonymous pattern is evaluated multiple times it will just stack and get louder and louder - - // it's very common for users to write code prefixed with '$' - // but to modify and override existing patterns, the patterns must be labeled, - // otherwise we'll have no idea of which pattern is being overridden - - // (we probably need to update the docs on this) - - // we could easily enable it, but it would confuse a lot of people - throw new Error( - 'anonymous labels disabled for block based evaluation (see https://strudel.cc/blog/#label-notation)', - ); + // Store code blocks in dictionary using labels as keys + if (labels.length > 0) { + for (let i = 0; i < labels.length; i++) { + // processing transpiler output instead of code is simply to avoid + // extra regex in detecting whether or not an inline widget has been commented out + processLabeledBlock(labels, i, meta.output, options, meta); } + } else if (pattern !== silence) { + // variable/function declarations that return silence are allowed, + // but anonymous pattern blocks pose an issue for block-based evaluation + // if an anonymous pattern is evaluated multiple times it will just stack and get louder and louder - // For block-based evaluation, collect from all blocks - // The highlight/widget/slider systems will handle selective updates based on the range - meta.miniLocations = collectFromBlocks('miniLocations'); - meta.widgets = collectFromBlocks('widgets'); - meta.sliders = collectFromBlocks('sliders'); + // it's very common for users to write code prefixed with '$' + // but to modify and override existing patterns, the patterns must be labeled, + // otherwise we'll have no idea of which pattern is being overridden - // Track activeVisualizer cleanup: check if any block's visualizer was removed - const blocksToUpdate = labels.map((label) => label.name); + // (we probably need to update the docs on this) - for (const [key, block] of Object.entries(codeBlocks)) { - if (blocksToUpdate.includes(key)) { - // This block was just updated - if (block.activeVisualizer !== null) { - // Block now has a visualizer, update tracking - lastActiveVisualizerLabel = key; - } else if (lastActiveVisualizerLabel === key) { - // This block lost its visualizer, trigger cleanup - widgetRemoved = true; - lastActiveVisualizerLabel = null; - } - } - } - } - - const allPatterns = Object.values(pPatterns); - - if (blockBased) { - pPatterns = Object.fromEntries( - Object.entries(pPatterns).filter(([key]) => { - return Object.keys(codeBlocks).includes(key); - }), + // we could easily enable it, but it would confuse a lot of people + throw new Error( + 'anonymous labels disabled for block based evaluation (see https://strudel.cc/blog/#label-notation)', ); } - if (allPatterns.length) { - let patterns = []; - let soloActive = false; - for (const [key, value] of Object.entries(pPatterns)) { - // handle soloed patterns ex: S$: s("bd!4") - const isSolod = key.length > 1 && key.startsWith('S'); - if (isSolod && soloActive === false) { - // first time we see a soloed pattern, clear existing patterns - patterns = []; - soloActive = true; + meta.miniLocations = collectFromBlocks('miniLocations'); + meta.widgets = collectFromBlocks('widgets'); + meta.sliders = collectFromBlocks('sliders'); + + // Track activeVisualizer cleanup: check if any block's visualizer was removed + const blocksToUpdate = labels.map((label) => label.name); + + // this is the hackiest bit + for (const [key, block] of Object.entries(codeBlocks)) { + if (blocksToUpdate.includes(key)) { + // This block was just updated + if (block.activeVisualizer !== null) { + // Block now has a visualizer, update tracking + lastActiveVisualizerLabel = key; + } else if (lastActiveVisualizerLabel === key) { + // This block lost its visualizer, trigger cleanup + widgetRemoved = true; + lastActiveVisualizerLabel = null; } - if (!soloActive || (soloActive && isSolod)) { - const valWithState = value.withState((state) => state.setControls({ id: key })); - patterns.push(valWithState); - } - } - if (eachTransform) { - // Explicit lambda so only element (not index and array) are passed - patterns = patterns.map((x) => eachTransform(x)); - } - pattern = stack(...patterns); - } else if (eachTransform) { - pattern = eachTransform(pattern); - } - if (allTransforms.length) { - for (const transform of allTransforms) { - pattern = transform(pattern); } } - if (!isPattern(pattern)) { - pattern = silence; - } + pPatterns = Object.fromEntries( + Object.entries(pPatterns).filter(([key]) => { + return Object.keys(codeBlocks).includes(key); + }), + ); + + pattern = applyPatternTransforms(pattern); logger(`[eval] code updated`); pattern = await setPattern(pattern, autostart); @@ -455,24 +497,25 @@ export function repl({ onEvalError?.(err); } }; + const setCode = (code) => updateState({ code }); - return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state }; + return { scheduler, evaluate, evaluateBlock, start, stop, pause, setCps, setPattern, setCode, toggle, state }; } export const getTrigger = ({ getTime, defaultOutput }) => - async (hap, deadline, duration, cps, t) => { - // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) - // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 - try { - if (!hap.context.onTrigger || !hap.context.dominantTrigger) { - await defaultOutput(hap, deadline, duration, cps, t); - } - if (hap.context.onTrigger) { - // call signature of output / onTrigger is different... - await hap.context.onTrigger(hap, getTime(), cps, t); - } - } catch (err) { - errorLogger(err, 'getTrigger'); + async (hap, deadline, duration, cps, t) => { + // ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger) + // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 + try { + if (!hap.context.onTrigger || !hap.context.dominantTrigger) { + await defaultOutput(hap, deadline, duration, cps, t); } - }; + if (hap.context.onTrigger) { + // call signature of output / onTrigger is different... + await hap.context.onTrigger(hap, getTime(), cps, t); + } + } catch (err) { + errorLogger(err, 'getTrigger'); + } + }; diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs index e7d4acfa..db08c60b 100644 --- a/packages/transpiler/transpiler.mjs +++ b/packages/transpiler/transpiler.mjs @@ -172,7 +172,7 @@ export function transpiler(input, options = {}) { }); } }, - leave(node, parent, prop, index) { }, + leave(node, parent, prop, index) {}, }); let { body } = ast; diff --git a/test/examples.test.mjs b/test/examples.test.mjs index 18f12bb4..a3dec47a 100644 --- a/test/examples.test.mjs +++ b/test/examples.test.mjs @@ -20,7 +20,7 @@ const skippedExamples = [ 'accelerationX', 'defaultmidimap', 'midimaps', - 'clearScope' + 'clearScope', ]; describe('runs examples', () => { From 726e5ef14a6de2aab36202434ac52ae45c5e551f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 10 Jan 2026 07:18:29 -0800 Subject: [PATCH 042/200] Fix formatting --- packages/midi/midi.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 9b32f6de..63faeaf4 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -488,7 +488,7 @@ const refsByChan = {}; * MIDI input: Opens a MIDI input port to receive MIDI control change messages. * * The output is a function that accepts a midi cc value to query as well as (optionally) a midi channel - * @tags external_io + * @tags external_io * @param {string | number} input MIDI device name or index defaulting to 0 * @returns {function(number, number=): Pattern} A function from (cc, channel?) to a pattern. * When queried, the pattern will produces the most recently received midi value (normalized to 0 to 1) From 550d7ff6cb84fe3273256c2153cb869bc27d1263 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 11 Jan 2026 12:32:54 +0100 Subject: [PATCH 043/200] fix: react error --- website/src/repl/components/panel/Reference.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 3053f03d..34701036 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useMemo, useState, Fragment } from 'react'; import jsdocJson from '../../../../../doc.json'; import { Textbox } from '../textbox/Textbox'; @@ -109,9 +109,8 @@ export function Reference() {
From 9ffd0e496fcb71fcbc46add25eef7e40f56d4fb6 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 11 Jan 2026 12:33:03 +0100 Subject: [PATCH 044/200] fix: all doc --- packages/core/repl.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 047989f7..9fe8eef8 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -120,7 +120,9 @@ export function repl({ // TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`.. - /** Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for + let allTransforms = []; + /** + * Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for * a version that applies the function to each pattern separately. * ``` * $: sound("bd - cp sd") @@ -135,7 +137,6 @@ export function repl({ * * @tags combiners */ - let allTransforms = []; const all = function (transform) { allTransforms.push(transform); return silence; From 055f009357f0b8b7438e9b7b06945c04c65167cd Mon Sep 17 00:00:00 2001 From: space-shell Date: Tue, 13 Jan 2026 19:47:12 +0100 Subject: [PATCH 045/200] feat: Refactor Vim/Helix keybindings with shared helper functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Factor out the repl-eval and repl-stop event dispatching code into replEval() and replStop() helper functions that are shared between Vim and Helix keybindings. This refactoring: - Reduces code duplication between Vim :w/:q and Helix :w/:q commands - Makes the keybinding handlers more maintainable - Properly implements Helix commands using the commands.of() API - Ensures consistent behavior across both keybinding modes The Helix keybindings now properly integrate with the REPL using the same event dispatch mechanism as Vim, dispatching custom repl-evaluate and repl-stop events with fallbacks to keyboard events. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/codemirror/keybindings.mjs | 163 ++++++++++++++++++---------- 1 file changed, 106 insertions(+), 57 deletions(-) diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs index bc13f323..3e660630 100644 --- a/packages/codemirror/keybindings.mjs +++ b/packages/codemirror/keybindings.mjs @@ -6,7 +6,7 @@ import { emacs } from '@replit/codemirror-emacs'; import { vim, Vim } from '@replit/codemirror-vim'; // import { vim } from './vim_test.mjs'; import { vscodeKeymap } from '@replit/codemirror-vscode-keymap'; -import { helix } from 'codemirror-helix'; +import { helix, commands } from 'codemirror-helix'; import { logger } from '@strudel/core'; const vscodePlugin = ViewPlugin.fromClass( @@ -21,6 +21,70 @@ const vscodePlugin = ViewPlugin.fromClass( ); const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []); +function replEval(view) { + try { + // Dispatch a dedicated evaluate event first + let handled = false; + try { + const ev = new CustomEvent('repl-evaluate', { detail: { source: 'vim', view }, cancelable: true }); + handled = document.dispatchEvent(ev) === false; // false means preventDefault was called + } catch (e) { + console.error('Error dispatching repl-evaluate event', e); + } + if (handled) { + return; + } + // Try Ctrl+Enter first if not handled by custom event + const ctrlEnter = new KeyboardEvent('keydown', { + key: 'Enter', + code: 'Enter', + ctrlKey: true, + bubbles: true, + cancelable: true, + }); + view?.dom?.dispatchEvent?.(ctrlEnter); + // If not handled (no handler called preventDefault), try Alt+Enter as + // fallback + if (!ctrlEnter.defaultPrevented) { + const altEnter = new KeyboardEvent('keydown', { + key: 'Enter', + code: 'Enter', + altKey: true, + bubbles: true, + cancelable: true, + }); + view?.dom?.dispatchEvent?.(altEnter); + } + } catch (e) { + console.error('Error dispatching repl evaluation event', e); + } +} + +function replStop(view) { + try { + // First try dispatching our custom stop event, then fallback to Alt+. + let handled = false; + try { + const ev = new CustomEvent('repl-stop', { detail: { source: 'vim', view }, cancelable: true }); + handled = document.dispatchEvent(ev) === false; + } catch (e) { + console.error('Error dispatching repl-stop event', e); + } + if (!handled) { + const altDot = new KeyboardEvent('keydown', { + key: '.', + code: 'Period', + altKey: true, + bubbles: true, + cancelable: true, + }); + view?.dom?.dispatchEvent?.(altDot); + } + } catch (e) { + console.error('Error dispatching repl stop event', e); + } +} + // Map Vim :w to trigger the same action as evaluation. We dispatch a custom // event 'repl-evaluate' that the editor listens for, and also simulate // Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it @@ -48,29 +112,8 @@ try { // :q to pause/stop Vim.defineEx('quit', 'q', (cm) => { - try { - const view = cm?.view || cm; - // First try dispatching our custom stop event, then fallback to Alt+. - let handled = false; - try { - const ev = new CustomEvent('repl-stop', { detail: { source: 'vim', view }, cancelable: true }); - handled = document.dispatchEvent(ev) === false; - } catch (e) { - console.error('Error dispatching repl-stop event', e); - } - if (!handled) { - const altDot = new KeyboardEvent('keydown', { - key: '.', - code: 'Period', - altKey: true, - bubbles: true, - cancelable: true, - }); - view?.dom?.dispatchEvent?.(altDot); - } - } catch (e) { - console.error('Error dispatching :q stop event', e); - } + const view = cm?.view || cm; + replStop(view); }); // :w to evaluate @@ -84,38 +127,7 @@ try { } catch (e) { console.error('Error logging Vim :w evaluation', e); } - // Dispatch a dedicated evaluate event first - let handled = false; - try { - const ev = new CustomEvent('repl-evaluate', { detail: { source: 'vim', view }, cancelable: true }); - handled = document.dispatchEvent(ev) === false; // false means preventDefault was called - } catch (e) { - console.error('Error dispatching repl-evaluate event', e); - } - if (handled) { - return; - } - // Try Ctrl+Enter first if not handled by custom event - const ctrlEnter = new KeyboardEvent('keydown', { - key: 'Enter', - code: 'Enter', - ctrlKey: true, - bubbles: true, - cancelable: true, - }); - view?.dom?.dispatchEvent?.(ctrlEnter); - // If not handled (no handler called preventDefault), try Alt+Enter as - // fallback - if (!ctrlEnter.defaultPrevented) { - const altEnter = new KeyboardEvent('keydown', { - key: 'Enter', - code: 'Enter', - altKey: true, - bubbles: true, - cancelable: true, - }); - view?.dom?.dispatchEvent?.(altEnter); - } + replEval(view); } catch (e) { console.error('Error dispatching :w evaluation event', e); } @@ -125,12 +137,49 @@ try { console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e); } +// Map Helix :w to trigger the same action as evaluation. We dispatch a custom +// event 'repl-evaluate' that the editor listens for, and also simulate +// Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it +// appears in the Console panel. +const helixCommands = commands.of([ + { + // :w to evaluate + name: 'write', + aliases: ['w'], + help: 'Repl-eval', + handler(view, args) { + try { + view?.focus?.(); // Let the app know this came from Helix :w + logger('[helix] :w — evaluating code'); + replEval(view); + } catch (e) { + console.error('Error dispatching helix :w evaluation event', e); + } + }, + }, + { + // :q to pause/stop + name: 'quit', + aliases: ['q'], + help: 'Repl-stop', + handler(view, args) { + try { + view?.focus?.(); // Let the app know this came from Helix :q + logger('[helix] :q — stopping repl'); + replStop(view); + } catch (e) { + console.error('Error dispatching helix :q stop event', e); + } + }, + }, +]); + const keymaps = { vim, emacs, - helix, codemirror: () => keymap.of(defaultKeymap), vscode: vscodeExtension, + helix: () => [helix(), helixCommands], }; export function keybindings(name) { From 44ad05fb203368f6cc1fa17144b7b527bdf0c453 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Wed, 14 Jan 2026 23:37:26 -0500 Subject: [PATCH 046/200] Whitespace fixes --- packages/midi/input.mjs | 360 ++++++++++++++++++++-------------------- packages/midi/util.mjs | 88 +++++----- 2 files changed, 224 insertions(+), 224 deletions(-) diff --git a/packages/midi/input.mjs b/packages/midi/input.mjs index 8e488942..498e6fe2 100644 --- a/packages/midi/input.mjs +++ b/packages/midi/input.mjs @@ -1,180 +1,180 @@ -/* -input.mjs - MIDI input wrapper -Copyright (C) 2022 Strudel contributors - see -This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . -*/ - -import { WebMidi } from 'webmidi'; -import { logger, ref } from '@strudel/core'; -import { getDevice } from './util.mjs'; - -export class MidiInput { - /** - * - * @param {string | number} input MIDI device name or index defaulting to 0 - */ - constructor(input) { - this.input = input; - this.stateKey = typeof input === 'string' ? input : undefined; // Saved state is not tracked for numeric index inputs - - this._refs = {}; - this._refsByChan = {}; - - this._loadAllStates(); - - this.initialDevice = this._startDeviceListener(); - } - - createCC(cc, chan) { - const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan]; - if (!(cc in lookupMap)) { - const initialState = this._loadState(chan); - lookupMap[cc] = initialState[cc] || 0; - } - - return ref(() => lookupMap[cc]); - } - - _startDeviceListener() { - const initialDevice = getDevice(this.input, WebMidi.inputs); - - // Background connection loop - (async () => { - const midiListener = this._onMidiMessage.bind(this); - let device = initialDevice; - - while (true) { - if (!device) { - device = await this._waitForDevice(); - } - - // Wait a bit for device to be ready to receive last state - await new Promise((resolve) => setTimeout(resolve, 2000)); - - try { - // Still continue if sending did not work - this._sendAllStates(device); - } catch (err) { - console.error('midiin: failed to send last state on connect:', device.name, err); - } - - // Listen for incoming MIDI messages and for disconnection - device.addListener('midimessage', midiListener); - - await this._waitForDeviceDisconnect(device); - - device.removeListener('midimessage', midiListener); - device = null; // Clear var to trigger wait for connection - } - })(); - - return initialDevice; - } - - _waitForDevice() { - return new Promise((resolve) => { - const connListener = () => { - const device = getDevice(this.input, WebMidi.inputs); - if (device) { - logger(`[midi] device reconnected: ${device.name}`); - - WebMidi.removeListener('connected', connListener); - resolve(device); - } - }; - - WebMidi.addListener('connected', connListener); - }); - } - - _waitForDeviceDisconnect(device) { - return new Promise((resolve) => { - const disconnListener = (e) => { - if (e.port.name === device.name) { - logger(`[midi] device disconnected: ${device.name}`); - - WebMidi.removeListener('disconnected', disconnListener); - resolve(); - } - }; - - WebMidi.addListener('disconnected', disconnListener); - }); - } - - _onMidiMessage(e) { - const ccNum = e.dataBytes[0]; - const v = e.dataBytes[1]; - const chan = e.message.channel; - const scaled = v / 127; - - this._refs[ccNum] = scaled; - this._refsByChan[ccNum] ??= {}; - this._refsByChan[ccNum][chan] = scaled; - - this._saveState(undefined, ccNum, scaled); - this._saveState(chan, ccNum, scaled); - } - - _loadAllStates() { - Object.assign(this._refs, this._loadState(undefined)); - - for (let chan = 1; chan <= 16; chan++) { - this._refsByChan[chan] ??= {}; - Object.assign(this._refsByChan[chan], this._loadState(chan)); - } - } - - _loadState(chan) { - if (!this.stateKey) { - return {}; - } - - const initialDataRaw = localStorage.getItem( - `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, - ); - if (!initialDataRaw) { - return {}; - } - - try { - return JSON.parse(initialDataRaw); - } catch (err) { - console.warn( - `Failed to parse MIDI state from localStorage for input "${this.stateKey}" and channel "${chan}"`, - initialDataRaw, - err, - ); - return {}; - } - } - - _saveState(chan, cc, value) { - if (!this.stateKey) { - return; - } - - const state = this._loadState(chan); - state[cc] = value; - localStorage.setItem( - `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, - JSON.stringify(state), - ); - } - - _sendAllStates(device) { - const output = WebMidi.outputs.find((o) => o.name === device.name); - if (!output) { - return; - } - - for (const [chan, refs] of Object.entries(this._refsByChan)) { - const channel = Number(chan); - for (const [cc, value] of Object.entries(refs)) { - const ccn = Number(cc); - const scaled = Math.round(value * 127); - output.sendControlChange(ccn, scaled, channel); - } - } - } -} +/* +input.mjs - MIDI input wrapper +Copyright (C) 2022 Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ + +import { WebMidi } from 'webmidi'; +import { logger, ref } from '@strudel/core'; +import { getDevice } from './util.mjs'; + +export class MidiInput { + /** + * + * @param {string | number} input MIDI device name or index defaulting to 0 + */ + constructor(input) { + this.input = input; + this.stateKey = typeof input === 'string' ? input : undefined; // Saved state is not tracked for numeric index inputs + + this._refs = {}; + this._refsByChan = {}; + + this._loadAllStates(); + + this.initialDevice = this._startDeviceListener(); + } + + createCC(cc, chan) { + const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan]; + if (!(cc in lookupMap)) { + const initialState = this._loadState(chan); + lookupMap[cc] = initialState[cc] || 0; + } + + return ref(() => lookupMap[cc]); + } + + _startDeviceListener() { + const initialDevice = getDevice(this.input, WebMidi.inputs); + + // Background connection loop + (async () => { + const midiListener = this._onMidiMessage.bind(this); + let device = initialDevice; + + while (true) { + if (!device) { + device = await this._waitForDevice(); + } + + // Wait a bit for device to be ready to receive last state + await new Promise((resolve) => setTimeout(resolve, 2000)); + + try { + // Still continue if sending did not work + this._sendAllStates(device); + } catch (err) { + console.error('midiin: failed to send last state on connect:', device.name, err); + } + + // Listen for incoming MIDI messages and for disconnection + device.addListener('midimessage', midiListener); + + await this._waitForDeviceDisconnect(device); + + device.removeListener('midimessage', midiListener); + device = null; // Clear var to trigger wait for connection + } + })(); + + return initialDevice; + } + + _waitForDevice() { + return new Promise((resolve) => { + const connListener = () => { + const device = getDevice(this.input, WebMidi.inputs); + if (device) { + logger(`[midi] device reconnected: ${device.name}`); + + WebMidi.removeListener('connected', connListener); + resolve(device); + } + }; + + WebMidi.addListener('connected', connListener); + }); + } + + _waitForDeviceDisconnect(device) { + return new Promise((resolve) => { + const disconnListener = (e) => { + if (e.port.name === device.name) { + logger(`[midi] device disconnected: ${device.name}`); + + WebMidi.removeListener('disconnected', disconnListener); + resolve(); + } + }; + + WebMidi.addListener('disconnected', disconnListener); + }); + } + + _onMidiMessage(e) { + const ccNum = e.dataBytes[0]; + const v = e.dataBytes[1]; + const chan = e.message.channel; + const scaled = v / 127; + + this._refs[ccNum] = scaled; + this._refsByChan[ccNum] ??= {}; + this._refsByChan[ccNum][chan] = scaled; + + this._saveState(undefined, ccNum, scaled); + this._saveState(chan, ccNum, scaled); + } + + _loadAllStates() { + Object.assign(this._refs, this._loadState(undefined)); + + for (let chan = 1; chan <= 16; chan++) { + this._refsByChan[chan] ??= {}; + Object.assign(this._refsByChan[chan], this._loadState(chan)); + } + } + + _loadState(chan) { + if (!this.stateKey) { + return {}; + } + + const initialDataRaw = localStorage.getItem( + `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, + ); + if (!initialDataRaw) { + return {}; + } + + try { + return JSON.parse(initialDataRaw); + } catch (err) { + console.warn( + `Failed to parse MIDI state from localStorage for input "${this.stateKey}" and channel "${chan}"`, + initialDataRaw, + err, + ); + return {}; + } + } + + _saveState(chan, cc, value) { + if (!this.stateKey) { + return; + } + + const state = this._loadState(chan); + state[cc] = value; + localStorage.setItem( + `strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`, + JSON.stringify(state), + ); + } + + _sendAllStates(device) { + const output = WebMidi.outputs.find((o) => o.name === device.name); + if (!output) { + return; + } + + for (const [chan, refs] of Object.entries(this._refsByChan)) { + const channel = Number(chan); + for (const [cc, value] of Object.entries(refs)) { + const ccn = Number(cc); + const scaled = Math.round(value * 127); + output.sendControlChange(ccn, scaled, channel); + } + } + } +} diff --git a/packages/midi/util.mjs b/packages/midi/util.mjs index 99158e68..f02f1f64 100644 --- a/packages/midi/util.mjs +++ b/packages/midi/util.mjs @@ -1,44 +1,44 @@ -/* -util.mjs - MIDI utility functions -Copyright (C) 2022 Strudel contributors - see -This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . -*/ - -import { Input, Output } from 'webmidi'; - -/** - * Get a string listing device names for error messages. - * @param {Input[] | Output[]} devices - * @returns {string} - */ -export function getMidiDeviceNamesString(devices) { - return devices.map((o) => `'${o.name}'`).join(' | '); -} - -/** - * Look up a device by index or name. Otherwise return a default device, or fail if none are connected. - * - * @param {string | number} indexOrName - * @param {Input[] | Output[]} devices - * @returns {Input | Output | undefined} - */ -export function getDevice(indexOrName, devices) { - if (typeof indexOrName === 'number') { - return devices[indexOrName]; - } - const byName = (name) => devices.find((output) => output.name.includes(name)); - if (typeof indexOrName === 'string') { - return byName(indexOrName); - } - // attempt to default to first IAC device if none is specified - const IACOutput = byName('IAC'); - const device = IACOutput ?? devices[0]; - if (!device) { - if (!devices.length) { - throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); - } - throw new Error(`🔌 Default MIDI device not found. Use one of ${getMidiDeviceNamesString(devices)}`); - } - - return device; -} +/* +util.mjs - MIDI utility functions +Copyright (C) 2022 Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ + +import { Input, Output } from 'webmidi'; + +/** + * Get a string listing device names for error messages. + * @param {Input[] | Output[]} devices + * @returns {string} + */ +export function getMidiDeviceNamesString(devices) { + return devices.map((o) => `'${o.name}'`).join(' | '); +} + +/** + * Look up a device by index or name. Otherwise return a default device, or fail if none are connected. + * + * @param {string | number} indexOrName + * @param {Input[] | Output[]} devices + * @returns {Input | Output | undefined} + */ +export function getDevice(indexOrName, devices) { + if (typeof indexOrName === 'number') { + return devices[indexOrName]; + } + const byName = (name) => devices.find((output) => output.name.includes(name)); + if (typeof indexOrName === 'string') { + return byName(indexOrName); + } + // attempt to default to first IAC device if none is specified + const IACOutput = byName('IAC'); + const device = IACOutput ?? devices[0]; + if (!device) { + if (!devices.length) { + throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); + } + throw new Error(`🔌 Default MIDI device not found. Use one of ${getMidiDeviceNamesString(devices)}`); + } + + return device; +} From bc9e97b9ac2473095d6b807297f530269b512ae8 Mon Sep 17 00:00:00 2001 From: Nick Matantsev Date: Thu, 15 Jan 2026 00:13:49 -0500 Subject: [PATCH 047/200] Add comments --- packages/midi/input.mjs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/midi/input.mjs b/packages/midi/input.mjs index 498e6fe2..b9f07581 100644 --- a/packages/midi/input.mjs +++ b/packages/midi/input.mjs @@ -8,6 +8,11 @@ import { WebMidi } from 'webmidi'; import { logger, ref } from '@strudel/core'; import { getDevice } from './util.mjs'; +/** + * MIDI input device wrapper that manages connection and reconnection, tracks + * persisted CC states, etc. These instances are long-lived and are maintained as singletons + * (keyed globally by input string/number). + */ export class MidiInput { /** * @@ -25,6 +30,11 @@ export class MidiInput { this.initialDevice = this._startDeviceListener(); } + /** + * Implementation for the cc() factory function tied to this specific input. + * @param {number} cc MIDI CC number + * @param {number | undefined} chan MIDI channel (1-16) or undefined for all channels + */ createCC(cc, chan) { const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan]; if (!(cc in lookupMap)) { @@ -71,6 +81,7 @@ export class MidiInput { return initialDevice; } + // Returns a promise that resolves when the specified device is connected _waitForDevice() { return new Promise((resolve) => { const connListener = () => { @@ -87,6 +98,7 @@ export class MidiInput { }); } + // Returns a promise that resolves when the specified device is disconnected _waitForDeviceDisconnect(device) { return new Promise((resolve) => { const disconnListener = (e) => { @@ -162,6 +174,7 @@ export class MidiInput { ); } + // Send CC values back to device to restore encoders and motorized sliders _sendAllStates(device) { const output = WebMidi.outputs.find((o) => o.name === device.name); if (!output) { From 2e7ec9ea272b368e4c9b4fe98990d0e662c0f57a Mon Sep 17 00:00:00 2001 From: Dsm0 Date: Thu, 15 Jan 2026 19:27:54 -0800 Subject: [PATCH 048/200] variable declarations with active patterns now have highlighting --- packages/core/repl.mjs | 56 +++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index e3efdd3e..09c76d60 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -126,7 +126,23 @@ export function repl({ ); } - // Helper function to handle single label code block storage + // helper + function cleanupConflictingRanges(codeBlocks, currentKey, newRange) { + for (const [existingKey, existingBlock] of Object.entries(codeBlocks)) { + if (existingKey === currentKey) continue; + if (!existingBlock.range) continue; + + const [existingStart, existingEnd] = existingBlock.range; + const [newStart, newEnd] = newRange; + + // If ranges overlap (not just touch), remove the stale block + if (!(newEnd <= existingStart || newStart >= existingEnd)) { + delete codeBlocks[existingKey]; + } + } + } + + // helper function handleSingleLabelBlock(label, code, options, meta) { // Detect if this block contains a non-inline widget // The activeVisualizer is now provided by the transpiler for all labels @@ -147,18 +163,30 @@ export function repl({ activeVisualizer: activeVisualizer, // Store the widget type if present, null otherwise }; - // Clean up any blocks with conflicting ranges - for (const [existingKey, existingBlock] of Object.entries(codeBlocks)) { - if (existingKey !== label.name && existingBlock.range && options.range) { - const [existingStart, existingEnd] = existingBlock.range; - const [newStart, newEnd] = options.range; + // Clean up any blocks with conflicting ranges (including declaration blocks) + cleanupConflictingRanges(codeBlocks, label.name, options.range); + } - // If ranges overlap (not just touch), remove the stale block - if (!(newEnd <= existingStart || newStart >= existingEnd)) { - delete codeBlocks[existingKey]; - } - } - } + // helper + // These blocks return silence but may contain mini notation strings that need highlighting + function handleDeclarationBlock(code, options, meta) { + const range = options.range || []; + if (range.length < 2) return; + + const blockKey = `_decl:${range[0]}:${range[1]}`; + + codeBlocks[blockKey] = { + code: code, + range: range, + labels: [], + miniLocations: meta?.miniLocations || [], + widgets: meta?.widgets || [], + sliders: meta?.sliders || [], + activeVisualizer: null, + }; + + // Clean up any overlapping declaration blocks + cleanupConflictingRanges(codeBlocks, blockKey, range); } const hush = function () { @@ -443,6 +471,10 @@ export function repl({ throw new Error( 'anonymous labels disabled for block based evaluation (see https://strudel.cc/blog/#label-notation)', ); + } else { + // Declaration block (variable/function that returns silence) + // Store it so its miniLocations are preserved for highlighting patterns stored in variables + handleDeclarationBlock(code, options, meta); } meta.miniLocations = collectFromBlocks('miniLocations'); From f9d0e154e841997f4260b9ceb81faa58bff5fee2 Mon Sep 17 00:00:00 2001 From: robojumper Date: Sat, 17 Jan 2026 17:47:48 +0100 Subject: [PATCH 049/200] fix: performance issues when reference is open --- website/src/repl/components/panel/Reference.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index c03ac0cf..4c48b6a3 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { memo, useMemo, useState } from 'react'; import jsdocJson from '../../../../../doc.json'; import { Textbox } from '../textbox/Textbox'; @@ -35,7 +35,7 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; -export function Reference() { +export const Reference = memo(function Reference() { const [search, setSearch] = useState(''); const visibleFunctions = useMemo(() => { @@ -112,4 +112,4 @@ export function Reference() {
); -} +}); From 77076a1c3eeed4a1d9f070336c8805ed9d6349b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 17 Jan 2026 19:12:23 +0100 Subject: [PATCH 050/200] Improve reference search/filtering semantics --- .../src/repl/components/panel/Reference.jsx | 125 +++++++++++++----- 1 file changed, 92 insertions(+), 33 deletions(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index c943db14..24d4a677 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import jsdocJson from '../../../../../doc.json'; import { Textbox } from '../textbox/Textbox'; @@ -46,6 +46,7 @@ const getInnerText = (html) => { export function Reference() { const [search, setSearch] = useState(''); const [selectedTag, setSelectedTag] = useState(null); + const [selectedFunction, setSelectedFunction] = useState(null); const toggleTag = (tag) => { if (selectedTag === tag) { @@ -55,7 +56,7 @@ export function Reference() { } }; - const visibleFunctions = useMemo(() => { + const searchVisibleFunctions = useMemo(() => { return availableFunctions.filter((entry) => { if (selectedTag) { if (!(entry.tags || ['untagged']).includes(selectedTag)) { @@ -63,6 +64,14 @@ export function Reference() { } } + // if (entry.name === selectedFunction) { + // return true; + // } + + // if (!selectedTag) { + // return false; + // } + if (!search) { return true; } @@ -75,6 +84,19 @@ export function Reference() { }); }, [search, selectedTag]); + const detailVisibleFunctions = useMemo(() => { + return searchVisibleFunctions.filter((x) => { + if (selectedTag === null) { + if (search) { + return true; + } + return x.name === selectedFunction; + } else { + return true; + } + }); + }, [searchVisibleFunctions, selectedFunction, selectedTag]); + const tagCounts = {}; for (const doc of availableFunctions) { (doc.tags || ['untagged']).forEach((t) => { @@ -84,42 +106,60 @@ export function Reference() { }); } + const onSearchTagFilterClick = () => { + const container = document.getElementById('reference-container'); + container.scrollTo(0, 0); + setSelectedTag(null); + setSelectedFunction(null); + }; + + useEffect(() => { + if (selectedFunction) { + const el = document.getElementById(`doc-${selectedFunction}`); + const container = document.getElementById('reference-container'); + container.scrollTo(0, el.offsetTop); + } + }, [selectedFunction]); + return ( - //
- //
- //
-
+
- -
- {Object.entries(tagCounts) - .sort(([a], [b]) => a.localeCompare(b)) - .map(([t, count]) => ( - - toggleTag(t)} - > - {t} ({count}) - {' '} - - ))} -
+ { + setSelectedFunction(null); + setSearch(e); + }} + />
-
- {visibleFunctions.map((entry, i) => ( + {selectedTag && ( +
+ + {selectedTag ? selectedTag + ' x' : 'Filter by tag'} + +
+ )} +
+ {searchVisibleFunctions.map((entry, i) => ( <> { - const el = document.getElementById(`doc-${entry.name}`); - const container = document.getElementById('reference-container'); - container.scrollTo(0, el.offsetTop); + if (entry.name === selectedFunction) { + setSelectedFunction(null); + } else { + setSelectedFunction(entry.name); + } }} > {entry.name} @@ -134,11 +174,28 @@ export function Reference() { >

API Reference

-

+

This is the long list of functions you can use. Remember that you don't need to remember all of those and that you can already make music with a small set of functions!

- {visibleFunctions.map((entry, i) => ( +
+ {detailVisibleFunctions.map((entry, i) => (

@@ -170,7 +227,9 @@ export function Reference() { ))}

- ))} + )) ||

Searcb or select a tag to get started.

} + {detailVisibleFunctions.length > 0 &&
} + {/* {!selectedTag &&

Select a tag to see the functions.

} */}
From 42d0bfadceba9cae8b276eba55aea578ddc28436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 17 Jan 2026 20:08:23 +0100 Subject: [PATCH 051/200] Clean up --- .../src/repl/components/panel/Reference.jsx | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 8a018ff9..950949b9 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -25,13 +25,13 @@ const availableFunctions = (() => { seen.add(s); // Swap `doc.name` in for `s` in the list of synonyms const synonymsWithDoc = [doc.name, ...synonyms].filter((x) => x && x !== s); - // functions.push({ - // ...doc, - // name: s, // update names for the synonym - // longname: s, - // synonyms: synonymsWithDoc, - // synonyms_text: synonymsWithDoc.join(', '), - // }); + functions.push({ + ...doc, + name: s, // update names for the synonym + longname: s, + synonyms: synonymsWithDoc, + synonyms_text: synonymsWithDoc.join(', '), + }); } } return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name)); @@ -64,14 +64,6 @@ export function Reference() { } } - // if (entry.name === selectedFunction) { - // return true; - // } - - // if (!selectedTag) { - // return false; - // } - if (!search) { return true; } @@ -228,7 +220,6 @@ export function Reference() {
)) ||

Searcb or select a tag to get started.

} {detailVisibleFunctions.length > 0 &&
} - {/* {!selectedTag &&

Select a tag to see the functions.

} */}
From 99cf256ee253b12057775e689dfd3c73c943b504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 17 Jan 2026 20:11:26 +0100 Subject: [PATCH 052/200] Clean up 2 --- website/src/repl/components/panel/Reference.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 950949b9..e5bb5600 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -99,8 +99,6 @@ export function Reference() { } const onSearchTagFilterClick = () => { - const container = document.getElementById('reference-container'); - container.scrollTo(0, 0); setSelectedTag(null); setSelectedFunction(null); }; @@ -133,7 +131,7 @@ export function Reference() { className="text-foreground border-2 border-gray-500 px-1 py-0.5 my-2 rounded-md cursor-pointer font-sans" onClick={onSearchTagFilterClick} > - {selectedTag ? selectedTag + ' x' : 'Filter by tag'} + {selectedTag}
)} From 7bc8576a68ed959dd563776ed54c47b053d73dd7 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 17 Jan 2026 11:16:58 -0800 Subject: [PATCH 053/200] Make kabelsalat stereo out --- packages/superdough/superdough.mjs | 2 +- packages/superdough/worklets.mjs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index ec44e8af..03a4c3da 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -613,7 +613,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) // Kabelsalat if (fx.workletSrc !== undefined) { - const workletNode = getWorklet(ac, 'generic-processor', {}); + const workletNode = getWorklet(ac, 'generic-processor', {}, { outputChannelCount: [2] }); chain.connect(workletNode); const workletSrc = fx.workletSrc .replace(/\bpat\[(\d+)\]/g, (_, i) => fx.workletInputs[i]) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 6969ee2c..26a0fa66 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1540,12 +1540,20 @@ class GenericProcessor extends AudioWorkletProcessor { this.gateNode?.setValue(0); this.gateEnded = true; } - const outL = outputs[0][0]; - const outR = outputs[0][1] ?? outputs[0][0]; + const output = outputs[0]; + const outL = output[0]; + const outR = output[1]; for (let n = 0; n < blockSize; n++) { this.genSample(this.playPos, this.nodes, input ? input[n] : 0, this.registers, this.outputs, this.sources); - outL[n] = this.outputs[0]; - outR[n] = this.outputs[1]; + const left = this.outputs[0]; + const right = this.outputs[1]; + // Spread to stereo if possible; else mixdown to mono + if (outR) { + outL[n] = left; + outR[n] = right; + } else { + outL[n] = 0.5 * (left + right); + } this.playPos += 1 / sampleRate; } return true; From d198910a86fcaecd658ef528bfa6d7884e93490b Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 15:26:28 -0800 Subject: [PATCH 054/200] Publish - @strudel/codemirror@1.2.7 - @strudel/core@1.2.6 - @strudel/csound@1.2.7 - @strudel/draw@1.2.6 - @strudel/embed@1.1.2 - @strudel/gamepad@1.2.6 - @strudel/hydra@1.2.6 - @strudel/midi@1.2.7 - @strudel/mini@1.2.6 - mondolang@1.1.2 - @strudel/mondo@1.1.6 - @strudel/motion@1.2.6 - @strudel/mqtt@1.2.6 - @strudel/osc@1.3.1 - @strudel/reference@1.2.2 - @strudel/repl@1.2.8 - @strudel/sampler@0.2.4 - @strudel/serial@1.2.6 - @strudel/soundfonts@1.2.7 - superdough@1.2.7 - supradough@1.2.5 - @strudel/tonal@1.2.6 - @strudel/transpiler@1.2.6 - vite-plugin-bundle-audioworklet@0.1.2 - @strudel/web@1.2.7 - @strudel/webaudio@1.2.7 - @strudel/xen@1.2.6 --- packages/codemirror/package.json | 2 +- packages/core/package.json | 2 +- packages/csound/package.json | 2 +- packages/draw/package.json | 2 +- packages/embed/package.json | 2 +- packages/gamepad/package.json | 2 +- packages/hydra/package.json | 2 +- packages/midi/package.json | 2 +- packages/mini/package.json | 2 +- packages/mondo/package.json | 2 +- packages/mondough/package.json | 2 +- packages/motion/package.json | 2 +- packages/mqtt/package.json | 2 +- packages/osc/package.json | 2 +- packages/reference/package.json | 2 +- packages/repl/package.json | 2 +- packages/sampler/package.json | 2 +- packages/serial/package.json | 2 +- packages/soundfonts/package.json | 2 +- packages/superdough/package.json | 2 +- packages/supradough/package.json | 2 +- packages/tonal/package.json | 2 +- packages/transpiler/package.json | 2 +- packages/vite-plugin-bundle-audioworklet/package.json | 2 +- packages/web/package.json | 2 +- packages/webaudio/package.json | 2 +- packages/xen/package.json | 2 +- 27 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index a3735491..2f87124a 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/codemirror", - "version": "1.2.6", + "version": "1.2.7", "description": "Codemirror Extensions for Strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/core/package.json b/packages/core/package.json index abb54e9c..e558c574 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/core", - "version": "1.2.5", + "version": "1.2.6", "description": "Port of Tidal Cycles to JavaScript", "main": "index.mjs", "type": "module", diff --git a/packages/csound/package.json b/packages/csound/package.json index 762131d6..03975275 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/csound", - "version": "1.2.6", + "version": "1.2.7", "description": "csound bindings for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/draw/package.json b/packages/draw/package.json index b0af8418..4a6f9a5a 100644 --- a/packages/draw/package.json +++ b/packages/draw/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/draw", - "version": "1.2.5", + "version": "1.2.6", "description": "Helpers for drawing with Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/embed/package.json b/packages/embed/package.json index ffafdc8b..84a8cd6f 100644 --- a/packages/embed/package.json +++ b/packages/embed/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/embed", - "version": "1.1.1", + "version": "1.1.2", "description": "Embeddable Web Component to load a Strudel REPL into an iframe", "main": "embed.js", "type": "module", diff --git a/packages/gamepad/package.json b/packages/gamepad/package.json index 6a07df96..21ad8eef 100644 --- a/packages/gamepad/package.json +++ b/packages/gamepad/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/gamepad", - "version": "1.2.5", + "version": "1.2.6", "description": "Gamepad Inputs for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/hydra/package.json b/packages/hydra/package.json index 16d6f0f4..67419ce0 100644 --- a/packages/hydra/package.json +++ b/packages/hydra/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/hydra", - "version": "1.2.5", + "version": "1.2.6", "description": "Hydra integration for strudel", "main": "hydra.mjs", "type": "module", diff --git a/packages/midi/package.json b/packages/midi/package.json index 11fe9a3f..36891127 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/midi", - "version": "1.2.6", + "version": "1.2.7", "description": "Midi API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mini/package.json b/packages/mini/package.json index b9a50fb6..43230655 100644 --- a/packages/mini/package.json +++ b/packages/mini/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mini", - "version": "1.2.5", + "version": "1.2.6", "description": "Mini notation for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mondo/package.json b/packages/mondo/package.json index ebdbd329..1dba882e 100644 --- a/packages/mondo/package.json +++ b/packages/mondo/package.json @@ -1,6 +1,6 @@ { "name": "mondolang", - "version": "1.1.1", + "version": "1.1.2", "description": "a language for functional composition that translates to js", "main": "mondo.mjs", "type": "module", diff --git a/packages/mondough/package.json b/packages/mondough/package.json index 9b170fde..919c5da2 100644 --- a/packages/mondough/package.json +++ b/packages/mondough/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mondo", - "version": "1.1.5", + "version": "1.1.6", "description": "mondo notation for strudel", "main": "mondough.mjs", "type": "module", diff --git a/packages/motion/package.json b/packages/motion/package.json index fd1fae89..dda3c368 100644 --- a/packages/motion/package.json +++ b/packages/motion/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/motion", - "version": "1.2.5", + "version": "1.2.6", "description": "DeviceMotion API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/mqtt/package.json b/packages/mqtt/package.json index 492d915d..9fa9da9b 100644 --- a/packages/mqtt/package.json +++ b/packages/mqtt/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/mqtt", - "version": "1.2.5", + "version": "1.2.6", "description": "MQTT API for strudel", "main": "mqtt.mjs", "type": "module", diff --git a/packages/osc/package.json b/packages/osc/package.json index 00faffbd..f1bbbdd5 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/osc", - "version": "1.3.0", + "version": "1.3.1", "description": "OSC messaging for strudel", "main": "osc.mjs", "bin": "./server.js", diff --git a/packages/reference/package.json b/packages/reference/package.json index 6d1fa180..783c1bbc 100644 --- a/packages/reference/package.json +++ b/packages/reference/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/reference", - "version": "1.2.1", + "version": "1.2.2", "description": "Headless reference of all strudel functions", "main": "index.mjs", "type": "module", diff --git a/packages/repl/package.json b/packages/repl/package.json index e5216316..003086a9 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "1.2.7", + "version": "1.2.8", "description": "Strudel REPL as a Web Component", "module": "index.mjs", "publishConfig": { diff --git a/packages/sampler/package.json b/packages/sampler/package.json index b9ea4c2a..a960fbbe 100644 --- a/packages/sampler/package.json +++ b/packages/sampler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/sampler", - "version": "0.2.3", + "version": "0.2.4", "description": "", "keywords": [ "tidalcycles", diff --git a/packages/serial/package.json b/packages/serial/package.json index 9dc6c899..e98a897e 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/serial", - "version": "1.2.5", + "version": "1.2.6", "description": "Webserial API for strudel", "main": "serial.mjs", "type": "module", diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index 9bf0c400..dfdce782 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/soundfonts", - "version": "1.2.6", + "version": "1.2.7", "description": "Soundsfont support for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 78db8338..c6ed7c64 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "1.2.6", + "version": "1.2.7", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", diff --git a/packages/supradough/package.json b/packages/supradough/package.json index ee3869d0..10cf8df5 100644 --- a/packages/supradough/package.json +++ b/packages/supradough/package.json @@ -1,6 +1,6 @@ { "name": "supradough", - "version": "1.2.4", + "version": "1.2.5", "description": "platform agnostic synth and sampler intended for live coding. a reimplementation of superdough.", "main": "index.mjs", "type": "module", diff --git a/packages/tonal/package.json b/packages/tonal/package.json index 14ffabc7..a6a539c9 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/tonal", - "version": "1.2.5", + "version": "1.2.6", "description": "Tonal functions for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/transpiler/package.json b/packages/transpiler/package.json index 51355044..7b2a053f 100644 --- a/packages/transpiler/package.json +++ b/packages/transpiler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/transpiler", - "version": "1.2.5", + "version": "1.2.6", "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.", "main": "index.mjs", "type": "module", diff --git a/packages/vite-plugin-bundle-audioworklet/package.json b/packages/vite-plugin-bundle-audioworklet/package.json index 198a4f9c..524af470 100644 --- a/packages/vite-plugin-bundle-audioworklet/package.json +++ b/packages/vite-plugin-bundle-audioworklet/package.json @@ -1,7 +1,7 @@ { "name": "vite-plugin-bundle-audioworklet", "main": "./vite-plugin-bundle-audioworklet.js", - "version": "0.1.1", + "version": "0.1.2", "description": "", "keywords": [ "vite", diff --git a/packages/web/package.json b/packages/web/package.json index f02d8613..40ba83c5 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/web", - "version": "1.2.6", + "version": "1.2.7", "description": "Easy to setup, opiniated bundle of Strudel for the browser.", "module": "web.mjs", "publishConfig": { diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index f0bc4043..2815c739 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/webaudio", - "version": "1.2.6", + "version": "1.2.7", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", diff --git a/packages/xen/package.json b/packages/xen/package.json index c178c5de..e7d9d203 100644 --- a/packages/xen/package.json +++ b/packages/xen/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/xen", - "version": "1.2.5", + "version": "1.2.6", "description": "Xenharmonic API for strudel", "main": "index.mjs", "type": "module", From 85e787214b476f0f6f03578e210269711a0175f6 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 15:59:38 -0800 Subject: [PATCH 055/200] rollback_supradough --- packages/superdough/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/package.json b/packages/superdough/package.json index c6ed7c64..23b64b29 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "1.2.7", + "version": "1.2.4", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", From 166985a05576a5cb88f2965748f0ef61d3fadedd Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 16:00:34 -0800 Subject: [PATCH 056/200] spr --- packages/osc/server.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packages/osc/server.js diff --git a/packages/osc/server.js b/packages/osc/server.js old mode 100644 new mode 100755 From 12b89f9f88b01de01de8a18e13f110cea499bcfa Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 16:12:51 -0800 Subject: [PATCH 057/200] rollback supradough --- packages/supradough/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supradough/package.json b/packages/supradough/package.json index 10cf8df5..ee3869d0 100644 --- a/packages/supradough/package.json +++ b/packages/supradough/package.json @@ -1,6 +1,6 @@ { "name": "supradough", - "version": "1.2.5", + "version": "1.2.4", "description": "platform agnostic synth and sampler intended for live coding. a reimplementation of superdough.", "main": "index.mjs", "type": "module", From a3ac9646f5cb86958bb6193cddd5f30f78a57368 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 18:54:39 -0800 Subject: [PATCH 058/200] inc webaudio --- packages/webaudio/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index 2815c739..18bd3290 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/webaudio", - "version": "1.2.7", + "version": "1.2.8", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", From f4f04a7d574684aea78a757ef0d92370225cb27d Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 19:01:10 -0800 Subject: [PATCH 059/200] Publish - @strudel/codemirror@1.2.8 - @strudel/csound@1.2.8 - @strudel/midi@1.2.8 - @strudel/osc@1.3.2 - @strudel/repl@1.2.9 - @strudel/soundfonts@1.2.8 - superdough@1.2.5 - supradough@1.2.4 - @strudel/web@1.2.8 - @strudel/webaudio@1.2.9 --- packages/codemirror/package.json | 2 +- packages/csound/package.json | 2 +- packages/midi/package.json | 2 +- packages/osc/package.json | 2 +- packages/repl/package.json | 2 +- packages/soundfonts/package.json | 2 +- packages/superdough/package.json | 2 +- packages/web/package.json | 2 +- packages/webaudio/package.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 2f87124a..9ad04097 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/codemirror", - "version": "1.2.7", + "version": "1.2.8", "description": "Codemirror Extensions for Strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/csound/package.json b/packages/csound/package.json index 03975275..19ba6819 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/csound", - "version": "1.2.7", + "version": "1.2.8", "description": "csound bindings for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/midi/package.json b/packages/midi/package.json index 36891127..38d0f0ca 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/midi", - "version": "1.2.7", + "version": "1.2.8", "description": "Midi API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/osc/package.json b/packages/osc/package.json index f1bbbdd5..56924225 100644 --- a/packages/osc/package.json +++ b/packages/osc/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/osc", - "version": "1.3.1", + "version": "1.3.2", "description": "OSC messaging for strudel", "main": "osc.mjs", "bin": "./server.js", diff --git a/packages/repl/package.json b/packages/repl/package.json index 003086a9..d2803492 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "1.2.8", + "version": "1.2.9", "description": "Strudel REPL as a Web Component", "module": "index.mjs", "publishConfig": { diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index dfdce782..2403adfc 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/soundfonts", - "version": "1.2.7", + "version": "1.2.8", "description": "Soundsfont support for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 23b64b29..d1e9b45f 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "1.2.4", + "version": "1.2.5", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", diff --git a/packages/web/package.json b/packages/web/package.json index 40ba83c5..8be1f89d 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/web", - "version": "1.2.7", + "version": "1.2.8", "description": "Easy to setup, opiniated bundle of Strudel for the browser.", "module": "web.mjs", "publishConfig": { diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index 18bd3290..6b6980e9 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/webaudio", - "version": "1.2.8", + "version": "1.2.9", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", From f0cb96dcad64e98cb7c692fc3949464317db2611 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 19:02:25 -0800 Subject: [PATCH 060/200] patch --- lerna-debug.log | 211 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 lerna-debug.log diff --git a/lerna-debug.log b/lerna-debug.log new file mode 100644 index 00000000..3c15de50 --- /dev/null +++ b/lerna-debug.log @@ -0,0 +1,211 @@ +0 silly argv { +0 silly argv _: [ 'version' ], +0 silly argv private: false, +0 silly argv lernaVersion: '8.1.9', +0 silly argv '$0': 'node_modules/lerna/dist/cli.js' +0 silly argv } +1 notice cli v8.1.9 +2 verbose packageConfigs Explicit "packages" configuration found in lerna.json. Resolving packages using the configured glob(s): ["packages/*"] +3 verbose rootPath /Users/jaderose/Documents/Github/cstrudel/strudel +4 info versioning independent +5 silly isAnythingCommitted +6 verbose isAnythingCommitted 1 +7 silly getCurrentBranch +8 verbose currentBranch main +9 silly remoteBranchExists +10 silly isBehindUpstream +11 silly isBehindUpstream main is behind origin/main by 0 commit(s) and ahead by 4 +12 silly hasTags +13 verbose hasTags true +14 silly git-describe.sync "@strudel/codemirror@1.2.7-4-ga3ac9646" => {"lastTagName":"@strudel/codemirror@1.2.7","lastVersion":"1.2.7","refCount":"4","sha":"a3ac9646","isDirty":false} +15 info Looking for changed packages since @strudel/codemirror@1.2.7 +16 silly checking diff packages/codemirror +17 silly no diff found in @strudel/codemirror +18 silly checking diff packages/core +19 silly no diff found in @strudel/core +20 silly checking diff packages/csound +21 silly no diff found in @strudel/csound +22 silly checking diff packages/desktopbridge +23 silly no diff found in @strudel/desktopbridge +24 silly checking diff packages/draw +25 silly no diff found in @strudel/draw +26 silly checking diff packages/embed +27 silly no diff found in @strudel/embed +28 silly checking diff packages/gamepad +29 silly no diff found in @strudel/gamepad +30 silly checking diff packages/hs2js +31 silly no diff found in hs2js +32 silly checking diff packages/hydra +33 silly no diff found in @strudel/hydra +34 silly checking diff packages/midi +35 silly no diff found in @strudel/midi +36 silly checking diff packages/mini +37 silly no diff found in @strudel/mini +38 silly checking diff packages/mondo +39 silly no diff found in mondolang +40 silly checking diff packages/mondough +41 silly no diff found in @strudel/mondo +42 silly checking diff packages/motion +43 silly no diff found in @strudel/motion +44 silly checking diff packages/mqtt +45 silly no diff found in @strudel/mqtt +46 silly checking diff packages/osc +47 silly found diff in packages/osc/server.js +48 verbose filtered diff [ 'packages/osc/server.js' ] +49 silly checking diff packages/reference +50 silly no diff found in @strudel/reference +51 silly checking diff packages/repl +52 silly no diff found in @strudel/repl +53 silly checking diff packages/sampler +54 silly no diff found in @strudel/sampler +55 silly checking diff packages/serial +56 silly no diff found in @strudel/serial +57 silly checking diff packages/soundfonts +58 silly no diff found in @strudel/soundfonts +59 silly checking diff packages/superdough +60 silly found diff in packages/superdough/package.json +61 verbose filtered diff [ 'packages/superdough/package.json' ] +62 silly checking diff packages/supradough +63 silly found diff in packages/supradough/package.json +64 verbose filtered diff [ 'packages/supradough/package.json' ] +65 silly checking diff packages/tidal +66 silly no diff found in @strudel/tidal +67 silly checking diff packages/tonal +68 silly no diff found in @strudel/tonal +69 silly checking diff packages/transpiler +70 silly no diff found in @strudel/transpiler +71 silly checking diff packages/vite-plugin-bundle-audioworklet +72 silly no diff found in vite-plugin-bundle-audioworklet +73 silly checking diff packages/web +74 silly no diff found in @strudel/web +75 silly checking diff packages/webaudio +76 silly found diff in packages/webaudio/package.json +77 verbose filtered diff [ 'packages/webaudio/package.json' ] +78 silly checking diff packages/xen +79 silly no diff found in @strudel/xen +80 verbose updated @strudel/codemirror +81 verbose updated @strudel/csound +82 verbose updated @strudel/midi +83 verbose updated @strudel/osc +84 verbose updated @strudel/repl +85 verbose updated @strudel/soundfonts +86 verbose updated superdough +87 verbose updated supradough +88 verbose updated @strudel/web +89 verbose updated @strudel/webaudio +90 verbose git-describe undefined => "@strudel/codemirror@1.2.7-4-ga3ac9646" +91 silly git-describe parsed => {"lastTagName":"@strudel/codemirror@1.2.7","lastVersion":"1.2.7","refCount":"4","sha":"a3ac9646","isDirty":false} +92 info execute Skipping releases +93 silly lifecycle No script for "preversion" in "@strudel/monorepo", continuing +94 silly lifecycle No script for "preversion" in "@strudel/osc", continuing +95 silly lifecycle No script for "preversion" in "superdough", continuing +96 silly lifecycle No script for "preversion" in "supradough", continuing +97 verbose version supradough has no lockfile. Skipping lockfile update. +98 verbose version @strudel/osc has no lockfile. Skipping lockfile update. +99 verbose version superdough has no lockfile. Skipping lockfile update. +100 silly lifecycle No script for "version" in "supradough", continuing +101 silly lifecycle No script for "version" in "superdough", continuing +102 silly lifecycle No script for "preversion" in "@strudel/codemirror", continuing +103 silly lifecycle No script for "preversion" in "@strudel/webaudio", continuing +104 silly lifecycle No script for "version" in "@strudel/osc", continuing +105 verbose version @strudel/webaudio has no lockfile. Skipping lockfile update. +106 verbose version @strudel/codemirror has no lockfile. Skipping lockfile update. +107 silly lifecycle No script for "version" in "@strudel/codemirror", continuing +108 silly lifecycle No script for "version" in "@strudel/webaudio", continuing +109 silly lifecycle No script for "preversion" in "@strudel/csound", continuing +110 silly lifecycle No script for "preversion" in "@strudel/midi", continuing +111 silly lifecycle No script for "preversion" in "@strudel/soundfonts", continuing +112 silly lifecycle No script for "preversion" in "@strudel/web", continuing +113 verbose version @strudel/csound has no lockfile. Skipping lockfile update. +114 verbose version @strudel/midi has no lockfile. Skipping lockfile update. +115 verbose version @strudel/soundfonts has no lockfile. Skipping lockfile update. +116 verbose version @strudel/web has no lockfile. Skipping lockfile update. +117 silly lifecycle No script for "version" in "@strudel/midi", continuing +118 silly lifecycle No script for "version" in "@strudel/csound", continuing +119 silly lifecycle No script for "version" in "@strudel/soundfonts", continuing +120 silly lifecycle No script for "preversion" in "@strudel/repl", continuing +121 verbose version @strudel/repl has no lockfile. Skipping lockfile update. +122 silly lifecycle No script for "version" in "@strudel/web", continuing +123 silly lifecycle No script for "version" in "@strudel/repl", continuing +124 silly lifecycle No script for "version" in "@strudel/monorepo", continuing +125 verbose version Updating root pnpm-lock.yaml +126 silly version Skipped applying prettier to ignored file: packages/supradough/package.json +127 silly version Skipped applying prettier to ignored file: packages/superdough/package.json +128 silly version Skipped applying prettier to ignored file: packages/osc/package.json +129 silly version Skipped applying prettier to ignored file: packages/codemirror/package.json +130 silly version Skipped applying prettier to ignored file: packages/webaudio/package.json +131 silly version Skipped applying prettier to ignored file: packages/midi/package.json +132 silly version Skipped applying prettier to ignored file: packages/csound/package.json +133 silly version Skipped applying prettier to ignored file: packages/soundfonts/package.json +134 silly version Skipped applying prettier to ignored file: packages/web/package.json +135 silly version Skipped applying prettier to ignored file: packages/repl/package.json +136 silly version Skipped applying prettier to ignored file: pnpm-lock.yaml +137 silly gitAdd [ +137 silly gitAdd 'packages/supradough/package.json', +137 silly gitAdd 'packages/superdough/package.json', +137 silly gitAdd 'packages/osc/package.json', +137 silly gitAdd 'packages/codemirror/package.json', +137 silly gitAdd 'packages/webaudio/package.json', +137 silly gitAdd 'packages/midi/package.json', +137 silly gitAdd 'packages/csound/package.json', +137 silly gitAdd 'packages/soundfonts/package.json', +137 silly gitAdd 'packages/web/package.json', +137 silly gitAdd 'packages/repl/package.json', +137 silly gitAdd 'pnpm-lock.yaml' +137 silly gitAdd ] +138 silly gitCommit Publish +138 silly gitCommit +138 silly gitCommit - @strudel/codemirror@1.2.8 +138 silly gitCommit - @strudel/csound@1.2.8 +138 silly gitCommit - @strudel/midi@1.2.8 +138 silly gitCommit - @strudel/osc@1.3.2 +138 silly gitCommit - @strudel/repl@1.2.9 +138 silly gitCommit - @strudel/soundfonts@1.2.8 +138 silly gitCommit - superdough@1.2.5 +138 silly gitCommit - supradough@1.2.4 +138 silly gitCommit - @strudel/web@1.2.8 +138 silly gitCommit - @strudel/webaudio@1.2.9 +139 verbose git [ +139 verbose git 'commit', +139 verbose git '-F', +139 verbose git '/private/var/folders/hc/yf_zr55547sbcpz5rj7q85sc0000gn/T/3c85b5d4-56ae-40e9-8df7-db2bc1048402/lerna-commit.txt' +139 verbose git ] +140 silly gitTag @strudel/codemirror@1.2.8 git tag %s -m %s +141 verbose git [ +141 verbose git 'tag', +141 verbose git '@strudel/codemirror@1.2.8', +141 verbose git '-m', +141 verbose git '@strudel/codemirror@1.2.8' +141 verbose git ] +142 silly gitTag @strudel/csound@1.2.8 git tag %s -m %s +143 verbose git [ 'tag', '@strudel/csound@1.2.8', '-m', '@strudel/csound@1.2.8' ] +144 silly gitTag @strudel/midi@1.2.8 git tag %s -m %s +145 verbose git [ 'tag', '@strudel/midi@1.2.8', '-m', '@strudel/midi@1.2.8' ] +146 silly gitTag @strudel/osc@1.3.2 git tag %s -m %s +147 verbose git [ 'tag', '@strudel/osc@1.3.2', '-m', '@strudel/osc@1.3.2' ] +148 silly gitTag @strudel/repl@1.2.9 git tag %s -m %s +149 verbose git [ 'tag', '@strudel/repl@1.2.9', '-m', '@strudel/repl@1.2.9' ] +150 silly gitTag @strudel/soundfonts@1.2.8 git tag %s -m %s +151 verbose git [ +151 verbose git 'tag', +151 verbose git '@strudel/soundfonts@1.2.8', +151 verbose git '-m', +151 verbose git '@strudel/soundfonts@1.2.8' +151 verbose git ] +152 silly gitTag superdough@1.2.5 git tag %s -m %s +153 verbose git [ 'tag', 'superdough@1.2.5', '-m', 'superdough@1.2.5' ] +154 silly gitTag supradough@1.2.4 git tag %s -m %s +155 verbose git [ 'tag', 'supradough@1.2.4', '-m', 'supradough@1.2.4' ] +156 silly gitTag @strudel/web@1.2.8 git tag %s -m %s +157 verbose git [ 'tag', '@strudel/web@1.2.8', '-m', '@strudel/web@1.2.8' ] +158 silly gitTag @strudel/webaudio@1.2.9 git tag %s -m %s +159 verbose git [ 'tag', '@strudel/webaudio@1.2.9', '-m', '@strudel/webaudio@1.2.9' ] +160 error Error: Command failed with exit code 128: git tag superdough@1.2.5 -m superdough@1.2.5 +160 error fatal: tag 'superdough@1.2.5' already exists +160 error at makeError (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/execa@5.0.0/node_modules/execa/lib/error.js:59:11) +160 error at handlePromise (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/execa@5.0.0/node_modules/execa/index.js:114:26) +160 error at process.processTicksAndRejections (node:internal/process/task_queues:105:5) +160 error at async Promise.all (index 6) +160 error at async VersionCommand.gitCommitAndTagVersionForUpdates (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/lerna@8.1.9_encoding@0.1.13/node_modules/lerna/dist/index.js:9957:11) +160 error at async VersionCommand.commitAndTagUpdates (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/lerna@8.1.9_encoding@0.1.13/node_modules/lerna/dist/index.js:9933:18) +160 error at async Promise.all (index 0) From 17ef84ed708fde6ea4df17aa3a142e79ca119bf4 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 19:11:08 -0800 Subject: [PATCH 061/200] sp --- packages/superdough/helpers.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 584b1967..07199aff 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -172,6 +172,7 @@ export const getADSRValues = (params, curve = 'linear', defaultValues) => { if (a == null && d == null && s == null && r == null) { return defaultValues ?? [envmin, envmin, envmax, releaseMin]; } + const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin; return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)]; }; From f610965f4332837febe45743105da170e8b331ed Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 17 Jan 2026 19:12:20 -0800 Subject: [PATCH 062/200] Publish - @strudel/codemirror@1.3.0 - @strudel/csound@1.3.0 - @strudel/midi@1.3.0 - @strudel/repl@1.3.0 - @strudel/soundfonts@1.3.0 - superdough@1.3.0 - @strudel/web@1.3.0 - @strudel/webaudio@1.3.0 --- packages/codemirror/package.json | 2 +- packages/csound/package.json | 2 +- packages/midi/package.json | 2 +- packages/repl/package.json | 2 +- packages/soundfonts/package.json | 2 +- packages/superdough/package.json | 2 +- packages/web/package.json | 2 +- packages/webaudio/package.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 9ad04097..c8cd4ae7 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/codemirror", - "version": "1.2.8", + "version": "1.3.0", "description": "Codemirror Extensions for Strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/csound/package.json b/packages/csound/package.json index 19ba6819..4af015bb 100644 --- a/packages/csound/package.json +++ b/packages/csound/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/csound", - "version": "1.2.8", + "version": "1.3.0", "description": "csound bindings for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/midi/package.json b/packages/midi/package.json index 38d0f0ca..1881e024 100644 --- a/packages/midi/package.json +++ b/packages/midi/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/midi", - "version": "1.2.8", + "version": "1.3.0", "description": "Midi API for strudel", "main": "index.mjs", "type": "module", diff --git a/packages/repl/package.json b/packages/repl/package.json index d2803492..1aff2bbe 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "1.2.9", + "version": "1.3.0", "description": "Strudel REPL as a Web Component", "module": "index.mjs", "publishConfig": { diff --git a/packages/soundfonts/package.json b/packages/soundfonts/package.json index 2403adfc..a904942a 100644 --- a/packages/soundfonts/package.json +++ b/packages/soundfonts/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/soundfonts", - "version": "1.2.8", + "version": "1.3.0", "description": "Soundsfont support for strudel", "main": "index.mjs", "publishConfig": { diff --git a/packages/superdough/package.json b/packages/superdough/package.json index d1e9b45f..8e2ffad6 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -1,6 +1,6 @@ { "name": "superdough", - "version": "1.2.5", + "version": "1.3.0", "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", "main": "index.mjs", "type": "module", diff --git a/packages/web/package.json b/packages/web/package.json index 8be1f89d..29c478c5 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/web", - "version": "1.2.8", + "version": "1.3.0", "description": "Easy to setup, opiniated bundle of Strudel for the browser.", "module": "web.mjs", "publishConfig": { diff --git a/packages/webaudio/package.json b/packages/webaudio/package.json index 6b6980e9..48b40470 100644 --- a/packages/webaudio/package.json +++ b/packages/webaudio/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/webaudio", - "version": "1.2.9", + "version": "1.3.0", "description": "Web Audio helpers for Strudel", "main": "index.mjs", "type": "module", From 6e36bdfff7995fcd93c3b3575b00003f6a60958a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 18 Jan 2026 12:08:12 +0100 Subject: [PATCH 063/200] Add tags for some of the new functions --- packages/core/controls.mjs | 34 ++++++++++++++++++++++++++++++ packages/core/pattern.mjs | 8 ++++++- packages/core/signal.mjs | 3 +++ packages/midi/midi.mjs | 1 + packages/superdough/superdough.mjs | 2 ++ 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 59d9f1aa..834d963c 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -702,6 +702,7 @@ export const { * any of the 8 individual FMs (e.g. `fmrel8`) * * @name fmrelease + * @tags fx, superdough, supradough * @synonyms fmrel * @param {number | Pattern} time release time * @@ -1553,6 +1554,7 @@ export const { fanchor } = registerControl('fanchor'); * Rate of the LFO for the lowpass filter * * @name lprate + * @tags fx, superdough * @param {number | Pattern} rate rate in hertz * @example * note("*16").s("sawtooth").lpf(600).lprate("<4 8 2 1>") @@ -1563,6 +1565,7 @@ export const { lprate } = registerControl('lprate'); * Cycle-synced rate of the LFO for the lowpass filter * * @name lpsync + * @tags fx, superdough * @param {number | Pattern} rate rate in cycles * @example * note("*16").s("sawtooth").lpf(600).lpsync("<4 8 2 1>") @@ -1573,6 +1576,7 @@ export const { lpsync } = registerControl('lpsync'); * Depth of the LFO for the lowpass filter * * @name lpdepth + * @tags fx, superdough * @param {number | Pattern} depth depth of modulation * @example * note("*16").s("sawtooth").lpf(600).lpdepth("<1 .5 1.8 0>") @@ -1583,6 +1587,7 @@ export const { lpdepth } = registerControl('lpdepth'); * Depth of the LFO for the lowpass filter, in HZ * * @name lpdepthfrequency + * @tags fx, superdough * @synonyms lpdepthfreq * @param {number | Pattern} depth depth of modulation * @example @@ -1595,6 +1600,7 @@ export const { lpdepthfrequency, lpdepthfreq } = registerControl('lpdepthfrequen * Shape of the LFO for the lowpass filter * * @name lpshape + * @tags fx, superdough * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { lpshape } = registerControl('lpshape'); @@ -1603,6 +1609,7 @@ export const { lpshape } = registerControl('lpshape'); * DC offset of the LFO for the lowpass filter * * @name lpdc + * @tags fx, superdough * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { lpdc } = registerControl('lpdc'); @@ -1611,6 +1618,7 @@ export const { lpdc } = registerControl('lpdc'); * Skew of the LFO for the lowpass filter * * @name lpskew + * @tags fx, superdough * @param {number | Pattern} skew How much to bend the LFO shape */ export const { lpskew } = registerControl('lpskew'); @@ -1619,6 +1627,7 @@ export const { lpskew } = registerControl('lpskew'); * Rate of the LFO for the bandpass filter * * @name bprate + * @tags fx, superdough * @param {number | Pattern} rate rate in hertz */ export const { bprate } = registerControl('bprate'); @@ -1627,6 +1636,7 @@ export const { bprate } = registerControl('bprate'); * Cycle-synced rate of the LFO for the bandpass filter * * @name bpsync + * @tags fx, superdough * @param {number | Pattern} rate rate in cycles */ export const { bpsync } = registerControl('bpsync'); @@ -1635,6 +1645,7 @@ export const { bpsync } = registerControl('bpsync'); * Depth of the LFO for the bandpass filter * * @name bpdepth + * @tags fx, superdough * @param {number | Pattern} depth depth of modulation */ export const { bpdepth } = registerControl('bpdepth'); @@ -1643,6 +1654,7 @@ export const { bpdepth } = registerControl('bpdepth'); * Depth of the LFO for the bandpass filter, in HZ * * @name bpdepthfrequency + * @tags fx, superdough * @synonyms bpdepthfreq * @param {number | Pattern} depth depth of modulation * @example @@ -1655,6 +1667,7 @@ export const { bpdepthfrequency, bpdepthfreq } = registerControl('bpdepthfrequen * Shape of the LFO for the bandpass filter * * @name bpshape + * @tags fx, superdough * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { bpshape } = registerControl('bpshape'); @@ -1663,6 +1676,7 @@ export const { bpshape } = registerControl('bpshape'); * DC offset of the LFO for the bandpass filter * * @name bpdc + * @tags fx, superdough * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { bpdc } = registerControl('bpdc'); @@ -1671,6 +1685,7 @@ export const { bpdc } = registerControl('bpdc'); * Skew of the LFO for the bandpass filter * * @name bpskew + * @tags fx, superdough * @param {number | Pattern} skew How much to bend the LFO shape */ export const { bpskew } = registerControl('bpskew'); @@ -1679,6 +1694,7 @@ export const { bpskew } = registerControl('bpskew'); * Rate of the LFO for the highpass filter * * @name hprate + * @tags fx, superdough * @param {number | Pattern} rate rate in hertz */ export const { hprate } = registerControl('hprate'); @@ -1687,6 +1703,7 @@ export const { hprate } = registerControl('hprate'); * Cycle-synced rate of the LFO for the highpass filter * * @name hpsync + * @tags fx, superdough * @param {number | Pattern} rate rate in cycles */ export const { hpsync } = registerControl('hpsync'); @@ -1695,6 +1712,7 @@ export const { hpsync } = registerControl('hpsync'); * Depth of the LFO for the highpass filter * * @name hpdepth + * @tags fx, superdough * @param {number | Pattern} depth depth of modulation */ export const { hpdepth } = registerControl('hpdepth'); @@ -1703,6 +1721,7 @@ export const { hpdepth } = registerControl('hpdepth'); * Depth of the LFO for the hipass filter, in hz * * @name hpdepthfrequency + * @tags fx, superdough * @synonyms hpdepthfreq * @param {number | Pattern} depth depth of modulation * @example @@ -1715,6 +1734,7 @@ export const { hpdepthfrequency, hpdepthfreq } = registerControl('hpdepthfrequen * Shape of the LFO for the highpass filter * * @name hpshape + * @tags fx, superdough * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ export const { hpshape } = registerControl('hpshape'); @@ -1723,6 +1743,7 @@ export const { hpshape } = registerControl('hpshape'); * DC offset of the LFO for the highpass filter * * @name hpdc + * @tags fx, superdough * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ export const { hpdc } = registerControl('hpdc'); @@ -1731,6 +1752,7 @@ export const { hpdc } = registerControl('hpdc'); * Skew of the LFO for the highpass filter * * @name hpskew + * @tags fx, superdough * @param {number | Pattern} skew How much to bend the LFO shape */ export const { hpskew } = registerControl('hpskew'); @@ -2182,6 +2204,7 @@ export const { orbit } = registerControl('orbit', 'o'); * otherPat.bmod(..) (to modulate another pattern with the bus) * * @name bus + * @tags fx, superdough * @param {number | Pattern} number */ export const { bus } = registerControl('bus'); @@ -2190,6 +2213,7 @@ export const { bus } = registerControl('bus'); * Postgain multiplier prior to sending the signal to the audio bus. * * @name busgain + * @tags fx, superdough * @synonyms bgain * @param {number | Pattern} number */ @@ -2252,6 +2276,7 @@ export const { voice } = registerControl('voice'); /** * The chord to voice * @name chord + * @tags music_theory * @param {string | Pattern} symbols chord symbols to voice e.g., C, Eb, Fm7, G7. The symbols can be defined via addVoicings * @example * chord("").voicing() @@ -2261,6 +2286,7 @@ export const { chord } = registerControl('chord'); * Which dictionary to use for the voicings. This falls back to the default dictionary if not provided * * @name dictionary + * @tags music_theory * @param {string} dictionaryName which dictionary (having been defined with `addVoicings`) to use * @example * addVoicings('house', { @@ -2275,6 +2301,7 @@ export const { dictionary, dict } = registerControl('dictionary', 'dict'); /** The top note to align the voicing to. Defaults to c5 * * @name anchor + * @tags music_theory * @param {string | Pattern} anchorNote the note to align the voicings to * @example * anchor("").chord("C").voicing() @@ -2284,6 +2311,7 @@ export const { anchor } = registerControl('anchor'); * Sets how the voicing is offset from the anchored position * * @name offset + * @tags music_theory * @param {number | Pattern} shift the amount to shift the voicing up or down * @example * chord("").offset("<0 1 2 3 4 5>") // alter the voicing each time @@ -2293,6 +2321,7 @@ export const { offset } = registerControl('offset'); * How many octaves are voicing steps spread apart, defaults to 1 * * @name octaves + * @tags music_theory * @param {number | Pattern} count the number of octaves * @example * chord("").octaves("<2 4>").voicing() @@ -2302,6 +2331,7 @@ export const { octaves } = registerControl('octaves'); * Remove anchor note from the voicing. Useful for melody harmonization * * @name mode + * @tags music_theory * @param {string | Pattern} modeName one of {below | above | duck | root} * @example * mode("").chord("C").voicing() @@ -3097,6 +3127,7 @@ Pattern.prototype.modulate = function (type, config, idPat) { * a `sometimes`. See example below. * * @name lfo + * @tags fx, superdough * @param {Object} config LFO configuration. * @param {string | Pattern} [config.control] Node to modulate. Aliases: c * @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc @@ -3150,6 +3181,7 @@ export const lfo = (config) => pure({}).lfo(config); * a `sometimes`. See example below. * * @name env + * @tags fx, superdough * @param {Object} config Envelope configuration. * @param {string | Pattern} [config.control] Node to modulate. Aliases: c * @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc @@ -3210,6 +3242,7 @@ export const env = (config) => pure({}).env(config); * a `sometimes`. See example below. * * @name bmod + * @tags fx, superdough * @param {Object} config Bus modulation configuration. * @param {string | Pattern} [config.bus] Bus to get modulation signal from * @param {string | Pattern} [config.control] Node to modulate. Aliases: c @@ -3236,6 +3269,7 @@ export const bmod = (config) => pure({}).bmod(config); * and sustains * * @name transient + * @tags fx, superdough * @param {number | Pattern} attack Emphasis on transients; between -1 (deaccentuate) and 1 (accentuate) * @param {number | Pattern} sustain Emphasis on the sustains; between -1 (deaccentuate) and 1 (accentuate) * @example diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index f22b29ff..2b78a68f 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1572,7 +1572,10 @@ export function fastcat(...pats) { return result; } -/** See `fastcat` */ +/** See `fastcat` + * @name sequence + * @tags combiners + */ export function sequence(...pats) { return fastcat(...pats); } @@ -2377,6 +2380,7 @@ export const rev = register( * Reverse a whole pattern. See also `rev` for reversing each cycle. * * @name revv + * @tags temporal * @memberof Pattern * @returns Pattern * @example @@ -3903,6 +3907,7 @@ export const phases = (list) => { * calls and/or in a single .FX(fx1, fx2, ..) call. The fx1, .. are _patterns_ which * establish the controls of the given effect. See examples. * @name FX + * @tags fx, superdough * @memberof Pattern * @returns Pattern * @example @@ -3953,6 +3958,7 @@ const _asArrayPattern = (pats) => { * by wrapping them inside a function in K (see example). * * @name K + * @tags generators, fx, superdough * @param {KabelsalatExpression | Function} expr Kabelsalat graph definition * @memberof Pattern * @returns Pattern diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 18090bbd..9827ddd2 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -286,6 +286,7 @@ export const getRandsAtTime = (t, n = 1, seed = 0) => { * precise RNG, try `useRNG('precise')`. * * @name useRNG + * @tags generators, internals * @param {string} mod - Mode. One of 'legacy', 'precise' * @example * useRNG('legacy') @@ -341,6 +342,7 @@ export const binaryN = (n, nBits = 16) => { * Creates a binary list pattern from a number. * * @name binaryL + * @tags generators * @param {number} n - input number to convert to binary * s("saw").seg(8) * .partials(binaryL(irand(4096).add(1))) @@ -354,6 +356,7 @@ export const binaryL = (n) => { * Creates a binary list pattern from a number, padded to n bits long. * * @name binaryNL + * @tags generators * @param {number} n - input number to convert to binary * @param {number} nBits - pattern length, defaults to 16 */ diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index e03dcbd0..8ce26c2a 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -577,6 +577,7 @@ export async function midin(input) { * note durations * * @name midikeys + * @tags external_io * @param {string | number} input MIDI device name or index defaulting to 0 * @returns {function((number | Pattern)=): Pattern} A function that produces a pattern. * When queried, the pattern will produces the most recently played midi notes and velocities, diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 45c77d67..bcf235f3 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -40,6 +40,7 @@ export let maxPolyphony = DEFAULT_MAX_POLYPHONY; * start to die out in first-in-first-out order once the max polyphony has been hit * * @name setMaxPolyphony + * @tags fx, superdough * @param {number} Max polyphony. Defaults to 128 * @example * setMaxPolyphony(4) @@ -73,6 +74,7 @@ export function applyGainCurve(val) { * quadratic, exponential, etc. rather than linear * * @name setGainCurve + * @tags fx, superdough * @param {Function} function to apply to all gain values * @example * setGainCurve((x) => x * x) // quadratic gain From f8750901f0e08d7c7ff43daed5efa04daa47344f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sun, 18 Jan 2026 12:16:50 +0100 Subject: [PATCH 064/200] Tag the remaining functions --- packages/core/pattern.mjs | 4 ++++ packages/core/signal.mjs | 9 ++++++++- packages/superdough/util.mjs | 4 +++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 2b78a68f..b7a047bf 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1653,6 +1653,7 @@ export const func = curry((a, b) => reify(b).func(a)); /** * Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register. * + * @tags functional * @param {string | string[]} name name of the function, or an array of names to be used as synonyms * @param {function} func function with 1 or more params, where last is the current pattern * @param {bool} patternify defaults to true; if set to false, you will have more control over the arguments to `func` as they will be @@ -3843,6 +3844,7 @@ export const chebyshev = _distortWithAlg('chebyshev'); * Turns a list of patterns into a single pattern which outputs list-values * * @name parray + * @tags combiners * @returns Pattern */ export const parray = (pats) => { @@ -3865,6 +3867,7 @@ const _ensureListPattern = (list) => { * Can also be used to create a new synth via `s('user').partials(...)` * * @name partials + * @tags fx, superdough * @param {number[] | Pattern} magnitudes List of [0, 1] magnitudes for partials. 0th entry is the fundamental harmonic (i.e. DC offset is skipped) * @example * s("user").seg(16).n(irand(8)).scale("A:major") @@ -3886,6 +3889,7 @@ export const partials = (list) => { * Rotates the harmonics of one of the core synths ('sine', 'tri', 'saw', 'user', ..) by a list of phases * * @name phases + * @tags fx, superdough * @param {number[] | Pattern} phases List of [0, 1) phases for partials. 0th entry is the fundamental phase (i.e. DC offset is skipped) * @example * // Phase cancellation diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 9827ddd2..2f9fe4b1 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -286,7 +286,7 @@ export const getRandsAtTime = (t, n = 1, seed = 0) => { * precise RNG, try `useRNG('precise')`. * * @name useRNG - * @tags generators, internals + * @tags generators, math * @param {string} mod - Mode. One of 'legacy', 'precise' * @example * useRNG('legacy') @@ -376,6 +376,7 @@ export const binaryNL = (n, nBits = 16) => { * Creates a list of random numbers of the given length * * @name randL + * @tags generators * @param {number} n Number of random numbers to sample * @example * s("saw").seg(16).n(irand(12)).scale("F1:minor") @@ -434,6 +435,7 @@ export const scramble = register('scramble', (n, pat) => { /** * Modify a pattern by applying a function to the `randomSeed` control if present * + * @tags math * @param {Function} func Function from seed (or undefined) to seed (or undefined) * @param {Pattern} pat Pattern to update * @returns Pattern @@ -453,6 +455,7 @@ export const withSeed = (func, pat) => { * that use randomness, like `shuffle` and `sometimes`. * * @name seed + * @tags math * @param {number} n A new seed. Can be any number. * @example * $: s("hh*4").degrade(); @@ -1031,6 +1034,8 @@ export const keyDown = register('keyDown', function (pat) { * event durations, from the pattern that it is combined with. * For example `cyclesPer.struct("1 1 [1 1] 1")` would give the same as `"0.25 0.25 [0.125 0.125] 0.25"`. * See also its reciprocal, `per`, also known as `perCycle`. + * + * @tags temporal * @example * // Shorter events are lower in pitch * sound("saw saw [saw saw] saw") @@ -1049,6 +1054,7 @@ export const cyclesPer = new Pattern(function (state) { * event durations, from the pattern that it is combined with. * For example `per.struct("1 1 [1 1] 1")` would give the same as `"4 4 [8 8] 4"`. * See also its reciprocal, `cyclesPer`. + * @tags temporal * @synonyms perCycle * @example * // Shorter events are more distorted @@ -1066,6 +1072,7 @@ export const perCycle = per; * particular, where the event duration halves, the * returned value increases by one. `perx.struct("1 1 [1 [1 1]] 1")` would therefore be * the same as `"3 3 [4 [5 5]] 3"`. + * @tags temporal */ export const perx = new Pattern(function (state) { const n = Fraction(1).div(state.span.duration); diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index 48830541..caa58fa8 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -110,7 +110,9 @@ export function getCommonSampleInfo(hapValue, bank) { return { transpose, url, index, midi, label }; } -/** Selects entries from `source` and renames them via `map` */ +/** Selects entries from `source` and renames them via `map` + * @tags internals + */ export const pickAndRename = (source, map) => { return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]])); }; From 6981638f706edd5ab68144bead3599852b562966 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 18 Jan 2026 22:04:49 +0100 Subject: [PATCH 065/200] fix: move kabelsalat web to superdough --- packages/core/package.json | 1 - packages/core/repl.mjs | 8 -------- packages/superdough/package.json | 1 + packages/superdough/superdough.mjs | 17 +++++++++++++++++ pnpm-lock.yaml | 6 +++--- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index e558c574..9316ec49 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -31,7 +31,6 @@ }, "homepage": "https://strudel.cc", "dependencies": { - "@kabelsalat/web": "^0.4.1", "fraction.js": "^5.2.1" }, "gitHead": "0e26d4e741500f5bae35b023608f062a794905c2", diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 58a3532c..bf86ba49 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -12,7 +12,6 @@ import { import { evalScope } from './evaluate.mjs'; import { register, Pattern, isPattern, silence, stack } from './pattern.mjs'; import { reset_state } from './impure.mjs'; -import { SalatRepl } from '@kabelsalat/web'; export function repl({ defaultOutput, @@ -31,7 +30,6 @@ export function repl({ id, mondo = false, }) { - const kabel = new SalatRepl({ localScope: true }); const state = { schedulerError: undefined, evalError: undefined, @@ -89,11 +87,6 @@ export function repl({ return silence; }; - const compileKabel = (code) => { - const node = kabel.evaluate(code); - return node.compile({ log: false }); - }; - // helper to get a patternified pure value out function unpure(pat) { if (pat._Pattern) { @@ -221,7 +214,6 @@ export function repl({ setcps: setCps, setCpm, setcpm: setCpm, - compileKabel, }); }; diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 8e2ffad6..ce17ab5e 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@kabelsalat/lib": "^0.4.1", + "@kabelsalat/web": "^0.4.1", "nanostores": "^0.11.3" }, "engines": { diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 529392e9..79da4f91 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -260,6 +260,14 @@ export function loadWorklets() { return workletsLoading; } +let kabel; +async function initKabelsalat() { + const { SalatRepl } = await import('@kabelsalat/web'); + logger('[kabelsalat] ready'); + kabel = new SalatRepl({ localScope: true }); + return kabel; +} + // this function should be called on first user interaction (to avoid console warning) export async function initAudio(options = {}) { const { @@ -306,6 +314,7 @@ export async function initAudio(options = {}) { } catch (err) { console.warn('could not load AudioWorklet effects', err); } + await initKabelsalat(); logger('[superdough] ready'); } let audioReady; @@ -441,6 +450,14 @@ class Chain { } } +const compileKabel = (code) => { + if (!kabel) { + throw new Error('kabelsalat not loaded'); + } + const node = kabel.evaluate(code); + return node.compile({ log: false }); +}; + export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => { // mapping from main FX and numbered FX chains to nodes const nodes = { main: {} }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53437f8f..468ceacf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,9 +234,6 @@ importers: packages/core: dependencies: - '@kabelsalat/web': - specifier: ^0.4.1 - version: 0.4.1 fraction.js: specifier: ^5.2.1 version: 5.2.1 @@ -524,6 +521,9 @@ importers: '@kabelsalat/lib': specifier: ^0.4.1 version: 0.4.1 + '@kabelsalat/web': + specifier: ^0.4.1 + version: 0.4.1 nanostores: specifier: ^0.11.3 version: 0.11.3 From 4b8d80016b0b4cabdd1231c7295023c05e5fe263 Mon Sep 17 00:00:00 2001 From: space-shell Date: Mon, 19 Jan 2026 10:13:48 +0100 Subject: [PATCH 066/200] Remove trailin whitespace from CHANGELOG.md --- CHANGELOG.md | 1073 +------------------------------------------------- 1 file changed, 9 insertions(+), 1064 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11edc7e4..2007ee11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ -# changelog - -NOTE: you can generate this with `node warm.js`. it might still not be perfectly sorted... - -## january 2026 - - +load page 1/1 +------------- +- 2026-01-18T22:27:46+01:00 fix: move kabelsalat web to superdough by @froos in: [#1932](https://codeberg.org/uzu/strudel/pulls/1932) +- 2026-01-18T12:36:50+01:00 Add tags to reference by @vvolhejn in: [#1645](https://codeberg.org/uzu/strudel/pulls/1645) +- 2026-01-18T10:36:02+01:00 fix: performance issues when reference is open by @robojumper in: [#1926](https://codeberg.org/uzu/strudel/pulls/1926) +- 2026-01-17T21:00:43+01:00 Make kabelsalat stereo out by @glossing in: [#1927](https://codeberg.org/uzu/strudel/pulls/1927) +- 2026-01-16T10:38:04+01:00 update changelog + fix script to not miss entries by @froos in: [#1916](https://codeberg.org/uzu/strudel/pulls/1916) - 2026-01-15T16:59:14+01:00 mondo fix: add registered functions to scope automatically by @daslyfe in: [#1896](https://codeberg.org/uzu/strudel/pulls/1896) - 2026-01-15T16:58:14+01:00 Added docs for pattern search by @JohnBjrk in: [#1905](https://codeberg.org/uzu/strudel/pulls/1905) - 2026-01-15T16:57:19+01:00 Update kabelsalat dependency by @jeromew in: [#1911](https://codeberg.org/uzu/strudel/pulls/1911) @@ -16,1067 +16,12 @@ NOTE: you can generate this with `node warm.js`. it might still not be perfectly - 2026-01-13T00:18:10+01:00 Add shortcut for navigating through labels! by @daslyfe in: [#1807](https://codeberg.org/uzu/strudel/pulls/1807) - 2026-01-12T00:17:37+01:00 add warm.strudel.cc to faq by @yaxu in: [#1891](https://codeberg.org/uzu/strudel/pulls/1891) - 2026-01-11T19:00:25+01:00 Fix sounds example to work in the REPL by @JesCoding in: [#1851](https://codeberg.org/uzu/strudel/pulls/1851) -- 2026-01-11T13:52:02+01:00 Improve hint text when sound search has no results by @floy in: [#1883](https://codeberg.org/uzu/strudel/pulls/1883) -- 2026-01-11T13:19:46+01:00 New page FAQ in "More" by @scrappy_fiddler in: [#1753](https://codeberg.org/uzu/strudel/pulls/1753) -- 2026-01-11T12:45:07+01:00 fix: remove faulty default readme by @froos in: [#1889](https://codeberg.org/uzu/strudel/pulls/1889) -- 2026-01-11T12:15:34+01:00 fix/self-hosted-config by @alienmind in: [#1880](https://codeberg.org/uzu/strudel/pulls/1880) - 2026-01-11T12:07:48+01:00 Feat: Support External AudioContext Injection by @1d10t in: [#1833](https://codeberg.org/uzu/strudel/pulls/1833) -- 2026-01-11T11:37:37+01:00 fix: add trem to top level by @froos in: [#1887](https://codeberg.org/uzu/strudel/pulls/1887) -- 2026-01-11T11:37:18+01:00 fix: export start cycle min 0 by @froos in: [#1888](https://codeberg.org/uzu/strudel/pulls/1888) -- 2026-01-11T11:05:44+01:00 fix: repl package init audio properly by @froos in: [#1836](https://codeberg.org/uzu/strudel/pulls/1836) -- 2026-01-11T06:51:24+01:00 Fix: show reload dialog when uploading prebake script by @daslyfe in: [#1886](https://codeberg.org/uzu/strudel/pulls/1886) -- 2026-01-11T06:01:42+01:00 fixes Serial onTrigger() params #1633 by @gueejla in: [#1885](https://codeberg.org/uzu/strudel/pulls/1885) - 2026-01-10T23:12:48+01:00 Perf: Targeted node pools by @glossing in: [#1810](https://codeberg.org/uzu/strudel/pulls/1810) - 2026-01-10T20:52:40+01:00 Allow top level distortions for the purpose of FX by @glossing in: [#1884](https://codeberg.org/uzu/strudel/pulls/1884) -- 2026-01-09T03:43:37+01:00 Bake in scaling by `freq` for FM with a gain node by @glossing in: [#1878](https://codeberg.org/uzu/strudel/pulls/1878) -- 2026-01-09T02:53:38+01:00 Bug fix: Properly handle subcontrols by @glossing in: [#1877](https://codeberg.org/uzu/strudel/pulls/1877) - 2026-01-07T20:06:18+01:00 Feat: Add ability to turn mini parsing off with mini-off decorator by @glossing in: [#1786](https://codeberg.org/uzu/strudel/pulls/1786) -- 2026-01-07T19:22:24+01:00 Bug Fix: Update loopStart/End to not be offset by @glossing in: [#1826](https://codeberg.org/uzu/strudel/pulls/1826) -- 2026-01-06T06:35:49+01:00 Update modulator docstrings and allow ids to be patterns by @glossing in: [#1874](https://codeberg.org/uzu/strudel/pulls/1874) -- 2026-01-04T17:14:11+01:00 Fix doc link in @strudel/osc README.md by @forrcaho in: [#1872](https://codeberg.org/uzu/strudel/pulls/1872) -- 2026-01-04T02:01:07+01:00 Make stretch modulatable by @glossing in: [#1870](https://codeberg.org/uzu/strudel/pulls/1870) -- 2026-01-02T18:44:53+01:00 Make pan modulatable by @glossing in: [#1865](https://codeberg.org/uzu/strudel/pulls/1865) -- 2026-01-01T21:42:23+01:00 Fix transpilation example to have same mini-notation by @JesCoding in: [#1850](https://codeberg.org/uzu/strudel/pulls/1850) -- 2026-01-01T21:39:05+01:00 fix: missing punctuation by @eddyflux in: [#1860](https://codeberg.org/uzu/strudel/pulls/1860) -- 2026-01-01T20:20:34+01:00 Fix formatting of docstring by @glossing in: [#1864](https://codeberg.org/uzu/strudel/pulls/1864) - 2026-01-01T19:52:15+01:00 Feat: FX Chains by @glossing in: [#1861](https://codeberg.org/uzu/strudel/pulls/1861) - -## december 2025 - -- 2025-12-29T21:59:11+01:00 Bugfix: Fix modulator clamping when min/max not specified by @glossing in: [#1859](https://codeberg.org/uzu/strudel/pulls/1859) - 2025-12-29T20:54:11+01:00 Feature: LFOs and Envelopes by @glossing in: [#1507](https://codeberg.org/uzu/strudel/pulls/1507) -- 2025-12-29T16:07:18+01:00 Fix AudioContext change detection. Use AudioNode.context by @jeromew in: [#1858](https://codeberg.org/uzu/strudel/pulls/1858) -- 2025-12-28T22:58:38+01:00 Say that @license should use SPDX identifier by @Wuzzy in: [#1817](https://codeberg.org/uzu/strudel/pulls/1817) -- 2025-12-28T22:56:07+01:00 Expose Vim object in order to create custom keybindings by @JohnBjrk in: [#1816](https://codeberg.org/uzu/strudel/pulls/1816) -- 2025-12-28T14:20:03+01:00 dough repl fixes by @froos in: [#1855](https://codeberg.org/uzu/strudel/pulls/1855) -- 2025-12-28T13:40:29+01:00 add basic dough repl by @froos in: [#1749](https://codeberg.org/uzu/strudel/pulls/1749) -- 2025-12-20T22:27:52+01:00 Document "-" in mini-notation by @Wuzzy in: [#1818](https://codeberg.org/uzu/strudel/pulls/1818) -- 2025-12-20T22:20:27+01:00 simplify envValAtTime and remove asymmetric behavior (fix #1653) by @pulu in: [#1815](https://codeberg.org/uzu/strudel/pulls/1815) -- 2025-12-19T08:11:50+01:00 fix: visual block selection mode for vim bindings by @Dsm0 in: [#1839](https://codeberg.org/uzu/strudel/pulls/1839) -- 2025-12-19T01:03:53+01:00 [perf] Add audiograph `await debugAudiograph()` feature by @jeromew in: [#1763](https://codeberg.org/uzu/strudel/pulls/1763) -- 2025-12-19T00:45:41+01:00 Feature: non-realtime exporting by @Ghost in: [#1674](https://codeberg.org/uzu/strudel/pulls/1674) -- 2025-12-14T19:33:37+01:00 Fix: wrong warning in build environments by @jeromew in: [#1835](https://codeberg.org/uzu/strudel/pulls/1835) -- 2025-12-14T15:09:12+01:00 delta -> per / perx / cyclesPer refinements by @yaxu in: [#1832](https://codeberg.org/uzu/strudel/pulls/1832) -- 2025-12-14T01:07:36+01:00 Improved randomness by @glossing in: [#1505](https://codeberg.org/uzu/strudel/pulls/1505) -- 2025-12-12T10:28:27+01:00 Add delta signal for representing the duration of events in patterns that are combined with it by @yaxu in: [#1831](https://codeberg.org/uzu/strudel/pulls/1831) - 2025-12-11T18:00:49+01:00 Feature: stateful timeline function for jumping between timelines by @yaxu in: [#1669](https://codeberg.org/uzu/strudel/pulls/1669) -- 2025-12-11T16:37:58+01:00 Updates relating to LLM, github, etc by @yaxu in: [#1830](https://codeberg.org/uzu/strudel/pulls/1830) -- 2025-12-10T15:32:44+01:00 fix .as so it doesn't set undefined values by @yaxu in: [#1827](https://codeberg.org/uzu/strudel/pulls/1827) -- 2025-12-08T19:27:31+01:00 [perf] propagate `onceEnded` and `releaseAudioNode` by @jeromew in: [#1809](https://codeberg.org/uzu/strudel/pulls/1809) -- 2025-12-08 Add Helix keybindings support to REPL editor - Added `codemirror-helix` package integration, enabling Helix editor keybindings as a new option in the REPL settings alongside Vim, Emacs, and VSCode modes -- 2025-12-07T21:51:40+01:00 Feat: Add channel support to midi in by @glossing in: [#1775](https://codeberg.org/uzu/strudel/pulls/1775) -- 2025-12-07T21:23:43+01:00 Feat: Transient shaper by @glossing in: [#1777](https://codeberg.org/uzu/strudel/pulls/1777) -- 2025-12-07T19:15:43+01:00 Add vel as a synonym for velocity & update a few docstrings by @glossing in: [#1781](https://codeberg.org/uzu/strudel/pulls/1781) -- 2025-12-07T18:53:04+01:00 [perf] fix phaser leak of unused biquads by @jeromew in: [#1800](https://codeberg.org/uzu/strudel/pulls/1800) -- 2025-12-07T18:42:50+01:00 Bug fix: Remove failing tests due to shabda removal by @glossing in: [#1820](https://codeberg.org/uzu/strudel/pulls/1820) -- 2025-12-03T18:27:28+01:00 Feature: Eight FMs by @glossing in: [#1628](https://codeberg.org/uzu/strudel/pulls/1628) -- 2025-12-03T17:35:05+01:00 [perf] release unused AudioBufferSourceNode + releaseAudioNode by @jeromew in: [#1805](https://codeberg.org/uzu/strudel/pulls/1805) -- **2025-12-01 strudel.cc deployed** - -## november 2025 - -- 2025-11-29T01:00:42+01:00 added export to getSuperdoughAudioController() so that its possible to route superdough audio through other webaudio applications. by @ndr0n in: [#1796](https://codeberg.org/uzu/strudel/pulls/1796) -- 2025-11-28T23:26:20+01:00 add revv() for reversing whole patterns by @yaxu in: [#1791](https://codeberg.org/uzu/strudel/pulls/1791) -- 2025-11-28T20:19:16+01:00 [perf] Disconnect lfos for phaser and filters by @glossing in: [#1787](https://codeberg.org/uzu/strudel/pulls/1787) -- 2025-11-27T23:08:52+01:00 fix: return silence when no pattern is returned by @froos in: [#1795](https://codeberg.org/uzu/strudel/pulls/1795) -- 2025-11-27T22:47:03+01:00 fix(tool/dbpatch): add missing package name by @peterpf in: [#1765](https://codeberg.org/uzu/strudel/pulls/1765) -- 2025-11-27T22:38:05+01:00 add CHANGELOG.md + basic script to generate by @froos in: [#1794](https://codeberg.org/uzu/strudel/pulls/1794) -- 2025-11-27T22:03:53+01:00 [perf] in `noise`, let noiseMix do the disconnect when it exists by @jeromew in: [#1783](https://codeberg.org/uzu/strudel/pulls/1783) -- 2025-11-27T22:01:01+01:00 Bug Fix: Retries for sounds tab by @glossing in: [#1754](https://codeberg.org/uzu/strudel/pulls/1754) -- 2025-11-27T20:36:46+01:00 [hydra] return the hydra object when await initHydra(..) is called by @jeromew in: [#1784](https://codeberg.org/uzu/strudel/pulls/1784) -- 2025-11-27T20:36:14+01:00 Use errorLogger for query and tonal errors by @glossing in: [#1782](https://codeberg.org/uzu/strudel/pulls/1782) -- 2025-11-27T20:27:10+01:00 [perf] level 5 `connect-leak` on `vowel` by @jeromew in: [#1779](https://codeberg.org/uzu/strudel/pulls/1779) -- 2025-11-26T08:23:38+01:00 feat: add prebake script import button for loading .strudel files by @daslyfe in: [#1774](https://codeberg.org/uzu/strudel/pulls/1774) -- 2025-11-26T07:39:28+01:00 Fix Sampler port trampling by @Dayglo in: [#1717](https://codeberg.org/uzu/strudel/pulls/1717) -- 2025-11-25T20:59:09+01:00 Feat: Hook up octave and fix example by @glossing in: [#1773](https://codeberg.org/uzu/strudel/pulls/1773) -- 2025-11-25T20:48:09+01:00 [perf] fix `connect-leak` in `tremolo` param by @jeromew in: [#1780](https://codeberg.org/uzu/strudel/pulls/1780) -- 2025-11-24T17:51:20+01:00 filter modulation improvements! by @daslyfe in: [#1769](https://codeberg.org/uzu/strudel/pulls/1769) -- 2025-11-24T01:20:53+01:00 Add o as a synonym for orbit by @daslyfe in: [#1766](https://codeberg.org/uzu/strudel/pulls/1766) -- 2025-11-23T22:23:26+01:00 [perf] fix `connect-leak` in `fm` modulation by @jeromew in: [#1758](https://codeberg.org/uzu/strudel/pulls/1758) -- 2025-11-23T09:47:24+01:00 Fix interoperability issue between `all` and `await initHydra()` by @jeromew in: [#1663](https://codeberg.org/uzu/strudel/pulls/1663) -- 2025-11-23T04:23:06+01:00 Bug fix: Swap l/r gains with temp variable by @glossing in: [#1768](https://codeberg.org/uzu/strudel/pulls/1768) -- 2025-11-23T00:52:28+01:00 FIX: prevent LFO filter modulation from popping from negative values by @daslyfe in: [#1767](https://codeberg.org/uzu/strudel/pulls/1767) -- 2025-11-23T00:03:32+01:00 prefix "S" for solo by @froos in: [#1481](https://codeberg.org/uzu/strudel/pulls/1481) -- 2025-11-21T01:56:11+01:00 Bug Fix: Use frac due to negative frequencies from FM by @glossing in: [#1759](https://codeberg.org/uzu/strudel/pulls/1759) -- 2025-11-20T19:48:16+01:00 [perf] fix `connect-leak` added by #1742 when noise() is not used by @jeromew in: [#1757](https://codeberg.org/uzu/strudel/pulls/1757) -- 2025-11-20T14:51:00+01:00 [perf] fix `connect-leak` in `delay` effect by @jeromew in: [#1755](https://codeberg.org/uzu/strudel/pulls/1755) -- 2025-11-20T14:49:41+01:00 [perf] fix `connect leak` when .noise() is in the mix by @jeromew in: [#1742](https://codeberg.org/uzu/strudel/pulls/1742) -- 2025-11-18T23:52:45+01:00 wchooseCycles has now notes in an example by @scrappy_fiddler in: [#1748](https://codeberg.org/uzu/strudel/pulls/1748) -- 2025-11-17T05:31:54+01:00 Feature: Partials by @glossing in: [#1659](https://codeberg.org/uzu/strudel/pulls/1659) -- 2025-11-16T22:06:14+01:00 README: update superdough documentation by @TristanMlct in: [#1741](https://codeberg.org/uzu/strudel/pulls/1741) -- 2025-11-16T22:00:43+01:00 Feature: Envelope worklet by @glossing in: [#1524](https://codeberg.org/uzu/strudel/pulls/1524) -- 2025-11-16T21:08:54+01:00 Worklet optimizations by @glossing in: [#1730](https://codeberg.org/uzu/strudel/pulls/1730) -- 2025-11-15T14:59:30+01:00 [perf] disconnect Offline AudioNode connections in generateReverb by @jeromew in: [#1740](https://codeberg.org/uzu/strudel/pulls/1740) -- 2025-11-15T14:57:47+01:00 [perf] disconnect `send` effect AudioNode when `room` is used by @jeromew in: [#1736](https://codeberg.org/uzu/strudel/pulls/1736) -- 2025-11-12T21:22:34+01:00 [supradough] fix: unstable filter by @froos in: [#1593](https://codeberg.org/uzu/strudel/pulls/1593) -- 2025-11-12T21:12:07+01:00 Fix link syntax in `project-start` by @Kissaki in: [#1724](https://codeberg.org/uzu/strudel/pulls/1724) -- 2025-11-12T21:06:13+01:00 Fix web README sample code by @Kissaki in: [#1725](https://codeberg.org/uzu/strudel/pulls/1725) -- 2025-11-12T20:59:39+01:00 Fix typo: 'studel' -> 'strudel' by @drhayes in: [#1726](https://codeberg.org/uzu/strudel/pulls/1726) -- 2025-11-12T15:23:41+01:00 fix for node 24 support in tests - #1718 by @ausav in: [#1719](https://codeberg.org/uzu/strudel/pulls/1719) -- 2025-11-11T09:00:49+01:00 soundAlias example fix by @PepsiiMan in: [#1720](https://codeberg.org/uzu/strudel/pulls/1720) -- 2025-11-10T08:18:36+01:00 fix: Make Supradough package build by @munshkr in: [#1711](https://codeberg.org/uzu/strudel/pulls/1711) -- 2025-11-10T08:18:00+01:00 added docs for spaces in scale names by @ondras in: [#1715](https://codeberg.org/uzu/strudel/pulls/1715) -- 2025-11-10T02:44:01+01:00 Feature: LFOs for Filters by @glossing in: [#1636](https://codeberg.org/uzu/strudel/pulls/1636) -- 2025-11-05T17:07:00+01:00 improvement: sync midi messages to audio context clock by @daslyfe in: [#1708](https://codeberg.org/uzu/strudel/pulls/1708) -- 2025-11-04T19:36:14+01:00 Add stick button mappings to gamepad implementation and improve docs by @kaosuryoko in: [#1696](https://codeberg.org/uzu/strudel/pulls/1696) -- 2025-11-04T19:29:35+01:00 Refactor sound stopping and triggering logic in SoundsTab component by @IJOL in: [#1688](https://codeberg.org/uzu/strudel/pulls/1688) -- 2025-11-04T19:13:21+01:00 Add setting to toggle pattern auto-start on pattern change by @moumar in: [#1690](https://codeberg.org/uzu/strudel/pulls/1690) -- 2025-11-04T18:58:50+01:00 Voicings JSDoc by @sharkeys_lunchbox in: [#1686](https://codeberg.org/uzu/strudel/pulls/1686) -- 2025-11-01T07:50:40+01:00 FIX: Loading local samples uses too much memory by @daslyfe in: [#1706](https://codeberg.org/uzu/strudel/pulls/1706) - - -## october 2025 - -- 2025-10-28T22:58:24+01:00 Bug Fix: Handle scale-for-notes when n also supplied by @glossing in: [#1625](https://codeberg.org/uzu/strudel/pulls/1625) -- 2025-10-28T22:51:16+01:00 Repurpose vim shortcuts for usability by @dtricks in: [#1624](https://codeberg.org/uzu/strudel/pulls/1624) -- 2025-10-28T22:35:21+01:00 Add support for euclidian in mondo with `bd&3:8` by @TristanCacqueray in: [#1630](https://codeberg.org/uzu/strudel/pulls/1630) -- 2025-10-27T10:42:07+01:00 Use bunny cdn for all samples and json files by @yaxu in: [#1701](https://codeberg.org/uzu/strudel/pulls/1701) -- **2025-10-27 @strudel/core@1.2.5** -- 2025-10-26T22:52:54+01:00 degithub - switch some samples to bunnycdn by @yaxu in: [#1697](https://codeberg.org/uzu/strudel/pulls/1697) -- 2025-10-26T17:09:22+01:00 Fix ZZFX example by @moumar in: [#1685](https://codeberg.org/uzu/strudel/pulls/1685) -- 2025-10-23T15:56:04+02:00 Make osc port and host configurable. Changes dependencies. by @yaxu in: [#1682](https://codeberg.org/uzu/strudel/pulls/1682) -- 2025-10-23T03:47:31+02:00 Adds back shape to superdough by @glossing in: [#1672](https://codeberg.org/uzu/strudel/pulls/1672) -- 2025-10-22T22:15:19+02:00 Fix sampler.mjs githubPath by @jeromew in: [#1684](https://codeberg.org/uzu/strudel/pulls/1684) -- 2025-10-22T16:20:50+02:00 Fix onPaint for widgets by @yaxu in: [#1658](https://codeberg.org/uzu/strudel/pulls/1658) -- 2025-10-22T15:19:12+02:00 Fix a bug introduced by #4e17cfbdd6 by @milliganf in: [#1679](https://codeberg.org/uzu/strudel/pulls/1679) -- 2025-10-20T22:37:15+02:00 feature/autocomplete-sound-names by @drdozer in: [#1564](https://codeberg.org/uzu/strudel/pulls/1564) -- 2025-10-20T21:58:12+02:00 add replicate + use it for ! in mondo by @JoStro in: [#1436](https://codeberg.org/uzu/strudel/pulls/1436) -- 2025-10-18T04:29:20+02:00 Bug Fix: Wavetable: phase wrapping at 1 and detune by @glossing in: [#1620](https://codeberg.org/uzu/strudel/pulls/1620) -- 2025-10-16T20:26:28+02:00 github samples: default to "samples" if repository is not specified by @prezmop in: [#1644](https://codeberg.org/uzu/strudel/pulls/1644) -- 2025-10-16T17:33:39+02:00 Docs: add example of custom chained function by @dariusk in: [#1642](https://codeberg.org/uzu/strudel/pulls/1642) -- 2025-10-14T12:39:48+02:00 textbox by @yaxu in: [#1650](https://codeberg.org/uzu/strudel/pulls/1650) -- 2025-10-13T07:26:51+02:00 Feature: Distortion Modes by @glossing in: [#1561](https://codeberg.org/uzu/strudel/pulls/1561) -- 2025-10-13T07:05:57+02:00 Feature: Wavetable FM by @glossing in: [#1623](https://codeberg.org/uzu/strudel/pulls/1623) -- 2025-10-12T20:43:42+02:00 fix: repair REPL sample sources and URL concat (#1640) by @erikfox in: [#1646](https://codeberg.org/uzu/strudel/pulls/1646) -- 2025-10-10T11:25:02+02:00 Add control-metadata by @yaxu in: [#1634](https://codeberg.org/uzu/strudel/pulls/1634) -- 2025-10-07T22:59:39+02:00 Fix MQTT: change the trigger handler to match new hap by @jurrchen in: [#1629](https://codeberg.org/uzu/strudel/pulls/1629) -- 2025-10-05T21:42:19+02:00 Fix case sensitivity for synonym search by @vvolhejn in: [#1618](https://codeberg.org/uzu/strudel/pulls/1618) -- 2025-10-01T08:56:35+02:00 Optimize wavetable synth by @glossing in: [#1613](https://codeberg.org/uzu/strudel/pulls/1613) -- 2025-10-01T02:02:33+02:00 fix: pattern switching by @daslyfe in: [#1616](https://codeberg.org/uzu/strudel/pulls/1616) - -## september 2025 - -- 2025-09-29T02:10:54+02:00 fix wavetable JSON by @daslyfe in: [#1611](https://codeberg.org/uzu/strudel/pulls/1611) -- 2025-09-28T21:28:16+02:00 rename wavetable controls to match existing naming conventions by @daslyfe in: [#1610](https://codeberg.org/uzu/strudel/pulls/1610) -- 2025-09-27T23:28:13+02:00 Feature: Wavetable synth improvements by @glossing in: [#1607](https://codeberg.org/uzu/strudel/pulls/1607) -- 2025-09-27T21:43:10+02:00 wavetable improvements by @daslyfe in: [#1608](https://codeberg.org/uzu/strudel/pulls/1608) -- 2025-09-26T08:48:46+02:00 create orbit based DJ filter by @daslyfe in: [#1603](https://codeberg.org/uzu/strudel/pulls/1603) -- 2025-09-26T08:15:49+02:00 Feature: Wavetable synth by @glossing in: [#1525](https://codeberg.org/uzu/strudel/pulls/1525) -- 2025-09-23T13:28:20+02:00 fix: time signal by @daslyfe in: [#1583](https://codeberg.org/uzu/strudel/pulls/1583) -- 2025-09-21T16:16:06+02:00 mondo: fix all + setcpm + setcps by @froos in: [#1600](https://codeberg.org/uzu/strudel/pulls/1600) -- 2025-09-19T21:59:43+02:00 fix: osc error message by @froos in: [#1597](https://codeberg.org/uzu/strudel/pulls/1597) -- 2025-09-18T21:09:56+02:00 osc: --debug flag to see incoming messages by @froos in: [#1595](https://codeberg.org/uzu/strudel/pulls/1595) -- 2025-09-17T14:12:10+02:00 simplify osc usage by @froos in: [#1588](https://codeberg.org/uzu/strudel/pulls/1588) -- 2025-09-16T16:59:58+02:00 dev: logger errors to console in dev mode by @daslyfe in: [#1585](https://codeberg.org/uzu/strudel/pulls/1585) -- 2025-09-16T03:19:37+02:00 Bug Fix / Feature: Updates to `duck` to avoid clicks and allow configuration over release/attack by @glossing in: [#1514](https://codeberg.org/uzu/strudel/pulls/1514) -- 2025-09-15T23:36:31+02:00 Feat: Enhance `scale` function to quantize notes to a named scale by @glossing in: [#1492](https://codeberg.org/uzu/strudel/pulls/1492) -- 2025-09-15T22:52:14+02:00 add tic80 font by @froos in: [#1579](https://codeberg.org/uzu/strudel/pulls/1579) -- 2025-09-15T00:00:19+02:00 supradough: fix delay + add some jsdoc types by @froos in: [#1578](https://codeberg.org/uzu/strudel/pulls/1578) -- 2025-09-14T12:42:58+02:00 added plyWith/plyWithClassic functions by @Dsm0 in: [#1571](https://codeberg.org/uzu/strudel/pulls/1571) -- 2025-09-14T10:39:12+02:00 feat: sound alias by @Options in: [#1494](https://codeberg.org/uzu/strudel/pulls/1494) -- 2025-09-14T01:20:15+02:00 Bug Fix: Allow penv values to be falsy by @glossing in: [#1559](https://codeberg.org/uzu/strudel/pulls/1559) -- 2025-09-14T01:10:48+02:00 Update website/src/pages/workshop/first-sounds.mdx by @fesmith in: [#1566](https://codeberg.org/uzu/strudel/pulls/1566) -- 2025-09-14T01:07:47+02:00 fix: autocomplete-styles + html rendering by @froos in: [#1570](https://codeberg.org/uzu/strudel/pulls/1570) -- 2025-09-14T00:49:17+02:00 Feature: Synonyms in autocomplete and reference by @glossing in: [#1535](https://codeberg.org/uzu/strudel/pulls/1535) -- 2025-09-13T20:01:48+02:00 Docs: add xen package examples by @dudymas in: [#1446](https://codeberg.org/uzu/strudel/pulls/1446) -- 2025-09-13T19:51:10+02:00 Update website/src/pages/learn/code.mdx by @anecondev in: [#1391](https://codeberg.org/uzu/strudel/pulls/1391) -- 2025-09-11T22:52:30+02:00 supradough poc by @froos in: [#1362](https://codeberg.org/uzu/strudel/pulls/1362) -- 2025-09-11T17:29:35+02:00 fix: exclude mondough dependencies by @froos in: [#1563](https://codeberg.org/uzu/strudel/pulls/1563) -- 2025-09-11T00:27:21+02:00 add basicSetup for keybindings by @Dsm0 in: [#1462](https://codeberg.org/uzu/strudel/pulls/1462) -- 2025-09-10T17:25:26+02:00 feat: add delete user samples button for convenience by @daslyfe in: [#1556](https://codeberg.org/uzu/strudel/pulls/1556) -- 2025-09-08T08:54:01+02:00 Fix formatting of REPL footnote by @ddbeck in: [#1547](https://codeberg.org/uzu/strudel/pulls/1547) -- 2025-09-08T05:38:08+02:00 fix: OSC by @daslyfe in: [#1557](https://codeberg.org/uzu/strudel/pulls/1557) -- 2025-09-07T22:47:13+02:00 Add examples for ? and | operators to documentation by james-@collapse in: [#1532](https://codeberg.org/uzu/strudel/pulls/1532) -- 2025-09-07T21:48:19+02:00 Signal flow documentation by @glossing in: [#1504](https://codeberg.org/uzu/strudel/pulls/1504) -- 2025-09-07T21:27:29+02:00 Allow flattening of dirs with sample server by @glossing in: [#1529](https://codeberg.org/uzu/strudel/pulls/1529) -- 2025-09-07T21:05:24+02:00 fix: prevent orbit clicking by defining max number of channels in an orbit by @daslyfe in: [#1552](https://codeberg.org/uzu/strudel/pulls/1552) -- 2025-09-02T08:42:42+02:00 Padding for tooltips, preserving linebreaks in descriptions, not closing autocompletes on click by @glossing in: [#1521](https://codeberg.org/uzu/strudel/pulls/1521) -- 2025-09-01T17:50:11+02:00 Update "restore defaults" to not delete patterns by @glossing in: [#1519](https://codeberg.org/uzu/strudel/pulls/1519) - -## august 2025 - -- 2025-08-31T19:10:04+02:00 feat: add the ability to control the speed and start time of the reverb IR by @daslyfe in: [#1501](https://codeberg.org/uzu/strudel/pulls/1501) -- 2025-08-25T02:31:22+02:00 Bug Fix: Duck on Mobile / Safari by @glossing in: [#1503](https://codeberg.org/uzu/strudel/pulls/1503) -- 2025-08-23T18:39:53+02:00 fix benchmarks by @yaxu in: [#1517](https://codeberg.org/uzu/strudel/pulls/1517) -- 2025-08-21T20:57:31+02:00 add --json flag to @strudel/sampler to generate a strudel.json by @froos in: [#1513](https://codeberg.org/uzu/strudel/pulls/1513) -- 2025-08-21T10:04:28+02:00 remove hs2js postinstall by @froos in: [#1510](https://codeberg.org/uzu/strudel/pulls/1510) -- 2025-08-20T15:15:34+02:00 doc: scrub, duckorbit, duckattack, duckdepth + fix: arp, arpWidth, hush by @fyynn in: [#1502](https://codeberg.org/uzu/strudel/pulls/1502) -- 2025-08-20T12:31:37+02:00 fix: repl autocomplete not rendering correctly by @robase in: [#1480](https://codeberg.org/uzu/strudel/pulls/1480) -- 2025-08-20T12:10:02+02:00 BUG FIX: make 'midin' initialization work with multiple controllers by @cbabraham in: [#1469](https://codeberg.org/uzu/strudel/pulls/1469) -- 2025-08-17T23:18:46+02:00 euclidish by @yaxu in: [#1482](https://codeberg.org/uzu/strudel/pulls/1482) -- 2025-08-17T06:37:23+02:00 feat: Create a duck (sidechain) orbit effect by @daslyfe in: [#1470](https://codeberg.org/uzu/strudel/pulls/1470) -- 2025-08-17T04:40:15+02:00 Bug Fix: Restore `log` functionality after onTrigger arg removal by @glossing in: [#1491](https://codeberg.org/uzu/strudel/pulls/1491) -- 2025-08-17T04:33:39+02:00 Bug Fix: Handle zero appearing first in FM's ADSR by @glossing in: [#1496](https://codeberg.org/uzu/strudel/pulls/1496) -- 2025-08-17T04:27:19+02:00 Bug Fix: FM (and vibrato) for Supersaws by @glossing in: [#1495](https://codeberg.org/uzu/strudel/pulls/1495) -- 2025-08-04T07:51:31+02:00 Fix incorrect stack Mini Notation by @samyk in: [#1484](https://codeberg.org/uzu/strudel/pulls/1484) -- 2025-08-03T18:57:45+02:00 fix: regression caused by incorrectly exported alias for transpose and scaleTranspose by @daslyfe in: [#1489](https://codeberg.org/uzu/strudel/pulls/1489) -- 2025-08-03T18:34:13+02:00 Add `strans` and `scaleTrans` as synonyms of `scaleTranspose` by @TodePond in: [#1488](https://codeberg.org/uzu/strudel/pulls/1488) -- 2025-08-02T18:18:28+02:00 Add `trans` alias for `transpose` by @TodePond in: [#1487](https://codeberg.org/uzu/strudel/pulls/1487) -- 2025-08-02T03:27:26+02:00 feat: Create Accurate 909 bass drum synthesizer by @daslyfe in: [#1478](https://codeberg.org/uzu/strudel/pulls/1478) -- 2025-08-02T03:23:49+02:00 feat: Add fmwave control and ability to fm with noise by @daslyfe in: [#1472](https://codeberg.org/uzu/strudel/pulls/1472) - -## july 2025 - -- 2025-07-30T00:22:41+02:00 hotfix: uzu kit JSON should be in the repo to avoid offline loading errors by @daslyfe in: [#1485](https://codeberg.org/uzu/strudel/pulls/1485) -- 2025-07-24T05:28:23+02:00 feat: make uzu-drumkit the default drumkit by @daslyfe in: [#1422](https://codeberg.org/uzu/strudel/pulls/1422) -- 2025-07-21T07:16:19+02:00 FIX: PWM modulation by @daslyfe in: [#1467](https://codeberg.org/uzu/strudel/pulls/1467) -- 2025-07-19T17:16:22+02:00 Tremolo modulation by @Ghost in: [#1095](https://codeberg.org/uzu/strudel/pulls/1095) -- 2025-07-15T09:39:33+02:00 fix: can now use def in mondough by @froos in: [#1461](https://codeberg.org/uzu/strudel/pulls/1461) -- 2025-07-11T11:10:24+02:00 fixed keybinding presedence issue by @Dsm0 in: [#1456](https://codeberg.org/uzu/strudel/pulls/1456) -- 2025-07-11T11:09:05+02:00 added multicursor support on ctrl/cmd + click by @Dsm0 in: [#1457](https://codeberg.org/uzu/strudel/pulls/1457) -- 2025-07-09T09:39:46+02:00 added tab-indent setting by @Dsm0 in: [#1454](https://codeberg.org/uzu/strudel/pulls/1454) -- 2025-07-08T15:48:41+02:00 ontrigger-refactoring by @froos in: [#1442](https://codeberg.org/uzu/strudel/pulls/1442) -- 2025-07-07T23:14:24+02:00 fix: remove first gm_synth_bass_1, as it doesn't work in safari by @froos in: [#1452](https://codeberg.org/uzu/strudel/pulls/1452) -- 2025-07-07T00:06:16+02:00 fix: minor doc changes by @Aurel300 in: [#1435](https://codeberg.org/uzu/strudel/pulls/1435) -- 2025-07-05T12:53:45+02:00 Fix randrun and deps including shuffle. Fixes #1441 by @yaxu in: [#1447](https://codeberg.org/uzu/strudel/pulls/1447) -- 2025-07-05T12:15:52+02:00 add setDefault + resetDefaults to superdough by @froos in: [#1433](https://codeberg.org/uzu/strudel/pulls/1433) -- 2025-07-04T22:36:19+02:00 disable fm for supersaw by @froos in: [#1443](https://codeberg.org/uzu/strudel/pulls/1443) - -## june 2025 - -- 2025-06-30T13:28:10+02:00 corrected minor spelling mistake (#1425) by tj-@mueller in: [#1434](https://codeberg.org/uzu/strudel/pulls/1434) -- 2025-06-30T05:26:11+02:00 Add browser cache explanation in samples.mdx by @Ghost in: [#1369](https://codeberg.org/uzu/strudel/pulls/1369) -- 2025-06-30T05:18:42+02:00 add-release-notes by @froos in: [#1432](https://codeberg.org/uzu/strudel/pulls/1432) -- 2025-06-28T20:30:49+02:00 feat: delaytime in cycles by @Ghost in: [#1341](https://codeberg.org/uzu/strudel/pulls/1341) -- 2025-06-28T13:44:49+02:00 Case insensitive search in the reference tab by @kdiab in: [#1420](https://codeberg.org/uzu/strudel/pulls/1420) -- 2025-06-28T00:04:13+02:00 add unjoin, into and chunkinto by @yaxu in: [#1418](https://codeberg.org/uzu/strudel/pulls/1418) -- 2025-06-26T15:29:46+02:00 mondo notation by @froos in: [#1311](https://codeberg.org/uzu/strudel/pulls/1311) -- 2025-06-24T06:14:15+02:00 allow calling `all` multiple times by @froos in: [#1417](https://codeberg.org/uzu/strudel/pulls/1417) -- 2025-06-24T05:53:22+02:00 tonal: allow scales without tonic (default to C) by @Ghost in: [#1092](https://codeberg.org/uzu/strudel/pulls/1092) -- 2025-06-20T20:00:12+02:00 website intro: fix whitespace and code hosting name by @trofi in: [#1400](https://codeberg.org/uzu/strudel/pulls/1400) -- 2025-06-19T10:15:07+02:00 fix stepcat #1396 by @yaxu in: [#1398](https://codeberg.org/uzu/strudel/pulls/1398) -- 2025-06-19T09:52:38+02:00 deprecate cpm + refactor docs to use setcpm by @froos in: [#1397](https://codeberg.org/uzu/strudel/pulls/1397) -- 2025-06-19T02:49:09+02:00 feat: 3 new codemirror themes by @daslyfe in: [#1394](https://codeberg.org/uzu/strudel/pulls/1394) -- 2025-06-14T16:23:00+02:00 link back to tech manual in wiki by @yaxu in: [#1382](https://codeberg.org/uzu/strudel/pulls/1382) -- 2025-06-14T14:55:37+02:00 degithub-links by @froos in: [#1380](https://codeberg.org/uzu/strudel/pulls/1380) -- 2025-06-13T15:24:42+02:00 fix euclidLegatoRot by @bwagner in: [#1378](https://codeberg.org/uzu/strudel/pulls/1378) -- 2025-06-13T10:07:51+02:00 remove-ms by @yaxu in: [#1377](https://codeberg.org/uzu/strudel/pulls/1377) -- 2025-06-12T10:25:48+02:00 Share fat URL by @Ghost in: [#1375](https://codeberg.org/uzu/strudel/pulls/1375) -- 2025-06-05T20:39:16+02:00 feat: onTriggerTime for interfacing with random APIs and doing illegal things on event time by @Ghost in: [#1364](https://codeberg.org/uzu/strudel/pulls/1364) -- 2025-06-05T01:08:18+02:00 feat: berlin noise by @Ghost in: [#1363](https://codeberg.org/uzu/strudel/pulls/1363) - -## may 2025 - -- 2025-05-29T14:36:43+02:00 Fix bytebeat sample offset by @Ghost in: [#1359](https://codeberg.org/uzu/strudel/pulls/1359) -- 2025-05-29T12:14:18+02:00 preserve stepcount in chunks by @yaxu in: [#1358](https://codeberg.org/uzu/strudel/pulls/1358) -- 2025-05-29T02:34:30+02:00 Byte Beat improvements -> 2 by @Ghost in: [#1357](https://codeberg.org/uzu/strudel/pulls/1357) -- 2025-05-28T16:40:32+02:00 Bytebeat improvements by @Ghost in: [#1356](https://codeberg.org/uzu/strudel/pulls/1356) -- 2025-05-27T17:42:39+02:00 feat: Byte Beats! by @Ghost in: [#1354](https://codeberg.org/uzu/strudel/pulls/1354) -- 2025-05-18T21:43:05+02:00 fix: typo on docs causing problems with autocompletion. by @Ghost in: [#1350](https://codeberg.org/uzu/strudel/pulls/1350) -- 2025-05-15T01:12:07+02:00 feat: Create pulsewidth (pw) and pulsewidth lfo parameters by @Ghost in: [#1343](https://codeberg.org/uzu/strudel/pulls/1343) -- 2025-05-14T18:25:17+02:00 feat: Multi Channel Orbits by @Ghost in: [#1344](https://codeberg.org/uzu/strudel/pulls/1344) -- 2025-05-13T17:45:29+02:00 Fix typo by @Ghost in: [#1346](https://codeberg.org/uzu/strudel/pulls/1346) -- 2025-05-02T00:01:27+02:00 Fix web + repl package builds by @froos in: [#1339](https://codeberg.org/uzu/strudel/pulls/1339) -- 2025-05-01T23:50:57+02:00 fix: superdough worklets bundling by @froos in: [#1338](https://codeberg.org/uzu/strudel/pulls/1338) - -## april 2025 - -- 2025-04-27T22:25:54+02:00 FIX: sound import order by @Ghost in: [#1333](https://codeberg.org/uzu/strudel/pulls/1333) -- 2025-04-27T18:39:49+02:00 update docs to reflect import sounds tab change by @hpunq in: [#1332](https://codeberg.org/uzu/strudel/pulls/1332) -- 2025-04-27T18:38:26+02:00 feat: Improve gain curve by @Ghost in: [#1318](https://codeberg.org/uzu/strudel/pulls/1318) -- 2025-04-27T07:54:49+02:00 Add Icon to import sample button by @Ghost in: [#1331](https://codeberg.org/uzu/strudel/pulls/1331) -- 2025-04-27T07:53:41+02:00 Add new "import-sounds" tab with explanation on folder import by @hpunq in: [#1329](https://codeberg.org/uzu/strudel/pulls/1329) -- 2025-04-22T00:04:38+02:00 feat: new themes + theme improvements by @Ghost in: [#1326](https://codeberg.org/uzu/strudel/pulls/1326) -- 2025-04-20T18:24:15+02:00 feat: Create scrub function for scrubbing an audio file by @Ghost in: [#1321](https://codeberg.org/uzu/strudel/pulls/1321) -- 2025-04-18T07:17:39+02:00 fix: disable astro toolbar by default by @Ghost in: [#1324](https://codeberg.org/uzu/strudel/pulls/1324) -- 2025-04-18T07:17:38+02:00 fix: udels header by @Ghost in: [#1325](https://codeberg.org/uzu/strudel/pulls/1325) -- 2025-04-11T09:18:58+02:00 Send delta in OSC message in seconds, to match tidal/superdirt by @yaxu in: [#1323](https://codeberg.org/uzu/strudel/pulls/1323) -- 2025-04-08T06:08:57+02:00 FIX: Multichannel Audio by @Ghost in: [#1322](https://codeberg.org/uzu/strudel/pulls/1322) -- 2025-04-08T04:18:03+02:00 feat: add max polyphony feature for superdough by @Ghost in: [#1317](https://codeberg.org/uzu/strudel/pulls/1317) -- 2025-04-05T21:12:05+02:00 enhancement: make error messages easier to read by @Ghost in: [#1315](https://codeberg.org/uzu/strudel/pulls/1315) -- 2025-04-04T06:26:57+02:00 fix: replace empty spaces in registered sound keys by @Ghost in: [#1319](https://codeberg.org/uzu/strudel/pulls/1319) - -## march 2025 - -- 2025-03-26T17:01:05+01:00 small feat: Add alias for segment and ribbon by @Ghost in: [#1314](https://codeberg.org/uzu/strudel/pulls/1314) -- 2025-03-26T14:54:18+01:00 Fix typo pattnr by @Ghost in: [#1316](https://codeberg.org/uzu/strudel/pulls/1316) -- 2025-03-23T20:05:31+01:00 bugfix: Allow single param to be used in the as function by @Ghost in: [#1312](https://codeberg.org/uzu/strudel/pulls/1312) -- 2025-03-20T23:35:18+01:00 Add MIDI Program Change, SysEx, NRPN, PitchBend and AfterTouch Output by @nkymut in: [#1244](https://codeberg.org/uzu/strudel/pulls/1244) -- 2025-03-19T17:32:30+01:00 make soundfont base url configurable by @Ghost in: [#1040](https://codeberg.org/uzu/strudel/pulls/1040) -- 2025-03-18T20:07:08+01:00 Add num samples from 0 up to 20 by @yaxu in: [#1310](https://codeberg.org/uzu/strudel/pulls/1310) -- 2025-03-17T10:38:54+01:00 add num samples (edited numbers) by @yaxu in: [#1309](https://codeberg.org/uzu/strudel/pulls/1309) -- 2025-03-08T22:17:47+01:00 @strudel/sampler improvements by @froos in: [#1288](https://codeberg.org/uzu/strudel/pulls/1288) -- 2025-03-08T21:56:50+01:00 Add Gamepad module by @nkymut in: [#1223](https://codeberg.org/uzu/strudel/pulls/1223) -- 2025-03-04T19:17:37+01:00 change behaviour of polymeter, and remove polymeterSteps by @yaxu in: [#1302](https://codeberg.org/uzu/strudel/pulls/1302) -- 2025-03-04T03:36:57+01:00 feat: Create Pulse Oscillator with variable PWM by @Ghost in: [#1304](https://codeberg.org/uzu/strudel/pulls/1304) -- 2025-03-02T17:08:31+01:00 feat: Theme improvements by @Ghost in: [#1295](https://codeberg.org/uzu/strudel/pulls/1295) -- 2025-03-02T11:58:46+01:00 bugfix zoom stepcount by @yaxu in: [#1301](https://codeberg.org/uzu/strudel/pulls/1301) - -## february 2025 - -- 2025-02-28T16:01:20+01:00 Fix test error #1297 by @nkymut in: [#1298](https://codeberg.org/uzu/strudel/pulls/1298) -- 2025-02-28T02:53:42+01:00 Hotfix: prevent undefined pattern code from crashing strudel on load by @Ghost in: [#1297](https://codeberg.org/uzu/strudel/pulls/1297) -- 2025-02-27T08:24:58+01:00 Fix misplaced ending sentence by @Ghost in: [#1296](https://codeberg.org/uzu/strudel/pulls/1296) -- 2025-02-23T10:54:13+01:00 Signpost licenses for source code and samples a bit more, ref #1277 by @yaxu in: [#1289](https://codeberg.org/uzu/strudel/pulls/1289) -- 2025-02-23T10:52:27+01:00 Allow wchooseCycles probabilities to be patterned by @yaxu in: [#1292](https://codeberg.org/uzu/strudel/pulls/1292) -- 2025-02-22T02:18:59+01:00 Create Pattern Page Pagination by @Ghost in: [#1287](https://codeberg.org/uzu/strudel/pulls/1287) -- 2025-02-21T22:38:10+01:00 showcase tweaks by @yaxu in: [#1291](https://codeberg.org/uzu/strudel/pulls/1291) -- 2025-02-11T16:34:24+01:00 Fixes inverted triangle wave by renaming it to `itri`, making non-inverted `tri` by @yaxu in: [#1283](https://codeberg.org/uzu/strudel/pulls/1283) -- 2025-02-11T10:02:34+01:00 Fix `squeezejoin` and functions using it, including `bite` by @yaxu in: [#1286](https://codeberg.org/uzu/strudel/pulls/1286) -- 2025-02-09T16:18:28+01:00 Rename repeat back to extend by @yaxu in: [#1285](https://codeberg.org/uzu/strudel/pulls/1285) -- 2025-02-07T18:11:52+01:00 mqtt bugfix - connection check by @yaxu in: [#1282](https://codeberg.org/uzu/strudel/pulls/1282) -- 2025-02-07T17:38:29+01:00 Bugfix: update mqtt connections dictionary by @yaxu in: [#1281](https://codeberg.org/uzu/strudel/pulls/1281) -- 2025-02-07T11:39:54+01:00 make mqtt topic patternable by @yaxu in: [#1280](https://codeberg.org/uzu/strudel/pulls/1280) -- 2025-02-07T11:07:32+01:00 midimaps by @froos in: [#1274](https://codeberg.org/uzu/strudel/pulls/1274) -- 2025-02-06T15:59:03+01:00 MQTT - support adding hap duration and cps metadata to JSON messages by @yaxu in: [#1279](https://codeberg.org/uzu/strudel/pulls/1279) -- 2025-02-05T16:10:54+01:00 [breaking change] Sample signals from query onset, rather than midpoint by @yaxu in: [#1278](https://codeberg.org/uzu/strudel/pulls/1278) -- 2025-02-03T00:55:36+01:00 Stepwise documentation tweaks, with mridangam samples by @yaxu in: [#1275](https://codeberg.org/uzu/strudel/pulls/1275) -- 2025-02-02T21:26:44+01:00 Polish, rename, and document stepwise functions by @yaxu in: [#1262](https://codeberg.org/uzu/strudel/pulls/1262) -- 2025-02-01T22:57:00+01:00 Fix sf2 timing by @froos in: [#1272](https://codeberg.org/uzu/strudel/pulls/1272) -- 2025-02-01T22:32:59+01:00 Add bank aliasing and case insensitivity by @TodePond in: [#1245](https://codeberg.org/uzu/strudel/pulls/1245) - -## january 2025 - -- 2025-01-31T09:39:54+01:00 Add Device Motion module by @nkymut in: [#1217](https://codeberg.org/uzu/strudel/pulls/1217) -- 2025-01-29T15:40:01+01:00 add "as" function + getControlName by @froos in: [#1247](https://codeberg.org/uzu/strudel/pulls/1247) -- 2025-01-29T15:30:04+01:00 Theme glowup by @froos in: [#1268](https://codeberg.org/uzu/strudel/pulls/1268) -- 2025-01-29T15:22:18+01:00 patchday by @froos in: [#1264](https://codeberg.org/uzu/strudel/pulls/1264) -- 2025-01-28T00:00:03+01:00 Revert "Fix sometimes" by @yaxu in: [#1267](https://codeberg.org/uzu/strudel/pulls/1267) -- 2025-01-24T15:32:48+01:00 add reference package by @froos in: [#1252](https://codeberg.org/uzu/strudel/pulls/1252) -- 2025-01-24T15:16:55+01:00 support `all(pianoroll)` and `all(pianoroll({labels: true}))` by @yaxu in: [#1234](https://codeberg.org/uzu/strudel/pulls/1234) -- 2025-01-24T12:13:49+01:00 MQTT - if password isn't provided, prompt for one by @yaxu in: [#1249](https://codeberg.org/uzu/strudel/pulls/1249) -- 2025-01-21T06:24:03+01:00 fix docs for beat function by @Ghost in: [#1248](https://codeberg.org/uzu/strudel/pulls/1248) -- 2025-01-18T23:27:51+01:00 understand voicings page by @froos in: [#1230](https://codeberg.org/uzu/strudel/pulls/1230) -- 2025-01-17T19:27:00+01:00 Add binary and binaryN by @Ghost in: [#1226](https://codeberg.org/uzu/strudel/pulls/1226) -- 2025-01-16T12:18:33+01:00 Fix sometimes by @yaxu in: [#1243](https://codeberg.org/uzu/strudel/pulls/1243) -- 2025-01-16T05:55:46+01:00 "beat" function for "step sequencer" style rhythm notation by @Ghost in: [#1237](https://codeberg.org/uzu/strudel/pulls/1237) -- 2025-01-14T14:39:15+01:00 Add stepBind, and some toplevel aliases for binds and withValue by @yaxu in: [#1241](https://codeberg.org/uzu/strudel/pulls/1241) -- 2025-01-14T06:11:10+01:00 Add onKey function for custom key commands for patterns by @Ghost in: [#1235](https://codeberg.org/uzu/strudel/pulls/1235) -- 2025-01-12T11:32:24+01:00 Update documentation for param value modification by @Ghost in: [#1238](https://codeberg.org/uzu/strudel/pulls/1238) - -## december 2024 - -- 2024-12-29T13:16:08+01:00 Documentation for all/each, and bugfix for each by @yaxu in: [#1233](https://codeberg.org/uzu/strudel/pulls/1233) -- 2024-12-29T11:29:52+01:00 Make `all()` post-stack again, and add `each()` for pre-stack by @yaxu in: [#1229](https://codeberg.org/uzu/strudel/pulls/1229) -- 2024-12-27T22:16:54+01:00 suggested changes to voicings.mdx by @Ghost in: [#1231](https://codeberg.org/uzu/strudel/pulls/1231) -- 2024-12-23T11:53:11+01:00 add basic spectrum function by @froos in: [#1213](https://codeberg.org/uzu/strudel/pulls/1213) -- 2024-12-22T21:04:45+01:00 Fix regression for d1, p1, p(n) by @yaxu in: [#1227](https://codeberg.org/uzu/strudel/pulls/1227) - -## november 2024 - -- 2024-11-30T10:09:36+01:00 Make cps patternable by @eefano in: [#1001](https://codeberg.org/uzu/strudel/pulls/1001) -- 2024-11-30T10:07:56+01:00 MQTT support by @yaxu in: [#1224](https://codeberg.org/uzu/strudel/pulls/1224) -- 2024-11-30T09:46:14+01:00 Apply `all` function to individual patterns rather than final stack by @yaxu in: [#1209](https://codeberg.org/uzu/strudel/pulls/1209) -- 2024-11-20T16:32:51+01:00 REPL: solo and sync configuration by @Ghost in: [#1214](https://codeberg.org/uzu/strudel/pulls/1214) - -## october 2024 - -- 2024-10-30T21:29:43+01:00 Add s_zip for 'cat'-ing patterns together step-by-step, bugfix `steps` by @yaxu in: [#1208](https://codeberg.org/uzu/strudel/pulls/1208) -- 2024-10-30T21:28:32+01:00 Preserve tactus for 'degrade' and friends, and tidy up 'pick' and friends by @yaxu in: [#1205](https://codeberg.org/uzu/strudel/pulls/1205) -- 2024-10-30T17:11:53+01:00 chore: Edit run locally instructions in README.md by @Ghost in: [#1206](https://codeberg.org/uzu/strudel/pulls/1206) -- 2024-10-23T23:08:14+02:00 colorize console + tweak header by @froos in: [#1203](https://codeberg.org/uzu/strudel/pulls/1203) -- 2024-10-22T22:55:00+02:00 markcss by @froos in: [#1202](https://codeberg.org/uzu/strudel/pulls/1202) -- 2024-10-21T22:56:37+02:00 add 2 new ui settings by @froos in: [#1200](https://codeberg.org/uzu/strudel/pulls/1200) -- 2024-10-21T20:22:52+02:00 Make panel hover behavior optional by @Ghost in: [#1199](https://codeberg.org/uzu/strudel/pulls/1199) -- 2024-10-19T04:28:36+02:00 Menu Panel Improvements! by @Ghost in: [#1193](https://codeberg.org/uzu/strudel/pulls/1193) -- 2024-10-19T01:10:06+02:00 update lockfile + minor versions by @froos in: [#1198](https://codeberg.org/uzu/strudel/pulls/1198) - -## september 2024 - -- 2024-09-27T00:17:00+02:00 remove redundant example for cat, update snapshot by @Ghost in: [#1189](https://codeberg.org/uzu/strudel/pulls/1189) -- 2024-09-25T13:18:19+02:00 Adding search bar (soundtab.jsx) by @Ghost in: [#1185](https://codeberg.org/uzu/strudel/pulls/1185) -- 2024-09-23T17:18:34+02:00 Screenreader improvements by @yaxu in: [#1158](https://codeberg.org/uzu/strudel/pulls/1158) -- 2024-09-20T22:26:41+02:00 Fix serial timing by @yaxu in: [#1188](https://codeberg.org/uzu/strudel/pulls/1188) -- 2024-09-20T00:26:30+02:00 Add bite function by @yaxu in: [#1187](https://codeberg.org/uzu/strudel/pulls/1187) -- 2024-09-14T13:30:53+02:00 better spacing in zen mode by @froos in: [#1147](https://codeberg.org/uzu/strudel/pulls/1147) -- 2024-09-14T10:50:24+02:00 add filter + filterWhen + within by @froos in: [#1039](https://codeberg.org/uzu/strudel/pulls/1039) -- 2024-09-14T10:49:21+02:00 refactor sampler by @froos in: [#1101](https://codeberg.org/uzu/strudel/pulls/1101) -- 2024-09-14T10:43:17+02:00 handle midin device not found error by @froos in: [#1146](https://codeberg.org/uzu/strudel/pulls/1146) -- 2024-09-13T21:59:23+02:00 Add a search bar to the REPL Reference tab by @Ghost in: [#1165](https://codeberg.org/uzu/strudel/pulls/1165) -- 2024-09-13T21:58:47+02:00 Correct spelling mistakes by @Ghost in: [#1183](https://codeberg.org/uzu/strudel/pulls/1183) -- 2024-09-07T23:41:29+02:00 Add seqPLoop from Tidal by @yaxu in: [#1182](https://codeberg.org/uzu/strudel/pulls/1182) -- 2024-09-05T05:52:50+02:00 make phaser control match superdirt by @Ghost in: [#1180](https://codeberg.org/uzu/strudel/pulls/1180) -- 2024-09-05T05:33:41+02:00 Revert "Make phaser control consistent with superdirt" by @Ghost in: [#1179](https://codeberg.org/uzu/strudel/pulls/1179) -- 2024-09-05T05:29:12+02:00 Make phaser control consistent with superdirt by @Ghost in: [#1178](https://codeberg.org/uzu/strudel/pulls/1178) -- 2024-09-03T04:37:15+02:00 fix sample speed when using splice and fit with superdirt by @Ghost in: [#1172](https://codeberg.org/uzu/strudel/pulls/1172) -- 2024-09-01T16:02:24+02:00 Create audio target selector for OSC/Superdirt by @Ghost in: [#1160](https://codeberg.org/uzu/strudel/pulls/1160) -- 2024-09-01T15:03:47+02:00 Fixes fit so it works after a chop or slice by @yaxu in: [#1171](https://codeberg.org/uzu/strudel/pulls/1171) - -## august 2024 - -- 2024-08-30T14:24:08+02:00 polyJoin by @yaxu in: [#1168](https://codeberg.org/uzu/strudel/pulls/1168) -- 2024-08-23T17:05:10+02:00 Add scramble and shuffle by @yaxu in: [#1167](https://codeberg.org/uzu/strudel/pulls/1167) -- 2024-08-18T18:22:20+02:00 Improve + simplify neocyclist timing by @Ghost in: [#1164](https://codeberg.org/uzu/strudel/pulls/1164) -- 2024-08-15T05:18:33+02:00 containerize/seperate out boolean checks for repl types/Repl logic into bespoke components. by @Ghost in: [#1163](https://codeberg.org/uzu/strudel/pulls/1163) -- 2024-08-12T18:57:21+02:00 [CORS HOTFIX] by @Ghost in: [#1162](https://codeberg.org/uzu/strudel/pulls/1162) -- 2024-08-09T05:11:28+02:00 Fix OSC clock jitter by @Ghost in: [#1157](https://codeberg.org/uzu/strudel/pulls/1157) - -## july 2024 - -- 2024-07-27T11:02:38+02:00 Fix loopAt tactus by @yaxu in: [#1145](https://codeberg.org/uzu/strudel/pulls/1145) -- 2024-07-26T04:37:34+02:00 "stretch" function (phase vocoder) by @Ghost in: [#1130](https://codeberg.org/uzu/strudel/pulls/1130) -- 2024-07-26T04:37:17+02:00 Udels (MultiFrame Strudel) Revisited by @Ghost in: [#1132](https://codeberg.org/uzu/strudel/pulls/1132) -- 2024-07-24T11:40:28+02:00 Fix tactus marking in mininotation by @yaxu in: [#1144](https://codeberg.org/uzu/strudel/pulls/1144) - -## june 2024 - -- 2024-06-25T18:13:28+02:00 export comment commands by @froos in: [#1136](https://codeberg.org/uzu/strudel/pulls/1136) -- 2024-06-24T18:19:22+02:00 Chop chop by @yaxu in: [#1078](https://codeberg.org/uzu/strudel/pulls/1078) -- 2024-06-18T23:58:08+02:00 Fix bug in Fraction.lcm by @yaxu in: [#1133](https://codeberg.org/uzu/strudel/pulls/1133) -- 2024-06-18T05:32:12+02:00 Fix clock worker dependency path in module builds by @Ghost in: [#1129](https://codeberg.org/uzu/strudel/pulls/1129) -- 2024-06-04T00:26:48+02:00 Labeled statements doc by @froos in: [#1126](https://codeberg.org/uzu/strudel/pulls/1126) -- 2024-06-03T22:40:21+02:00 doc: visual functions + refactor onPaint by @froos in: [#1125](https://codeberg.org/uzu/strudel/pulls/1125) -- 2024-06-02T18:36:29+02:00 Fix indexDB failing with large amount of files by @Ghost in: [#1124](https://codeberg.org/uzu/strudel/pulls/1124) - -## may 2024 - -- 2024-05-31T12:17:34+02:00 Migrate tutorial fanchor by @froos in: [#1122](https://codeberg.org/uzu/strudel/pulls/1122) -- 2024-05-31T10:46:47+02:00 [BUG FIX] Audio worklets sometimes dont load by @Ghost in: [#1121](https://codeberg.org/uzu/strudel/pulls/1121) -- 2024-05-31T10:25:24+02:00 change fanchor to 0 by @Ghost in: [#1107](https://codeberg.org/uzu/strudel/pulls/1107) -- 2024-05-30T14:39:30+02:00 fix: use full repl in web package by @froos in: [#1119](https://codeberg.org/uzu/strudel/pulls/1119) -- 2024-05-30T09:36:32+02:00 can now access strudelMirror from repl by @froos in: [#1117](https://codeberg.org/uzu/strudel/pulls/1117) -- 2024-05-29T13:06:34+02:00 Add the mousex and mousey signal by @Ghost in: [#1112](https://codeberg.org/uzu/strudel/pulls/1112) -- 2024-05-29T13:02:00+02:00 Fixes drawPianoroll import in codemirror example by @Ghost in: [#1116](https://codeberg.org/uzu/strudel/pulls/1116) -- 2024-05-28T22:43:46+02:00 clarify `off` in pattern-effects.mdx by @Ghost in: [#1074](https://codeberg.org/uzu/strudel/pulls/1074) -- 2024-05-26T17:33:07+02:00 rollback phaser by @Ghost in: [#1113](https://codeberg.org/uzu/strudel/pulls/1113) -- 2024-05-26T17:33:06+02:00 Fix audio worklets by @Ghost in: [#1114](https://codeberg.org/uzu/strudel/pulls/1114) -- 2024-05-26T16:53:56+02:00 Calculate phaser modulation phase based on time by @Ghost in: [#1110](https://codeberg.org/uzu/strudel/pulls/1110) -- 2024-05-23T15:06:01+02:00 Add analog-style ladder filter by @Ghost in: [#1103](https://codeberg.org/uzu/strudel/pulls/1103) -- 2024-05-22T12:53:20+02:00 fix sampler on windows by @Ghost in: [#1109](https://codeberg.org/uzu/strudel/pulls/1109) -- 2024-05-20T23:26:20+02:00 web package fixes by @froos in: [#1044](https://codeberg.org/uzu/strudel/pulls/1044) -- 2024-05-20T22:41:28+02:00 Fix sampler windows by @froos in: [#1108](https://codeberg.org/uzu/strudel/pulls/1108) -- 2024-05-19T12:07:26+02:00 hs2js package / tidal parser by @froos in: [#870](https://codeberg.org/uzu/strudel/pulls/870) -- 2024-05-18T10:49:08+02:00 fix little dub tune example by @Ghost in: [#1104](https://codeberg.org/uzu/strudel/pulls/1104) -- 2024-05-17T14:43:58+02:00 Samples tab improvements by @Ghost in: [#1102](https://codeberg.org/uzu/strudel/pulls/1102) -- 2024-05-13T10:10:34+02:00 osc: couple of fixes by @Ghost in: [#1093](https://codeberg.org/uzu/strudel/pulls/1093) -- 2024-05-13T06:31:20+02:00 Use sessionStorage for viewingPatternData and activePattern by @Ghost in: [#1091](https://codeberg.org/uzu/strudel/pulls/1091) -- 2024-05-12T18:23:09+02:00 repl: set document.title from @title by @Ghost in: [#1090](https://codeberg.org/uzu/strudel/pulls/1090) -- 2024-05-07T14:29:22+02:00 fix: missing events due to premature worklet cleanup by @froos in: [#1089](https://codeberg.org/uzu/strudel/pulls/1089) -- 2024-05-03T11:52:57+02:00 Benchmarks by @yaxu in: [#1079](https://codeberg.org/uzu/strudel/pulls/1079) -- 2024-05-03T08:46:52+02:00 fix: csound + dough timing by @froos in: [#1086](https://codeberg.org/uzu/strudel/pulls/1086) -- 2024-05-03T00:37:32+02:00 Improve performance of ! (replicate) by @yaxu in: [#1084](https://codeberg.org/uzu/strudel/pulls/1084) -- 2024-05-02T23:40:22+02:00 fix: url parsing with extra params by @froos in: [#1083](https://codeberg.org/uzu/strudel/pulls/1083) - -## april 2024 - -- 2024-04-29T12:36:11+02:00 Tactus calculation toggle and breaking change to tactus calculation in fast/slow/hurry by @yaxu in: [#1081](https://codeberg.org/uzu/strudel/pulls/1081) -- 2024-04-28T20:56:21+02:00 fix docs on alignment.mdx by @Ghost in: [#1076](https://codeberg.org/uzu/strudel/pulls/1076) -- 2024-04-28T20:49:18+02:00 add signals to recap in first-effects.mdx by @Ghost in: [#1073](https://codeberg.org/uzu/strudel/pulls/1073) -- 2024-04-28T20:44:57+02:00 fix translation issue in first-effects.mdx by @Ghost in: [#1072](https://codeberg.org/uzu/strudel/pulls/1072) -- 2024-04-28T20:44:29+02:00 fix failing format test by @Ghost in: [#1077](https://codeberg.org/uzu/strudel/pulls/1077) -- 2024-04-27T22:50:17+02:00 add nesting to `off` example variation in pattern-effects.mdx by @Ghost in: [#1075](https://codeberg.org/uzu/strudel/pulls/1075) -- 2024-04-27T22:48:58+02:00 add `<...>` to first-sounds.mdx recap by @Ghost in: [#1070](https://codeberg.org/uzu/strudel/pulls/1070) -- 2024-04-27T22:48:07+02:00 fix first sounds typo by @Ghost in: [#1069](https://codeberg.org/uzu/strudel/pulls/1069) -- 2024-04-27T22:47:44+02:00 fix cr typo on first-sounds.mdx by @Ghost in: [#1068](https://codeberg.org/uzu/strudel/pulls/1068) -- 2024-04-26T15:12:30+02:00 More tactus tidying by @yaxu in: [#1071](https://codeberg.org/uzu/strudel/pulls/1071) -- 2024-04-23T23:37:21+02:00 Fix stepjoin by @yaxu in: [#1067](https://codeberg.org/uzu/strudel/pulls/1067) -- 2024-04-23T23:32:00+02:00 clarify license by @yaxu in: [#1064](https://codeberg.org/uzu/strudel/pulls/1064) -- 2024-04-23T15:14:30+02:00 Tactus tweaks - fixes for maintaining tactus and highlight locations by @yaxu in: [#1065](https://codeberg.org/uzu/strudel/pulls/1065) -- 2024-04-21T23:57:57+02:00 fix OSC timing for recent scheduler updates by @Ghost in: [#1062](https://codeberg.org/uzu/strudel/pulls/1062) -- 2024-04-21T22:17:07+02:00 Stepwise functions from Tidal by @yaxu in: [#1060](https://codeberg.org/uzu/strudel/pulls/1060) -- 2024-04-21T11:03:55+02:00 Fix wchooseCycles not picking the whole pattern by @Ghost in: [#1061](https://codeberg.org/uzu/strudel/pulls/1061) -- 2024-04-19T00:05:52+02:00 add swing + swingBy by @froos in: [#1038](https://codeberg.org/uzu/strudel/pulls/1038) -- 2024-04-19T00:05:08+02:00 anonymous patterns + muting by @froos in: [#1059](https://codeberg.org/uzu/strudel/pulls/1059) -- 2024-04-12T12:34:27+02:00 improve tutorial + custom samples doc by @froos in: [#1053](https://codeberg.org/uzu/strudel/pulls/1053) -- 2024-04-12T12:31:49+02:00 fix: do not reset cc input values on each eval by @froos in: [#1054](https://codeberg.org/uzu/strudel/pulls/1054) -- 2024-04-08T17:25:27+02:00 transpose: support all combinations of numbers and strings for notes and intervals by @froos in: [#1048](https://codeberg.org/uzu/strudel/pulls/1048) -- 2024-04-08T10:46:18+02:00 Wax, wane, taper and taperlist by @yaxu in: [#1042](https://codeberg.org/uzu/strudel/pulls/1042) -- 2024-04-06T23:44:57+02:00 Midi Time hotfix for scheduler updates by @Ghost in: [#1047](https://codeberg.org/uzu/strudel/pulls/1047) -- 2024-04-05T12:48:03+02:00 pitchwheel visual by @froos in: [#1041](https://codeberg.org/uzu/strudel/pulls/1041) -- 2024-04-05T12:47:19+02:00 fix cyclist fizzling out by @froos in: [#1046](https://codeberg.org/uzu/strudel/pulls/1046) - -## march 2024 - -- 2024-03-30T16:05:59+01:00 remove dangerous arithmetic feature by @froos in: [#1030](https://codeberg.org/uzu/strudel/pulls/1030) -- 2024-03-30T16:05:27+01:00 Fix sampler paths by @froos in: [#1034](https://codeberg.org/uzu/strudel/pulls/1034) -- 2024-03-30T14:43:08+01:00 local sample server cli by @froos in: [#1033](https://codeberg.org/uzu/strudel/pulls/1033) -- 2024-03-29T17:14:28+01:00 add font file types to offline cache by @froos in: [#1032](https://codeberg.org/uzu/strudel/pulls/1032) -- 2024-03-29T17:10:16+01:00 add closeBrackets setting by @froos in: [#1031](https://codeberg.org/uzu/strudel/pulls/1031) -- 2024-03-29T14:55:06+01:00 Tactus tidy by @yaxu in: [#1027](https://codeberg.org/uzu/strudel/pulls/1027) -- 2024-03-28T17:06:44+01:00 add setting for sync flag by @froos in: [#1025](https://codeberg.org/uzu/strudel/pulls/1025) -- 2024-03-28T11:37:57+01:00 better theme integration for visuals + various fixes by @froos in: [#1024](https://codeberg.org/uzu/strudel/pulls/1024) -- 2024-03-28T11:33:25+01:00 More fonts by @froos in: [#1023](https://codeberg.org/uzu/strudel/pulls/1023) -- 2024-03-27T13:06:05+01:00 Feature: tactus marking by @yaxu in: [#1021](https://codeberg.org/uzu/strudel/pulls/1021) -- 2024-03-25T06:02:02+01:00 hotfix for 1017 by @Ghost in: [#1020](https://codeberg.org/uzu/strudel/pulls/1020) -- 2024-03-24T15:06:33+01:00 Document signals by @Ghost in: [#1015](https://codeberg.org/uzu/strudel/pulls/1015) -- 2024-03-24T10:16:11+01:00 Repl sync fixes by @Ghost in: [#1014](https://codeberg.org/uzu/strudel/pulls/1014) -- 2024-03-23T20:21:51+01:00 eliminate chromium clock jitter by @froos in: [#1004](https://codeberg.org/uzu/strudel/pulls/1004) -- 2024-03-23T15:51:07+01:00 update undocumented script by @froos in: [#1013](https://codeberg.org/uzu/strudel/pulls/1013) -- 2024-03-23T15:26:52+01:00 fix: await injectPatternMethods by @froos in: [#1012](https://codeberg.org/uzu/strudel/pulls/1012) -- 2024-03-23T15:18:12+01:00 rename trig -> reset, trigzero -> restart by @froos in: [#1010](https://codeberg.org/uzu/strudel/pulls/1010) -- 2024-03-23T12:30:03+01:00 Inline punchcard + spiral by @froos in: [#1008](https://codeberg.org/uzu/strudel/pulls/1008) -- 2024-03-23T12:27:22+01:00 Color in hap value by @froos in: [#1007](https://codeberg.org/uzu/strudel/pulls/1007) -- 2024-03-22T23:53:51+01:00 using strudel in your project guide + cleanup examples by @froos in: [#1006](https://codeberg.org/uzu/strudel/pulls/1006) -- 2024-03-22T23:41:50+01:00 accidentals in scale degrees by @eefano in: [#1000](https://codeberg.org/uzu/strudel/pulls/1000) -- 2024-03-21T22:53:55+01:00 supersaw oscillator by @Ghost in: [#978](https://codeberg.org/uzu/strudel/pulls/978) -- 2024-03-21T13:00:04+01:00 remove canvas, externalize samples, delete junk by @froos in: [#1003](https://codeberg.org/uzu/strudel/pulls/1003) -- 2024-03-18T11:37:55+01:00 Fix pure mini highlight by @yaxu in: [#994](https://codeberg.org/uzu/strudel/pulls/994) -- 2024-03-18T07:12:14+01:00 inline viz / widgets package by @froos in: [#989](https://codeberg.org/uzu/strudel/pulls/989) -- 2024-03-17T04:07:00+01:00 Labeled statements by @froos in: [#991](https://codeberg.org/uzu/strudel/pulls/991) -- 2024-03-16T18:24:38+01:00 Beat-oriented functionality by @yaxu in: [#976](https://codeberg.org/uzu/strudel/pulls/976) -- 2024-03-15T01:47:35+01:00 REPL sync between windows by @Ghost in: [#900](https://codeberg.org/uzu/strudel/pulls/900) -- 2024-03-14T00:20:07+01:00 Update synths.mdx by @Ghost in: [#984](https://codeberg.org/uzu/strudel/pulls/984) -- 2024-03-13T00:36:22+01:00 fix: share now shares what's visible instead of active by @froos in: [#985](https://codeberg.org/uzu/strudel/pulls/985) -- 2024-03-10T01:18:57+01:00 use ireal as default voicing dict by @froos in: [#967](https://codeberg.org/uzu/strudel/pulls/967) -- 2024-03-10T00:50:23+01:00 little fix for withVal by @eefano in: [#980](https://codeberg.org/uzu/strudel/pulls/980) -- 2024-03-10T00:46:51+01:00 Velocity in value by @froos in: [#974](https://codeberg.org/uzu/strudel/pulls/974) -- 2024-03-10T00:42:50+01:00 fix: clear hydra on reset by @froos in: [#983](https://codeberg.org/uzu/strudel/pulls/983) -- 2024-03-10T00:38:43+01:00 move canvas related helpers from core to new draw package by @froos in: [#971](https://codeberg.org/uzu/strudel/pulls/971) -- 2024-03-08T05:38:07+01:00 replace shape with distort in learn doc by @Ghost in: [#982](https://codeberg.org/uzu/strudel/pulls/982) -- 2024-03-06T12:14:49+01:00 alias - for ~ by @yaxu in: [#981](https://codeberg.org/uzu/strudel/pulls/981) -- 2024-03-04T16:04:23+01:00 Worklet Improvents / fixes by @Ghost in: [#963](https://codeberg.org/uzu/strudel/pulls/963) -- 2024-03-01T17:30:19+01:00 Nested controls by @froos in: [#973](https://codeberg.org/uzu/strudel/pulls/973) - -## february 2024 - -- 2024-02-29T15:33:12+01:00 feat: can now invert euclid pulses with negative numbers by @froos in: [#959](https://codeberg.org/uzu/strudel/pulls/959) -- 2024-02-29T15:31:23+01:00 pickOut(), pickRestart(), pickReset() by @eefano in: [#950](https://codeberg.org/uzu/strudel/pulls/950) -- 2024-02-29T15:12:45+01:00 remove legacy legato + duration implementations by @froos in: [#965](https://codeberg.org/uzu/strudel/pulls/965) -- 2024-02-29T08:47:53+01:00 fix for transpose(): preserve hap value object structure by @eefano in: [#966](https://codeberg.org/uzu/strudel/pulls/966) -- 2024-02-29T08:32:00+01:00 add debounce to logger by @froos in: [#968](https://codeberg.org/uzu/strudel/pulls/968) -- 2024-02-28T18:43:52+01:00 controls refactoring: simplify exports by @froos in: [#962](https://codeberg.org/uzu/strudel/pulls/962) -- 2024-02-25T19:17:00+01:00 fix: reset global fx on pattern change by @froos in: [#960](https://codeberg.org/uzu/strudel/pulls/960) -- 2024-02-25T14:15:30+01:00 fix midi issue on firefox and added quote error by @Ghost in: [#936](https://codeberg.org/uzu/strudel/pulls/936) -- 2024-02-25T13:19:47+01:00 'Enable Bracket Matching' option in Codemirror by @eefano in: [#956](https://codeberg.org/uzu/strudel/pulls/956) -- 2024-02-23T14:37:46+01:00 fix script importable packages (web + repl) by @froos in: [#957](https://codeberg.org/uzu/strudel/pulls/957) -- 2024-02-21T16:17:37+01:00 account for cps in midi time duration by @Ghost in: [#954](https://codeberg.org/uzu/strudel/pulls/954) -- 2024-02-21T10:27:12+01:00 Auto await samples by @froos in: [#955](https://codeberg.org/uzu/strudel/pulls/955) -- 2024-02-08T13:16:15+01:00 remove cjs builds by @froos in: [#945](https://codeberg.org/uzu/strudel/pulls/945) -- 2024-02-04T23:15:37+01:00 Minor documentation error: Update first-sounds.mdx by @Ghost in: [#941](https://codeberg.org/uzu/strudel/pulls/941) - - -## january 2024 - -- 2024-01-24T16:48:57+01:00 fix: pianoroll sorting by @froos in: [#938](https://codeberg.org/uzu/strudel/pulls/938) -- 2024-01-23T00:04:03+01:00 V1 release notes by @froos in: [#935](https://codeberg.org/uzu/strudel/pulls/935) -- 2024-01-22T20:20:53+01:00 2 years blog post by @froos in: [#929](https://codeberg.org/uzu/strudel/pulls/929) -- 2024-01-22T20:02:34+01:00 make 0.5hz cps the default by @yaxu in: [#931](https://codeberg.org/uzu/strudel/pulls/931) -- 2024-01-22T00:52:01+01:00 Fix pattern tab not showing patterns without created date by @Ghost in: [#934](https://codeberg.org/uzu/strudel/pulls/934) -- 2024-01-21T20:46:28+01:00 Add useful pattern selection behavior for performing. by @Ghost in: [#897](https://codeberg.org/uzu/strudel/pulls/897) -- 2024-01-21T01:30:28+01:00 Refactor cps functions by @froos in: [#933](https://codeberg.org/uzu/strudel/pulls/933) -- 2024-01-20T23:47:31+01:00 Make splice cps-aware by @yaxu in: [#932](https://codeberg.org/uzu/strudel/pulls/932) -- 2024-01-19T18:50:57+01:00 community bakery by @froos in: [#923](https://codeberg.org/uzu/strudel/pulls/923) -- 2024-01-19T18:49:54+01:00 add pickF and pickmodF by @Ghost in: [#924](https://codeberg.org/uzu/strudel/pulls/924) -- 2024-01-19T15:10:48+01:00 Mini-notation additions towards tidal compatibility by @yaxu in: [#926](https://codeberg.org/uzu/strudel/pulls/926) -- 2024-01-18T23:34:11+01:00 Blog improvements by @froos in: [#919](https://codeberg.org/uzu/strudel/pulls/919) -- 2024-01-18T18:08:29+01:00 pick, pickmod, inhabit, inhabitmod by @yaxu in: [#921](https://codeberg.org/uzu/strudel/pulls/921) -- 2024-01-18T18:04:27+01:00 Revert "`pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function" by @yaxu in: [#920](https://codeberg.org/uzu/strudel/pulls/920) -- 2024-01-18T17:45:39+01:00 `pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function by @yaxu in: [#918](https://codeberg.org/uzu/strudel/pulls/918) -- 2024-01-18T10:30:08+01:00 rename @strudel.cycles/* packages to @strudel/* by @froos in: [#917](https://codeberg.org/uzu/strudel/pulls/917) -- 2024-01-18T06:54:33+01:00 pitch envelopes by @froos in: [#913](https://codeberg.org/uzu/strudel/pulls/913) -- 2024-01-18T05:09:38+01:00 Fix: swatch/[name].png.js static path by @Ghost in: [#916](https://codeberg.org/uzu/strudel/pulls/916) -- 2024-01-16T08:22:28+01:00 Add more vowel qualities for the vowels function by @Ghost in: [#907](https://codeberg.org/uzu/strudel/pulls/907) -- 2024-01-14T23:56:36+01:00 adds a blog by @froos in: [#911](https://codeberg.org/uzu/strudel/pulls/911) -- 2024-01-14T23:52:41+01:00 Remove hideHeader for better mobile UI and consistency by @Ghost in: [#894](https://codeberg.org/uzu/strudel/pulls/894) -- 2024-01-14T22:43:06+01:00 Further Envelope improvements by @Ghost in: [#868](https://codeberg.org/uzu/strudel/pulls/868) -- 2024-01-14T00:48:04+01:00 public sharing by @froos in: [#910](https://codeberg.org/uzu/strudel/pulls/910) -- 2024-01-12T18:31:41+01:00 fix some build warnings by @froos in: [#902](https://codeberg.org/uzu/strudel/pulls/902) -- 2024-01-12T18:01:55+01:00 prevent vite from complaining about additional exports in jsx files by @Ghost in: [#891](https://codeberg.org/uzu/strudel/pulls/891) -- 2024-01-12T17:05:47+01:00 Update Vite version so hot reload works properly with newest pnpm version by @Ghost in: [#892](https://codeberg.org/uzu/strudel/pulls/892) -- 2024-01-12T15:09:41+01:00 support , in < > by @froos in: [#886](https://codeberg.org/uzu/strudel/pulls/886) -- 2024-01-10T13:55:39+01:00 fix: autocomplete / tooltip code example bug by @froos in: [#898](https://codeberg.org/uzu/strudel/pulls/898) -- 2024-01-06T15:23:55+01:00 fix: invisible selection on vim + emacs mode by @froos in: [#889](https://codeberg.org/uzu/strudel/pulls/889) -- 2024-01-05T22:29:20+01:00 scales can now be anchored by @froos in: [#888](https://codeberg.org/uzu/strudel/pulls/888) -- 2024-01-04T11:21:42+01:00 add root mode for voicings by @froos in: [#887](https://codeberg.org/uzu/strudel/pulls/887) -- 2024-01-01T18:32:17+01:00 Showcase by @froos in: [#885](https://codeberg.org/uzu/strudel/pulls/885) -- 2024-01-01T14:22:11+01:00 bugfix: suspend and close exisiting audio context when changing interface by @Ghost in: [#882](https://codeberg.org/uzu/strudel/pulls/882) -- 2024-01-01T14:21:52+01:00 add mastodon link by @froos in: [#884](https://codeberg.org/uzu/strudel/pulls/884) - -## december 2023 - -- 2023-12-31T17:02:57+01:00 fix: make sure n is never undefined before nanFallback by @froos in: [#881](https://codeberg.org/uzu/strudel/pulls/881) -- 2023-12-31T16:47:22+01:00 Error tolerance by @froos in: [#880](https://codeberg.org/uzu/strudel/pulls/880) -- 2023-12-31T10:06:24+01:00 bugfix: sound select indexes out of bounds by @Ghost in: [#871](https://codeberg.org/uzu/strudel/pulls/871) -- 2023-12-31T10:03:01+01:00 Audio device selection by @Ghost in: [#854](https://codeberg.org/uzu/strudel/pulls/854) -- 2023-12-31T00:59:49+01:00 Dependency update by @froos in: [#879](https://codeberg.org/uzu/strudel/pulls/879) -- 2023-12-29T16:53:41+01:00 move all examples to separate examples folder by @froos in: [#878](https://codeberg.org/uzu/strudel/pulls/878) -- 2023-12-29T15:29:17+01:00 final vanillification by @froos in: [#876](https://codeberg.org/uzu/strudel/pulls/876) -- 2023-12-27T18:38:09+01:00 Bug Fix #119: Clock drift by @Ghost in: [#874](https://codeberg.org/uzu/strudel/pulls/874) -- 2023-12-27T13:17:03+01:00 main repl vanillification by @froos in: [#873](https://codeberg.org/uzu/strudel/pulls/873) -- 2023-12-25T17:47:25+01:00 more work on vanilla repl: repl web component + package + MicroRepl by @froos in: [#866](https://codeberg.org/uzu/strudel/pulls/866) -- 2023-12-15T00:11:35+01:00 Vanilla repl 3 by @froos in: [#865](https://codeberg.org/uzu/strudel/pulls/865) -- 2023-12-14T21:28:49+01:00 Vanilla repl 2 by @froos in: [#863](https://codeberg.org/uzu/strudel/pulls/863) -- 2023-12-12T21:49:23+01:00 fix: finally repair envelopes by @froos in: [#861](https://codeberg.org/uzu/strudel/pulls/861) -- 2023-12-12T21:20:00+01:00 add missing trailing slashes by @froos in: [#860](https://codeberg.org/uzu/strudel/pulls/860) -- 2023-12-12T19:08:31+01:00 Sound Import from local file system by @Ghost in: [#839](https://codeberg.org/uzu/strudel/pulls/839) -- 2023-12-11T22:48:35+01:00 Pattern organization by @froos in: [#858](https://codeberg.org/uzu/strudel/pulls/858) -- 2023-12-09T17:25:11+01:00 Export patterns + ui tweaks by @froos in: [#855](https://codeberg.org/uzu/strudel/pulls/855) -- 2023-12-08T09:41:10+01:00 patterns tab: import patterns + style by @froos in: [#852](https://codeberg.org/uzu/strudel/pulls/852) -- 2023-12-07T20:42:00+01:00 Patterns tab + Refactor Panel by @froos in: [#769](https://codeberg.org/uzu/strudel/pulls/769) -- 2023-12-07T10:25:16+01:00 CHANGES: pnpm 8.1.3 to 8.11.0 by @Ghost in: [#850](https://codeberg.org/uzu/strudel/pulls/850) -- 2023-12-07T09:35:14+01:00 Fix edge case with rehype-urls and trailing slashes in image file paths by @Ghost in: [#849](https://codeberg.org/uzu/strudel/pulls/849) -- 2023-12-06T23:02:31+01:00 Fix examples page, piano() and a few workshop imgs by @Ghost in: [#848](https://codeberg.org/uzu/strudel/pulls/848) -- 2023-12-06T22:30:59+01:00 fix: swatch png src by @froos in: [#846](https://codeberg.org/uzu/strudel/pulls/846) -- 2023-12-06T22:19:30+01:00 fix: missing hash for links starting with / by @froos in: [#845](https://codeberg.org/uzu/strudel/pulls/845) -- 2023-12-06T22:05:05+01:00 improve slashing + base href behavior by @froos in: [#842](https://codeberg.org/uzu/strudel/pulls/842) -- 2023-12-06T21:26:49+01:00 Add in fixes from my fork to slashocalypse branch by @Ghost in: [#843](https://codeberg.org/uzu/strudel/pulls/843) -- 2023-12-05T18:43:58+01:00 Prevent 404 on Algolia crawls by @Ghost in: [#838](https://codeberg.org/uzu/strudel/pulls/838) -- 2023-12-05T18:26:47+01:00 Multichannel audio by @Ghost in: [#820](https://codeberg.org/uzu/strudel/pulls/820) -- 2023-12-05T12:19:58+01:00 ADDS: JetBrains IDE files and directories to .gitignore by @Ghost in: [#840](https://codeberg.org/uzu/strudel/pulls/840) -- 2023-12-05T12:19:27+01:00 CHANGES: github action pnpm version from 7 to 8.3.1 by @Ghost in: [#835](https://codeberg.org/uzu/strudel/pulls/835) -- 2023-12-05T12:19:17+01:00 CHANGES: pin pnpm to version 8.3.1 by @Ghost in: [#834](https://codeberg.org/uzu/strudel/pulls/834) -- 2023-12-05T12:19:06+01:00 CHANGES: github action checkout v2 -> v4 by @Ghost in: [#837](https://codeberg.org/uzu/strudel/pulls/837) -- 2023-12-05T11:15:14+01:00 FIXES: palindrome abc -> abccba by @Ghost in: [#831](https://codeberg.org/uzu/strudel/pulls/831) -- 2023-12-02T09:43:50+01:00 Fix a typo by @Ghost in: [#830](https://codeberg.org/uzu/strudel/pulls/830) - -## november 2023 - -- 2023-11-30T10:42:41+01:00 Add and style algolia search by @Ghost in: [#827](https://codeberg.org/uzu/strudel/pulls/827) -- 2023-11-25T15:50:44+01:00 Hydra fixes and improvements by @Ghost in: [#818](https://codeberg.org/uzu/strudel/pulls/818) -- 2023-11-24T10:07:17+01:00 add options param to initHydra by @Ghost in: [#808](https://codeberg.org/uzu/strudel/pulls/808) -- 2023-11-24T09:57:02+01:00 Improve documentation for synonym functions by @Ghost in: [#800](https://codeberg.org/uzu/strudel/pulls/800) -- 2023-11-24T09:00:54+01:00 New noise type: "crackle" by @Ghost in: [#806](https://codeberg.org/uzu/strudel/pulls/806) -- 2023-11-18T21:18:16+01:00 Color hsl by @froos in: [#815](https://codeberg.org/uzu/strudel/pulls/815) -- 2023-11-17T23:18:23+01:00 fix: multiple repls by @froos in: [#813](https://codeberg.org/uzu/strudel/pulls/813) -- 2023-11-17T20:52:25+01:00 upstream changes by @yaxu in: [#809](https://codeberg.org/uzu/strudel/pulls/809) -- 2023-11-17T16:04:10+01:00 Add doc for euclidLegatoRot, wordfall and slider by @Ghost in: [#801](https://codeberg.org/uzu/strudel/pulls/801) -- 2023-11-17T14:42:58+01:00 add option to disable active line highlighting in Code Settings by @Ghost in: [#804](https://codeberg.org/uzu/strudel/pulls/804) -- 2023-11-17T14:37:52+01:00 tidal style d1 ... d9 functions + more by @froos in: [#805](https://codeberg.org/uzu/strudel/pulls/805) -- 2023-11-15T20:12:23+01:00 remove unwanted cm6 outline for strudelTheme by @Ghost in: [#802](https://codeberg.org/uzu/strudel/pulls/802) -- 2023-11-13T23:30:15+01:00 Create phaser effect by @Ghost in: [#798](https://codeberg.org/uzu/strudel/pulls/798) -- 2023-11-10T12:17:35+01:00 support multiple named serial connections, change default baudrate by @yaxu in: [#551](https://codeberg.org/uzu/strudel/pulls/551) -- 2023-11-09T09:27:57+01:00 Fix for #1. Enables named instruments for csoundm. by @gogins in: [#662](https://codeberg.org/uzu/strudel/pulls/662) -- 2023-11-09T09:26:45+01:00 Adding vibrato to Superdough sampler by @Ghost in: [#706](https://codeberg.org/uzu/strudel/pulls/706) -- 2023-11-09T08:46:03+01:00 Document pianoroll by @Ghost in: [#784](https://codeberg.org/uzu/strudel/pulls/784) -- 2023-11-09T08:45:29+01:00 Update first-effects.mdx by @Ghost in: [#795](https://codeberg.org/uzu/strudel/pulls/795) -- 2023-11-09T08:44:43+01:00 Update pattern-effects.mdx by @Ghost in: [#796](https://codeberg.org/uzu/strudel/pulls/796) -- 2023-11-09T08:43:50+01:00 Update recap.mdx by @Ghost in: [#797](https://codeberg.org/uzu/strudel/pulls/797) -- 2023-11-07T11:53:49+01:00 Update first-sounds.mdx by @Ghost in: [#794](https://codeberg.org/uzu/strudel/pulls/794) -- 2023-11-06T23:17:32+01:00 don't use anchor links for reference by @froos in: [#791](https://codeberg.org/uzu/strudel/pulls/791) -- 2023-11-06T22:40:44+01:00 samples loading shortcuts: by @froos in: [#788](https://codeberg.org/uzu/strudel/pulls/788) -- 2023-11-05T22:47:48+01:00 Fix scope pos + document by @froos in: [#786](https://codeberg.org/uzu/strudel/pulls/786) -- 2023-11-05T22:10:25+01:00 Implement optional hover tooltip with function documentation by @Ghost in: [#783](https://codeberg.org/uzu/strudel/pulls/783) -- 2023-11-05T16:46:06+01:00 Add function params in reference tab by @Ghost in: [#785](https://codeberg.org/uzu/strudel/pulls/785) -- 2023-11-05T12:42:00+01:00 fix: style issues by @froos in: [#781](https://codeberg.org/uzu/strudel/pulls/781) -- 2023-11-05T12:21:28+01:00 add xfade by @froos in: [#780](https://codeberg.org/uzu/strudel/pulls/780) -- 2023-11-02T09:30:26+01:00 Update to Astro 3 by @froos in: [#775](https://codeberg.org/uzu/strudel/pulls/775) -- 2023-11-02T08:57:14+01:00 add vscode bindings by @Ghost in: [#773](https://codeberg.org/uzu/strudel/pulls/773) -- 2023-11-02T08:31:30+01:00 fix: share copy to clipboard + alert by @froos in: [#774](https://codeberg.org/uzu/strudel/pulls/774) -- 2023-11-01T22:22:34+01:00 Fix chunk, add fastChunk and repeatCycles by @yaxu in: [#712](https://codeberg.org/uzu/strudel/pulls/712) -- 2023-11-01T22:12:49+01:00 Document adsr function by @Ghost in: [#767](https://codeberg.org/uzu/strudel/pulls/767) -- 2023-11-01T22:11:49+01:00 Add pick and squeeze functions by @Ghost in: [#771](https://codeberg.org/uzu/strudel/pulls/771) -- 2023-11-01T22:04:00+01:00 Update vite pwa by @froos in: [#772](https://codeberg.org/uzu/strudel/pulls/772) - -## october 2023 - -- 2023-10-28T23:55:07+02:00 replace strudel.tidalcycles.org with strudel.cc by @froos in: [#768](https://codeberg.org/uzu/strudel/pulls/768) -- 2023-10-28T12:52:37+02:00 Document striate function by @Ghost in: [#766](https://codeberg.org/uzu/strudel/pulls/766) -- 2023-10-27T23:06:20+02:00 fix zen mode logo overlap by @froos in: [#760](https://codeberg.org/uzu/strudel/pulls/760) -- 2023-10-27T23:01:18+02:00 fix: scale offset by @froos in: [#764](https://codeberg.org/uzu/strudel/pulls/764) -- 2023-10-27T21:59:35+02:00 Fix addivite synthesis phases by @froos in: [#762](https://codeberg.org/uzu/strudel/pulls/762) -- 2023-10-26T16:28:16+02:00 Hydra integration by @froos in: [#759](https://codeberg.org/uzu/strudel/pulls/759) -- 2023-10-26T14:14:27+02:00 add play function by @froos in: [#758](https://codeberg.org/uzu/strudel/pulls/758) -- 2023-10-26T13:11:00+02:00 Add shabda shortcut by @Ghost in: [#740](https://codeberg.org/uzu/strudel/pulls/740) -- 2023-10-26T13:07:23+02:00 mini notation: international alphabets support by @Ghost in: [#751](https://codeberg.org/uzu/strudel/pulls/751) -- 2023-10-22T23:00:52+02:00 hopefully fix trainling slashes bug by @froos in: [#753](https://codeberg.org/uzu/strudel/pulls/753) -- 2023-10-21T23:38:50+02:00 Fix krill build command in README by @Ghost in: [#748](https://codeberg.org/uzu/strudel/pulls/748) -- 2023-10-21T00:20:50+02:00 completely revert config mess by @froos in: [#745](https://codeberg.org/uzu/strudel/pulls/745) -- 2023-10-20T22:41:39+02:00 fix: try different trailing slash behavior by @froos in: [#744](https://codeberg.org/uzu/strudel/pulls/744) -- 2023-10-20T22:34:03+02:00 fix: trailing slash confusion by @froos in: [#743](https://codeberg.org/uzu/strudel/pulls/743) -- 2023-10-20T12:07:04+02:00 [Bug Fix] chooseWith: prevent pattern from stopping audio when selection is >= 1 or < 0 by @Ghost in: [#741](https://codeberg.org/uzu/strudel/pulls/741) -- 2023-10-20T11:34:26+02:00 Recipes by @froos in: [#742](https://codeberg.org/uzu/strudel/pulls/742) -- 2023-10-13T12:57:24+02:00 vite-vanilla-repl readme fix by @froos in: [#737](https://codeberg.org/uzu/strudel/pulls/737) -- 2023-10-10T00:17:59+02:00 Add support for using samples as impulse response buffers for the reverb by @Ghost in: [#717](https://codeberg.org/uzu/strudel/pulls/717) -- 2023-10-09T21:43:42+02:00 fix: reverb sampleRate by @froos in: [#732](https://codeberg.org/uzu/strudel/pulls/732) -- 2023-10-09T21:34:33+02:00 fix: reverb roomsize not required by @froos in: [#731](https://codeberg.org/uzu/strudel/pulls/731) -- 2023-10-08T13:54:52+02:00 Compressor by @froos in: [#729](https://codeberg.org/uzu/strudel/pulls/729) -- 2023-10-08T13:25:57+02:00 fix: hashes in urls by @froos in: [#728](https://codeberg.org/uzu/strudel/pulls/728) -- 2023-10-07T15:48:06+02:00 consume n with scale by @froos in: [#727](https://codeberg.org/uzu/strudel/pulls/727) -- 2023-10-07T00:27:21+02:00 fix: reverb regenerate loophole by @froos in: [#726](https://codeberg.org/uzu/strudel/pulls/726) -- 2023-10-05T00:04:17+02:00 Better convolution reverb by generating impulse responses by @Ghost in: [#718](https://codeberg.org/uzu/strudel/pulls/718) -- 2023-10-04T10:31:53+02:00 Slider afterthoughts by @froos in: [#723](https://codeberg.org/uzu/strudel/pulls/723) -- 2023-10-03T16:39:06+02:00 Add 'white', 'pink' and 'brown' oscillators + refactor synth by @Ghost in: [#713](https://codeberg.org/uzu/strudel/pulls/713) -- 2023-10-01T14:20:50+02:00 support mininotation '..' range operator, fixes #715 by @yaxu in: [#716](https://codeberg.org/uzu/strudel/pulls/716) -- 2023-10-01T14:15:09+02:00 widgets by @froos in: [#714](https://codeberg.org/uzu/strudel/pulls/714) - -## september 2023 - -- 2023-09-28T11:03:09+02:00 Midi in by @froos in: [#699](https://codeberg.org/uzu/strudel/pulls/699) -- 2023-09-27T22:53:49+02:00 add midi clock support by @froos in: [#710](https://codeberg.org/uzu/strudel/pulls/710) -- 2023-09-25T23:36:10+02:00 Update bournemouth.mdx by @Ghost in: [#708](https://codeberg.org/uzu/strudel/pulls/708) -- 2023-09-25T23:06:30+02:00 add dough function for raw dsp by @froos in: [#707](https://codeberg.org/uzu/strudel/pulls/707) -- 2023-09-17T15:53:27+02:00 Update tauri.yml workflow file by @Ghost in: [#705](https://codeberg.org/uzu/strudel/pulls/705) -- 2023-09-17T11:05:06+02:00 Adding vibrato to base oscillators by @Ghost in: [#693](https://codeberg.org/uzu/strudel/pulls/693) -- 2023-09-17T08:27:36+02:00 Adding loop points and thus wavetable synthesis by @Ghost in: [#698](https://codeberg.org/uzu/strudel/pulls/698) -- 2023-09-16T02:03:49+02:00 Adding filter envelopes and filter order selection by @Ghost in: [#692](https://codeberg.org/uzu/strudel/pulls/692) -- 2023-09-09T11:19:58+02:00 Add logging from tauri by @Ghost in: [#697](https://codeberg.org/uzu/strudel/pulls/697) -- 2023-09-05T00:33:54+02:00 Direct OSC Support in Tauri by @Ghost in: [#694](https://codeberg.org/uzu/strudel/pulls/694) -- 2023-09-03T22:19:22+02:00 fix MIDI CC messages by @Ghost in: [#690](https://codeberg.org/uzu/strudel/pulls/690) -- 2023-09-03T10:22:15+02:00 add sleep timer + improve message iterating by @Ghost in: [#688](https://codeberg.org/uzu/strudel/pulls/688) - -## august 2023 - -- 2023-08-31T13:00:31+02:00 ZZFX Synth support by @Ghost in: [#684](https://codeberg.org/uzu/strudel/pulls/684) -- 2023-08-31T12:36:37+02:00 Wave Selection and Global Envelope on the FM Synth Modulator by @Ghost in: [#683](https://codeberg.org/uzu/strudel/pulls/683) -- 2023-08-31T05:52:17+02:00 Create Midi Integration for Tauri Desktop app by @Ghost in: [#685](https://codeberg.org/uzu/strudel/pulls/685) -- 2023-08-31T05:32:16+02:00 teletext theme + fonts by @froos in: [#681](https://codeberg.org/uzu/strudel/pulls/681) -- 2023-08-31T05:20:55+02:00 control osc partial count with n by @froos in: [#674](https://codeberg.org/uzu/strudel/pulls/674) -- 2023-08-27T22:18:06+02:00 add emoji support by @froos in: [#680](https://codeberg.org/uzu/strudel/pulls/680) -- 2023-08-27T16:12:32+02:00 Pianoroll improvements by @froos in: [#679](https://codeberg.org/uzu/strudel/pulls/679) -- 2023-08-26T21:22:17+02:00 Scope by @froos in: [#677](https://codeberg.org/uzu/strudel/pulls/677) -- 2023-08-23T21:50:50+02:00 Midi time fixes by @Ghost in: [#668](https://codeberg.org/uzu/strudel/pulls/668) -- 2023-08-20T23:19:08+02:00 basic fm by @froos in: [#669](https://codeberg.org/uzu/strudel/pulls/669) -- 2023-08-18T23:56:20+02:00 togglable panel position by @froos in: [#667](https://codeberg.org/uzu/strudel/pulls/667) -- 2023-08-18T15:59:20+02:00 fix osc bundle timestamp glitches caused by drifting clock by @Ghost in: [#666](https://codeberg.org/uzu/strudel/pulls/666) -- 2023-08-17T11:36:07+02:00 superdough: encapsulates web audio output by @froos in: [#664](https://codeberg.org/uzu/strudel/pulls/664) -- 2023-08-11T00:06:21+02:00 fix: always run previous trigger by @froos in: [#660](https://codeberg.org/uzu/strudel/pulls/660) -- 2023-08-10T23:54:58+02:00 fix: welcome message for latestCode by @froos in: [#659](https://codeberg.org/uzu/strudel/pulls/659) -- 2023-08-07T07:45:26+02:00 [Bug Fix] Midi: Don't treat note 0 as false by @Ghost in: [#657](https://codeberg.org/uzu/strudel/pulls/657) - -## july 2023 - -- 2023-07-29T09:06:18+02:00 [Bug Fix] Account for numeral notation when converting to midi by @Ghost in: [#656](https://codeberg.org/uzu/strudel/pulls/656) -- 2023-07-23T22:18:49+02:00 ireal voicings by @froos in: [#653](https://codeberg.org/uzu/strudel/pulls/653) -- 2023-07-22T09:30:21+02:00 Understand pitch by @froos in: [#652](https://codeberg.org/uzu/strudel/pulls/652) -- 2023-07-17T23:42:59+02:00 update vitest by @froos in: [#651](https://codeberg.org/uzu/strudel/pulls/651) -- 2023-07-17T23:34:33+02:00 stateless voicings + tonleiter lib by @froos in: [#647](https://codeberg.org/uzu/strudel/pulls/647) -- 2023-07-17T17:57:17+02:00 FIXES: TODO in rotateChroma by @Ghost in: [#650](https://codeberg.org/uzu/strudel/pulls/650) -- 2023-07-10T19:07:45+02:00 slice: list mode by @froos in: [#645](https://codeberg.org/uzu/strudel/pulls/645) -- 2023-07-04T23:50:30+02:00 Delete old packages by @froos in: [#639](https://codeberg.org/uzu/strudel/pulls/639) -- 2023-07-04T23:38:42+02:00 Adaptive Highlighting by @froos in: [#634](https://codeberg.org/uzu/strudel/pulls/634) -- 2023-07-04T21:55:57+02:00 More work on highlight IDs by @Ghost in: [#636](https://codeberg.org/uzu/strudel/pulls/636) -- 2023-07-04T18:44:48+02:00 snapshot tests: sort haps by part by @froos in: [#637](https://codeberg.org/uzu/strudel/pulls/637) - -## juny 2023 - -- 2023-06-30T22:47:40+02:00 fix: update canvas size on window resize by @froos in: [#631](https://codeberg.org/uzu/strudel/pulls/631) -- 2023-06-30T22:45:41+02:00 fix: out of range error by @froos in: [#630](https://codeberg.org/uzu/strudel/pulls/630) -- 2023-06-30T08:14:40+02:00 fix: midi clock drift by @froos in: [#627](https://codeberg.org/uzu/strudel/pulls/627) -- 2023-06-29T22:02:28+02:00 desktop: play samples from disk by @froos in: [#621](https://codeberg.org/uzu/strudel/pulls/621) -- 2023-06-29T21:59:43+02:00 cps dependent functions by @froos in: [#620](https://codeberg.org/uzu/strudel/pulls/620) -- 2023-06-26T22:45:04+02:00 Fix typo on packages.mdx by @Ghost in: [#520](https://codeberg.org/uzu/strudel/pulls/520) -- 2023-06-26T22:35:02+02:00 patterning ui settings by @froos in: [#606](https://codeberg.org/uzu/strudel/pulls/606) -- 2023-06-26T22:32:46+02:00 add spiral viz by @froos in: [#614](https://codeberg.org/uzu/strudel/pulls/614) -- 2023-06-26T22:17:46+02:00 tauri desktop app by @Ghost in: [#613](https://codeberg.org/uzu/strudel/pulls/613) -- 2023-06-23T09:59:43+02:00 fix: doc links by @froos in: [#612](https://codeberg.org/uzu/strudel/pulls/612) -- 2023-06-23T09:55:18+02:00 clip now works like legato in tidal by @froos in: [#598](https://codeberg.org/uzu/strudel/pulls/598) -- 2023-06-18T10:36:19+02:00 fix: flatten scale lists by @froos in: [#605](https://codeberg.org/uzu/strudel/pulls/605) -- 2023-06-18T10:36:17+02:00 tonal fixes by @froos in: [#607](https://codeberg.org/uzu/strudel/pulls/607) -- 2023-06-15T12:51:40+02:00 editor: enable line wrapping by @Ghost in: [#581](https://codeberg.org/uzu/strudel/pulls/581) -- 2023-06-15T10:22:46+02:00 add ratio function by @froos in: [#602](https://codeberg.org/uzu/strudel/pulls/602) -- 2023-06-12T23:24:29+02:00 enable auto-completion by @Ghost in: [#588](https://codeberg.org/uzu/strudel/pulls/588) -- 2023-06-11T22:04:17+02:00 improve cursor by @froos in: [#597](https://codeberg.org/uzu/strudel/pulls/597) -- 2023-06-11T20:00:45+02:00 Solmization added by @Ghost in: [#570](https://codeberg.org/uzu/strudel/pulls/570) -- 2023-06-11T19:45:00+02:00 fix: division by zero by @froos in: [#591](https://codeberg.org/uzu/strudel/pulls/591) -- 2023-06-11T19:44:43+02:00 fix: allow f for flat notes like tidal by @froos in: [#593](https://codeberg.org/uzu/strudel/pulls/593) -- 2023-06-11T19:44:41+02:00 Fix option dot by @froos in: [#596](https://codeberg.org/uzu/strudel/pulls/596) -- 2023-06-09T21:02:12+02:00 New Workshop by @froos in: [#587](https://codeberg.org/uzu/strudel/pulls/587) -- 2023-06-09T17:31:58+02:00 Music metadata by @Ghost in: [#580](https://codeberg.org/uzu/strudel/pulls/580) -- 2023-06-08T09:33:34+02:00 learn/tonal: fix typo in "scaleTran[s]pose" by @Ghost in: [#585](https://codeberg.org/uzu/strudel/pulls/585) -- 2023-06-07T20:19:11+02:00 repl: add option to display line numbers by @Ghost in: [#582](https://codeberg.org/uzu/strudel/pulls/582) -- 2023-06-05T21:35:49+02:00 Vanilla JS Refactoring by @froos in: [#563](https://codeberg.org/uzu/strudel/pulls/563) - -## may 2023 - -- 2023-05-05T08:34:37+02:00 Patchday by @froos in: [#559](https://codeberg.org/uzu/strudel/pulls/559) -- 2023-05-02T21:48:33+02:00 add basic triads and guidetone voicings by @froos in: [#557](https://codeberg.org/uzu/strudel/pulls/557) - -## april 2023 - -- 2023-04-28T12:46:56+02:00 fix: make soundfonts import dynamic by @froos in: [#556](https://codeberg.org/uzu/strudel/pulls/556) -- 2023-04-22T15:43:22+02:00 fix: colorable highlighting by @froos in: [#553](https://codeberg.org/uzu/strudel/pulls/553) -- 2023-04-06T00:11:02+02:00 fix: load soundfonts in prebake by @froos in: [#550](https://codeberg.org/uzu/strudel/pulls/550) - -## march 2023 - -- 2023-03-29T22:23:07+02:00 fix: reset time on stop by @froos in: [#548](https://codeberg.org/uzu/strudel/pulls/548) -- 2023-03-29T22:16:38+02:00 fix: allow whitespace at the end of a mini pattern by @froos in: [#547](https://codeberg.org/uzu/strudel/pulls/547) -- 2023-03-24T22:10:56+01:00 add firacode font by @froos in: [#544](https://codeberg.org/uzu/strudel/pulls/544) -- 2023-03-24T12:54:20+01:00 feat: add loader bar to animate loading state by @froos in: [#542](https://codeberg.org/uzu/strudel/pulls/542) -- 2023-03-23T22:39:27+01:00 do not reset cps before eval #517 by @froos in: [#539](https://codeberg.org/uzu/strudel/pulls/539) -- 2023-03-23T22:27:31+01:00 improve initial loading + wait before eval by @froos in: [#538](https://codeberg.org/uzu/strudel/pulls/538) -- 2023-03-23T21:40:20+01:00 fix period key for dvorak + remove duplicated code by @froos in: [#537](https://codeberg.org/uzu/strudel/pulls/537) -- 2023-03-23T11:44:56+01:00 Update lerna by @froos in: [#535](https://codeberg.org/uzu/strudel/pulls/535) -- 2023-03-23T10:21:55+01:00 feat: add freq support to gm soundfonts by @froos in: [#534](https://codeberg.org/uzu/strudel/pulls/534) -- 2023-03-23T10:18:58+01:00 Maintain random seed state in parser, not globally by @Ghost in: [#531](https://codeberg.org/uzu/strudel/pulls/531) -- 2023-03-21T22:35:18+01:00 FIXES: alias pm for polymeter by @Ghost in: [#527](https://codeberg.org/uzu/strudel/pulls/527) -- 2023-03-21T22:29:07+01:00 fix(footer): fix link to tidalcycles by @julienbouquillon in: [#529](https://codeberg.org/uzu/strudel/pulls/529) -- 2023-03-18T20:25:21+01:00 Update intro.mdx by @Ghost in: [#525](https://codeberg.org/uzu/strudel/pulls/525) -- 2023-03-18T20:24:38+01:00 Update samples.mdx by @Ghost in: [#524](https://codeberg.org/uzu/strudel/pulls/524) -- 2023-03-17T09:03:11+01:00 fix: envelopes in chrome by @froos in: [#521](https://codeberg.org/uzu/strudel/pulls/521) -- 2023-03-16T16:13:30+01:00 registerSound API + improved sounds tab + regroup soundfonts by @froos in: [#516](https://codeberg.org/uzu/strudel/pulls/516) -- 2023-03-14T21:54:53+01:00 add 2 illegible fonts by @froos in: [#518](https://codeberg.org/uzu/strudel/pulls/518) -- 2023-03-06T22:55:00+01:00 Update README.md by @Ghost in: [#474](https://codeberg.org/uzu/strudel/pulls/474) -- 2023-03-05T14:52:30+01:00 add arrange function by @froos in: [#508](https://codeberg.org/uzu/strudel/pulls/508) -- 2023-03-05T14:29:08+01:00 update react to 18 by @froos in: [#514](https://codeberg.org/uzu/strudel/pulls/514) -- 2023-03-04T19:06:18+01:00 Support list syntax in mininotation by @yaxu in: [#512](https://codeberg.org/uzu/strudel/pulls/512) -- 2023-03-03T12:40:23+01:00 can now use : as a replacement for space in scales by @froos in: [#502](https://codeberg.org/uzu/strudel/pulls/502) -- 2023-03-02T15:44:41+01:00 Reinstate slice and splice by @yaxu in: [#500](https://codeberg.org/uzu/strudel/pulls/500) -- 2023-03-02T14:49:30+01:00 fix: nano-repl highlighting by @froos in: [#501](https://codeberg.org/uzu/strudel/pulls/501) -- 2023-03-02T14:17:13+01:00 Add control aliases by @yaxu in: [#497](https://codeberg.org/uzu/strudel/pulls/497) -- 2023-03-01T09:27:27+01:00 implement cps in scheduler by @froos in: [#493](https://codeberg.org/uzu/strudel/pulls/493) - -## february 2023 - -- 2023-02-28T23:06:29+01:00 react style fixes by @froos in: [#491](https://codeberg.org/uzu/strudel/pulls/491) -- 2023-02-28T22:57:20+01:00 refactor react package by @froos in: [#490](https://codeberg.org/uzu/strudel/pulls/490) -- 2023-02-27T23:47:34+01:00 add algolia creds + optimize sidebar for crawling by @froos in: [#488](https://codeberg.org/uzu/strudel/pulls/488) -- 2023-02-27T19:28:10+01:00 fix app height by @froos in: [#485](https://codeberg.org/uzu/strudel/pulls/485) -- 2023-02-27T16:04:21+01:00 Revert "Another attempt at composable functions - WIP (#390)" by @froos in: [#484](https://codeberg.org/uzu/strudel/pulls/484) -- 2023-02-27T15:45:20+01:00 Update mini-notation.mdx by @yaxu in: [#365](https://codeberg.org/uzu/strudel/pulls/365) -- 2023-02-27T12:52:09+01:00 docs: packages + offline by @froos in: [#482](https://codeberg.org/uzu/strudel/pulls/482) -- 2023-02-25T14:26:49+01:00 Fix array args by @froos in: [#480](https://codeberg.org/uzu/strudel/pulls/480) -- 2023-02-25T12:33:22+01:00 midi cc support by @froos in: [#478](https://codeberg.org/uzu/strudel/pulls/478) -- 2023-02-23T00:11:05+01:00 fix: hash links by @froos in: [#473](https://codeberg.org/uzu/strudel/pulls/473) -- 2023-02-22T22:54:39+01:00 settings tab with vim / emacs modes + additional themes and fonts by @froos in: [#467](https://codeberg.org/uzu/strudel/pulls/467) -- 2023-02-22T22:53:22+01:00 Update input-output.mdx by @Ghost in: [#471](https://codeberg.org/uzu/strudel/pulls/471) -- 2023-02-22T22:52:50+01:00 FIXES: freqs instead of pitches by @Ghost in: [#464](https://codeberg.org/uzu/strudel/pulls/464) -- 2023-02-22T20:01:00+01:00 fix: osc should not return a promise by @froos in: [#472](https://codeberg.org/uzu/strudel/pulls/472) -- 2023-02-22T12:51:31+01:00 slice and splice by @yaxu in: [#466](https://codeberg.org/uzu/strudel/pulls/466) -- 2023-02-18T01:00:19+01:00 weave and weaveWith by @yaxu in: [#465](https://codeberg.org/uzu/strudel/pulls/465) -- 2023-02-17T00:15:21+01:00 Composable functions by @yaxu in: [#390](https://codeberg.org/uzu/strudel/pulls/390) -- 2023-02-16T20:38:45+01:00 FIXES: Warning about jsxBracketSameLine deprecation by @Ghost in: [#461](https://codeberg.org/uzu/strudel/pulls/461) -- 2023-02-14T22:11:02+01:00 Update synths.mdx by @Ghost in: [#438](https://codeberg.org/uzu/strudel/pulls/438) -- 2023-02-14T19:54:42+01:00 Update mini-notation.mdx by @Ghost in: [#437](https://codeberg.org/uzu/strudel/pulls/437) -- 2023-02-14T19:54:25+01:00 Update code.mdx by @Ghost in: [#436](https://codeberg.org/uzu/strudel/pulls/436) -- 2023-02-13T00:43:29+01:00 Fix anchors by @froos in: [#433](https://codeberg.org/uzu/strudel/pulls/433) -- 2023-02-11T21:02:11+01:00 autocomplete preparations by @froos in: [#427](https://codeberg.org/uzu/strudel/pulls/427) -- 2023-02-10T23:14:48+01:00 Themes by @froos in: [#431](https://codeberg.org/uzu/strudel/pulls/431) -- 2023-02-09T19:22:56+01:00 minirepl: add keyboard shortcuts by @froos in: [#429](https://codeberg.org/uzu/strudel/pulls/429) -- 2023-02-09T08:56:42+01:00 add cdn.freesound to cache list by @froos in: [#425](https://codeberg.org/uzu/strudel/pulls/425) -- 2023-02-08T20:36:00+01:00 add more offline caching by @froos in: [#421](https://codeberg.org/uzu/strudel/pulls/421) -- 2023-02-07T22:08:01+01:00 add caching strategy for missing file types + cache all samples loaded from github by @froos in: [#419](https://codeberg.org/uzu/strudel/pulls/419) -- 2023-02-06T23:29:57+01:00 PWA with offline support by @froos in: [#417](https://codeberg.org/uzu/strudel/pulls/417) -- 2023-02-05T17:27:32+01:00 improve samples doc by @froos in: [#411](https://codeberg.org/uzu/strudel/pulls/411) -- 2023-02-05T17:27:10+01:00 google gtfo by @froos in: [#413](https://codeberg.org/uzu/strudel/pulls/413) -- 2023-02-05T16:27:59+01:00 improve effects doc by @froos in: [#409](https://codeberg.org/uzu/strudel/pulls/409) -- 2023-02-05T14:56:03+01:00 Update effects.mdx by @Ghost in: [#410](https://codeberg.org/uzu/strudel/pulls/410) -- 2023-02-03T19:54:47+01:00 add shabda doc by @froos in: [#407](https://codeberg.org/uzu/strudel/pulls/407) -- 2023-02-02T21:45:56+01:00 fix: share url on subpath by @froos in: [#405](https://codeberg.org/uzu/strudel/pulls/405) -- 2023-02-02T21:35:45+01:00 update csound + fix sound output by @froos in: [#404](https://codeberg.org/uzu/strudel/pulls/404) -- 2023-02-02T19:53:36+01:00 pin @csound/browser to 6.18.3 + bump by @froos in: [#403](https://codeberg.org/uzu/strudel/pulls/403) -- 2023-02-01T22:47:27+01:00 release webaudio by @froos in: [#400](https://codeberg.org/uzu/strudel/pulls/400) -- 2023-02-01T22:45:16+01:00 can now await initAudio + initAudioOnFirstClick by @froos in: [#399](https://codeberg.org/uzu/strudel/pulls/399) -- 2023-02-01T22:32:18+01:00 fix: minirepl styles by @froos in: [#398](https://codeberg.org/uzu/strudel/pulls/398) -- 2023-02-01T22:17:19+01:00 proper builds + use pnpm workspaces by @froos in: [#396](https://codeberg.org/uzu/strudel/pulls/396) -- 2023-02-01T16:49:55+01:00 add pattern methods hurry, press and pressBy by @yaxu in: [#397](https://codeberg.org/uzu/strudel/pulls/397) - -## january 2023 - -- 2023-01-27T14:40:40+01:00 Add tidal-drum-patterns to examples by @Ghost in: [#379](https://codeberg.org/uzu/strudel/pulls/379) -- 2023-01-27T14:39:56+01:00 add ribbon + test + docs by @froos in: [#388](https://codeberg.org/uzu/strudel/pulls/388) -- 2023-01-27T12:10:37+01:00 Notes are not essential :) by @yaxu in: [#393](https://codeberg.org/uzu/strudel/pulls/393) -- 2023-01-21T17:18:13+01:00 document csound by @froos in: [#391](https://codeberg.org/uzu/strudel/pulls/391) -- 2023-01-19T12:04:51+01:00 Rename a to angle by @froos in: [#387](https://codeberg.org/uzu/strudel/pulls/387) -- 2023-01-19T11:49:39+01:00 add run + test + docs by @froos in: [#386](https://codeberg.org/uzu/strudel/pulls/386) -- 2023-01-19T11:32:56+01:00 docs: use note instead of n to mitigate confusion by @froos in: [#385](https://codeberg.org/uzu/strudel/pulls/385) -- 2023-01-18T17:10:09+01:00 update my-patterns instructions by @froos in: [#384](https://codeberg.org/uzu/strudel/pulls/384) -- 2023-01-15T23:19:43+01:00 Draw fixes by @froos in: [#377](https://codeberg.org/uzu/strudel/pulls/377) -- 2023-01-14T11:07:08+01:00 improve new draw logic by @froos in: [#372](https://codeberg.org/uzu/strudel/pulls/372) -- 2023-01-12T14:40:59+01:00 document more functions + change arp join by @froos in: [#369](https://codeberg.org/uzu/strudel/pulls/369) -- 2023-01-10T00:03:47+01:00 add https to url by @Ghost in: [#364](https://codeberg.org/uzu/strudel/pulls/364) -- 2023-01-09T23:37:34+01:00 doc structuring by @froos in: [#360](https://codeberg.org/uzu/strudel/pulls/360) -- 2023-01-09T23:23:28+01:00 Support for multiple mininotation operators by @yaxu in: [#350](https://codeberg.org/uzu/strudel/pulls/350) -- 2023-01-09T00:40:15+01:00 Fix .out(), renaming webaudio's out() to webaudio() by @yaxu in: [#361](https://codeberg.org/uzu/strudel/pulls/361) -- 2023-01-06T22:02:31+01:00 docs: tidal comparison + add global fx + add missing sampler fx by @froos in: [#356](https://codeberg.org/uzu/strudel/pulls/356) -- 2023-01-06T12:31:32+01:00 Fix Bjorklund by @yaxu in: [#343](https://codeberg.org/uzu/strudel/pulls/343) -- 2023-01-04T20:29:35+01:00 Fix prebake base path by @froos in: [#345](https://codeberg.org/uzu/strudel/pulls/345) -- 2023-01-04T19:55:49+01:00 fixes #346 by @froos in: [#347](https://codeberg.org/uzu/strudel/pulls/347) -- 2023-01-02T21:28:07+01:00 Patternify euclid, fast, slow and polymeter step parameters in mininotation by @yaxu in: [#341](https://codeberg.org/uzu/strudel/pulls/341) -- 2023-01-02T00:30:16+01:00 more animate functions + mini repl fix by @froos in: [#340](https://codeberg.org/uzu/strudel/pulls/340) -- 2023-01-01T12:34:11+01:00 move /my-patterns to /swatch by @yaxu in: [#338](https://codeberg.org/uzu/strudel/pulls/338) -- 2023-01-01T12:22:55+01:00 animation options by @froos in: [#337](https://codeberg.org/uzu/strudel/pulls/337) - -## december 2022 - -- 2022-12-31T22:42:49+01:00 Tidy parser, implement polymeters by @yaxu in: [#336](https://codeberg.org/uzu/strudel/pulls/336) -- 2022-12-31T16:51:53+01:00 animate mvp by @froos in: [#335](https://codeberg.org/uzu/strudel/pulls/335) -- 2022-12-30T20:16:10+01:00 testing + docs docs by @froos in: [#334](https://codeberg.org/uzu/strudel/pulls/334) -- 2022-12-29T21:11:16+01:00 Embed mode improvements by @froos in: [#333](https://codeberg.org/uzu/strudel/pulls/333) -- 2022-12-29T14:06:20+01:00 fix: can now multiply floats in mini notation by @froos in: [#332](https://codeberg.org/uzu/strudel/pulls/332) -- 2022-12-29T13:50:07+01:00 improve displaying 's' in pianoroll by @froos in: [#331](https://codeberg.org/uzu/strudel/pulls/331) -- 2022-12-28T17:35:35+01:00 my-patterns: fix paths + update readme by @froos in: [#330](https://codeberg.org/uzu/strudel/pulls/330) -- 2022-12-28T17:11:40+01:00 my-patterns build + deploy by @froos in: [#329](https://codeberg.org/uzu/strudel/pulls/329) -- 2022-12-28T15:43:31+01:00 add my-patterns by @froos in: [#328](https://codeberg.org/uzu/strudel/pulls/328) -- 2022-12-28T14:47:14+01:00 add examples route by @froos in: [#327](https://codeberg.org/uzu/strudel/pulls/327) -- 2022-12-28T14:44:31+01:00 fix: workaround Object.assign globalThis by @froos in: [#326](https://codeberg.org/uzu/strudel/pulls/326) -- 2022-12-26T23:29:47+01:00 mini repl improvements by @froos in: [#324](https://codeberg.org/uzu/strudel/pulls/324) -- 2022-12-26T23:00:34+01:00 support notes without octave by @froos in: [#323](https://codeberg.org/uzu/strudel/pulls/323) -- 2022-12-26T12:37:36+01:00 tutorial updates by @Ghost in: [#320](https://codeberg.org/uzu/strudel/pulls/320) -- 2022-12-23T18:26:30+01:00 Reference tab sort by @froos in: [#318](https://codeberg.org/uzu/strudel/pulls/318) -- 2022-12-23T00:49:56+01:00 Astro build by @froos in: [#315](https://codeberg.org/uzu/strudel/pulls/315) -- 2022-12-19T21:02:37+01:00 object support for .scale by @froos in: [#307](https://codeberg.org/uzu/strudel/pulls/307) -- 2022-12-19T21:02:22+01:00 Jsdoc component by @froos in: [#312](https://codeberg.org/uzu/strudel/pulls/312) -- 2022-12-19T20:31:46+01:00 fix: copy share link to clipboard was broken for some browers by @froos in: [#311](https://codeberg.org/uzu/strudel/pulls/311) -- 2022-12-19T12:11:24+01:00 ICLC2023 paper WIP by @yaxu in: [#306](https://codeberg.org/uzu/strudel/pulls/306) -- 2022-12-15T21:23:22+01:00 support freq in pianoroll by @froos in: [#308](https://codeberg.org/uzu/strudel/pulls/308) -- 2022-12-13T22:07:27+01:00 Updated csoundm to use the register facility . by @gogins in: [#303](https://codeberg.org/uzu/strudel/pulls/303) -- 2022-12-13T21:57:03+01:00 add lint + prettier check before test by @froos in: [#305](https://codeberg.org/uzu/strudel/pulls/305) -- 2022-12-13T21:21:44+01:00 add freq support to sampler by @froos in: [#301](https://codeberg.org/uzu/strudel/pulls/301) -- 2022-12-12T00:48:41+01:00 fix whitespace trimming by @froos in: [#300](https://codeberg.org/uzu/strudel/pulls/300) -- 2022-12-12T00:21:53+01:00 .defragmentHaps() for merging touching haps that share a whole and value by @yaxu in: [#299](https://codeberg.org/uzu/strudel/pulls/299) -- 2022-12-11T23:17:49+01:00 remove whitespace from highlighted region by @froos in: [#298](https://codeberg.org/uzu/strudel/pulls/298) -- 2022-12-11T22:03:26+01:00 update vitest by @froos in: [#297](https://codeberg.org/uzu/strudel/pulls/297) -- 2022-12-11T21:44:16+01:00 can now add bare numbers to numeral object props by @froos in: [#287](https://codeberg.org/uzu/strudel/pulls/287) -- 2022-12-11T21:27:58+01:00 Move stuff to new register function by @froos in: [#295](https://codeberg.org/uzu/strudel/pulls/295) -- 2022-12-11T20:07:12+01:00 add prettier task by @froos in: [#296](https://codeberg.org/uzu/strudel/pulls/296) -- 2022-12-10T15:39:04+01:00 Reorganise pattern.mjs with a 'toplevel first' regime by @yaxu in: [#286](https://codeberg.org/uzu/strudel/pulls/286) -- 2022-12-10T11:56:16+01:00 Fancy hap show, include part in snapshots by @yaxu in: [#291](https://codeberg.org/uzu/strudel/pulls/291) -- 2022-12-07T20:07:55+01:00 Switch 'operators' from .whatHow to .what.how by @yaxu in: [#285](https://codeberg.org/uzu/strudel/pulls/285) -- 2022-12-04T12:51:01+01:00 implement collect + arp function by @froos in: [#281](https://codeberg.org/uzu/strudel/pulls/281) -- 2022-12-02T13:38:27+01:00 do not recompile orc by @froos in: [#278](https://codeberg.org/uzu/strudel/pulls/278) -- 2022-12-02T12:49:26+01:00 add basic csound output by @froos in: [#275](https://codeberg.org/uzu/strudel/pulls/275) -- 2022-12-02T12:18:41+01:00 add licenses / credits to all tunes + remove some by @froos in: [#277](https://codeberg.org/uzu/strudel/pulls/277) -- 2022-12-02T11:45:02+01:00 Support sending CRC16 bytes with serial messages by @yaxu in: [#276](https://codeberg.org/uzu/strudel/pulls/276) - -## november 2022 - -- 2022-11-29T23:41:39+01:00 release version bumps by @froos in: [#273](https://codeberg.org/uzu/strudel/pulls/273) -- 2022-11-29T23:34:50+01:00 add eslint by @froos in: [#271](https://codeberg.org/uzu/strudel/pulls/271) -- 2022-11-29T23:34:26+01:00 tonal update with fixed memory leak by @froos in: [#272](https://codeberg.org/uzu/strudel/pulls/272) -- 2022-11-22T09:51:26+01:00 Tidying up core by @yaxu in: [#256](https://codeberg.org/uzu/strudel/pulls/256) -- 2022-11-21T22:15:48+01:00 fix performance bottleneck by @froos in: [#266](https://codeberg.org/uzu/strudel/pulls/266) -- 2022-11-17T11:08:38+01:00 fix tutorial bugs by @froos in: [#263](https://codeberg.org/uzu/strudel/pulls/263) -- 2022-11-16T13:15:28+01:00 Binaries by @froos in: [#254](https://codeberg.org/uzu/strudel/pulls/254) -- 2022-11-13T20:20:32+01:00 Repl refactoring by @froos in: [#255](https://codeberg.org/uzu/strudel/pulls/255) -- 2022-11-08T23:35:11+01:00 Webaudio build by @froos in: [#250](https://codeberg.org/uzu/strudel/pulls/250) -- 2022-11-08T22:43:32+01:00 new transpiler based on acorn by @froos in: [#249](https://codeberg.org/uzu/strudel/pulls/249) -- 2022-11-06T19:03:19+01:00 General purpose scheduler by @froos in: [#248](https://codeberg.org/uzu/strudel/pulls/248) -- 2022-11-06T11:05:23+01:00 snapshot tests on shared snippets by @froos in: [#243](https://codeberg.org/uzu/strudel/pulls/243) -- 2022-11-06T11:03:42+01:00 Some tunes by @froos in: [#247](https://codeberg.org/uzu/strudel/pulls/247) -- 2022-11-06T00:37:11+01:00 patchday by @froos in: [#246](https://codeberg.org/uzu/strudel/pulls/246) -- 2022-11-04T20:28:23+01:00 Readme + TLC by @froos in: [#244](https://codeberg.org/uzu/strudel/pulls/244) -- 2022-11-03T15:16:04+01:00 in source example tests by @froos in: [#242](https://codeberg.org/uzu/strudel/pulls/242) -- 2022-11-02T23:16:43+01:00 feat: support github: links by @froos in: [#240](https://codeberg.org/uzu/strudel/pulls/240) -- 2022-11-02T21:26:44+01:00 Load samples from url by @froos in: [#239](https://codeberg.org/uzu/strudel/pulls/239) -- 2022-11-02T11:42:32+01:00 Object arithmetic by @froos in: [#238](https://codeberg.org/uzu/strudel/pulls/238) -- 2022-11-01T00:36:14+01:00 Tidal drum machines by @froos in: [#237](https://codeberg.org/uzu/strudel/pulls/237) -- 2022-10-31T21:39:23+01:00 fx on stereo speakers by @froos in: [#236](https://codeberg.org/uzu/strudel/pulls/236) - -## october 2022 - -- 2022-10-30T19:10:33+01:00 add vcsl sample library by @froos in: [#235](https://codeberg.org/uzu/strudel/pulls/235) -- 2022-10-30T00:23:10+02:00 Fix zero length queries WIP by @yaxu in: [#234](https://codeberg.org/uzu/strudel/pulls/234) -- 2022-10-29T17:54:05+02:00 Out by default by @froos in: [#232](https://codeberg.org/uzu/strudel/pulls/232) -- 2022-10-26T23:53:49+02:00 Patternify range by @yaxu in: [#231](https://codeberg.org/uzu/strudel/pulls/231) -- 2022-10-26T21:42:12+02:00 Just another docs branch by @froos in: [#228](https://codeberg.org/uzu/strudel/pulls/228) -- 2022-10-26T21:41:12+02:00 Refactor tunes away from tone by @froos in: [#230](https://codeberg.org/uzu/strudel/pulls/230) -- 2022-10-20T09:26:28+02:00 Core util tests by @Ghost in: [#226](https://codeberg.org/uzu/strudel/pulls/226) -- 2022-10-06T22:35:45+02:00 fix fastgap for events that go across cycle boundaries by @yaxu in: [#225](https://codeberg.org/uzu/strudel/pulls/225) - -## september 2022 - -- 2022-09-25T21:15:36+02:00 Reverb by @froos in: [#224](https://codeberg.org/uzu/strudel/pulls/224) -- 2022-09-25T00:27:26+02:00 focus tweak for squeezeJoin - another go at fixing #216 by @yaxu in: [#221](https://codeberg.org/uzu/strudel/pulls/221) -- 2022-09-24T23:17:21+02:00 support negative speeds by @froos in: [#222](https://codeberg.org/uzu/strudel/pulls/222) -- 2022-09-24T21:55:15+02:00 Feedback Delay by @froos in: [#213](https://codeberg.org/uzu/strudel/pulls/213) -- 2022-09-23T12:06:17+02:00 Fix squeeze join by @yaxu in: [#220](https://codeberg.org/uzu/strudel/pulls/220) -- 2022-09-22T21:23:11+02:00 encapsulate webaudio output by @froos in: [#219](https://codeberg.org/uzu/strudel/pulls/219) -- 2022-09-22T19:18:18+02:00 samples now have envelopes by @froos in: [#218](https://codeberg.org/uzu/strudel/pulls/218) -- 2022-09-22T00:03:30+02:00 sampler features + fixes by @froos in: [#217](https://codeberg.org/uzu/strudel/pulls/217) -- 2022-09-19T23:32:55+02:00 Just another docs PR by @froos in: [#215](https://codeberg.org/uzu/strudel/pulls/215) -- 2022-09-17T23:47:43+02:00 Even more docs by @froos in: [#212](https://codeberg.org/uzu/strudel/pulls/212) -- 2022-09-17T15:36:53+02:00 Webaudio guide by @froos in: [#207](https://codeberg.org/uzu/strudel/pulls/207) -- 2022-09-16T00:21:29+02:00 Coarse crush shape by @froos in: [#205](https://codeberg.org/uzu/strudel/pulls/205) -- 2022-09-15T21:04:28+02:00 add vowel to .out by @froos in: [#201](https://codeberg.org/uzu/strudel/pulls/201) -- 2022-09-15T16:55:52+02:00 add rollup-plugin-visualizer to build by @froos in: [#200](https://codeberg.org/uzu/strudel/pulls/200) -- 2022-09-14T23:46:39+02:00 document random functions by @froos in: [#199](https://codeberg.org/uzu/strudel/pulls/199) -- 2022-09-09T22:04:40+02:00 Fix numbers in sampler by @froos in: [#196](https://codeberg.org/uzu/strudel/pulls/196) - -## august 2022 - -- 2022-08-14T17:40:41+02:00 change "stride"/"offset" of successive degradeBy/chooseIn by @Ghost in: [#185](https://codeberg.org/uzu/strudel/pulls/185) -- 2022-08-14T16:04:14+02:00 Soundfont file support by @froos in: [#183](https://codeberg.org/uzu/strudel/pulls/183) -- 2022-08-14T15:55:50+02:00 fix regression: old way of setting frequencies was broken by @froos in: [#190](https://codeberg.org/uzu/strudel/pulls/190) -- 2022-08-14T15:45:53+02:00 wait for prebake to finish before evaluating by @froos in: [#189](https://codeberg.org/uzu/strudel/pulls/189) -- 2022-08-14T11:31:00+02:00 Fix codemirror bug by @froos in: [#186](https://codeberg.org/uzu/strudel/pulls/186) -- 2022-08-13T16:29:53+02:00 scheduler improvements by @froos in: [#181](https://codeberg.org/uzu/strudel/pulls/181) -- 2022-08-12T23:10:36+02:00 replace mocha with vitest by @froos in: [#175](https://codeberg.org/uzu/strudel/pulls/175) -- 2022-08-07T00:59:07+02:00 incorporate elements of randomness to the mini notation by @Ghost in: [#165](https://codeberg.org/uzu/strudel/pulls/165) -- 2022-08-06T23:32:16+02:00 fix some annoying bugs by @froos in: [#177](https://codeberg.org/uzu/strudel/pulls/177) -- 2022-08-06T00:27:17+02:00 Replace react-codemirror6 with @uiw/react-codemirror by @froos in: [#173](https://codeberg.org/uzu/strudel/pulls/173) -- 2022-08-04T22:19:46+02:00 add more shapeshifter flags by @froos in: [#99](https://codeberg.org/uzu/strudel/pulls/99) -- 2022-08-04T22:06:38+02:00 Amend shapeshifter to allow use of dynamic import by @Ghost in: [#171](https://codeberg.org/uzu/strudel/pulls/171) -- 2022-08-02T23:43:07+02:00 Talk fixes by @froos in: [#164](https://codeberg.org/uzu/strudel/pulls/164) -- 2022-08-02T23:04:34+02:00 Pianoroll fixes by @froos in: [#163](https://codeberg.org/uzu/strudel/pulls/163) -- 2022-08-02T22:57:43+02:00 fix: jsdoc comments by @froos in: [#169](https://codeberg.org/uzu/strudel/pulls/169) - -## july 2022 - -- 2022-07-30T23:51:11+02:00 add chooseInWith/chooseCycles by @yaxu in: [#166](https://codeberg.org/uzu/strudel/pulls/166) -- 2022-07-28T19:26:42+02:00 update to tutorial documentation by @Ghost in: [#162](https://codeberg.org/uzu/strudel/pulls/162) -- 2022-07-12T18:58:47+02:00 add webdirt drum samples to prebake for general availability by @Ghost in: [#150](https://codeberg.org/uzu/strudel/pulls/150) -- 2022-07-12T08:34:17+02:00 Final update to demo.pdf by @yaxu in: [#151](https://codeberg.org/uzu/strudel/pulls/151) - -## june 2022 - -- 2022-06-28T21:54:35+02:00 Sampler optimizations and more by @froos in: [#148](https://codeberg.org/uzu/strudel/pulls/148) -- 2022-06-26T12:58:53+02:00 can now generate short link for sharing by @froos in: [#146](https://codeberg.org/uzu/strudel/pulls/146) -- 2022-06-24T22:16:04+02:00 flash effect on ctrl enter by @froos in: [#144](https://codeberg.org/uzu/strudel/pulls/144) -- 2022-06-22T20:18:17+02:00 Pianoroll Object Support by @froos in: [#142](https://codeberg.org/uzu/strudel/pulls/142) -- 2022-06-21T22:57:28+02:00 Serial twiddles by @yaxu in: [#141](https://codeberg.org/uzu/strudel/pulls/141) -- 2022-06-21T22:20:05+02:00 Soundfont Support by @froos in: [#139](https://codeberg.org/uzu/strudel/pulls/139) -- 2022-06-21T14:11:50+02:00 Fix createParam() by @yaxu in: [#140](https://codeberg.org/uzu/strudel/pulls/140) -- 2022-06-18T23:24:42+02:00 Webaudio rewrite by @froos in: [#138](https://codeberg.org/uzu/strudel/pulls/138) -- 2022-06-16T21:58:38+02:00 add onTrigger helper by @froos in: [#136](https://codeberg.org/uzu/strudel/pulls/136) -- 2022-06-16T20:48:23+02:00 Scheduler improvements by @froos in: [#134](https://codeberg.org/uzu/strudel/pulls/134) -- 2022-06-16T20:48:07+02:00 remove cycle + delta from onTrigger by @froos in: [#135](https://codeberg.org/uzu/strudel/pulls/135) -- 2022-06-13T21:28:27+02:00 add createParam + createParams by @froos in: [#110](https://codeberg.org/uzu/strudel/pulls/110) -- 2022-06-13T21:27:09+02:00 Pianoroll enhancements by @froos in: [#131](https://codeberg.org/uzu/strudel/pulls/131) -- 2022-06-05T13:06:03+02:00 Fix link to contributing to tutorial docs by @Ghost in: [#129](https://codeberg.org/uzu/strudel/pulls/129) -- 2022-06-02T00:20:45+02:00 Webdirt by @froos in: [#121](https://codeberg.org/uzu/strudel/pulls/121) -- 2022-06-01T23:56:04+02:00 fix: #122 ctrl enter would add newline by @froos in: [#124](https://codeberg.org/uzu/strudel/pulls/124) -- 2022-06-01T18:21:56+02:00 fix: #108 by @froos in: [#123](https://codeberg.org/uzu/strudel/pulls/123) - -## may 2022 - -- 2022-05-29T09:54:59+02:00 In source doc by @froos in: [#117](https://codeberg.org/uzu/strudel/pulls/117) -- 2022-05-20T11:49:10+02:00 react package + vite build by @froos in: [#116](https://codeberg.org/uzu/strudel/pulls/116) -- 2022-05-15T22:29:26+02:00 Osc timing improvements by @yaxu in: [#113](https://codeberg.org/uzu/strudel/pulls/113) -- 2022-05-15T22:28:24+02:00 loopAt by @yaxu in: [#114](https://codeberg.org/uzu/strudel/pulls/114) -- 2022-05-09T20:09:47+02:00 `.brak()`, `.inside()` and `.outside()` by @yaxu in: [#112](https://codeberg.org/uzu/strudel/pulls/112) -- 2022-05-06T15:18:41+02:00 In source doc by @yaxu in: [#105](https://codeberg.org/uzu/strudel/pulls/105) -- 2022-05-04T22:59:31+02:00 Embed style by @froos in: [#109](https://codeberg.org/uzu/strudel/pulls/109) -- 2022-05-03T01:40:22+02:00 Reset, Restart and other composers by @froos in: [#88](https://codeberg.org/uzu/strudel/pulls/88) -- 2022-05-02T22:47:21+02:00 /embed package: web component for repl by @froos in: [#106](https://codeberg.org/uzu/strudel/pulls/106) -- 2022-05-02T22:05:34+02:00 Tune tests by @froos in: [#104](https://codeberg.org/uzu/strudel/pulls/104) -- 2022-05-01T14:33:11+02:00 Codemirror 6 by @froos in: [#97](https://codeberg.org/uzu/strudel/pulls/97) - -## april 2022 - -- 2022-04-28T23:45:53+02:00 Work on Codemirror 6 highlighting by @Ghost in: [#102](https://codeberg.org/uzu/strudel/pulls/102) -- 2022-04-28T15:46:06+02:00 Change to Affero GPL by @yaxu in: [#101](https://codeberg.org/uzu/strudel/pulls/101) -- 2022-04-26T00:13:07+02:00 Paper by @froos in: [#98](https://codeberg.org/uzu/strudel/pulls/98) -- 2022-04-25T22:10:21+02:00 Fiddles with cat/stack by @yaxu in: [#90](https://codeberg.org/uzu/strudel/pulls/90) -- 2022-04-22T13:08:11+02:00 Add pattern composers, implements #82 by @yaxu in: [#83](https://codeberg.org/uzu/strudel/pulls/83) -- 2022-04-21T20:57:45+02:00 Tidy up a couple of old files by @Ghost in: [#84](https://codeberg.org/uzu/strudel/pulls/84) -- 2022-04-21T15:21:29+02:00 add `striate()` by @yaxu in: [#76](https://codeberg.org/uzu/strudel/pulls/76) -- 2022-04-20T20:17:27+02:00 Webaudio in REPL by @froos in: [#77](https://codeberg.org/uzu/strudel/pulls/77) -- 2022-04-19T15:31:34+02:00 Basic webserial support by @yaxu in: [#80](https://codeberg.org/uzu/strudel/pulls/80) -- 2022-04-17T19:29:49+02:00 Try to fix appLeft / appRight by @yaxu in: [#75](https://codeberg.org/uzu/strudel/pulls/75) -- 2022-04-16T13:26:57+02:00 More random functions by @yaxu in: [#74](https://codeberg.org/uzu/strudel/pulls/74) -- 2022-04-16T11:13:26+02:00 Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in: [#73](https://codeberg.org/uzu/strudel/pulls/73) -- 2022-04-16T10:26:07+02:00 webaudio package by @froos in: [#26](https://codeberg.org/uzu/strudel/pulls/26) -- 2022-04-15T20:18:06+02:00 More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in: [#70](https://codeberg.org/uzu/strudel/pulls/70) -- 2022-04-15T11:29:51+02:00 First effort at rand() by @yaxu in: [#69](https://codeberg.org/uzu/strudel/pulls/69) -- 2022-04-14T17:41:18+02:00 use new fixed version of osc-js package by @froos in: [#68](https://codeberg.org/uzu/strudel/pulls/68) -- 2022-04-14T00:09:18+02:00 Speech output by @froos in: [#67](https://codeberg.org/uzu/strudel/pulls/67) -- 2022-04-13T23:55:33+02:00 Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in: [#62](https://codeberg.org/uzu/strudel/pulls/62) -- 2022-04-13T17:26:45+02:00 More functions by @yaxu in: [#61](https://codeberg.org/uzu/strudel/pulls/61) -- 2022-04-13T10:04:29+02:00 More functions by @yaxu in: [#56](https://codeberg.org/uzu/strudel/pulls/56) -- 2022-04-12T17:04:04+02:00 OSC and SuperDirt support by @yaxu in: [#27](https://codeberg.org/uzu/strudel/pulls/27) -- 2022-04-12T13:24:14+02:00 Implement `chop()` by @yaxu in: [#50](https://codeberg.org/uzu/strudel/pulls/50) -- 2022-04-12T12:37:32+02:00 First run at squeezeBind, ref #32 by @yaxu in: [#48](https://codeberg.org/uzu/strudel/pulls/48) -- 2022-04-12T00:03:37+02:00 Fix polymeter by @yaxu in: [#44](https://codeberg.org/uzu/strudel/pulls/44) -- 2022-04-11T22:39:44+02:00 Compose by @froos in: [#40](https://codeberg.org/uzu/strudel/pulls/40) -- 2022-04-11T21:49:35+02:00 Update tutorial.mdx by @Ghost in: [#38](https://codeberg.org/uzu/strudel/pulls/38) -- 2022-04-11T08:43:44+02:00 Update tutorial.mdx by @Ghost in: [#37](https://codeberg.org/uzu/strudel/pulls/37) - -## march 2022 - -- 2022-03-28T17:27:22+02:00 Add chunk, chunkBack and iterBack by @yaxu in: [#25](https://codeberg.org/uzu/strudel/pulls/25) -- 2022-03-28T11:48:22+02:00 packaging by @froos in: [#24](https://codeberg.org/uzu/strudel/pulls/24) -- 2022-03-22T12:06:48+01:00 Update package.json by @Ghost in: [#23](https://codeberg.org/uzu/strudel/pulls/23) -- 2022-03-06T20:35:20+01:00 added _asNumber + interprete numbers as midi by @froos in: [#21](https://codeberg.org/uzu/strudel/pulls/21) - -## february 2022 - -- 2022-02-28T00:32:29+01:00 Fix resolveState by @yaxu in: [#22](https://codeberg.org/uzu/strudel/pulls/22) -- 2022-02-27T20:52:52+01:00 Stateful queries and events (WIP) by @yaxu in: [#14](https://codeberg.org/uzu/strudel/pulls/14) -- 2022-02-27T18:04:07+01:00 fix: 💄 Enhance visualisation of the Tutorial on mobile by @Ghost in: [#19](https://codeberg.org/uzu/strudel/pulls/19) -- 2022-02-26T21:16:34+01:00 test: 📦 Add missing dependency and a CI check, to prevent oversights ;p by @Ghost in: [#17](https://codeberg.org/uzu/strudel/pulls/17) -- 2022-02-26T13:29:19+01:00 higher latencyHint by @froos in: [#16](https://codeberg.org/uzu/strudel/pulls/16) -- 2022-02-23T22:15:11+01:00 add apply and layer, and missing div/mul methods by @yaxu in: [#15](https://codeberg.org/uzu/strudel/pulls/15) -- 2022-02-21T01:02:07+01:00 Add continuous signals (sine, cosine, saw, etc) by @yaxu in: [#13](https://codeberg.org/uzu/strudel/pulls/13) -- 2022-02-19T21:30:04+01:00 Added mask() and struct() by @yaxu in: [#11](https://codeberg.org/uzu/strudel/pulls/11) -- 2022-02-19T16:07:07+01:00 Failing test for `when` WIP by @yaxu in: [#10](https://codeberg.org/uzu/strudel/pulls/10) -- 2022-02-10T17:21:31+01:00 Bugfix every, and create more top level functions by @yaxu in: [#9](https://codeberg.org/uzu/strudel/pulls/9) -- 2022-02-10T00:51:21+01:00 timeCat by @yaxu in: [#8](https://codeberg.org/uzu/strudel/pulls/8) -- 2022-02-07T23:07:58+01:00 fixed editor crash by @froos in: [#7](https://codeberg.org/uzu/strudel/pulls/7) -- 2022-02-07T19:08:40+01:00 krill parser + improved repl by @froos in: [#6](https://codeberg.org/uzu/strudel/pulls/6) -- 2022-02-06T00:59:16+01:00 Patternify all the things by @yaxu in: [#5](https://codeberg.org/uzu/strudel/pulls/5) -- 2022-02-05T23:31:37+01:00 update readme for local dev by @Ghost in: [#4](https://codeberg.org/uzu/strudel/pulls/4) -- 2022-02-05T22:36:06+01:00 Fix path by @yaxu in: [#3](https://codeberg.org/uzu/strudel/pulls/3) -- 2022-02-05T21:52:51+01:00 repl + reify functions by @froos in: [#2](https://codeberg.org/uzu/strudel/pulls/2) - -... hello from the bottom of this file! you seem to be interested in the history of strudel, so it's worth noting that the first [commit](https://codeberg.org/uzu/strudel/commit/38b5a0d5cdf28685b2b5e18d460772b70246207b) to the repo was actually on Jan 22, 2022, 09:24 PM GMT+1 by @yaxu which could be seen as strudel's birthday 🎂 +- 2025-11-26T08:23:38+01:00 feat: add prebake script import button for loading .strudel files by @daslyfe in: [#1774](https://codeberg.org/uzu/strudel/pulls/1774) +- 2025-11-11T09:00:49+01:00 soundAlias example fix by @PepsiiMan in: [#1720](https://codeberg.org/uzu/strudel/pulls/1720) From e35a2000de9a6f1538a552340f002cca1a654056 Mon Sep 17 00:00:00 2001 From: space-shell Date: Mon, 19 Jan 2026 10:15:10 +0100 Subject: [PATCH 067/200] reverts changelog changes because human... --- CHANGELOG.md | 1108 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1081 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2007ee11..911463de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,1081 @@ -load page 1/1 -------------- -- 2026-01-18T22:27:46+01:00 fix: move kabelsalat web to superdough by @froos in: [#1932](https://codeberg.org/uzu/strudel/pulls/1932) -- 2026-01-18T12:36:50+01:00 Add tags to reference by @vvolhejn in: [#1645](https://codeberg.org/uzu/strudel/pulls/1645) -- 2026-01-18T10:36:02+01:00 fix: performance issues when reference is open by @robojumper in: [#1926](https://codeberg.org/uzu/strudel/pulls/1926) -- 2026-01-17T21:00:43+01:00 Make kabelsalat stereo out by @glossing in: [#1927](https://codeberg.org/uzu/strudel/pulls/1927) -- 2026-01-16T10:38:04+01:00 update changelog + fix script to not miss entries by @froos in: [#1916](https://codeberg.org/uzu/strudel/pulls/1916) -- 2026-01-15T16:59:14+01:00 mondo fix: add registered functions to scope automatically by @daslyfe in: [#1896](https://codeberg.org/uzu/strudel/pulls/1896) -- 2026-01-15T16:58:14+01:00 Added docs for pattern search by @JohnBjrk in: [#1905](https://codeberg.org/uzu/strudel/pulls/1905) -- 2026-01-15T16:57:19+01:00 Update kabelsalat dependency by @jeromew in: [#1911](https://codeberg.org/uzu/strudel/pulls/1911) -- 2026-01-15T00:24:39+01:00 Bug Fix: Set pooled values immediately instead of scheduling by @glossing in: [#1907](https://codeberg.org/uzu/strudel/pulls/1907) -- 2026-01-14T20:29:16+01:00 Feat: Kabelsalat integration by @glossing in: [#1876](https://codeberg.org/uzu/strudel/pulls/1876) -- 2026-01-14T19:07:49+01:00 Feat: MIDI Keyboard 🎹🐈 by @glossing in: [#1828](https://codeberg.org/uzu/strudel/pulls/1828) -- 2026-01-14T18:14:52+01:00 Bug Fix: Fix race condition between worklet termination and port messages by @glossing in: [#1897](https://codeberg.org/uzu/strudel/pulls/1897) -- 2026-01-13T04:06:52+01:00 Add search/filter in patterns tab by @JohnBjrk in: [#1842](https://codeberg.org/uzu/strudel/pulls/1842) -- 2026-01-13T00:18:10+01:00 Add shortcut for navigating through labels! by @daslyfe in: [#1807](https://codeberg.org/uzu/strudel/pulls/1807) -- 2026-01-12T00:17:37+01:00 add warm.strudel.cc to faq by @yaxu in: [#1891](https://codeberg.org/uzu/strudel/pulls/1891) -- 2026-01-11T19:00:25+01:00 Fix sounds example to work in the REPL by @JesCoding in: [#1851](https://codeberg.org/uzu/strudel/pulls/1851) -- 2026-01-11T12:07:48+01:00 Feat: Support External AudioContext Injection by @1d10t in: [#1833](https://codeberg.org/uzu/strudel/pulls/1833) -- 2026-01-10T23:12:48+01:00 Perf: Targeted node pools by @glossing in: [#1810](https://codeberg.org/uzu/strudel/pulls/1810) -- 2026-01-10T20:52:40+01:00 Allow top level distortions for the purpose of FX by @glossing in: [#1884](https://codeberg.org/uzu/strudel/pulls/1884) -- 2026-01-07T20:06:18+01:00 Feat: Add ability to turn mini parsing off with mini-off decorator by @glossing in: [#1786](https://codeberg.org/uzu/strudel/pulls/1786) -- 2026-01-01T19:52:15+01:00 Feat: FX Chains by @glossing in: [#1861](https://codeberg.org/uzu/strudel/pulls/1861) -- 2025-12-29T20:54:11+01:00 Feature: LFOs and Envelopes by @glossing in: [#1507](https://codeberg.org/uzu/strudel/pulls/1507) -- 2025-12-11T18:00:49+01:00 Feature: stateful timeline function for jumping between timelines by @yaxu in: [#1669](https://codeberg.org/uzu/strudel/pulls/1669) -- 2025-11-26T08:23:38+01:00 feat: add prebake script import button for loading .strudel files by @daslyfe in: [#1774](https://codeberg.org/uzu/strudel/pulls/1774) -- 2025-11-11T09:00:49+01:00 soundAlias example fix by @PepsiiMan in: [#1720](https://codeberg.org/uzu/strudel/pulls/1720) +# changelog + +NOTE: you can generate this with `node warm.js`. it might still not be perfectly sorted... + +## january 2026 + + +- 2026-01-15T16:59:14+01:00 mondo fix: add registered functions to scope automatically by @daslyfe in: [#1896](https://codeberg.org/uzu/strudel/pulls/1896) +- 2026-01-15T16:58:14+01:00 Added docs for pattern search by @JohnBjrk in: [#1905](https://codeberg.org/uzu/strudel/pulls/1905) +- 2026-01-15T16:57:19+01:00 Update kabelsalat dependency by @jeromew in: [#1911](https://codeberg.org/uzu/strudel/pulls/1911) +- 2026-01-15T00:24:39+01:00 Bug Fix: Set pooled values immediately instead of scheduling by @glossing in: [#1907](https://codeberg.org/uzu/strudel/pulls/1907) +- 2026-01-14T20:29:16+01:00 Feat: Kabelsalat integration by @glossing in: [#1876](https://codeberg.org/uzu/strudel/pulls/1876) +- 2026-01-14T19:07:49+01:00 Feat: MIDI Keyboard 🎹🐈 by @glossing in: [#1828](https://codeberg.org/uzu/strudel/pulls/1828) +- 2026-01-14T18:14:52+01:00 Bug Fix: Fix race condition between worklet termination and port messages by @glossing in: [#1897](https://codeberg.org/uzu/strudel/pulls/1897) +- 2026-01-13T04:06:52+01:00 Add search/filter in patterns tab by @JohnBjrk in: [#1842](https://codeberg.org/uzu/strudel/pulls/1842) +- 2026-01-13T00:18:10+01:00 Add shortcut for navigating through labels! by @daslyfe in: [#1807](https://codeberg.org/uzu/strudel/pulls/1807) +- 2026-01-12T00:17:37+01:00 add warm.strudel.cc to faq by @yaxu in: [#1891](https://codeberg.org/uzu/strudel/pulls/1891) +- 2026-01-11T19:00:25+01:00 Fix sounds example to work in the REPL by @JesCoding in: [#1851](https://codeberg.org/uzu/strudel/pulls/1851) +- 2026-01-11T13:52:02+01:00 Improve hint text when sound search has no results by @floy in: [#1883](https://codeberg.org/uzu/strudel/pulls/1883) +- 2026-01-11T13:19:46+01:00 New page FAQ in "More" by @scrappy_fiddler in: [#1753](https://codeberg.org/uzu/strudel/pulls/1753) +- 2026-01-11T12:45:07+01:00 fix: remove faulty default readme by @froos in: [#1889](https://codeberg.org/uzu/strudel/pulls/1889) +- 2026-01-11T12:15:34+01:00 fix/self-hosted-config by @alienmind in: [#1880](https://codeberg.org/uzu/strudel/pulls/1880) +- 2026-01-11T12:07:48+01:00 Feat: Support External AudioContext Injection by @1d10t in: [#1833](https://codeberg.org/uzu/strudel/pulls/1833) +- 2026-01-11T11:37:37+01:00 fix: add trem to top level by @froos in: [#1887](https://codeberg.org/uzu/strudel/pulls/1887) +- 2026-01-11T11:37:18+01:00 fix: export start cycle min 0 by @froos in: [#1888](https://codeberg.org/uzu/strudel/pulls/1888) +- 2026-01-11T11:05:44+01:00 fix: repl package init audio properly by @froos in: [#1836](https://codeberg.org/uzu/strudel/pulls/1836) +- 2026-01-11T06:51:24+01:00 Fix: show reload dialog when uploading prebake script by @daslyfe in: [#1886](https://codeberg.org/uzu/strudel/pulls/1886) +- 2026-01-11T06:01:42+01:00 fixes Serial onTrigger() params #1633 by @gueejla in: [#1885](https://codeberg.org/uzu/strudel/pulls/1885) +- 2026-01-10T23:12:48+01:00 Perf: Targeted node pools by @glossing in: [#1810](https://codeberg.org/uzu/strudel/pulls/1810) +- 2026-01-10T20:52:40+01:00 Allow top level distortions for the purpose of FX by @glossing in: [#1884](https://codeberg.org/uzu/strudel/pulls/1884) +- 2026-01-09T03:43:37+01:00 Bake in scaling by `freq` for FM with a gain node by @glossing in: [#1878](https://codeberg.org/uzu/strudel/pulls/1878) +- 2026-01-09T02:53:38+01:00 Bug fix: Properly handle subcontrols by @glossing in: [#1877](https://codeberg.org/uzu/strudel/pulls/1877) +- 2026-01-07T20:06:18+01:00 Feat: Add ability to turn mini parsing off with mini-off decorator by @glossing in: [#1786](https://codeberg.org/uzu/strudel/pulls/1786) +- 2026-01-07T19:22:24+01:00 Bug Fix: Update loopStart/End to not be offset by @glossing in: [#1826](https://codeberg.org/uzu/strudel/pulls/1826) +- 2026-01-06T06:35:49+01:00 Update modulator docstrings and allow ids to be patterns by @glossing in: [#1874](https://codeberg.org/uzu/strudel/pulls/1874) +- 2026-01-04T17:14:11+01:00 Fix doc link in @strudel/osc README.md by @forrcaho in: [#1872](https://codeberg.org/uzu/strudel/pulls/1872) +- 2026-01-04T02:01:07+01:00 Make stretch modulatable by @glossing in: [#1870](https://codeberg.org/uzu/strudel/pulls/1870) +- 2026-01-02T18:44:53+01:00 Make pan modulatable by @glossing in: [#1865](https://codeberg.org/uzu/strudel/pulls/1865) +- 2026-01-01T21:42:23+01:00 Fix transpilation example to have same mini-notation by @JesCoding in: [#1850](https://codeberg.org/uzu/strudel/pulls/1850) +- 2026-01-01T21:39:05+01:00 fix: missing punctuation by @eddyflux in: [#1860](https://codeberg.org/uzu/strudel/pulls/1860) +- 2026-01-01T20:20:34+01:00 Fix formatting of docstring by @glossing in: [#1864](https://codeberg.org/uzu/strudel/pulls/1864) +- 2026-01-01T19:52:15+01:00 Feat: FX Chains by @glossing in: [#1861](https://codeberg.org/uzu/strudel/pulls/1861) + +## december 2025 + +- 2025-12-29T21:59:11+01:00 Bugfix: Fix modulator clamping when min/max not specified by @glossing in: [#1859](https://codeberg.org/uzu/strudel/pulls/1859) +- 2025-12-29T20:54:11+01:00 Feature: LFOs and Envelopes by @glossing in: [#1507](https://codeberg.org/uzu/strudel/pulls/1507) +- 2025-12-29T16:07:18+01:00 Fix AudioContext change detection. Use AudioNode.context by @jeromew in: [#1858](https://codeberg.org/uzu/strudel/pulls/1858) +- 2025-12-28T22:58:38+01:00 Say that @license should use SPDX identifier by @Wuzzy in: [#1817](https://codeberg.org/uzu/strudel/pulls/1817) +- 2025-12-28T22:56:07+01:00 Expose Vim object in order to create custom keybindings by @JohnBjrk in: [#1816](https://codeberg.org/uzu/strudel/pulls/1816) +- 2025-12-28T14:20:03+01:00 dough repl fixes by @froos in: [#1855](https://codeberg.org/uzu/strudel/pulls/1855) +- 2025-12-28T13:40:29+01:00 add basic dough repl by @froos in: [#1749](https://codeberg.org/uzu/strudel/pulls/1749) +- 2025-12-20T22:27:52+01:00 Document "-" in mini-notation by @Wuzzy in: [#1818](https://codeberg.org/uzu/strudel/pulls/1818) +- 2025-12-20T22:20:27+01:00 simplify envValAtTime and remove asymmetric behavior (fix #1653) by @pulu in: [#1815](https://codeberg.org/uzu/strudel/pulls/1815) +- 2025-12-19T08:11:50+01:00 fix: visual block selection mode for vim bindings by @Dsm0 in: [#1839](https://codeberg.org/uzu/strudel/pulls/1839) +- 2025-12-19T01:03:53+01:00 [perf] Add audiograph `await debugAudiograph()` feature by @jeromew in: [#1763](https://codeberg.org/uzu/strudel/pulls/1763) +- 2025-12-19T00:45:41+01:00 Feature: non-realtime exporting by @Ghost in: [#1674](https://codeberg.org/uzu/strudel/pulls/1674) +- 2025-12-14T19:33:37+01:00 Fix: wrong warning in build environments by @jeromew in: [#1835](https://codeberg.org/uzu/strudel/pulls/1835) +- 2025-12-14T15:09:12+01:00 delta -> per / perx / cyclesPer refinements by @yaxu in: [#1832](https://codeberg.org/uzu/strudel/pulls/1832) +- 2025-12-14T01:07:36+01:00 Improved randomness by @glossing in: [#1505](https://codeberg.org/uzu/strudel/pulls/1505) +- 2025-12-12T10:28:27+01:00 Add delta signal for representing the duration of events in patterns that are combined with it by @yaxu in: [#1831](https://codeberg.org/uzu/strudel/pulls/1831) +- 2025-12-11T18:00:49+01:00 Feature: stateful timeline function for jumping between timelines by @yaxu in: [#1669](https://codeberg.org/uzu/strudel/pulls/1669) +- 2025-12-11T16:37:58+01:00 Updates relating to LLM, github, etc by @yaxu in: [#1830](https://codeberg.org/uzu/strudel/pulls/1830) +- 2025-12-10T15:32:44+01:00 fix .as so it doesn't set undefined values by @yaxu in: [#1827](https://codeberg.org/uzu/strudel/pulls/1827) +- 2025-12-08T19:27:31+01:00 [perf] propagate `onceEnded` and `releaseAudioNode` by @jeromew in: [#1809](https://codeberg.org/uzu/strudel/pulls/1809) +- 2025-12-07T21:51:40+01:00 Feat: Add channel support to midi in by @glossing in: [#1775](https://codeberg.org/uzu/strudel/pulls/1775) +- 2025-12-07T21:23:43+01:00 Feat: Transient shaper by @glossing in: [#1777](https://codeberg.org/uzu/strudel/pulls/1777) +- 2025-12-07T19:15:43+01:00 Add vel as a synonym for velocity & update a few docstrings by @glossing in: [#1781](https://codeberg.org/uzu/strudel/pulls/1781) +- 2025-12-07T18:53:04+01:00 [perf] fix phaser leak of unused biquads by @jeromew in: [#1800](https://codeberg.org/uzu/strudel/pulls/1800) +- 2025-12-07T18:42:50+01:00 Bug fix: Remove failing tests due to shabda removal by @glossing in: [#1820](https://codeberg.org/uzu/strudel/pulls/1820) +- 2025-12-03T18:27:28+01:00 Feature: Eight FMs by @glossing in: [#1628](https://codeberg.org/uzu/strudel/pulls/1628) +- 2025-12-03T17:35:05+01:00 [perf] release unused AudioBufferSourceNode + releaseAudioNode by @jeromew in: [#1805](https://codeberg.org/uzu/strudel/pulls/1805) +- **2025-12-01 strudel.cc deployed** + +## november 2025 + +- 2025-11-29T01:00:42+01:00 added export to getSuperdoughAudioController() so that its possible to route superdough audio through other webaudio applications. by @ndr0n in: [#1796](https://codeberg.org/uzu/strudel/pulls/1796) +- 2025-11-28T23:26:20+01:00 add revv() for reversing whole patterns by @yaxu in: [#1791](https://codeberg.org/uzu/strudel/pulls/1791) +- 2025-11-28T20:19:16+01:00 [perf] Disconnect lfos for phaser and filters by @glossing in: [#1787](https://codeberg.org/uzu/strudel/pulls/1787) +- 2025-11-27T23:08:52+01:00 fix: return silence when no pattern is returned by @froos in: [#1795](https://codeberg.org/uzu/strudel/pulls/1795) +- 2025-11-27T22:47:03+01:00 fix(tool/dbpatch): add missing package name by @peterpf in: [#1765](https://codeberg.org/uzu/strudel/pulls/1765) +- 2025-11-27T22:38:05+01:00 add CHANGELOG.md + basic script to generate by @froos in: [#1794](https://codeberg.org/uzu/strudel/pulls/1794) +- 2025-11-27T22:03:53+01:00 [perf] in `noise`, let noiseMix do the disconnect when it exists by @jeromew in: [#1783](https://codeberg.org/uzu/strudel/pulls/1783) +- 2025-11-27T22:01:01+01:00 Bug Fix: Retries for sounds tab by @glossing in: [#1754](https://codeberg.org/uzu/strudel/pulls/1754) +- 2025-11-27T20:36:46+01:00 [hydra] return the hydra object when await initHydra(..) is called by @jeromew in: [#1784](https://codeberg.org/uzu/strudel/pulls/1784) +- 2025-11-27T20:36:14+01:00 Use errorLogger for query and tonal errors by @glossing in: [#1782](https://codeberg.org/uzu/strudel/pulls/1782) +- 2025-11-27T20:27:10+01:00 [perf] level 5 `connect-leak` on `vowel` by @jeromew in: [#1779](https://codeberg.org/uzu/strudel/pulls/1779) +- 2025-11-26T08:23:38+01:00 feat: add prebake script import button for loading .strudel files by @daslyfe in: [#1774](https://codeberg.org/uzu/strudel/pulls/1774) +- 2025-11-26T07:39:28+01:00 Fix Sampler port trampling by @Dayglo in: [#1717](https://codeberg.org/uzu/strudel/pulls/1717) +- 2025-11-25T20:59:09+01:00 Feat: Hook up octave and fix example by @glossing in: [#1773](https://codeberg.org/uzu/strudel/pulls/1773) +- 2025-11-25T20:48:09+01:00 [perf] fix `connect-leak` in `tremolo` param by @jeromew in: [#1780](https://codeberg.org/uzu/strudel/pulls/1780) +- 2025-11-24T17:51:20+01:00 filter modulation improvements! by @daslyfe in: [#1769](https://codeberg.org/uzu/strudel/pulls/1769) +- 2025-11-24T01:20:53+01:00 Add o as a synonym for orbit by @daslyfe in: [#1766](https://codeberg.org/uzu/strudel/pulls/1766) +- 2025-11-23T22:23:26+01:00 [perf] fix `connect-leak` in `fm` modulation by @jeromew in: [#1758](https://codeberg.org/uzu/strudel/pulls/1758) +- 2025-11-23T09:47:24+01:00 Fix interoperability issue between `all` and `await initHydra()` by @jeromew in: [#1663](https://codeberg.org/uzu/strudel/pulls/1663) +- 2025-11-23T04:23:06+01:00 Bug fix: Swap l/r gains with temp variable by @glossing in: [#1768](https://codeberg.org/uzu/strudel/pulls/1768) +- 2025-11-23T00:52:28+01:00 FIX: prevent LFO filter modulation from popping from negative values by @daslyfe in: [#1767](https://codeberg.org/uzu/strudel/pulls/1767) +- 2025-11-23T00:03:32+01:00 prefix "S" for solo by @froos in: [#1481](https://codeberg.org/uzu/strudel/pulls/1481) +- 2025-11-21T01:56:11+01:00 Bug Fix: Use frac due to negative frequencies from FM by @glossing in: [#1759](https://codeberg.org/uzu/strudel/pulls/1759) +- 2025-11-20T19:48:16+01:00 [perf] fix `connect-leak` added by #1742 when noise() is not used by @jeromew in: [#1757](https://codeberg.org/uzu/strudel/pulls/1757) +- 2025-11-20T14:51:00+01:00 [perf] fix `connect-leak` in `delay` effect by @jeromew in: [#1755](https://codeberg.org/uzu/strudel/pulls/1755) +- 2025-11-20T14:49:41+01:00 [perf] fix `connect leak` when .noise() is in the mix by @jeromew in: [#1742](https://codeberg.org/uzu/strudel/pulls/1742) +- 2025-11-18T23:52:45+01:00 wchooseCycles has now notes in an example by @scrappy_fiddler in: [#1748](https://codeberg.org/uzu/strudel/pulls/1748) +- 2025-11-17T05:31:54+01:00 Feature: Partials by @glossing in: [#1659](https://codeberg.org/uzu/strudel/pulls/1659) +- 2025-11-16T22:06:14+01:00 README: update superdough documentation by @TristanMlct in: [#1741](https://codeberg.org/uzu/strudel/pulls/1741) +- 2025-11-16T22:00:43+01:00 Feature: Envelope worklet by @glossing in: [#1524](https://codeberg.org/uzu/strudel/pulls/1524) +- 2025-11-16T21:08:54+01:00 Worklet optimizations by @glossing in: [#1730](https://codeberg.org/uzu/strudel/pulls/1730) +- 2025-11-15T14:59:30+01:00 [perf] disconnect Offline AudioNode connections in generateReverb by @jeromew in: [#1740](https://codeberg.org/uzu/strudel/pulls/1740) +- 2025-11-15T14:57:47+01:00 [perf] disconnect `send` effect AudioNode when `room` is used by @jeromew in: [#1736](https://codeberg.org/uzu/strudel/pulls/1736) +- 2025-11-12T21:22:34+01:00 [supradough] fix: unstable filter by @froos in: [#1593](https://codeberg.org/uzu/strudel/pulls/1593) +- 2025-11-12T21:12:07+01:00 Fix link syntax in `project-start` by @Kissaki in: [#1724](https://codeberg.org/uzu/strudel/pulls/1724) +- 2025-11-12T21:06:13+01:00 Fix web README sample code by @Kissaki in: [#1725](https://codeberg.org/uzu/strudel/pulls/1725) +- 2025-11-12T20:59:39+01:00 Fix typo: 'studel' -> 'strudel' by @drhayes in: [#1726](https://codeberg.org/uzu/strudel/pulls/1726) +- 2025-11-12T15:23:41+01:00 fix for node 24 support in tests - #1718 by @ausav in: [#1719](https://codeberg.org/uzu/strudel/pulls/1719) +- 2025-11-11T09:00:49+01:00 soundAlias example fix by @PepsiiMan in: [#1720](https://codeberg.org/uzu/strudel/pulls/1720) +- 2025-11-10T08:18:36+01:00 fix: Make Supradough package build by @munshkr in: [#1711](https://codeberg.org/uzu/strudel/pulls/1711) +- 2025-11-10T08:18:00+01:00 added docs for spaces in scale names by @ondras in: [#1715](https://codeberg.org/uzu/strudel/pulls/1715) +- 2025-11-10T02:44:01+01:00 Feature: LFOs for Filters by @glossing in: [#1636](https://codeberg.org/uzu/strudel/pulls/1636) +- 2025-11-05T17:07:00+01:00 improvement: sync midi messages to audio context clock by @daslyfe in: [#1708](https://codeberg.org/uzu/strudel/pulls/1708) +- 2025-11-04T19:36:14+01:00 Add stick button mappings to gamepad implementation and improve docs by @kaosuryoko in: [#1696](https://codeberg.org/uzu/strudel/pulls/1696) +- 2025-11-04T19:29:35+01:00 Refactor sound stopping and triggering logic in SoundsTab component by @IJOL in: [#1688](https://codeberg.org/uzu/strudel/pulls/1688) +- 2025-11-04T19:13:21+01:00 Add setting to toggle pattern auto-start on pattern change by @moumar in: [#1690](https://codeberg.org/uzu/strudel/pulls/1690) +- 2025-11-04T18:58:50+01:00 Voicings JSDoc by @sharkeys_lunchbox in: [#1686](https://codeberg.org/uzu/strudel/pulls/1686) +- 2025-11-01T07:50:40+01:00 FIX: Loading local samples uses too much memory by @daslyfe in: [#1706](https://codeberg.org/uzu/strudel/pulls/1706) + + +## october 2025 + +- 2025-10-28T22:58:24+01:00 Bug Fix: Handle scale-for-notes when n also supplied by @glossing in: [#1625](https://codeberg.org/uzu/strudel/pulls/1625) +- 2025-10-28T22:51:16+01:00 Repurpose vim shortcuts for usability by @dtricks in: [#1624](https://codeberg.org/uzu/strudel/pulls/1624) +- 2025-10-28T22:35:21+01:00 Add support for euclidian in mondo with `bd&3:8` by @TristanCacqueray in: [#1630](https://codeberg.org/uzu/strudel/pulls/1630) +- 2025-10-27T10:42:07+01:00 Use bunny cdn for all samples and json files by @yaxu in: [#1701](https://codeberg.org/uzu/strudel/pulls/1701) +- **2025-10-27 @strudel/core@1.2.5** +- 2025-10-26T22:52:54+01:00 degithub - switch some samples to bunnycdn by @yaxu in: [#1697](https://codeberg.org/uzu/strudel/pulls/1697) +- 2025-10-26T17:09:22+01:00 Fix ZZFX example by @moumar in: [#1685](https://codeberg.org/uzu/strudel/pulls/1685) +- 2025-10-23T15:56:04+02:00 Make osc port and host configurable. Changes dependencies. by @yaxu in: [#1682](https://codeberg.org/uzu/strudel/pulls/1682) +- 2025-10-23T03:47:31+02:00 Adds back shape to superdough by @glossing in: [#1672](https://codeberg.org/uzu/strudel/pulls/1672) +- 2025-10-22T22:15:19+02:00 Fix sampler.mjs githubPath by @jeromew in: [#1684](https://codeberg.org/uzu/strudel/pulls/1684) +- 2025-10-22T16:20:50+02:00 Fix onPaint for widgets by @yaxu in: [#1658](https://codeberg.org/uzu/strudel/pulls/1658) +- 2025-10-22T15:19:12+02:00 Fix a bug introduced by #4e17cfbdd6 by @milliganf in: [#1679](https://codeberg.org/uzu/strudel/pulls/1679) +- 2025-10-20T22:37:15+02:00 feature/autocomplete-sound-names by @drdozer in: [#1564](https://codeberg.org/uzu/strudel/pulls/1564) +- 2025-10-20T21:58:12+02:00 add replicate + use it for ! in mondo by @JoStro in: [#1436](https://codeberg.org/uzu/strudel/pulls/1436) +- 2025-10-18T04:29:20+02:00 Bug Fix: Wavetable: phase wrapping at 1 and detune by @glossing in: [#1620](https://codeberg.org/uzu/strudel/pulls/1620) +- 2025-10-16T20:26:28+02:00 github samples: default to "samples" if repository is not specified by @prezmop in: [#1644](https://codeberg.org/uzu/strudel/pulls/1644) +- 2025-10-16T17:33:39+02:00 Docs: add example of custom chained function by @dariusk in: [#1642](https://codeberg.org/uzu/strudel/pulls/1642) +- 2025-10-14T12:39:48+02:00 textbox by @yaxu in: [#1650](https://codeberg.org/uzu/strudel/pulls/1650) +- 2025-10-13T07:26:51+02:00 Feature: Distortion Modes by @glossing in: [#1561](https://codeberg.org/uzu/strudel/pulls/1561) +- 2025-10-13T07:05:57+02:00 Feature: Wavetable FM by @glossing in: [#1623](https://codeberg.org/uzu/strudel/pulls/1623) +- 2025-10-12T20:43:42+02:00 fix: repair REPL sample sources and URL concat (#1640) by @erikfox in: [#1646](https://codeberg.org/uzu/strudel/pulls/1646) +- 2025-10-10T11:25:02+02:00 Add control-metadata by @yaxu in: [#1634](https://codeberg.org/uzu/strudel/pulls/1634) +- 2025-10-07T22:59:39+02:00 Fix MQTT: change the trigger handler to match new hap by @jurrchen in: [#1629](https://codeberg.org/uzu/strudel/pulls/1629) +- 2025-10-05T21:42:19+02:00 Fix case sensitivity for synonym search by @vvolhejn in: [#1618](https://codeberg.org/uzu/strudel/pulls/1618) +- 2025-10-01T08:56:35+02:00 Optimize wavetable synth by @glossing in: [#1613](https://codeberg.org/uzu/strudel/pulls/1613) +- 2025-10-01T02:02:33+02:00 fix: pattern switching by @daslyfe in: [#1616](https://codeberg.org/uzu/strudel/pulls/1616) + +## september 2025 + +- 2025-09-29T02:10:54+02:00 fix wavetable JSON by @daslyfe in: [#1611](https://codeberg.org/uzu/strudel/pulls/1611) +- 2025-09-28T21:28:16+02:00 rename wavetable controls to match existing naming conventions by @daslyfe in: [#1610](https://codeberg.org/uzu/strudel/pulls/1610) +- 2025-09-27T23:28:13+02:00 Feature: Wavetable synth improvements by @glossing in: [#1607](https://codeberg.org/uzu/strudel/pulls/1607) +- 2025-09-27T21:43:10+02:00 wavetable improvements by @daslyfe in: [#1608](https://codeberg.org/uzu/strudel/pulls/1608) +- 2025-09-26T08:48:46+02:00 create orbit based DJ filter by @daslyfe in: [#1603](https://codeberg.org/uzu/strudel/pulls/1603) +- 2025-09-26T08:15:49+02:00 Feature: Wavetable synth by @glossing in: [#1525](https://codeberg.org/uzu/strudel/pulls/1525) +- 2025-09-23T13:28:20+02:00 fix: time signal by @daslyfe in: [#1583](https://codeberg.org/uzu/strudel/pulls/1583) +- 2025-09-21T16:16:06+02:00 mondo: fix all + setcpm + setcps by @froos in: [#1600](https://codeberg.org/uzu/strudel/pulls/1600) +- 2025-09-19T21:59:43+02:00 fix: osc error message by @froos in: [#1597](https://codeberg.org/uzu/strudel/pulls/1597) +- 2025-09-18T21:09:56+02:00 osc: --debug flag to see incoming messages by @froos in: [#1595](https://codeberg.org/uzu/strudel/pulls/1595) +- 2025-09-17T14:12:10+02:00 simplify osc usage by @froos in: [#1588](https://codeberg.org/uzu/strudel/pulls/1588) +- 2025-09-16T16:59:58+02:00 dev: logger errors to console in dev mode by @daslyfe in: [#1585](https://codeberg.org/uzu/strudel/pulls/1585) +- 2025-09-16T03:19:37+02:00 Bug Fix / Feature: Updates to `duck` to avoid clicks and allow configuration over release/attack by @glossing in: [#1514](https://codeberg.org/uzu/strudel/pulls/1514) +- 2025-09-15T23:36:31+02:00 Feat: Enhance `scale` function to quantize notes to a named scale by @glossing in: [#1492](https://codeberg.org/uzu/strudel/pulls/1492) +- 2025-09-15T22:52:14+02:00 add tic80 font by @froos in: [#1579](https://codeberg.org/uzu/strudel/pulls/1579) +- 2025-09-15T00:00:19+02:00 supradough: fix delay + add some jsdoc types by @froos in: [#1578](https://codeberg.org/uzu/strudel/pulls/1578) +- 2025-09-14T12:42:58+02:00 added plyWith/plyWithClassic functions by @Dsm0 in: [#1571](https://codeberg.org/uzu/strudel/pulls/1571) +- 2025-09-14T10:39:12+02:00 feat: sound alias by @Options in: [#1494](https://codeberg.org/uzu/strudel/pulls/1494) +- 2025-09-14T01:20:15+02:00 Bug Fix: Allow penv values to be falsy by @glossing in: [#1559](https://codeberg.org/uzu/strudel/pulls/1559) +- 2025-09-14T01:10:48+02:00 Update website/src/pages/workshop/first-sounds.mdx by @fesmith in: [#1566](https://codeberg.org/uzu/strudel/pulls/1566) +- 2025-09-14T01:07:47+02:00 fix: autocomplete-styles + html rendering by @froos in: [#1570](https://codeberg.org/uzu/strudel/pulls/1570) +- 2025-09-14T00:49:17+02:00 Feature: Synonyms in autocomplete and reference by @glossing in: [#1535](https://codeberg.org/uzu/strudel/pulls/1535) +- 2025-09-13T20:01:48+02:00 Docs: add xen package examples by @dudymas in: [#1446](https://codeberg.org/uzu/strudel/pulls/1446) +- 2025-09-13T19:51:10+02:00 Update website/src/pages/learn/code.mdx by @anecondev in: [#1391](https://codeberg.org/uzu/strudel/pulls/1391) +- 2025-09-11T22:52:30+02:00 supradough poc by @froos in: [#1362](https://codeberg.org/uzu/strudel/pulls/1362) +- 2025-09-11T17:29:35+02:00 fix: exclude mondough dependencies by @froos in: [#1563](https://codeberg.org/uzu/strudel/pulls/1563) +- 2025-09-11T00:27:21+02:00 add basicSetup for keybindings by @Dsm0 in: [#1462](https://codeberg.org/uzu/strudel/pulls/1462) +- 2025-09-10T17:25:26+02:00 feat: add delete user samples button for convenience by @daslyfe in: [#1556](https://codeberg.org/uzu/strudel/pulls/1556) +- 2025-09-08T08:54:01+02:00 Fix formatting of REPL footnote by @ddbeck in: [#1547](https://codeberg.org/uzu/strudel/pulls/1547) +- 2025-09-08T05:38:08+02:00 fix: OSC by @daslyfe in: [#1557](https://codeberg.org/uzu/strudel/pulls/1557) +- 2025-09-07T22:47:13+02:00 Add examples for ? and | operators to documentation by james-@collapse in: [#1532](https://codeberg.org/uzu/strudel/pulls/1532) +- 2025-09-07T21:48:19+02:00 Signal flow documentation by @glossing in: [#1504](https://codeberg.org/uzu/strudel/pulls/1504) +- 2025-09-07T21:27:29+02:00 Allow flattening of dirs with sample server by @glossing in: [#1529](https://codeberg.org/uzu/strudel/pulls/1529) +- 2025-09-07T21:05:24+02:00 fix: prevent orbit clicking by defining max number of channels in an orbit by @daslyfe in: [#1552](https://codeberg.org/uzu/strudel/pulls/1552) +- 2025-09-02T08:42:42+02:00 Padding for tooltips, preserving linebreaks in descriptions, not closing autocompletes on click by @glossing in: [#1521](https://codeberg.org/uzu/strudel/pulls/1521) +- 2025-09-01T17:50:11+02:00 Update "restore defaults" to not delete patterns by @glossing in: [#1519](https://codeberg.org/uzu/strudel/pulls/1519) + +## august 2025 + +- 2025-08-31T19:10:04+02:00 feat: add the ability to control the speed and start time of the reverb IR by @daslyfe in: [#1501](https://codeberg.org/uzu/strudel/pulls/1501) +- 2025-08-25T02:31:22+02:00 Bug Fix: Duck on Mobile / Safari by @glossing in: [#1503](https://codeberg.org/uzu/strudel/pulls/1503) +- 2025-08-23T18:39:53+02:00 fix benchmarks by @yaxu in: [#1517](https://codeberg.org/uzu/strudel/pulls/1517) +- 2025-08-21T20:57:31+02:00 add --json flag to @strudel/sampler to generate a strudel.json by @froos in: [#1513](https://codeberg.org/uzu/strudel/pulls/1513) +- 2025-08-21T10:04:28+02:00 remove hs2js postinstall by @froos in: [#1510](https://codeberg.org/uzu/strudel/pulls/1510) +- 2025-08-20T15:15:34+02:00 doc: scrub, duckorbit, duckattack, duckdepth + fix: arp, arpWidth, hush by @fyynn in: [#1502](https://codeberg.org/uzu/strudel/pulls/1502) +- 2025-08-20T12:31:37+02:00 fix: repl autocomplete not rendering correctly by @robase in: [#1480](https://codeberg.org/uzu/strudel/pulls/1480) +- 2025-08-20T12:10:02+02:00 BUG FIX: make 'midin' initialization work with multiple controllers by @cbabraham in: [#1469](https://codeberg.org/uzu/strudel/pulls/1469) +- 2025-08-17T23:18:46+02:00 euclidish by @yaxu in: [#1482](https://codeberg.org/uzu/strudel/pulls/1482) +- 2025-08-17T06:37:23+02:00 feat: Create a duck (sidechain) orbit effect by @daslyfe in: [#1470](https://codeberg.org/uzu/strudel/pulls/1470) +- 2025-08-17T04:40:15+02:00 Bug Fix: Restore `log` functionality after onTrigger arg removal by @glossing in: [#1491](https://codeberg.org/uzu/strudel/pulls/1491) +- 2025-08-17T04:33:39+02:00 Bug Fix: Handle zero appearing first in FM's ADSR by @glossing in: [#1496](https://codeberg.org/uzu/strudel/pulls/1496) +- 2025-08-17T04:27:19+02:00 Bug Fix: FM (and vibrato) for Supersaws by @glossing in: [#1495](https://codeberg.org/uzu/strudel/pulls/1495) +- 2025-08-04T07:51:31+02:00 Fix incorrect stack Mini Notation by @samyk in: [#1484](https://codeberg.org/uzu/strudel/pulls/1484) +- 2025-08-03T18:57:45+02:00 fix: regression caused by incorrectly exported alias for transpose and scaleTranspose by @daslyfe in: [#1489](https://codeberg.org/uzu/strudel/pulls/1489) +- 2025-08-03T18:34:13+02:00 Add `strans` and `scaleTrans` as synonyms of `scaleTranspose` by @TodePond in: [#1488](https://codeberg.org/uzu/strudel/pulls/1488) +- 2025-08-02T18:18:28+02:00 Add `trans` alias for `transpose` by @TodePond in: [#1487](https://codeberg.org/uzu/strudel/pulls/1487) +- 2025-08-02T03:27:26+02:00 feat: Create Accurate 909 bass drum synthesizer by @daslyfe in: [#1478](https://codeberg.org/uzu/strudel/pulls/1478) +- 2025-08-02T03:23:49+02:00 feat: Add fmwave control and ability to fm with noise by @daslyfe in: [#1472](https://codeberg.org/uzu/strudel/pulls/1472) + +## july 2025 + +- 2025-07-30T00:22:41+02:00 hotfix: uzu kit JSON should be in the repo to avoid offline loading errors by @daslyfe in: [#1485](https://codeberg.org/uzu/strudel/pulls/1485) +- 2025-07-24T05:28:23+02:00 feat: make uzu-drumkit the default drumkit by @daslyfe in: [#1422](https://codeberg.org/uzu/strudel/pulls/1422) +- 2025-07-21T07:16:19+02:00 FIX: PWM modulation by @daslyfe in: [#1467](https://codeberg.org/uzu/strudel/pulls/1467) +- 2025-07-19T17:16:22+02:00 Tremolo modulation by @Ghost in: [#1095](https://codeberg.org/uzu/strudel/pulls/1095) +- 2025-07-15T09:39:33+02:00 fix: can now use def in mondough by @froos in: [#1461](https://codeberg.org/uzu/strudel/pulls/1461) +- 2025-07-11T11:10:24+02:00 fixed keybinding presedence issue by @Dsm0 in: [#1456](https://codeberg.org/uzu/strudel/pulls/1456) +- 2025-07-11T11:09:05+02:00 added multicursor support on ctrl/cmd + click by @Dsm0 in: [#1457](https://codeberg.org/uzu/strudel/pulls/1457) +- 2025-07-09T09:39:46+02:00 added tab-indent setting by @Dsm0 in: [#1454](https://codeberg.org/uzu/strudel/pulls/1454) +- 2025-07-08T15:48:41+02:00 ontrigger-refactoring by @froos in: [#1442](https://codeberg.org/uzu/strudel/pulls/1442) +- 2025-07-07T23:14:24+02:00 fix: remove first gm_synth_bass_1, as it doesn't work in safari by @froos in: [#1452](https://codeberg.org/uzu/strudel/pulls/1452) +- 2025-07-07T00:06:16+02:00 fix: minor doc changes by @Aurel300 in: [#1435](https://codeberg.org/uzu/strudel/pulls/1435) +- 2025-07-05T12:53:45+02:00 Fix randrun and deps including shuffle. Fixes #1441 by @yaxu in: [#1447](https://codeberg.org/uzu/strudel/pulls/1447) +- 2025-07-05T12:15:52+02:00 add setDefault + resetDefaults to superdough by @froos in: [#1433](https://codeberg.org/uzu/strudel/pulls/1433) +- 2025-07-04T22:36:19+02:00 disable fm for supersaw by @froos in: [#1443](https://codeberg.org/uzu/strudel/pulls/1443) + +## june 2025 + +- 2025-06-30T13:28:10+02:00 corrected minor spelling mistake (#1425) by tj-@mueller in: [#1434](https://codeberg.org/uzu/strudel/pulls/1434) +- 2025-06-30T05:26:11+02:00 Add browser cache explanation in samples.mdx by @Ghost in: [#1369](https://codeberg.org/uzu/strudel/pulls/1369) +- 2025-06-30T05:18:42+02:00 add-release-notes by @froos in: [#1432](https://codeberg.org/uzu/strudel/pulls/1432) +- 2025-06-28T20:30:49+02:00 feat: delaytime in cycles by @Ghost in: [#1341](https://codeberg.org/uzu/strudel/pulls/1341) +- 2025-06-28T13:44:49+02:00 Case insensitive search in the reference tab by @kdiab in: [#1420](https://codeberg.org/uzu/strudel/pulls/1420) +- 2025-06-28T00:04:13+02:00 add unjoin, into and chunkinto by @yaxu in: [#1418](https://codeberg.org/uzu/strudel/pulls/1418) +- 2025-06-26T15:29:46+02:00 mondo notation by @froos in: [#1311](https://codeberg.org/uzu/strudel/pulls/1311) +- 2025-06-24T06:14:15+02:00 allow calling `all` multiple times by @froos in: [#1417](https://codeberg.org/uzu/strudel/pulls/1417) +- 2025-06-24T05:53:22+02:00 tonal: allow scales without tonic (default to C) by @Ghost in: [#1092](https://codeberg.org/uzu/strudel/pulls/1092) +- 2025-06-20T20:00:12+02:00 website intro: fix whitespace and code hosting name by @trofi in: [#1400](https://codeberg.org/uzu/strudel/pulls/1400) +- 2025-06-19T10:15:07+02:00 fix stepcat #1396 by @yaxu in: [#1398](https://codeberg.org/uzu/strudel/pulls/1398) +- 2025-06-19T09:52:38+02:00 deprecate cpm + refactor docs to use setcpm by @froos in: [#1397](https://codeberg.org/uzu/strudel/pulls/1397) +- 2025-06-19T02:49:09+02:00 feat: 3 new codemirror themes by @daslyfe in: [#1394](https://codeberg.org/uzu/strudel/pulls/1394) +- 2025-06-14T16:23:00+02:00 link back to tech manual in wiki by @yaxu in: [#1382](https://codeberg.org/uzu/strudel/pulls/1382) +- 2025-06-14T14:55:37+02:00 degithub-links by @froos in: [#1380](https://codeberg.org/uzu/strudel/pulls/1380) +- 2025-06-13T15:24:42+02:00 fix euclidLegatoRot by @bwagner in: [#1378](https://codeberg.org/uzu/strudel/pulls/1378) +- 2025-06-13T10:07:51+02:00 remove-ms by @yaxu in: [#1377](https://codeberg.org/uzu/strudel/pulls/1377) +- 2025-06-12T10:25:48+02:00 Share fat URL by @Ghost in: [#1375](https://codeberg.org/uzu/strudel/pulls/1375) +- 2025-06-05T20:39:16+02:00 feat: onTriggerTime for interfacing with random APIs and doing illegal things on event time by @Ghost in: [#1364](https://codeberg.org/uzu/strudel/pulls/1364) +- 2025-06-05T01:08:18+02:00 feat: berlin noise by @Ghost in: [#1363](https://codeberg.org/uzu/strudel/pulls/1363) + +## may 2025 + +- 2025-05-29T14:36:43+02:00 Fix bytebeat sample offset by @Ghost in: [#1359](https://codeberg.org/uzu/strudel/pulls/1359) +- 2025-05-29T12:14:18+02:00 preserve stepcount in chunks by @yaxu in: [#1358](https://codeberg.org/uzu/strudel/pulls/1358) +- 2025-05-29T02:34:30+02:00 Byte Beat improvements -> 2 by @Ghost in: [#1357](https://codeberg.org/uzu/strudel/pulls/1357) +- 2025-05-28T16:40:32+02:00 Bytebeat improvements by @Ghost in: [#1356](https://codeberg.org/uzu/strudel/pulls/1356) +- 2025-05-27T17:42:39+02:00 feat: Byte Beats! by @Ghost in: [#1354](https://codeberg.org/uzu/strudel/pulls/1354) +- 2025-05-18T21:43:05+02:00 fix: typo on docs causing problems with autocompletion. by @Ghost in: [#1350](https://codeberg.org/uzu/strudel/pulls/1350) +- 2025-05-15T01:12:07+02:00 feat: Create pulsewidth (pw) and pulsewidth lfo parameters by @Ghost in: [#1343](https://codeberg.org/uzu/strudel/pulls/1343) +- 2025-05-14T18:25:17+02:00 feat: Multi Channel Orbits by @Ghost in: [#1344](https://codeberg.org/uzu/strudel/pulls/1344) +- 2025-05-13T17:45:29+02:00 Fix typo by @Ghost in: [#1346](https://codeberg.org/uzu/strudel/pulls/1346) +- 2025-05-02T00:01:27+02:00 Fix web + repl package builds by @froos in: [#1339](https://codeberg.org/uzu/strudel/pulls/1339) +- 2025-05-01T23:50:57+02:00 fix: superdough worklets bundling by @froos in: [#1338](https://codeberg.org/uzu/strudel/pulls/1338) + +## april 2025 + +- 2025-04-27T22:25:54+02:00 FIX: sound import order by @Ghost in: [#1333](https://codeberg.org/uzu/strudel/pulls/1333) +- 2025-04-27T18:39:49+02:00 update docs to reflect import sounds tab change by @hpunq in: [#1332](https://codeberg.org/uzu/strudel/pulls/1332) +- 2025-04-27T18:38:26+02:00 feat: Improve gain curve by @Ghost in: [#1318](https://codeberg.org/uzu/strudel/pulls/1318) +- 2025-04-27T07:54:49+02:00 Add Icon to import sample button by @Ghost in: [#1331](https://codeberg.org/uzu/strudel/pulls/1331) +- 2025-04-27T07:53:41+02:00 Add new "import-sounds" tab with explanation on folder import by @hpunq in: [#1329](https://codeberg.org/uzu/strudel/pulls/1329) +- 2025-04-22T00:04:38+02:00 feat: new themes + theme improvements by @Ghost in: [#1326](https://codeberg.org/uzu/strudel/pulls/1326) +- 2025-04-20T18:24:15+02:00 feat: Create scrub function for scrubbing an audio file by @Ghost in: [#1321](https://codeberg.org/uzu/strudel/pulls/1321) +- 2025-04-18T07:17:39+02:00 fix: disable astro toolbar by default by @Ghost in: [#1324](https://codeberg.org/uzu/strudel/pulls/1324) +- 2025-04-18T07:17:38+02:00 fix: udels header by @Ghost in: [#1325](https://codeberg.org/uzu/strudel/pulls/1325) +- 2025-04-11T09:18:58+02:00 Send delta in OSC message in seconds, to match tidal/superdirt by @yaxu in: [#1323](https://codeberg.org/uzu/strudel/pulls/1323) +- 2025-04-08T06:08:57+02:00 FIX: Multichannel Audio by @Ghost in: [#1322](https://codeberg.org/uzu/strudel/pulls/1322) +- 2025-04-08T04:18:03+02:00 feat: add max polyphony feature for superdough by @Ghost in: [#1317](https://codeberg.org/uzu/strudel/pulls/1317) +- 2025-04-05T21:12:05+02:00 enhancement: make error messages easier to read by @Ghost in: [#1315](https://codeberg.org/uzu/strudel/pulls/1315) +- 2025-04-04T06:26:57+02:00 fix: replace empty spaces in registered sound keys by @Ghost in: [#1319](https://codeberg.org/uzu/strudel/pulls/1319) + +## march 2025 + +- 2025-03-26T17:01:05+01:00 small feat: Add alias for segment and ribbon by @Ghost in: [#1314](https://codeberg.org/uzu/strudel/pulls/1314) +- 2025-03-26T14:54:18+01:00 Fix typo pattnr by @Ghost in: [#1316](https://codeberg.org/uzu/strudel/pulls/1316) +- 2025-03-23T20:05:31+01:00 bugfix: Allow single param to be used in the as function by @Ghost in: [#1312](https://codeberg.org/uzu/strudel/pulls/1312) +- 2025-03-20T23:35:18+01:00 Add MIDI Program Change, SysEx, NRPN, PitchBend and AfterTouch Output by @nkymut in: [#1244](https://codeberg.org/uzu/strudel/pulls/1244) +- 2025-03-19T17:32:30+01:00 make soundfont base url configurable by @Ghost in: [#1040](https://codeberg.org/uzu/strudel/pulls/1040) +- 2025-03-18T20:07:08+01:00 Add num samples from 0 up to 20 by @yaxu in: [#1310](https://codeberg.org/uzu/strudel/pulls/1310) +- 2025-03-17T10:38:54+01:00 add num samples (edited numbers) by @yaxu in: [#1309](https://codeberg.org/uzu/strudel/pulls/1309) +- 2025-03-08T22:17:47+01:00 @strudel/sampler improvements by @froos in: [#1288](https://codeberg.org/uzu/strudel/pulls/1288) +- 2025-03-08T21:56:50+01:00 Add Gamepad module by @nkymut in: [#1223](https://codeberg.org/uzu/strudel/pulls/1223) +- 2025-03-04T19:17:37+01:00 change behaviour of polymeter, and remove polymeterSteps by @yaxu in: [#1302](https://codeberg.org/uzu/strudel/pulls/1302) +- 2025-03-04T03:36:57+01:00 feat: Create Pulse Oscillator with variable PWM by @Ghost in: [#1304](https://codeberg.org/uzu/strudel/pulls/1304) +- 2025-03-02T17:08:31+01:00 feat: Theme improvements by @Ghost in: [#1295](https://codeberg.org/uzu/strudel/pulls/1295) +- 2025-03-02T11:58:46+01:00 bugfix zoom stepcount by @yaxu in: [#1301](https://codeberg.org/uzu/strudel/pulls/1301) + +## february 2025 + +- 2025-02-28T16:01:20+01:00 Fix test error #1297 by @nkymut in: [#1298](https://codeberg.org/uzu/strudel/pulls/1298) +- 2025-02-28T02:53:42+01:00 Hotfix: prevent undefined pattern code from crashing strudel on load by @Ghost in: [#1297](https://codeberg.org/uzu/strudel/pulls/1297) +- 2025-02-27T08:24:58+01:00 Fix misplaced ending sentence by @Ghost in: [#1296](https://codeberg.org/uzu/strudel/pulls/1296) +- 2025-02-23T10:54:13+01:00 Signpost licenses for source code and samples a bit more, ref #1277 by @yaxu in: [#1289](https://codeberg.org/uzu/strudel/pulls/1289) +- 2025-02-23T10:52:27+01:00 Allow wchooseCycles probabilities to be patterned by @yaxu in: [#1292](https://codeberg.org/uzu/strudel/pulls/1292) +- 2025-02-22T02:18:59+01:00 Create Pattern Page Pagination by @Ghost in: [#1287](https://codeberg.org/uzu/strudel/pulls/1287) +- 2025-02-21T22:38:10+01:00 showcase tweaks by @yaxu in: [#1291](https://codeberg.org/uzu/strudel/pulls/1291) +- 2025-02-11T16:34:24+01:00 Fixes inverted triangle wave by renaming it to `itri`, making non-inverted `tri` by @yaxu in: [#1283](https://codeberg.org/uzu/strudel/pulls/1283) +- 2025-02-11T10:02:34+01:00 Fix `squeezejoin` and functions using it, including `bite` by @yaxu in: [#1286](https://codeberg.org/uzu/strudel/pulls/1286) +- 2025-02-09T16:18:28+01:00 Rename repeat back to extend by @yaxu in: [#1285](https://codeberg.org/uzu/strudel/pulls/1285) +- 2025-02-07T18:11:52+01:00 mqtt bugfix - connection check by @yaxu in: [#1282](https://codeberg.org/uzu/strudel/pulls/1282) +- 2025-02-07T17:38:29+01:00 Bugfix: update mqtt connections dictionary by @yaxu in: [#1281](https://codeberg.org/uzu/strudel/pulls/1281) +- 2025-02-07T11:39:54+01:00 make mqtt topic patternable by @yaxu in: [#1280](https://codeberg.org/uzu/strudel/pulls/1280) +- 2025-02-07T11:07:32+01:00 midimaps by @froos in: [#1274](https://codeberg.org/uzu/strudel/pulls/1274) +- 2025-02-06T15:59:03+01:00 MQTT - support adding hap duration and cps metadata to JSON messages by @yaxu in: [#1279](https://codeberg.org/uzu/strudel/pulls/1279) +- 2025-02-05T16:10:54+01:00 [breaking change] Sample signals from query onset, rather than midpoint by @yaxu in: [#1278](https://codeberg.org/uzu/strudel/pulls/1278) +- 2025-02-03T00:55:36+01:00 Stepwise documentation tweaks, with mridangam samples by @yaxu in: [#1275](https://codeberg.org/uzu/strudel/pulls/1275) +- 2025-02-02T21:26:44+01:00 Polish, rename, and document stepwise functions by @yaxu in: [#1262](https://codeberg.org/uzu/strudel/pulls/1262) +- 2025-02-01T22:57:00+01:00 Fix sf2 timing by @froos in: [#1272](https://codeberg.org/uzu/strudel/pulls/1272) +- 2025-02-01T22:32:59+01:00 Add bank aliasing and case insensitivity by @TodePond in: [#1245](https://codeberg.org/uzu/strudel/pulls/1245) + +## january 2025 + +- 2025-01-31T09:39:54+01:00 Add Device Motion module by @nkymut in: [#1217](https://codeberg.org/uzu/strudel/pulls/1217) +- 2025-01-29T15:40:01+01:00 add "as" function + getControlName by @froos in: [#1247](https://codeberg.org/uzu/strudel/pulls/1247) +- 2025-01-29T15:30:04+01:00 Theme glowup by @froos in: [#1268](https://codeberg.org/uzu/strudel/pulls/1268) +- 2025-01-29T15:22:18+01:00 patchday by @froos in: [#1264](https://codeberg.org/uzu/strudel/pulls/1264) +- 2025-01-28T00:00:03+01:00 Revert "Fix sometimes" by @yaxu in: [#1267](https://codeberg.org/uzu/strudel/pulls/1267) +- 2025-01-24T15:32:48+01:00 add reference package by @froos in: [#1252](https://codeberg.org/uzu/strudel/pulls/1252) +- 2025-01-24T15:16:55+01:00 support `all(pianoroll)` and `all(pianoroll({labels: true}))` by @yaxu in: [#1234](https://codeberg.org/uzu/strudel/pulls/1234) +- 2025-01-24T12:13:49+01:00 MQTT - if password isn't provided, prompt for one by @yaxu in: [#1249](https://codeberg.org/uzu/strudel/pulls/1249) +- 2025-01-21T06:24:03+01:00 fix docs for beat function by @Ghost in: [#1248](https://codeberg.org/uzu/strudel/pulls/1248) +- 2025-01-18T23:27:51+01:00 understand voicings page by @froos in: [#1230](https://codeberg.org/uzu/strudel/pulls/1230) +- 2025-01-17T19:27:00+01:00 Add binary and binaryN by @Ghost in: [#1226](https://codeberg.org/uzu/strudel/pulls/1226) +- 2025-01-16T12:18:33+01:00 Fix sometimes by @yaxu in: [#1243](https://codeberg.org/uzu/strudel/pulls/1243) +- 2025-01-16T05:55:46+01:00 "beat" function for "step sequencer" style rhythm notation by @Ghost in: [#1237](https://codeberg.org/uzu/strudel/pulls/1237) +- 2025-01-14T14:39:15+01:00 Add stepBind, and some toplevel aliases for binds and withValue by @yaxu in: [#1241](https://codeberg.org/uzu/strudel/pulls/1241) +- 2025-01-14T06:11:10+01:00 Add onKey function for custom key commands for patterns by @Ghost in: [#1235](https://codeberg.org/uzu/strudel/pulls/1235) +- 2025-01-12T11:32:24+01:00 Update documentation for param value modification by @Ghost in: [#1238](https://codeberg.org/uzu/strudel/pulls/1238) + +## december 2024 + +- 2024-12-29T13:16:08+01:00 Documentation for all/each, and bugfix for each by @yaxu in: [#1233](https://codeberg.org/uzu/strudel/pulls/1233) +- 2024-12-29T11:29:52+01:00 Make `all()` post-stack again, and add `each()` for pre-stack by @yaxu in: [#1229](https://codeberg.org/uzu/strudel/pulls/1229) +- 2024-12-27T22:16:54+01:00 suggested changes to voicings.mdx by @Ghost in: [#1231](https://codeberg.org/uzu/strudel/pulls/1231) +- 2024-12-23T11:53:11+01:00 add basic spectrum function by @froos in: [#1213](https://codeberg.org/uzu/strudel/pulls/1213) +- 2024-12-22T21:04:45+01:00 Fix regression for d1, p1, p(n) by @yaxu in: [#1227](https://codeberg.org/uzu/strudel/pulls/1227) + +## november 2024 + +- 2024-11-30T10:09:36+01:00 Make cps patternable by @eefano in: [#1001](https://codeberg.org/uzu/strudel/pulls/1001) +- 2024-11-30T10:07:56+01:00 MQTT support by @yaxu in: [#1224](https://codeberg.org/uzu/strudel/pulls/1224) +- 2024-11-30T09:46:14+01:00 Apply `all` function to individual patterns rather than final stack by @yaxu in: [#1209](https://codeberg.org/uzu/strudel/pulls/1209) +- 2024-11-20T16:32:51+01:00 REPL: solo and sync configuration by @Ghost in: [#1214](https://codeberg.org/uzu/strudel/pulls/1214) + +## october 2024 + +- 2024-10-30T21:29:43+01:00 Add s_zip for 'cat'-ing patterns together step-by-step, bugfix `steps` by @yaxu in: [#1208](https://codeberg.org/uzu/strudel/pulls/1208) +- 2024-10-30T21:28:32+01:00 Preserve tactus for 'degrade' and friends, and tidy up 'pick' and friends by @yaxu in: [#1205](https://codeberg.org/uzu/strudel/pulls/1205) +- 2024-10-30T17:11:53+01:00 chore: Edit run locally instructions in README.md by @Ghost in: [#1206](https://codeberg.org/uzu/strudel/pulls/1206) +- 2024-10-23T23:08:14+02:00 colorize console + tweak header by @froos in: [#1203](https://codeberg.org/uzu/strudel/pulls/1203) +- 2024-10-22T22:55:00+02:00 markcss by @froos in: [#1202](https://codeberg.org/uzu/strudel/pulls/1202) +- 2024-10-21T22:56:37+02:00 add 2 new ui settings by @froos in: [#1200](https://codeberg.org/uzu/strudel/pulls/1200) +- 2024-10-21T20:22:52+02:00 Make panel hover behavior optional by @Ghost in: [#1199](https://codeberg.org/uzu/strudel/pulls/1199) +- 2024-10-19T04:28:36+02:00 Menu Panel Improvements! by @Ghost in: [#1193](https://codeberg.org/uzu/strudel/pulls/1193) +- 2024-10-19T01:10:06+02:00 update lockfile + minor versions by @froos in: [#1198](https://codeberg.org/uzu/strudel/pulls/1198) + +## september 2024 + +- 2024-09-27T00:17:00+02:00 remove redundant example for cat, update snapshot by @Ghost in: [#1189](https://codeberg.org/uzu/strudel/pulls/1189) +- 2024-09-25T13:18:19+02:00 Adding search bar (soundtab.jsx) by @Ghost in: [#1185](https://codeberg.org/uzu/strudel/pulls/1185) +- 2024-09-23T17:18:34+02:00 Screenreader improvements by @yaxu in: [#1158](https://codeberg.org/uzu/strudel/pulls/1158) +- 2024-09-20T22:26:41+02:00 Fix serial timing by @yaxu in: [#1188](https://codeberg.org/uzu/strudel/pulls/1188) +- 2024-09-20T00:26:30+02:00 Add bite function by @yaxu in: [#1187](https://codeberg.org/uzu/strudel/pulls/1187) +- 2024-09-14T13:30:53+02:00 better spacing in zen mode by @froos in: [#1147](https://codeberg.org/uzu/strudel/pulls/1147) +- 2024-09-14T10:50:24+02:00 add filter + filterWhen + within by @froos in: [#1039](https://codeberg.org/uzu/strudel/pulls/1039) +- 2024-09-14T10:49:21+02:00 refactor sampler by @froos in: [#1101](https://codeberg.org/uzu/strudel/pulls/1101) +- 2024-09-14T10:43:17+02:00 handle midin device not found error by @froos in: [#1146](https://codeberg.org/uzu/strudel/pulls/1146) +- 2024-09-13T21:59:23+02:00 Add a search bar to the REPL Reference tab by @Ghost in: [#1165](https://codeberg.org/uzu/strudel/pulls/1165) +- 2024-09-13T21:58:47+02:00 Correct spelling mistakes by @Ghost in: [#1183](https://codeberg.org/uzu/strudel/pulls/1183) +- 2024-09-07T23:41:29+02:00 Add seqPLoop from Tidal by @yaxu in: [#1182](https://codeberg.org/uzu/strudel/pulls/1182) +- 2024-09-05T05:52:50+02:00 make phaser control match superdirt by @Ghost in: [#1180](https://codeberg.org/uzu/strudel/pulls/1180) +- 2024-09-05T05:33:41+02:00 Revert "Make phaser control consistent with superdirt" by @Ghost in: [#1179](https://codeberg.org/uzu/strudel/pulls/1179) +- 2024-09-05T05:29:12+02:00 Make phaser control consistent with superdirt by @Ghost in: [#1178](https://codeberg.org/uzu/strudel/pulls/1178) +- 2024-09-03T04:37:15+02:00 fix sample speed when using splice and fit with superdirt by @Ghost in: [#1172](https://codeberg.org/uzu/strudel/pulls/1172) +- 2024-09-01T16:02:24+02:00 Create audio target selector for OSC/Superdirt by @Ghost in: [#1160](https://codeberg.org/uzu/strudel/pulls/1160) +- 2024-09-01T15:03:47+02:00 Fixes fit so it works after a chop or slice by @yaxu in: [#1171](https://codeberg.org/uzu/strudel/pulls/1171) + +## august 2024 + +- 2024-08-30T14:24:08+02:00 polyJoin by @yaxu in: [#1168](https://codeberg.org/uzu/strudel/pulls/1168) +- 2024-08-23T17:05:10+02:00 Add scramble and shuffle by @yaxu in: [#1167](https://codeberg.org/uzu/strudel/pulls/1167) +- 2024-08-18T18:22:20+02:00 Improve + simplify neocyclist timing by @Ghost in: [#1164](https://codeberg.org/uzu/strudel/pulls/1164) +- 2024-08-15T05:18:33+02:00 containerize/seperate out boolean checks for repl types/Repl logic into bespoke components. by @Ghost in: [#1163](https://codeberg.org/uzu/strudel/pulls/1163) +- 2024-08-12T18:57:21+02:00 [CORS HOTFIX] by @Ghost in: [#1162](https://codeberg.org/uzu/strudel/pulls/1162) +- 2024-08-09T05:11:28+02:00 Fix OSC clock jitter by @Ghost in: [#1157](https://codeberg.org/uzu/strudel/pulls/1157) + +## july 2024 + +- 2024-07-27T11:02:38+02:00 Fix loopAt tactus by @yaxu in: [#1145](https://codeberg.org/uzu/strudel/pulls/1145) +- 2024-07-26T04:37:34+02:00 "stretch" function (phase vocoder) by @Ghost in: [#1130](https://codeberg.org/uzu/strudel/pulls/1130) +- 2024-07-26T04:37:17+02:00 Udels (MultiFrame Strudel) Revisited by @Ghost in: [#1132](https://codeberg.org/uzu/strudel/pulls/1132) +- 2024-07-24T11:40:28+02:00 Fix tactus marking in mininotation by @yaxu in: [#1144](https://codeberg.org/uzu/strudel/pulls/1144) + +## june 2024 + +- 2024-06-25T18:13:28+02:00 export comment commands by @froos in: [#1136](https://codeberg.org/uzu/strudel/pulls/1136) +- 2024-06-24T18:19:22+02:00 Chop chop by @yaxu in: [#1078](https://codeberg.org/uzu/strudel/pulls/1078) +- 2024-06-18T23:58:08+02:00 Fix bug in Fraction.lcm by @yaxu in: [#1133](https://codeberg.org/uzu/strudel/pulls/1133) +- 2024-06-18T05:32:12+02:00 Fix clock worker dependency path in module builds by @Ghost in: [#1129](https://codeberg.org/uzu/strudel/pulls/1129) +- 2024-06-04T00:26:48+02:00 Labeled statements doc by @froos in: [#1126](https://codeberg.org/uzu/strudel/pulls/1126) +- 2024-06-03T22:40:21+02:00 doc: visual functions + refactor onPaint by @froos in: [#1125](https://codeberg.org/uzu/strudel/pulls/1125) +- 2024-06-02T18:36:29+02:00 Fix indexDB failing with large amount of files by @Ghost in: [#1124](https://codeberg.org/uzu/strudel/pulls/1124) + +## may 2024 + +- 2024-05-31T12:17:34+02:00 Migrate tutorial fanchor by @froos in: [#1122](https://codeberg.org/uzu/strudel/pulls/1122) +- 2024-05-31T10:46:47+02:00 [BUG FIX] Audio worklets sometimes dont load by @Ghost in: [#1121](https://codeberg.org/uzu/strudel/pulls/1121) +- 2024-05-31T10:25:24+02:00 change fanchor to 0 by @Ghost in: [#1107](https://codeberg.org/uzu/strudel/pulls/1107) +- 2024-05-30T14:39:30+02:00 fix: use full repl in web package by @froos in: [#1119](https://codeberg.org/uzu/strudel/pulls/1119) +- 2024-05-30T09:36:32+02:00 can now access strudelMirror from repl by @froos in: [#1117](https://codeberg.org/uzu/strudel/pulls/1117) +- 2024-05-29T13:06:34+02:00 Add the mousex and mousey signal by @Ghost in: [#1112](https://codeberg.org/uzu/strudel/pulls/1112) +- 2024-05-29T13:02:00+02:00 Fixes drawPianoroll import in codemirror example by @Ghost in: [#1116](https://codeberg.org/uzu/strudel/pulls/1116) +- 2024-05-28T22:43:46+02:00 clarify `off` in pattern-effects.mdx by @Ghost in: [#1074](https://codeberg.org/uzu/strudel/pulls/1074) +- 2024-05-26T17:33:07+02:00 rollback phaser by @Ghost in: [#1113](https://codeberg.org/uzu/strudel/pulls/1113) +- 2024-05-26T17:33:06+02:00 Fix audio worklets by @Ghost in: [#1114](https://codeberg.org/uzu/strudel/pulls/1114) +- 2024-05-26T16:53:56+02:00 Calculate phaser modulation phase based on time by @Ghost in: [#1110](https://codeberg.org/uzu/strudel/pulls/1110) +- 2024-05-23T15:06:01+02:00 Add analog-style ladder filter by @Ghost in: [#1103](https://codeberg.org/uzu/strudel/pulls/1103) +- 2024-05-22T12:53:20+02:00 fix sampler on windows by @Ghost in: [#1109](https://codeberg.org/uzu/strudel/pulls/1109) +- 2024-05-20T23:26:20+02:00 web package fixes by @froos in: [#1044](https://codeberg.org/uzu/strudel/pulls/1044) +- 2024-05-20T22:41:28+02:00 Fix sampler windows by @froos in: [#1108](https://codeberg.org/uzu/strudel/pulls/1108) +- 2024-05-19T12:07:26+02:00 hs2js package / tidal parser by @froos in: [#870](https://codeberg.org/uzu/strudel/pulls/870) +- 2024-05-18T10:49:08+02:00 fix little dub tune example by @Ghost in: [#1104](https://codeberg.org/uzu/strudel/pulls/1104) +- 2024-05-17T14:43:58+02:00 Samples tab improvements by @Ghost in: [#1102](https://codeberg.org/uzu/strudel/pulls/1102) +- 2024-05-13T10:10:34+02:00 osc: couple of fixes by @Ghost in: [#1093](https://codeberg.org/uzu/strudel/pulls/1093) +- 2024-05-13T06:31:20+02:00 Use sessionStorage for viewingPatternData and activePattern by @Ghost in: [#1091](https://codeberg.org/uzu/strudel/pulls/1091) +- 2024-05-12T18:23:09+02:00 repl: set document.title from @title by @Ghost in: [#1090](https://codeberg.org/uzu/strudel/pulls/1090) +- 2024-05-07T14:29:22+02:00 fix: missing events due to premature worklet cleanup by @froos in: [#1089](https://codeberg.org/uzu/strudel/pulls/1089) +- 2024-05-03T11:52:57+02:00 Benchmarks by @yaxu in: [#1079](https://codeberg.org/uzu/strudel/pulls/1079) +- 2024-05-03T08:46:52+02:00 fix: csound + dough timing by @froos in: [#1086](https://codeberg.org/uzu/strudel/pulls/1086) +- 2024-05-03T00:37:32+02:00 Improve performance of ! (replicate) by @yaxu in: [#1084](https://codeberg.org/uzu/strudel/pulls/1084) +- 2024-05-02T23:40:22+02:00 fix: url parsing with extra params by @froos in: [#1083](https://codeberg.org/uzu/strudel/pulls/1083) + +## april 2024 + +- 2024-04-29T12:36:11+02:00 Tactus calculation toggle and breaking change to tactus calculation in fast/slow/hurry by @yaxu in: [#1081](https://codeberg.org/uzu/strudel/pulls/1081) +- 2024-04-28T20:56:21+02:00 fix docs on alignment.mdx by @Ghost in: [#1076](https://codeberg.org/uzu/strudel/pulls/1076) +- 2024-04-28T20:49:18+02:00 add signals to recap in first-effects.mdx by @Ghost in: [#1073](https://codeberg.org/uzu/strudel/pulls/1073) +- 2024-04-28T20:44:57+02:00 fix translation issue in first-effects.mdx by @Ghost in: [#1072](https://codeberg.org/uzu/strudel/pulls/1072) +- 2024-04-28T20:44:29+02:00 fix failing format test by @Ghost in: [#1077](https://codeberg.org/uzu/strudel/pulls/1077) +- 2024-04-27T22:50:17+02:00 add nesting to `off` example variation in pattern-effects.mdx by @Ghost in: [#1075](https://codeberg.org/uzu/strudel/pulls/1075) +- 2024-04-27T22:48:58+02:00 add `<...>` to first-sounds.mdx recap by @Ghost in: [#1070](https://codeberg.org/uzu/strudel/pulls/1070) +- 2024-04-27T22:48:07+02:00 fix first sounds typo by @Ghost in: [#1069](https://codeberg.org/uzu/strudel/pulls/1069) +- 2024-04-27T22:47:44+02:00 fix cr typo on first-sounds.mdx by @Ghost in: [#1068](https://codeberg.org/uzu/strudel/pulls/1068) +- 2024-04-26T15:12:30+02:00 More tactus tidying by @yaxu in: [#1071](https://codeberg.org/uzu/strudel/pulls/1071) +- 2024-04-23T23:37:21+02:00 Fix stepjoin by @yaxu in: [#1067](https://codeberg.org/uzu/strudel/pulls/1067) +- 2024-04-23T23:32:00+02:00 clarify license by @yaxu in: [#1064](https://codeberg.org/uzu/strudel/pulls/1064) +- 2024-04-23T15:14:30+02:00 Tactus tweaks - fixes for maintaining tactus and highlight locations by @yaxu in: [#1065](https://codeberg.org/uzu/strudel/pulls/1065) +- 2024-04-21T23:57:57+02:00 fix OSC timing for recent scheduler updates by @Ghost in: [#1062](https://codeberg.org/uzu/strudel/pulls/1062) +- 2024-04-21T22:17:07+02:00 Stepwise functions from Tidal by @yaxu in: [#1060](https://codeberg.org/uzu/strudel/pulls/1060) +- 2024-04-21T11:03:55+02:00 Fix wchooseCycles not picking the whole pattern by @Ghost in: [#1061](https://codeberg.org/uzu/strudel/pulls/1061) +- 2024-04-19T00:05:52+02:00 add swing + swingBy by @froos in: [#1038](https://codeberg.org/uzu/strudel/pulls/1038) +- 2024-04-19T00:05:08+02:00 anonymous patterns + muting by @froos in: [#1059](https://codeberg.org/uzu/strudel/pulls/1059) +- 2024-04-12T12:34:27+02:00 improve tutorial + custom samples doc by @froos in: [#1053](https://codeberg.org/uzu/strudel/pulls/1053) +- 2024-04-12T12:31:49+02:00 fix: do not reset cc input values on each eval by @froos in: [#1054](https://codeberg.org/uzu/strudel/pulls/1054) +- 2024-04-08T17:25:27+02:00 transpose: support all combinations of numbers and strings for notes and intervals by @froos in: [#1048](https://codeberg.org/uzu/strudel/pulls/1048) +- 2024-04-08T10:46:18+02:00 Wax, wane, taper and taperlist by @yaxu in: [#1042](https://codeberg.org/uzu/strudel/pulls/1042) +- 2024-04-06T23:44:57+02:00 Midi Time hotfix for scheduler updates by @Ghost in: [#1047](https://codeberg.org/uzu/strudel/pulls/1047) +- 2024-04-05T12:48:03+02:00 pitchwheel visual by @froos in: [#1041](https://codeberg.org/uzu/strudel/pulls/1041) +- 2024-04-05T12:47:19+02:00 fix cyclist fizzling out by @froos in: [#1046](https://codeberg.org/uzu/strudel/pulls/1046) + +## march 2024 + +- 2024-03-30T16:05:59+01:00 remove dangerous arithmetic feature by @froos in: [#1030](https://codeberg.org/uzu/strudel/pulls/1030) +- 2024-03-30T16:05:27+01:00 Fix sampler paths by @froos in: [#1034](https://codeberg.org/uzu/strudel/pulls/1034) +- 2024-03-30T14:43:08+01:00 local sample server cli by @froos in: [#1033](https://codeberg.org/uzu/strudel/pulls/1033) +- 2024-03-29T17:14:28+01:00 add font file types to offline cache by @froos in: [#1032](https://codeberg.org/uzu/strudel/pulls/1032) +- 2024-03-29T17:10:16+01:00 add closeBrackets setting by @froos in: [#1031](https://codeberg.org/uzu/strudel/pulls/1031) +- 2024-03-29T14:55:06+01:00 Tactus tidy by @yaxu in: [#1027](https://codeberg.org/uzu/strudel/pulls/1027) +- 2024-03-28T17:06:44+01:00 add setting for sync flag by @froos in: [#1025](https://codeberg.org/uzu/strudel/pulls/1025) +- 2024-03-28T11:37:57+01:00 better theme integration for visuals + various fixes by @froos in: [#1024](https://codeberg.org/uzu/strudel/pulls/1024) +- 2024-03-28T11:33:25+01:00 More fonts by @froos in: [#1023](https://codeberg.org/uzu/strudel/pulls/1023) +- 2024-03-27T13:06:05+01:00 Feature: tactus marking by @yaxu in: [#1021](https://codeberg.org/uzu/strudel/pulls/1021) +- 2024-03-25T06:02:02+01:00 hotfix for 1017 by @Ghost in: [#1020](https://codeberg.org/uzu/strudel/pulls/1020) +- 2024-03-24T15:06:33+01:00 Document signals by @Ghost in: [#1015](https://codeberg.org/uzu/strudel/pulls/1015) +- 2024-03-24T10:16:11+01:00 Repl sync fixes by @Ghost in: [#1014](https://codeberg.org/uzu/strudel/pulls/1014) +- 2024-03-23T20:21:51+01:00 eliminate chromium clock jitter by @froos in: [#1004](https://codeberg.org/uzu/strudel/pulls/1004) +- 2024-03-23T15:51:07+01:00 update undocumented script by @froos in: [#1013](https://codeberg.org/uzu/strudel/pulls/1013) +- 2024-03-23T15:26:52+01:00 fix: await injectPatternMethods by @froos in: [#1012](https://codeberg.org/uzu/strudel/pulls/1012) +- 2024-03-23T15:18:12+01:00 rename trig -> reset, trigzero -> restart by @froos in: [#1010](https://codeberg.org/uzu/strudel/pulls/1010) +- 2024-03-23T12:30:03+01:00 Inline punchcard + spiral by @froos in: [#1008](https://codeberg.org/uzu/strudel/pulls/1008) +- 2024-03-23T12:27:22+01:00 Color in hap value by @froos in: [#1007](https://codeberg.org/uzu/strudel/pulls/1007) +- 2024-03-22T23:53:51+01:00 using strudel in your project guide + cleanup examples by @froos in: [#1006](https://codeberg.org/uzu/strudel/pulls/1006) +- 2024-03-22T23:41:50+01:00 accidentals in scale degrees by @eefano in: [#1000](https://codeberg.org/uzu/strudel/pulls/1000) +- 2024-03-21T22:53:55+01:00 supersaw oscillator by @Ghost in: [#978](https://codeberg.org/uzu/strudel/pulls/978) +- 2024-03-21T13:00:04+01:00 remove canvas, externalize samples, delete junk by @froos in: [#1003](https://codeberg.org/uzu/strudel/pulls/1003) +- 2024-03-18T11:37:55+01:00 Fix pure mini highlight by @yaxu in: [#994](https://codeberg.org/uzu/strudel/pulls/994) +- 2024-03-18T07:12:14+01:00 inline viz / widgets package by @froos in: [#989](https://codeberg.org/uzu/strudel/pulls/989) +- 2024-03-17T04:07:00+01:00 Labeled statements by @froos in: [#991](https://codeberg.org/uzu/strudel/pulls/991) +- 2024-03-16T18:24:38+01:00 Beat-oriented functionality by @yaxu in: [#976](https://codeberg.org/uzu/strudel/pulls/976) +- 2024-03-15T01:47:35+01:00 REPL sync between windows by @Ghost in: [#900](https://codeberg.org/uzu/strudel/pulls/900) +- 2024-03-14T00:20:07+01:00 Update synths.mdx by @Ghost in: [#984](https://codeberg.org/uzu/strudel/pulls/984) +- 2024-03-13T00:36:22+01:00 fix: share now shares what's visible instead of active by @froos in: [#985](https://codeberg.org/uzu/strudel/pulls/985) +- 2024-03-10T01:18:57+01:00 use ireal as default voicing dict by @froos in: [#967](https://codeberg.org/uzu/strudel/pulls/967) +- 2024-03-10T00:50:23+01:00 little fix for withVal by @eefano in: [#980](https://codeberg.org/uzu/strudel/pulls/980) +- 2024-03-10T00:46:51+01:00 Velocity in value by @froos in: [#974](https://codeberg.org/uzu/strudel/pulls/974) +- 2024-03-10T00:42:50+01:00 fix: clear hydra on reset by @froos in: [#983](https://codeberg.org/uzu/strudel/pulls/983) +- 2024-03-10T00:38:43+01:00 move canvas related helpers from core to new draw package by @froos in: [#971](https://codeberg.org/uzu/strudel/pulls/971) +- 2024-03-08T05:38:07+01:00 replace shape with distort in learn doc by @Ghost in: [#982](https://codeberg.org/uzu/strudel/pulls/982) +- 2024-03-06T12:14:49+01:00 alias - for ~ by @yaxu in: [#981](https://codeberg.org/uzu/strudel/pulls/981) +- 2024-03-04T16:04:23+01:00 Worklet Improvents / fixes by @Ghost in: [#963](https://codeberg.org/uzu/strudel/pulls/963) +- 2024-03-01T17:30:19+01:00 Nested controls by @froos in: [#973](https://codeberg.org/uzu/strudel/pulls/973) + +## february 2024 + +- 2024-02-29T15:33:12+01:00 feat: can now invert euclid pulses with negative numbers by @froos in: [#959](https://codeberg.org/uzu/strudel/pulls/959) +- 2024-02-29T15:31:23+01:00 pickOut(), pickRestart(), pickReset() by @eefano in: [#950](https://codeberg.org/uzu/strudel/pulls/950) +- 2024-02-29T15:12:45+01:00 remove legacy legato + duration implementations by @froos in: [#965](https://codeberg.org/uzu/strudel/pulls/965) +- 2024-02-29T08:47:53+01:00 fix for transpose(): preserve hap value object structure by @eefano in: [#966](https://codeberg.org/uzu/strudel/pulls/966) +- 2024-02-29T08:32:00+01:00 add debounce to logger by @froos in: [#968](https://codeberg.org/uzu/strudel/pulls/968) +- 2024-02-28T18:43:52+01:00 controls refactoring: simplify exports by @froos in: [#962](https://codeberg.org/uzu/strudel/pulls/962) +- 2024-02-25T19:17:00+01:00 fix: reset global fx on pattern change by @froos in: [#960](https://codeberg.org/uzu/strudel/pulls/960) +- 2024-02-25T14:15:30+01:00 fix midi issue on firefox and added quote error by @Ghost in: [#936](https://codeberg.org/uzu/strudel/pulls/936) +- 2024-02-25T13:19:47+01:00 'Enable Bracket Matching' option in Codemirror by @eefano in: [#956](https://codeberg.org/uzu/strudel/pulls/956) +- 2024-02-23T14:37:46+01:00 fix script importable packages (web + repl) by @froos in: [#957](https://codeberg.org/uzu/strudel/pulls/957) +- 2024-02-21T16:17:37+01:00 account for cps in midi time duration by @Ghost in: [#954](https://codeberg.org/uzu/strudel/pulls/954) +- 2024-02-21T10:27:12+01:00 Auto await samples by @froos in: [#955](https://codeberg.org/uzu/strudel/pulls/955) +- 2024-02-08T13:16:15+01:00 remove cjs builds by @froos in: [#945](https://codeberg.org/uzu/strudel/pulls/945) +- 2024-02-04T23:15:37+01:00 Minor documentation error: Update first-sounds.mdx by @Ghost in: [#941](https://codeberg.org/uzu/strudel/pulls/941) + + +## january 2024 + +- 2024-01-24T16:48:57+01:00 fix: pianoroll sorting by @froos in: [#938](https://codeberg.org/uzu/strudel/pulls/938) +- 2024-01-23T00:04:03+01:00 V1 release notes by @froos in: [#935](https://codeberg.org/uzu/strudel/pulls/935) +- 2024-01-22T20:20:53+01:00 2 years blog post by @froos in: [#929](https://codeberg.org/uzu/strudel/pulls/929) +- 2024-01-22T20:02:34+01:00 make 0.5hz cps the default by @yaxu in: [#931](https://codeberg.org/uzu/strudel/pulls/931) +- 2024-01-22T00:52:01+01:00 Fix pattern tab not showing patterns without created date by @Ghost in: [#934](https://codeberg.org/uzu/strudel/pulls/934) +- 2024-01-21T20:46:28+01:00 Add useful pattern selection behavior for performing. by @Ghost in: [#897](https://codeberg.org/uzu/strudel/pulls/897) +- 2024-01-21T01:30:28+01:00 Refactor cps functions by @froos in: [#933](https://codeberg.org/uzu/strudel/pulls/933) +- 2024-01-20T23:47:31+01:00 Make splice cps-aware by @yaxu in: [#932](https://codeberg.org/uzu/strudel/pulls/932) +- 2024-01-19T18:50:57+01:00 community bakery by @froos in: [#923](https://codeberg.org/uzu/strudel/pulls/923) +- 2024-01-19T18:49:54+01:00 add pickF and pickmodF by @Ghost in: [#924](https://codeberg.org/uzu/strudel/pulls/924) +- 2024-01-19T15:10:48+01:00 Mini-notation additions towards tidal compatibility by @yaxu in: [#926](https://codeberg.org/uzu/strudel/pulls/926) +- 2024-01-18T23:34:11+01:00 Blog improvements by @froos in: [#919](https://codeberg.org/uzu/strudel/pulls/919) +- 2024-01-18T18:08:29+01:00 pick, pickmod, inhabit, inhabitmod by @yaxu in: [#921](https://codeberg.org/uzu/strudel/pulls/921) +- 2024-01-18T18:04:27+01:00 Revert "`pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function" by @yaxu in: [#920](https://codeberg.org/uzu/strudel/pulls/920) +- 2024-01-18T17:45:39+01:00 `pick` now accepts lookup tables, with alternate cycle squeezing behaviour as new `inhabit` function by @yaxu in: [#918](https://codeberg.org/uzu/strudel/pulls/918) +- 2024-01-18T10:30:08+01:00 rename @strudel.cycles/* packages to @strudel/* by @froos in: [#917](https://codeberg.org/uzu/strudel/pulls/917) +- 2024-01-18T06:54:33+01:00 pitch envelopes by @froos in: [#913](https://codeberg.org/uzu/strudel/pulls/913) +- 2024-01-18T05:09:38+01:00 Fix: swatch/[name].png.js static path by @Ghost in: [#916](https://codeberg.org/uzu/strudel/pulls/916) +- 2024-01-16T08:22:28+01:00 Add more vowel qualities for the vowels function by @Ghost in: [#907](https://codeberg.org/uzu/strudel/pulls/907) +- 2024-01-14T23:56:36+01:00 adds a blog by @froos in: [#911](https://codeberg.org/uzu/strudel/pulls/911) +- 2024-01-14T23:52:41+01:00 Remove hideHeader for better mobile UI and consistency by @Ghost in: [#894](https://codeberg.org/uzu/strudel/pulls/894) +- 2024-01-14T22:43:06+01:00 Further Envelope improvements by @Ghost in: [#868](https://codeberg.org/uzu/strudel/pulls/868) +- 2024-01-14T00:48:04+01:00 public sharing by @froos in: [#910](https://codeberg.org/uzu/strudel/pulls/910) +- 2024-01-12T18:31:41+01:00 fix some build warnings by @froos in: [#902](https://codeberg.org/uzu/strudel/pulls/902) +- 2024-01-12T18:01:55+01:00 prevent vite from complaining about additional exports in jsx files by @Ghost in: [#891](https://codeberg.org/uzu/strudel/pulls/891) +- 2024-01-12T17:05:47+01:00 Update Vite version so hot reload works properly with newest pnpm version by @Ghost in: [#892](https://codeberg.org/uzu/strudel/pulls/892) +- 2024-01-12T15:09:41+01:00 support , in < > by @froos in: [#886](https://codeberg.org/uzu/strudel/pulls/886) +- 2024-01-10T13:55:39+01:00 fix: autocomplete / tooltip code example bug by @froos in: [#898](https://codeberg.org/uzu/strudel/pulls/898) +- 2024-01-06T15:23:55+01:00 fix: invisible selection on vim + emacs mode by @froos in: [#889](https://codeberg.org/uzu/strudel/pulls/889) +- 2024-01-05T22:29:20+01:00 scales can now be anchored by @froos in: [#888](https://codeberg.org/uzu/strudel/pulls/888) +- 2024-01-04T11:21:42+01:00 add root mode for voicings by @froos in: [#887](https://codeberg.org/uzu/strudel/pulls/887) +- 2024-01-01T18:32:17+01:00 Showcase by @froos in: [#885](https://codeberg.org/uzu/strudel/pulls/885) +- 2024-01-01T14:22:11+01:00 bugfix: suspend and close exisiting audio context when changing interface by @Ghost in: [#882](https://codeberg.org/uzu/strudel/pulls/882) +- 2024-01-01T14:21:52+01:00 add mastodon link by @froos in: [#884](https://codeberg.org/uzu/strudel/pulls/884) + +## december 2023 + +- 2023-12-31T17:02:57+01:00 fix: make sure n is never undefined before nanFallback by @froos in: [#881](https://codeberg.org/uzu/strudel/pulls/881) +- 2023-12-31T16:47:22+01:00 Error tolerance by @froos in: [#880](https://codeberg.org/uzu/strudel/pulls/880) +- 2023-12-31T10:06:24+01:00 bugfix: sound select indexes out of bounds by @Ghost in: [#871](https://codeberg.org/uzu/strudel/pulls/871) +- 2023-12-31T10:03:01+01:00 Audio device selection by @Ghost in: [#854](https://codeberg.org/uzu/strudel/pulls/854) +- 2023-12-31T00:59:49+01:00 Dependency update by @froos in: [#879](https://codeberg.org/uzu/strudel/pulls/879) +- 2023-12-29T16:53:41+01:00 move all examples to separate examples folder by @froos in: [#878](https://codeberg.org/uzu/strudel/pulls/878) +- 2023-12-29T15:29:17+01:00 final vanillification by @froos in: [#876](https://codeberg.org/uzu/strudel/pulls/876) +- 2023-12-27T18:38:09+01:00 Bug Fix #119: Clock drift by @Ghost in: [#874](https://codeberg.org/uzu/strudel/pulls/874) +- 2023-12-27T13:17:03+01:00 main repl vanillification by @froos in: [#873](https://codeberg.org/uzu/strudel/pulls/873) +- 2023-12-25T17:47:25+01:00 more work on vanilla repl: repl web component + package + MicroRepl by @froos in: [#866](https://codeberg.org/uzu/strudel/pulls/866) +- 2023-12-15T00:11:35+01:00 Vanilla repl 3 by @froos in: [#865](https://codeberg.org/uzu/strudel/pulls/865) +- 2023-12-14T21:28:49+01:00 Vanilla repl 2 by @froos in: [#863](https://codeberg.org/uzu/strudel/pulls/863) +- 2023-12-12T21:49:23+01:00 fix: finally repair envelopes by @froos in: [#861](https://codeberg.org/uzu/strudel/pulls/861) +- 2023-12-12T21:20:00+01:00 add missing trailing slashes by @froos in: [#860](https://codeberg.org/uzu/strudel/pulls/860) +- 2023-12-12T19:08:31+01:00 Sound Import from local file system by @Ghost in: [#839](https://codeberg.org/uzu/strudel/pulls/839) +- 2023-12-11T22:48:35+01:00 Pattern organization by @froos in: [#858](https://codeberg.org/uzu/strudel/pulls/858) +- 2023-12-09T17:25:11+01:00 Export patterns + ui tweaks by @froos in: [#855](https://codeberg.org/uzu/strudel/pulls/855) +- 2023-12-08T09:41:10+01:00 patterns tab: import patterns + style by @froos in: [#852](https://codeberg.org/uzu/strudel/pulls/852) +- 2023-12-07T20:42:00+01:00 Patterns tab + Refactor Panel by @froos in: [#769](https://codeberg.org/uzu/strudel/pulls/769) +- 2023-12-07T10:25:16+01:00 CHANGES: pnpm 8.1.3 to 8.11.0 by @Ghost in: [#850](https://codeberg.org/uzu/strudel/pulls/850) +- 2023-12-07T09:35:14+01:00 Fix edge case with rehype-urls and trailing slashes in image file paths by @Ghost in: [#849](https://codeberg.org/uzu/strudel/pulls/849) +- 2023-12-06T23:02:31+01:00 Fix examples page, piano() and a few workshop imgs by @Ghost in: [#848](https://codeberg.org/uzu/strudel/pulls/848) +- 2023-12-06T22:30:59+01:00 fix: swatch png src by @froos in: [#846](https://codeberg.org/uzu/strudel/pulls/846) +- 2023-12-06T22:19:30+01:00 fix: missing hash for links starting with / by @froos in: [#845](https://codeberg.org/uzu/strudel/pulls/845) +- 2023-12-06T22:05:05+01:00 improve slashing + base href behavior by @froos in: [#842](https://codeberg.org/uzu/strudel/pulls/842) +- 2023-12-06T21:26:49+01:00 Add in fixes from my fork to slashocalypse branch by @Ghost in: [#843](https://codeberg.org/uzu/strudel/pulls/843) +- 2023-12-05T18:43:58+01:00 Prevent 404 on Algolia crawls by @Ghost in: [#838](https://codeberg.org/uzu/strudel/pulls/838) +- 2023-12-05T18:26:47+01:00 Multichannel audio by @Ghost in: [#820](https://codeberg.org/uzu/strudel/pulls/820) +- 2023-12-05T12:19:58+01:00 ADDS: JetBrains IDE files and directories to .gitignore by @Ghost in: [#840](https://codeberg.org/uzu/strudel/pulls/840) +- 2023-12-05T12:19:27+01:00 CHANGES: github action pnpm version from 7 to 8.3.1 by @Ghost in: [#835](https://codeberg.org/uzu/strudel/pulls/835) +- 2023-12-05T12:19:17+01:00 CHANGES: pin pnpm to version 8.3.1 by @Ghost in: [#834](https://codeberg.org/uzu/strudel/pulls/834) +- 2023-12-05T12:19:06+01:00 CHANGES: github action checkout v2 -> v4 by @Ghost in: [#837](https://codeberg.org/uzu/strudel/pulls/837) +- 2023-12-05T11:15:14+01:00 FIXES: palindrome abc -> abccba by @Ghost in: [#831](https://codeberg.org/uzu/strudel/pulls/831) +- 2023-12-02T09:43:50+01:00 Fix a typo by @Ghost in: [#830](https://codeberg.org/uzu/strudel/pulls/830) + +## november 2023 + +- 2023-11-30T10:42:41+01:00 Add and style algolia search by @Ghost in: [#827](https://codeberg.org/uzu/strudel/pulls/827) +- 2023-11-25T15:50:44+01:00 Hydra fixes and improvements by @Ghost in: [#818](https://codeberg.org/uzu/strudel/pulls/818) +- 2023-11-24T10:07:17+01:00 add options param to initHydra by @Ghost in: [#808](https://codeberg.org/uzu/strudel/pulls/808) +- 2023-11-24T09:57:02+01:00 Improve documentation for synonym functions by @Ghost in: [#800](https://codeberg.org/uzu/strudel/pulls/800) +- 2023-11-24T09:00:54+01:00 New noise type: "crackle" by @Ghost in: [#806](https://codeberg.org/uzu/strudel/pulls/806) +- 2023-11-18T21:18:16+01:00 Color hsl by @froos in: [#815](https://codeberg.org/uzu/strudel/pulls/815) +- 2023-11-17T23:18:23+01:00 fix: multiple repls by @froos in: [#813](https://codeberg.org/uzu/strudel/pulls/813) +- 2023-11-17T20:52:25+01:00 upstream changes by @yaxu in: [#809](https://codeberg.org/uzu/strudel/pulls/809) +- 2023-11-17T16:04:10+01:00 Add doc for euclidLegatoRot, wordfall and slider by @Ghost in: [#801](https://codeberg.org/uzu/strudel/pulls/801) +- 2023-11-17T14:42:58+01:00 add option to disable active line highlighting in Code Settings by @Ghost in: [#804](https://codeberg.org/uzu/strudel/pulls/804) +- 2023-11-17T14:37:52+01:00 tidal style d1 ... d9 functions + more by @froos in: [#805](https://codeberg.org/uzu/strudel/pulls/805) +- 2023-11-15T20:12:23+01:00 remove unwanted cm6 outline for strudelTheme by @Ghost in: [#802](https://codeberg.org/uzu/strudel/pulls/802) +- 2023-11-13T23:30:15+01:00 Create phaser effect by @Ghost in: [#798](https://codeberg.org/uzu/strudel/pulls/798) +- 2023-11-10T12:17:35+01:00 support multiple named serial connections, change default baudrate by @yaxu in: [#551](https://codeberg.org/uzu/strudel/pulls/551) +- 2023-11-09T09:27:57+01:00 Fix for #1. Enables named instruments for csoundm. by @gogins in: [#662](https://codeberg.org/uzu/strudel/pulls/662) +- 2023-11-09T09:26:45+01:00 Adding vibrato to Superdough sampler by @Ghost in: [#706](https://codeberg.org/uzu/strudel/pulls/706) +- 2023-11-09T08:46:03+01:00 Document pianoroll by @Ghost in: [#784](https://codeberg.org/uzu/strudel/pulls/784) +- 2023-11-09T08:45:29+01:00 Update first-effects.mdx by @Ghost in: [#795](https://codeberg.org/uzu/strudel/pulls/795) +- 2023-11-09T08:44:43+01:00 Update pattern-effects.mdx by @Ghost in: [#796](https://codeberg.org/uzu/strudel/pulls/796) +- 2023-11-09T08:43:50+01:00 Update recap.mdx by @Ghost in: [#797](https://codeberg.org/uzu/strudel/pulls/797) +- 2023-11-07T11:53:49+01:00 Update first-sounds.mdx by @Ghost in: [#794](https://codeberg.org/uzu/strudel/pulls/794) +- 2023-11-06T23:17:32+01:00 don't use anchor links for reference by @froos in: [#791](https://codeberg.org/uzu/strudel/pulls/791) +- 2023-11-06T22:40:44+01:00 samples loading shortcuts: by @froos in: [#788](https://codeberg.org/uzu/strudel/pulls/788) +- 2023-11-05T22:47:48+01:00 Fix scope pos + document by @froos in: [#786](https://codeberg.org/uzu/strudel/pulls/786) +- 2023-11-05T22:10:25+01:00 Implement optional hover tooltip with function documentation by @Ghost in: [#783](https://codeberg.org/uzu/strudel/pulls/783) +- 2023-11-05T16:46:06+01:00 Add function params in reference tab by @Ghost in: [#785](https://codeberg.org/uzu/strudel/pulls/785) +- 2023-11-05T12:42:00+01:00 fix: style issues by @froos in: [#781](https://codeberg.org/uzu/strudel/pulls/781) +- 2023-11-05T12:21:28+01:00 add xfade by @froos in: [#780](https://codeberg.org/uzu/strudel/pulls/780) +- 2023-11-02T09:30:26+01:00 Update to Astro 3 by @froos in: [#775](https://codeberg.org/uzu/strudel/pulls/775) +- 2023-11-02T08:57:14+01:00 add vscode bindings by @Ghost in: [#773](https://codeberg.org/uzu/strudel/pulls/773) +- 2023-11-02T08:31:30+01:00 fix: share copy to clipboard + alert by @froos in: [#774](https://codeberg.org/uzu/strudel/pulls/774) +- 2023-11-01T22:22:34+01:00 Fix chunk, add fastChunk and repeatCycles by @yaxu in: [#712](https://codeberg.org/uzu/strudel/pulls/712) +- 2023-11-01T22:12:49+01:00 Document adsr function by @Ghost in: [#767](https://codeberg.org/uzu/strudel/pulls/767) +- 2023-11-01T22:11:49+01:00 Add pick and squeeze functions by @Ghost in: [#771](https://codeberg.org/uzu/strudel/pulls/771) +- 2023-11-01T22:04:00+01:00 Update vite pwa by @froos in: [#772](https://codeberg.org/uzu/strudel/pulls/772) + +## october 2023 + +- 2023-10-28T23:55:07+02:00 replace strudel.tidalcycles.org with strudel.cc by @froos in: [#768](https://codeberg.org/uzu/strudel/pulls/768) +- 2023-10-28T12:52:37+02:00 Document striate function by @Ghost in: [#766](https://codeberg.org/uzu/strudel/pulls/766) +- 2023-10-27T23:06:20+02:00 fix zen mode logo overlap by @froos in: [#760](https://codeberg.org/uzu/strudel/pulls/760) +- 2023-10-27T23:01:18+02:00 fix: scale offset by @froos in: [#764](https://codeberg.org/uzu/strudel/pulls/764) +- 2023-10-27T21:59:35+02:00 Fix addivite synthesis phases by @froos in: [#762](https://codeberg.org/uzu/strudel/pulls/762) +- 2023-10-26T16:28:16+02:00 Hydra integration by @froos in: [#759](https://codeberg.org/uzu/strudel/pulls/759) +- 2023-10-26T14:14:27+02:00 add play function by @froos in: [#758](https://codeberg.org/uzu/strudel/pulls/758) +- 2023-10-26T13:11:00+02:00 Add shabda shortcut by @Ghost in: [#740](https://codeberg.org/uzu/strudel/pulls/740) +- 2023-10-26T13:07:23+02:00 mini notation: international alphabets support by @Ghost in: [#751](https://codeberg.org/uzu/strudel/pulls/751) +- 2023-10-22T23:00:52+02:00 hopefully fix trainling slashes bug by @froos in: [#753](https://codeberg.org/uzu/strudel/pulls/753) +- 2023-10-21T23:38:50+02:00 Fix krill build command in README by @Ghost in: [#748](https://codeberg.org/uzu/strudel/pulls/748) +- 2023-10-21T00:20:50+02:00 completely revert config mess by @froos in: [#745](https://codeberg.org/uzu/strudel/pulls/745) +- 2023-10-20T22:41:39+02:00 fix: try different trailing slash behavior by @froos in: [#744](https://codeberg.org/uzu/strudel/pulls/744) +- 2023-10-20T22:34:03+02:00 fix: trailing slash confusion by @froos in: [#743](https://codeberg.org/uzu/strudel/pulls/743) +- 2023-10-20T12:07:04+02:00 [Bug Fix] chooseWith: prevent pattern from stopping audio when selection is >= 1 or < 0 by @Ghost in: [#741](https://codeberg.org/uzu/strudel/pulls/741) +- 2023-10-20T11:34:26+02:00 Recipes by @froos in: [#742](https://codeberg.org/uzu/strudel/pulls/742) +- 2023-10-13T12:57:24+02:00 vite-vanilla-repl readme fix by @froos in: [#737](https://codeberg.org/uzu/strudel/pulls/737) +- 2023-10-10T00:17:59+02:00 Add support for using samples as impulse response buffers for the reverb by @Ghost in: [#717](https://codeberg.org/uzu/strudel/pulls/717) +- 2023-10-09T21:43:42+02:00 fix: reverb sampleRate by @froos in: [#732](https://codeberg.org/uzu/strudel/pulls/732) +- 2023-10-09T21:34:33+02:00 fix: reverb roomsize not required by @froos in: [#731](https://codeberg.org/uzu/strudel/pulls/731) +- 2023-10-08T13:54:52+02:00 Compressor by @froos in: [#729](https://codeberg.org/uzu/strudel/pulls/729) +- 2023-10-08T13:25:57+02:00 fix: hashes in urls by @froos in: [#728](https://codeberg.org/uzu/strudel/pulls/728) +- 2023-10-07T15:48:06+02:00 consume n with scale by @froos in: [#727](https://codeberg.org/uzu/strudel/pulls/727) +- 2023-10-07T00:27:21+02:00 fix: reverb regenerate loophole by @froos in: [#726](https://codeberg.org/uzu/strudel/pulls/726) +- 2023-10-05T00:04:17+02:00 Better convolution reverb by generating impulse responses by @Ghost in: [#718](https://codeberg.org/uzu/strudel/pulls/718) +- 2023-10-04T10:31:53+02:00 Slider afterthoughts by @froos in: [#723](https://codeberg.org/uzu/strudel/pulls/723) +- 2023-10-03T16:39:06+02:00 Add 'white', 'pink' and 'brown' oscillators + refactor synth by @Ghost in: [#713](https://codeberg.org/uzu/strudel/pulls/713) +- 2023-10-01T14:20:50+02:00 support mininotation '..' range operator, fixes #715 by @yaxu in: [#716](https://codeberg.org/uzu/strudel/pulls/716) +- 2023-10-01T14:15:09+02:00 widgets by @froos in: [#714](https://codeberg.org/uzu/strudel/pulls/714) + +## september 2023 + +- 2023-09-28T11:03:09+02:00 Midi in by @froos in: [#699](https://codeberg.org/uzu/strudel/pulls/699) +- 2023-09-27T22:53:49+02:00 add midi clock support by @froos in: [#710](https://codeberg.org/uzu/strudel/pulls/710) +- 2023-09-25T23:36:10+02:00 Update bournemouth.mdx by @Ghost in: [#708](https://codeberg.org/uzu/strudel/pulls/708) +- 2023-09-25T23:06:30+02:00 add dough function for raw dsp by @froos in: [#707](https://codeberg.org/uzu/strudel/pulls/707) +- 2023-09-17T15:53:27+02:00 Update tauri.yml workflow file by @Ghost in: [#705](https://codeberg.org/uzu/strudel/pulls/705) +- 2023-09-17T11:05:06+02:00 Adding vibrato to base oscillators by @Ghost in: [#693](https://codeberg.org/uzu/strudel/pulls/693) +- 2023-09-17T08:27:36+02:00 Adding loop points and thus wavetable synthesis by @Ghost in: [#698](https://codeberg.org/uzu/strudel/pulls/698) +- 2023-09-16T02:03:49+02:00 Adding filter envelopes and filter order selection by @Ghost in: [#692](https://codeberg.org/uzu/strudel/pulls/692) +- 2023-09-09T11:19:58+02:00 Add logging from tauri by @Ghost in: [#697](https://codeberg.org/uzu/strudel/pulls/697) +- 2023-09-05T00:33:54+02:00 Direct OSC Support in Tauri by @Ghost in: [#694](https://codeberg.org/uzu/strudel/pulls/694) +- 2023-09-03T22:19:22+02:00 fix MIDI CC messages by @Ghost in: [#690](https://codeberg.org/uzu/strudel/pulls/690) +- 2023-09-03T10:22:15+02:00 add sleep timer + improve message iterating by @Ghost in: [#688](https://codeberg.org/uzu/strudel/pulls/688) + +## august 2023 + +- 2023-08-31T13:00:31+02:00 ZZFX Synth support by @Ghost in: [#684](https://codeberg.org/uzu/strudel/pulls/684) +- 2023-08-31T12:36:37+02:00 Wave Selection and Global Envelope on the FM Synth Modulator by @Ghost in: [#683](https://codeberg.org/uzu/strudel/pulls/683) +- 2023-08-31T05:52:17+02:00 Create Midi Integration for Tauri Desktop app by @Ghost in: [#685](https://codeberg.org/uzu/strudel/pulls/685) +- 2023-08-31T05:32:16+02:00 teletext theme + fonts by @froos in: [#681](https://codeberg.org/uzu/strudel/pulls/681) +- 2023-08-31T05:20:55+02:00 control osc partial count with n by @froos in: [#674](https://codeberg.org/uzu/strudel/pulls/674) +- 2023-08-27T22:18:06+02:00 add emoji support by @froos in: [#680](https://codeberg.org/uzu/strudel/pulls/680) +- 2023-08-27T16:12:32+02:00 Pianoroll improvements by @froos in: [#679](https://codeberg.org/uzu/strudel/pulls/679) +- 2023-08-26T21:22:17+02:00 Scope by @froos in: [#677](https://codeberg.org/uzu/strudel/pulls/677) +- 2023-08-23T21:50:50+02:00 Midi time fixes by @Ghost in: [#668](https://codeberg.org/uzu/strudel/pulls/668) +- 2023-08-20T23:19:08+02:00 basic fm by @froos in: [#669](https://codeberg.org/uzu/strudel/pulls/669) +- 2023-08-18T23:56:20+02:00 togglable panel position by @froos in: [#667](https://codeberg.org/uzu/strudel/pulls/667) +- 2023-08-18T15:59:20+02:00 fix osc bundle timestamp glitches caused by drifting clock by @Ghost in: [#666](https://codeberg.org/uzu/strudel/pulls/666) +- 2023-08-17T11:36:07+02:00 superdough: encapsulates web audio output by @froos in: [#664](https://codeberg.org/uzu/strudel/pulls/664) +- 2023-08-11T00:06:21+02:00 fix: always run previous trigger by @froos in: [#660](https://codeberg.org/uzu/strudel/pulls/660) +- 2023-08-10T23:54:58+02:00 fix: welcome message for latestCode by @froos in: [#659](https://codeberg.org/uzu/strudel/pulls/659) +- 2023-08-07T07:45:26+02:00 [Bug Fix] Midi: Don't treat note 0 as false by @Ghost in: [#657](https://codeberg.org/uzu/strudel/pulls/657) + +## july 2023 + +- 2023-07-29T09:06:18+02:00 [Bug Fix] Account for numeral notation when converting to midi by @Ghost in: [#656](https://codeberg.org/uzu/strudel/pulls/656) +- 2023-07-23T22:18:49+02:00 ireal voicings by @froos in: [#653](https://codeberg.org/uzu/strudel/pulls/653) +- 2023-07-22T09:30:21+02:00 Understand pitch by @froos in: [#652](https://codeberg.org/uzu/strudel/pulls/652) +- 2023-07-17T23:42:59+02:00 update vitest by @froos in: [#651](https://codeberg.org/uzu/strudel/pulls/651) +- 2023-07-17T23:34:33+02:00 stateless voicings + tonleiter lib by @froos in: [#647](https://codeberg.org/uzu/strudel/pulls/647) +- 2023-07-17T17:57:17+02:00 FIXES: TODO in rotateChroma by @Ghost in: [#650](https://codeberg.org/uzu/strudel/pulls/650) +- 2023-07-10T19:07:45+02:00 slice: list mode by @froos in: [#645](https://codeberg.org/uzu/strudel/pulls/645) +- 2023-07-04T23:50:30+02:00 Delete old packages by @froos in: [#639](https://codeberg.org/uzu/strudel/pulls/639) +- 2023-07-04T23:38:42+02:00 Adaptive Highlighting by @froos in: [#634](https://codeberg.org/uzu/strudel/pulls/634) +- 2023-07-04T21:55:57+02:00 More work on highlight IDs by @Ghost in: [#636](https://codeberg.org/uzu/strudel/pulls/636) +- 2023-07-04T18:44:48+02:00 snapshot tests: sort haps by part by @froos in: [#637](https://codeberg.org/uzu/strudel/pulls/637) + +## juny 2023 + +- 2023-06-30T22:47:40+02:00 fix: update canvas size on window resize by @froos in: [#631](https://codeberg.org/uzu/strudel/pulls/631) +- 2023-06-30T22:45:41+02:00 fix: out of range error by @froos in: [#630](https://codeberg.org/uzu/strudel/pulls/630) +- 2023-06-30T08:14:40+02:00 fix: midi clock drift by @froos in: [#627](https://codeberg.org/uzu/strudel/pulls/627) +- 2023-06-29T22:02:28+02:00 desktop: play samples from disk by @froos in: [#621](https://codeberg.org/uzu/strudel/pulls/621) +- 2023-06-29T21:59:43+02:00 cps dependent functions by @froos in: [#620](https://codeberg.org/uzu/strudel/pulls/620) +- 2023-06-26T22:45:04+02:00 Fix typo on packages.mdx by @Ghost in: [#520](https://codeberg.org/uzu/strudel/pulls/520) +- 2023-06-26T22:35:02+02:00 patterning ui settings by @froos in: [#606](https://codeberg.org/uzu/strudel/pulls/606) +- 2023-06-26T22:32:46+02:00 add spiral viz by @froos in: [#614](https://codeberg.org/uzu/strudel/pulls/614) +- 2023-06-26T22:17:46+02:00 tauri desktop app by @Ghost in: [#613](https://codeberg.org/uzu/strudel/pulls/613) +- 2023-06-23T09:59:43+02:00 fix: doc links by @froos in: [#612](https://codeberg.org/uzu/strudel/pulls/612) +- 2023-06-23T09:55:18+02:00 clip now works like legato in tidal by @froos in: [#598](https://codeberg.org/uzu/strudel/pulls/598) +- 2023-06-18T10:36:19+02:00 fix: flatten scale lists by @froos in: [#605](https://codeberg.org/uzu/strudel/pulls/605) +- 2023-06-18T10:36:17+02:00 tonal fixes by @froos in: [#607](https://codeberg.org/uzu/strudel/pulls/607) +- 2023-06-15T12:51:40+02:00 editor: enable line wrapping by @Ghost in: [#581](https://codeberg.org/uzu/strudel/pulls/581) +- 2023-06-15T10:22:46+02:00 add ratio function by @froos in: [#602](https://codeberg.org/uzu/strudel/pulls/602) +- 2023-06-12T23:24:29+02:00 enable auto-completion by @Ghost in: [#588](https://codeberg.org/uzu/strudel/pulls/588) +- 2023-06-11T22:04:17+02:00 improve cursor by @froos in: [#597](https://codeberg.org/uzu/strudel/pulls/597) +- 2023-06-11T20:00:45+02:00 Solmization added by @Ghost in: [#570](https://codeberg.org/uzu/strudel/pulls/570) +- 2023-06-11T19:45:00+02:00 fix: division by zero by @froos in: [#591](https://codeberg.org/uzu/strudel/pulls/591) +- 2023-06-11T19:44:43+02:00 fix: allow f for flat notes like tidal by @froos in: [#593](https://codeberg.org/uzu/strudel/pulls/593) +- 2023-06-11T19:44:41+02:00 Fix option dot by @froos in: [#596](https://codeberg.org/uzu/strudel/pulls/596) +- 2023-06-09T21:02:12+02:00 New Workshop by @froos in: [#587](https://codeberg.org/uzu/strudel/pulls/587) +- 2023-06-09T17:31:58+02:00 Music metadata by @Ghost in: [#580](https://codeberg.org/uzu/strudel/pulls/580) +- 2023-06-08T09:33:34+02:00 learn/tonal: fix typo in "scaleTran[s]pose" by @Ghost in: [#585](https://codeberg.org/uzu/strudel/pulls/585) +- 2023-06-07T20:19:11+02:00 repl: add option to display line numbers by @Ghost in: [#582](https://codeberg.org/uzu/strudel/pulls/582) +- 2023-06-05T21:35:49+02:00 Vanilla JS Refactoring by @froos in: [#563](https://codeberg.org/uzu/strudel/pulls/563) + +## may 2023 + +- 2023-05-05T08:34:37+02:00 Patchday by @froos in: [#559](https://codeberg.org/uzu/strudel/pulls/559) +- 2023-05-02T21:48:33+02:00 add basic triads and guidetone voicings by @froos in: [#557](https://codeberg.org/uzu/strudel/pulls/557) + +## april 2023 + +- 2023-04-28T12:46:56+02:00 fix: make soundfonts import dynamic by @froos in: [#556](https://codeberg.org/uzu/strudel/pulls/556) +- 2023-04-22T15:43:22+02:00 fix: colorable highlighting by @froos in: [#553](https://codeberg.org/uzu/strudel/pulls/553) +- 2023-04-06T00:11:02+02:00 fix: load soundfonts in prebake by @froos in: [#550](https://codeberg.org/uzu/strudel/pulls/550) + +## march 2023 + +- 2023-03-29T22:23:07+02:00 fix: reset time on stop by @froos in: [#548](https://codeberg.org/uzu/strudel/pulls/548) +- 2023-03-29T22:16:38+02:00 fix: allow whitespace at the end of a mini pattern by @froos in: [#547](https://codeberg.org/uzu/strudel/pulls/547) +- 2023-03-24T22:10:56+01:00 add firacode font by @froos in: [#544](https://codeberg.org/uzu/strudel/pulls/544) +- 2023-03-24T12:54:20+01:00 feat: add loader bar to animate loading state by @froos in: [#542](https://codeberg.org/uzu/strudel/pulls/542) +- 2023-03-23T22:39:27+01:00 do not reset cps before eval #517 by @froos in: [#539](https://codeberg.org/uzu/strudel/pulls/539) +- 2023-03-23T22:27:31+01:00 improve initial loading + wait before eval by @froos in: [#538](https://codeberg.org/uzu/strudel/pulls/538) +- 2023-03-23T21:40:20+01:00 fix period key for dvorak + remove duplicated code by @froos in: [#537](https://codeberg.org/uzu/strudel/pulls/537) +- 2023-03-23T11:44:56+01:00 Update lerna by @froos in: [#535](https://codeberg.org/uzu/strudel/pulls/535) +- 2023-03-23T10:21:55+01:00 feat: add freq support to gm soundfonts by @froos in: [#534](https://codeberg.org/uzu/strudel/pulls/534) +- 2023-03-23T10:18:58+01:00 Maintain random seed state in parser, not globally by @Ghost in: [#531](https://codeberg.org/uzu/strudel/pulls/531) +- 2023-03-21T22:35:18+01:00 FIXES: alias pm for polymeter by @Ghost in: [#527](https://codeberg.org/uzu/strudel/pulls/527) +- 2023-03-21T22:29:07+01:00 fix(footer): fix link to tidalcycles by @julienbouquillon in: [#529](https://codeberg.org/uzu/strudel/pulls/529) +- 2023-03-18T20:25:21+01:00 Update intro.mdx by @Ghost in: [#525](https://codeberg.org/uzu/strudel/pulls/525) +- 2023-03-18T20:24:38+01:00 Update samples.mdx by @Ghost in: [#524](https://codeberg.org/uzu/strudel/pulls/524) +- 2023-03-17T09:03:11+01:00 fix: envelopes in chrome by @froos in: [#521](https://codeberg.org/uzu/strudel/pulls/521) +- 2023-03-16T16:13:30+01:00 registerSound API + improved sounds tab + regroup soundfonts by @froos in: [#516](https://codeberg.org/uzu/strudel/pulls/516) +- 2023-03-14T21:54:53+01:00 add 2 illegible fonts by @froos in: [#518](https://codeberg.org/uzu/strudel/pulls/518) +- 2023-03-06T22:55:00+01:00 Update README.md by @Ghost in: [#474](https://codeberg.org/uzu/strudel/pulls/474) +- 2023-03-05T14:52:30+01:00 add arrange function by @froos in: [#508](https://codeberg.org/uzu/strudel/pulls/508) +- 2023-03-05T14:29:08+01:00 update react to 18 by @froos in: [#514](https://codeberg.org/uzu/strudel/pulls/514) +- 2023-03-04T19:06:18+01:00 Support list syntax in mininotation by @yaxu in: [#512](https://codeberg.org/uzu/strudel/pulls/512) +- 2023-03-03T12:40:23+01:00 can now use : as a replacement for space in scales by @froos in: [#502](https://codeberg.org/uzu/strudel/pulls/502) +- 2023-03-02T15:44:41+01:00 Reinstate slice and splice by @yaxu in: [#500](https://codeberg.org/uzu/strudel/pulls/500) +- 2023-03-02T14:49:30+01:00 fix: nano-repl highlighting by @froos in: [#501](https://codeberg.org/uzu/strudel/pulls/501) +- 2023-03-02T14:17:13+01:00 Add control aliases by @yaxu in: [#497](https://codeberg.org/uzu/strudel/pulls/497) +- 2023-03-01T09:27:27+01:00 implement cps in scheduler by @froos in: [#493](https://codeberg.org/uzu/strudel/pulls/493) + +## february 2023 + +- 2023-02-28T23:06:29+01:00 react style fixes by @froos in: [#491](https://codeberg.org/uzu/strudel/pulls/491) +- 2023-02-28T22:57:20+01:00 refactor react package by @froos in: [#490](https://codeberg.org/uzu/strudel/pulls/490) +- 2023-02-27T23:47:34+01:00 add algolia creds + optimize sidebar for crawling by @froos in: [#488](https://codeberg.org/uzu/strudel/pulls/488) +- 2023-02-27T19:28:10+01:00 fix app height by @froos in: [#485](https://codeberg.org/uzu/strudel/pulls/485) +- 2023-02-27T16:04:21+01:00 Revert "Another attempt at composable functions - WIP (#390)" by @froos in: [#484](https://codeberg.org/uzu/strudel/pulls/484) +- 2023-02-27T15:45:20+01:00 Update mini-notation.mdx by @yaxu in: [#365](https://codeberg.org/uzu/strudel/pulls/365) +- 2023-02-27T12:52:09+01:00 docs: packages + offline by @froos in: [#482](https://codeberg.org/uzu/strudel/pulls/482) +- 2023-02-25T14:26:49+01:00 Fix array args by @froos in: [#480](https://codeberg.org/uzu/strudel/pulls/480) +- 2023-02-25T12:33:22+01:00 midi cc support by @froos in: [#478](https://codeberg.org/uzu/strudel/pulls/478) +- 2023-02-23T00:11:05+01:00 fix: hash links by @froos in: [#473](https://codeberg.org/uzu/strudel/pulls/473) +- 2023-02-22T22:54:39+01:00 settings tab with vim / emacs modes + additional themes and fonts by @froos in: [#467](https://codeberg.org/uzu/strudel/pulls/467) +- 2023-02-22T22:53:22+01:00 Update input-output.mdx by @Ghost in: [#471](https://codeberg.org/uzu/strudel/pulls/471) +- 2023-02-22T22:52:50+01:00 FIXES: freqs instead of pitches by @Ghost in: [#464](https://codeberg.org/uzu/strudel/pulls/464) +- 2023-02-22T20:01:00+01:00 fix: osc should not return a promise by @froos in: [#472](https://codeberg.org/uzu/strudel/pulls/472) +- 2023-02-22T12:51:31+01:00 slice and splice by @yaxu in: [#466](https://codeberg.org/uzu/strudel/pulls/466) +- 2023-02-18T01:00:19+01:00 weave and weaveWith by @yaxu in: [#465](https://codeberg.org/uzu/strudel/pulls/465) +- 2023-02-17T00:15:21+01:00 Composable functions by @yaxu in: [#390](https://codeberg.org/uzu/strudel/pulls/390) +- 2023-02-16T20:38:45+01:00 FIXES: Warning about jsxBracketSameLine deprecation by @Ghost in: [#461](https://codeberg.org/uzu/strudel/pulls/461) +- 2023-02-14T22:11:02+01:00 Update synths.mdx by @Ghost in: [#438](https://codeberg.org/uzu/strudel/pulls/438) +- 2023-02-14T19:54:42+01:00 Update mini-notation.mdx by @Ghost in: [#437](https://codeberg.org/uzu/strudel/pulls/437) +- 2023-02-14T19:54:25+01:00 Update code.mdx by @Ghost in: [#436](https://codeberg.org/uzu/strudel/pulls/436) +- 2023-02-13T00:43:29+01:00 Fix anchors by @froos in: [#433](https://codeberg.org/uzu/strudel/pulls/433) +- 2023-02-11T21:02:11+01:00 autocomplete preparations by @froos in: [#427](https://codeberg.org/uzu/strudel/pulls/427) +- 2023-02-10T23:14:48+01:00 Themes by @froos in: [#431](https://codeberg.org/uzu/strudel/pulls/431) +- 2023-02-09T19:22:56+01:00 minirepl: add keyboard shortcuts by @froos in: [#429](https://codeberg.org/uzu/strudel/pulls/429) +- 2023-02-09T08:56:42+01:00 add cdn.freesound to cache list by @froos in: [#425](https://codeberg.org/uzu/strudel/pulls/425) +- 2023-02-08T20:36:00+01:00 add more offline caching by @froos in: [#421](https://codeberg.org/uzu/strudel/pulls/421) +- 2023-02-07T22:08:01+01:00 add caching strategy for missing file types + cache all samples loaded from github by @froos in: [#419](https://codeberg.org/uzu/strudel/pulls/419) +- 2023-02-06T23:29:57+01:00 PWA with offline support by @froos in: [#417](https://codeberg.org/uzu/strudel/pulls/417) +- 2023-02-05T17:27:32+01:00 improve samples doc by @froos in: [#411](https://codeberg.org/uzu/strudel/pulls/411) +- 2023-02-05T17:27:10+01:00 google gtfo by @froos in: [#413](https://codeberg.org/uzu/strudel/pulls/413) +- 2023-02-05T16:27:59+01:00 improve effects doc by @froos in: [#409](https://codeberg.org/uzu/strudel/pulls/409) +- 2023-02-05T14:56:03+01:00 Update effects.mdx by @Ghost in: [#410](https://codeberg.org/uzu/strudel/pulls/410) +- 2023-02-03T19:54:47+01:00 add shabda doc by @froos in: [#407](https://codeberg.org/uzu/strudel/pulls/407) +- 2023-02-02T21:45:56+01:00 fix: share url on subpath by @froos in: [#405](https://codeberg.org/uzu/strudel/pulls/405) +- 2023-02-02T21:35:45+01:00 update csound + fix sound output by @froos in: [#404](https://codeberg.org/uzu/strudel/pulls/404) +- 2023-02-02T19:53:36+01:00 pin @csound/browser to 6.18.3 + bump by @froos in: [#403](https://codeberg.org/uzu/strudel/pulls/403) +- 2023-02-01T22:47:27+01:00 release webaudio by @froos in: [#400](https://codeberg.org/uzu/strudel/pulls/400) +- 2023-02-01T22:45:16+01:00 can now await initAudio + initAudioOnFirstClick by @froos in: [#399](https://codeberg.org/uzu/strudel/pulls/399) +- 2023-02-01T22:32:18+01:00 fix: minirepl styles by @froos in: [#398](https://codeberg.org/uzu/strudel/pulls/398) +- 2023-02-01T22:17:19+01:00 proper builds + use pnpm workspaces by @froos in: [#396](https://codeberg.org/uzu/strudel/pulls/396) +- 2023-02-01T16:49:55+01:00 add pattern methods hurry, press and pressBy by @yaxu in: [#397](https://codeberg.org/uzu/strudel/pulls/397) + +## january 2023 + +- 2023-01-27T14:40:40+01:00 Add tidal-drum-patterns to examples by @Ghost in: [#379](https://codeberg.org/uzu/strudel/pulls/379) +- 2023-01-27T14:39:56+01:00 add ribbon + test + docs by @froos in: [#388](https://codeberg.org/uzu/strudel/pulls/388) +- 2023-01-27T12:10:37+01:00 Notes are not essential :) by @yaxu in: [#393](https://codeberg.org/uzu/strudel/pulls/393) +- 2023-01-21T17:18:13+01:00 document csound by @froos in: [#391](https://codeberg.org/uzu/strudel/pulls/391) +- 2023-01-19T12:04:51+01:00 Rename a to angle by @froos in: [#387](https://codeberg.org/uzu/strudel/pulls/387) +- 2023-01-19T11:49:39+01:00 add run + test + docs by @froos in: [#386](https://codeberg.org/uzu/strudel/pulls/386) +- 2023-01-19T11:32:56+01:00 docs: use note instead of n to mitigate confusion by @froos in: [#385](https://codeberg.org/uzu/strudel/pulls/385) +- 2023-01-18T17:10:09+01:00 update my-patterns instructions by @froos in: [#384](https://codeberg.org/uzu/strudel/pulls/384) +- 2023-01-15T23:19:43+01:00 Draw fixes by @froos in: [#377](https://codeberg.org/uzu/strudel/pulls/377) +- 2023-01-14T11:07:08+01:00 improve new draw logic by @froos in: [#372](https://codeberg.org/uzu/strudel/pulls/372) +- 2023-01-12T14:40:59+01:00 document more functions + change arp join by @froos in: [#369](https://codeberg.org/uzu/strudel/pulls/369) +- 2023-01-10T00:03:47+01:00 add https to url by @Ghost in: [#364](https://codeberg.org/uzu/strudel/pulls/364) +- 2023-01-09T23:37:34+01:00 doc structuring by @froos in: [#360](https://codeberg.org/uzu/strudel/pulls/360) +- 2023-01-09T23:23:28+01:00 Support for multiple mininotation operators by @yaxu in: [#350](https://codeberg.org/uzu/strudel/pulls/350) +- 2023-01-09T00:40:15+01:00 Fix .out(), renaming webaudio's out() to webaudio() by @yaxu in: [#361](https://codeberg.org/uzu/strudel/pulls/361) +- 2023-01-06T22:02:31+01:00 docs: tidal comparison + add global fx + add missing sampler fx by @froos in: [#356](https://codeberg.org/uzu/strudel/pulls/356) +- 2023-01-06T12:31:32+01:00 Fix Bjorklund by @yaxu in: [#343](https://codeberg.org/uzu/strudel/pulls/343) +- 2023-01-04T20:29:35+01:00 Fix prebake base path by @froos in: [#345](https://codeberg.org/uzu/strudel/pulls/345) +- 2023-01-04T19:55:49+01:00 fixes #346 by @froos in: [#347](https://codeberg.org/uzu/strudel/pulls/347) +- 2023-01-02T21:28:07+01:00 Patternify euclid, fast, slow and polymeter step parameters in mininotation by @yaxu in: [#341](https://codeberg.org/uzu/strudel/pulls/341) +- 2023-01-02T00:30:16+01:00 more animate functions + mini repl fix by @froos in: [#340](https://codeberg.org/uzu/strudel/pulls/340) +- 2023-01-01T12:34:11+01:00 move /my-patterns to /swatch by @yaxu in: [#338](https://codeberg.org/uzu/strudel/pulls/338) +- 2023-01-01T12:22:55+01:00 animation options by @froos in: [#337](https://codeberg.org/uzu/strudel/pulls/337) + +## december 2022 + +- 2022-12-31T22:42:49+01:00 Tidy parser, implement polymeters by @yaxu in: [#336](https://codeberg.org/uzu/strudel/pulls/336) +- 2022-12-31T16:51:53+01:00 animate mvp by @froos in: [#335](https://codeberg.org/uzu/strudel/pulls/335) +- 2022-12-30T20:16:10+01:00 testing + docs docs by @froos in: [#334](https://codeberg.org/uzu/strudel/pulls/334) +- 2022-12-29T21:11:16+01:00 Embed mode improvements by @froos in: [#333](https://codeberg.org/uzu/strudel/pulls/333) +- 2022-12-29T14:06:20+01:00 fix: can now multiply floats in mini notation by @froos in: [#332](https://codeberg.org/uzu/strudel/pulls/332) +- 2022-12-29T13:50:07+01:00 improve displaying 's' in pianoroll by @froos in: [#331](https://codeberg.org/uzu/strudel/pulls/331) +- 2022-12-28T17:35:35+01:00 my-patterns: fix paths + update readme by @froos in: [#330](https://codeberg.org/uzu/strudel/pulls/330) +- 2022-12-28T17:11:40+01:00 my-patterns build + deploy by @froos in: [#329](https://codeberg.org/uzu/strudel/pulls/329) +- 2022-12-28T15:43:31+01:00 add my-patterns by @froos in: [#328](https://codeberg.org/uzu/strudel/pulls/328) +- 2022-12-28T14:47:14+01:00 add examples route by @froos in: [#327](https://codeberg.org/uzu/strudel/pulls/327) +- 2022-12-28T14:44:31+01:00 fix: workaround Object.assign globalThis by @froos in: [#326](https://codeberg.org/uzu/strudel/pulls/326) +- 2022-12-26T23:29:47+01:00 mini repl improvements by @froos in: [#324](https://codeberg.org/uzu/strudel/pulls/324) +- 2022-12-26T23:00:34+01:00 support notes without octave by @froos in: [#323](https://codeberg.org/uzu/strudel/pulls/323) +- 2022-12-26T12:37:36+01:00 tutorial updates by @Ghost in: [#320](https://codeberg.org/uzu/strudel/pulls/320) +- 2022-12-23T18:26:30+01:00 Reference tab sort by @froos in: [#318](https://codeberg.org/uzu/strudel/pulls/318) +- 2022-12-23T00:49:56+01:00 Astro build by @froos in: [#315](https://codeberg.org/uzu/strudel/pulls/315) +- 2022-12-19T21:02:37+01:00 object support for .scale by @froos in: [#307](https://codeberg.org/uzu/strudel/pulls/307) +- 2022-12-19T21:02:22+01:00 Jsdoc component by @froos in: [#312](https://codeberg.org/uzu/strudel/pulls/312) +- 2022-12-19T20:31:46+01:00 fix: copy share link to clipboard was broken for some browers by @froos in: [#311](https://codeberg.org/uzu/strudel/pulls/311) +- 2022-12-19T12:11:24+01:00 ICLC2023 paper WIP by @yaxu in: [#306](https://codeberg.org/uzu/strudel/pulls/306) +- 2022-12-15T21:23:22+01:00 support freq in pianoroll by @froos in: [#308](https://codeberg.org/uzu/strudel/pulls/308) +- 2022-12-13T22:07:27+01:00 Updated csoundm to use the register facility . by @gogins in: [#303](https://codeberg.org/uzu/strudel/pulls/303) +- 2022-12-13T21:57:03+01:00 add lint + prettier check before test by @froos in: [#305](https://codeberg.org/uzu/strudel/pulls/305) +- 2022-12-13T21:21:44+01:00 add freq support to sampler by @froos in: [#301](https://codeberg.org/uzu/strudel/pulls/301) +- 2022-12-12T00:48:41+01:00 fix whitespace trimming by @froos in: [#300](https://codeberg.org/uzu/strudel/pulls/300) +- 2022-12-12T00:21:53+01:00 .defragmentHaps() for merging touching haps that share a whole and value by @yaxu in: [#299](https://codeberg.org/uzu/strudel/pulls/299) +- 2022-12-11T23:17:49+01:00 remove whitespace from highlighted region by @froos in: [#298](https://codeberg.org/uzu/strudel/pulls/298) +- 2022-12-11T22:03:26+01:00 update vitest by @froos in: [#297](https://codeberg.org/uzu/strudel/pulls/297) +- 2022-12-11T21:44:16+01:00 can now add bare numbers to numeral object props by @froos in: [#287](https://codeberg.org/uzu/strudel/pulls/287) +- 2022-12-11T21:27:58+01:00 Move stuff to new register function by @froos in: [#295](https://codeberg.org/uzu/strudel/pulls/295) +- 2022-12-11T20:07:12+01:00 add prettier task by @froos in: [#296](https://codeberg.org/uzu/strudel/pulls/296) +- 2022-12-10T15:39:04+01:00 Reorganise pattern.mjs with a 'toplevel first' regime by @yaxu in: [#286](https://codeberg.org/uzu/strudel/pulls/286) +- 2022-12-10T11:56:16+01:00 Fancy hap show, include part in snapshots by @yaxu in: [#291](https://codeberg.org/uzu/strudel/pulls/291) +- 2022-12-07T20:07:55+01:00 Switch 'operators' from .whatHow to .what.how by @yaxu in: [#285](https://codeberg.org/uzu/strudel/pulls/285) +- 2022-12-04T12:51:01+01:00 implement collect + arp function by @froos in: [#281](https://codeberg.org/uzu/strudel/pulls/281) +- 2022-12-02T13:38:27+01:00 do not recompile orc by @froos in: [#278](https://codeberg.org/uzu/strudel/pulls/278) +- 2022-12-02T12:49:26+01:00 add basic csound output by @froos in: [#275](https://codeberg.org/uzu/strudel/pulls/275) +- 2022-12-02T12:18:41+01:00 add licenses / credits to all tunes + remove some by @froos in: [#277](https://codeberg.org/uzu/strudel/pulls/277) +- 2022-12-02T11:45:02+01:00 Support sending CRC16 bytes with serial messages by @yaxu in: [#276](https://codeberg.org/uzu/strudel/pulls/276) + +## november 2022 + +- 2022-11-29T23:41:39+01:00 release version bumps by @froos in: [#273](https://codeberg.org/uzu/strudel/pulls/273) +- 2022-11-29T23:34:50+01:00 add eslint by @froos in: [#271](https://codeberg.org/uzu/strudel/pulls/271) +- 2022-11-29T23:34:26+01:00 tonal update with fixed memory leak by @froos in: [#272](https://codeberg.org/uzu/strudel/pulls/272) +- 2022-11-22T09:51:26+01:00 Tidying up core by @yaxu in: [#256](https://codeberg.org/uzu/strudel/pulls/256) +- 2022-11-21T22:15:48+01:00 fix performance bottleneck by @froos in: [#266](https://codeberg.org/uzu/strudel/pulls/266) +- 2022-11-17T11:08:38+01:00 fix tutorial bugs by @froos in: [#263](https://codeberg.org/uzu/strudel/pulls/263) +- 2022-11-16T13:15:28+01:00 Binaries by @froos in: [#254](https://codeberg.org/uzu/strudel/pulls/254) +- 2022-11-13T20:20:32+01:00 Repl refactoring by @froos in: [#255](https://codeberg.org/uzu/strudel/pulls/255) +- 2022-11-08T23:35:11+01:00 Webaudio build by @froos in: [#250](https://codeberg.org/uzu/strudel/pulls/250) +- 2022-11-08T22:43:32+01:00 new transpiler based on acorn by @froos in: [#249](https://codeberg.org/uzu/strudel/pulls/249) +- 2022-11-06T19:03:19+01:00 General purpose scheduler by @froos in: [#248](https://codeberg.org/uzu/strudel/pulls/248) +- 2022-11-06T11:05:23+01:00 snapshot tests on shared snippets by @froos in: [#243](https://codeberg.org/uzu/strudel/pulls/243) +- 2022-11-06T11:03:42+01:00 Some tunes by @froos in: [#247](https://codeberg.org/uzu/strudel/pulls/247) +- 2022-11-06T00:37:11+01:00 patchday by @froos in: [#246](https://codeberg.org/uzu/strudel/pulls/246) +- 2022-11-04T20:28:23+01:00 Readme + TLC by @froos in: [#244](https://codeberg.org/uzu/strudel/pulls/244) +- 2022-11-03T15:16:04+01:00 in source example tests by @froos in: [#242](https://codeberg.org/uzu/strudel/pulls/242) +- 2022-11-02T23:16:43+01:00 feat: support github: links by @froos in: [#240](https://codeberg.org/uzu/strudel/pulls/240) +- 2022-11-02T21:26:44+01:00 Load samples from url by @froos in: [#239](https://codeberg.org/uzu/strudel/pulls/239) +- 2022-11-02T11:42:32+01:00 Object arithmetic by @froos in: [#238](https://codeberg.org/uzu/strudel/pulls/238) +- 2022-11-01T00:36:14+01:00 Tidal drum machines by @froos in: [#237](https://codeberg.org/uzu/strudel/pulls/237) +- 2022-10-31T21:39:23+01:00 fx on stereo speakers by @froos in: [#236](https://codeberg.org/uzu/strudel/pulls/236) + +## october 2022 + +- 2022-10-30T19:10:33+01:00 add vcsl sample library by @froos in: [#235](https://codeberg.org/uzu/strudel/pulls/235) +- 2022-10-30T00:23:10+02:00 Fix zero length queries WIP by @yaxu in: [#234](https://codeberg.org/uzu/strudel/pulls/234) +- 2022-10-29T17:54:05+02:00 Out by default by @froos in: [#232](https://codeberg.org/uzu/strudel/pulls/232) +- 2022-10-26T23:53:49+02:00 Patternify range by @yaxu in: [#231](https://codeberg.org/uzu/strudel/pulls/231) +- 2022-10-26T21:42:12+02:00 Just another docs branch by @froos in: [#228](https://codeberg.org/uzu/strudel/pulls/228) +- 2022-10-26T21:41:12+02:00 Refactor tunes away from tone by @froos in: [#230](https://codeberg.org/uzu/strudel/pulls/230) +- 2022-10-20T09:26:28+02:00 Core util tests by @Ghost in: [#226](https://codeberg.org/uzu/strudel/pulls/226) +- 2022-10-06T22:35:45+02:00 fix fastgap for events that go across cycle boundaries by @yaxu in: [#225](https://codeberg.org/uzu/strudel/pulls/225) + +## september 2022 + +- 2022-09-25T21:15:36+02:00 Reverb by @froos in: [#224](https://codeberg.org/uzu/strudel/pulls/224) +- 2022-09-25T00:27:26+02:00 focus tweak for squeezeJoin - another go at fixing #216 by @yaxu in: [#221](https://codeberg.org/uzu/strudel/pulls/221) +- 2022-09-24T23:17:21+02:00 support negative speeds by @froos in: [#222](https://codeberg.org/uzu/strudel/pulls/222) +- 2022-09-24T21:55:15+02:00 Feedback Delay by @froos in: [#213](https://codeberg.org/uzu/strudel/pulls/213) +- 2022-09-23T12:06:17+02:00 Fix squeeze join by @yaxu in: [#220](https://codeberg.org/uzu/strudel/pulls/220) +- 2022-09-22T21:23:11+02:00 encapsulate webaudio output by @froos in: [#219](https://codeberg.org/uzu/strudel/pulls/219) +- 2022-09-22T19:18:18+02:00 samples now have envelopes by @froos in: [#218](https://codeberg.org/uzu/strudel/pulls/218) +- 2022-09-22T00:03:30+02:00 sampler features + fixes by @froos in: [#217](https://codeberg.org/uzu/strudel/pulls/217) +- 2022-09-19T23:32:55+02:00 Just another docs PR by @froos in: [#215](https://codeberg.org/uzu/strudel/pulls/215) +- 2022-09-17T23:47:43+02:00 Even more docs by @froos in: [#212](https://codeberg.org/uzu/strudel/pulls/212) +- 2022-09-17T15:36:53+02:00 Webaudio guide by @froos in: [#207](https://codeberg.org/uzu/strudel/pulls/207) +- 2022-09-16T00:21:29+02:00 Coarse crush shape by @froos in: [#205](https://codeberg.org/uzu/strudel/pulls/205) +- 2022-09-15T21:04:28+02:00 add vowel to .out by @froos in: [#201](https://codeberg.org/uzu/strudel/pulls/201) +- 2022-09-15T16:55:52+02:00 add rollup-plugin-visualizer to build by @froos in: [#200](https://codeberg.org/uzu/strudel/pulls/200) +- 2022-09-14T23:46:39+02:00 document random functions by @froos in: [#199](https://codeberg.org/uzu/strudel/pulls/199) +- 2022-09-09T22:04:40+02:00 Fix numbers in sampler by @froos in: [#196](https://codeberg.org/uzu/strudel/pulls/196) + +## august 2022 + +- 2022-08-14T17:40:41+02:00 change "stride"/"offset" of successive degradeBy/chooseIn by @Ghost in: [#185](https://codeberg.org/uzu/strudel/pulls/185) +- 2022-08-14T16:04:14+02:00 Soundfont file support by @froos in: [#183](https://codeberg.org/uzu/strudel/pulls/183) +- 2022-08-14T15:55:50+02:00 fix regression: old way of setting frequencies was broken by @froos in: [#190](https://codeberg.org/uzu/strudel/pulls/190) +- 2022-08-14T15:45:53+02:00 wait for prebake to finish before evaluating by @froos in: [#189](https://codeberg.org/uzu/strudel/pulls/189) +- 2022-08-14T11:31:00+02:00 Fix codemirror bug by @froos in: [#186](https://codeberg.org/uzu/strudel/pulls/186) +- 2022-08-13T16:29:53+02:00 scheduler improvements by @froos in: [#181](https://codeberg.org/uzu/strudel/pulls/181) +- 2022-08-12T23:10:36+02:00 replace mocha with vitest by @froos in: [#175](https://codeberg.org/uzu/strudel/pulls/175) +- 2022-08-07T00:59:07+02:00 incorporate elements of randomness to the mini notation by @Ghost in: [#165](https://codeberg.org/uzu/strudel/pulls/165) +- 2022-08-06T23:32:16+02:00 fix some annoying bugs by @froos in: [#177](https://codeberg.org/uzu/strudel/pulls/177) +- 2022-08-06T00:27:17+02:00 Replace react-codemirror6 with @uiw/react-codemirror by @froos in: [#173](https://codeberg.org/uzu/strudel/pulls/173) +- 2022-08-04T22:19:46+02:00 add more shapeshifter flags by @froos in: [#99](https://codeberg.org/uzu/strudel/pulls/99) +- 2022-08-04T22:06:38+02:00 Amend shapeshifter to allow use of dynamic import by @Ghost in: [#171](https://codeberg.org/uzu/strudel/pulls/171) +- 2022-08-02T23:43:07+02:00 Talk fixes by @froos in: [#164](https://codeberg.org/uzu/strudel/pulls/164) +- 2022-08-02T23:04:34+02:00 Pianoroll fixes by @froos in: [#163](https://codeberg.org/uzu/strudel/pulls/163) +- 2022-08-02T22:57:43+02:00 fix: jsdoc comments by @froos in: [#169](https://codeberg.org/uzu/strudel/pulls/169) + +## july 2022 + +- 2022-07-30T23:51:11+02:00 add chooseInWith/chooseCycles by @yaxu in: [#166](https://codeberg.org/uzu/strudel/pulls/166) +- 2022-07-28T19:26:42+02:00 update to tutorial documentation by @Ghost in: [#162](https://codeberg.org/uzu/strudel/pulls/162) +- 2022-07-12T18:58:47+02:00 add webdirt drum samples to prebake for general availability by @Ghost in: [#150](https://codeberg.org/uzu/strudel/pulls/150) +- 2022-07-12T08:34:17+02:00 Final update to demo.pdf by @yaxu in: [#151](https://codeberg.org/uzu/strudel/pulls/151) + +## june 2022 + +- 2022-06-28T21:54:35+02:00 Sampler optimizations and more by @froos in: [#148](https://codeberg.org/uzu/strudel/pulls/148) +- 2022-06-26T12:58:53+02:00 can now generate short link for sharing by @froos in: [#146](https://codeberg.org/uzu/strudel/pulls/146) +- 2022-06-24T22:16:04+02:00 flash effect on ctrl enter by @froos in: [#144](https://codeberg.org/uzu/strudel/pulls/144) +- 2022-06-22T20:18:17+02:00 Pianoroll Object Support by @froos in: [#142](https://codeberg.org/uzu/strudel/pulls/142) +- 2022-06-21T22:57:28+02:00 Serial twiddles by @yaxu in: [#141](https://codeberg.org/uzu/strudel/pulls/141) +- 2022-06-21T22:20:05+02:00 Soundfont Support by @froos in: [#139](https://codeberg.org/uzu/strudel/pulls/139) +- 2022-06-21T14:11:50+02:00 Fix createParam() by @yaxu in: [#140](https://codeberg.org/uzu/strudel/pulls/140) +- 2022-06-18T23:24:42+02:00 Webaudio rewrite by @froos in: [#138](https://codeberg.org/uzu/strudel/pulls/138) +- 2022-06-16T21:58:38+02:00 add onTrigger helper by @froos in: [#136](https://codeberg.org/uzu/strudel/pulls/136) +- 2022-06-16T20:48:23+02:00 Scheduler improvements by @froos in: [#134](https://codeberg.org/uzu/strudel/pulls/134) +- 2022-06-16T20:48:07+02:00 remove cycle + delta from onTrigger by @froos in: [#135](https://codeberg.org/uzu/strudel/pulls/135) +- 2022-06-13T21:28:27+02:00 add createParam + createParams by @froos in: [#110](https://codeberg.org/uzu/strudel/pulls/110) +- 2022-06-13T21:27:09+02:00 Pianoroll enhancements by @froos in: [#131](https://codeberg.org/uzu/strudel/pulls/131) +- 2022-06-05T13:06:03+02:00 Fix link to contributing to tutorial docs by @Ghost in: [#129](https://codeberg.org/uzu/strudel/pulls/129) +- 2022-06-02T00:20:45+02:00 Webdirt by @froos in: [#121](https://codeberg.org/uzu/strudel/pulls/121) +- 2022-06-01T23:56:04+02:00 fix: #122 ctrl enter would add newline by @froos in: [#124](https://codeberg.org/uzu/strudel/pulls/124) +- 2022-06-01T18:21:56+02:00 fix: #108 by @froos in: [#123](https://codeberg.org/uzu/strudel/pulls/123) + +## may 2022 + +- 2022-05-29T09:54:59+02:00 In source doc by @froos in: [#117](https://codeberg.org/uzu/strudel/pulls/117) +- 2022-05-20T11:49:10+02:00 react package + vite build by @froos in: [#116](https://codeberg.org/uzu/strudel/pulls/116) +- 2022-05-15T22:29:26+02:00 Osc timing improvements by @yaxu in: [#113](https://codeberg.org/uzu/strudel/pulls/113) +- 2022-05-15T22:28:24+02:00 loopAt by @yaxu in: [#114](https://codeberg.org/uzu/strudel/pulls/114) +- 2022-05-09T20:09:47+02:00 `.brak()`, `.inside()` and `.outside()` by @yaxu in: [#112](https://codeberg.org/uzu/strudel/pulls/112) +- 2022-05-06T15:18:41+02:00 In source doc by @yaxu in: [#105](https://codeberg.org/uzu/strudel/pulls/105) +- 2022-05-04T22:59:31+02:00 Embed style by @froos in: [#109](https://codeberg.org/uzu/strudel/pulls/109) +- 2022-05-03T01:40:22+02:00 Reset, Restart and other composers by @froos in: [#88](https://codeberg.org/uzu/strudel/pulls/88) +- 2022-05-02T22:47:21+02:00 /embed package: web component for repl by @froos in: [#106](https://codeberg.org/uzu/strudel/pulls/106) +- 2022-05-02T22:05:34+02:00 Tune tests by @froos in: [#104](https://codeberg.org/uzu/strudel/pulls/104) +- 2022-05-01T14:33:11+02:00 Codemirror 6 by @froos in: [#97](https://codeberg.org/uzu/strudel/pulls/97) + +## april 2022 + +- 2022-04-28T23:45:53+02:00 Work on Codemirror 6 highlighting by @Ghost in: [#102](https://codeberg.org/uzu/strudel/pulls/102) +- 2022-04-28T15:46:06+02:00 Change to Affero GPL by @yaxu in: [#101](https://codeberg.org/uzu/strudel/pulls/101) +- 2022-04-26T00:13:07+02:00 Paper by @froos in: [#98](https://codeberg.org/uzu/strudel/pulls/98) +- 2022-04-25T22:10:21+02:00 Fiddles with cat/stack by @yaxu in: [#90](https://codeberg.org/uzu/strudel/pulls/90) +- 2022-04-22T13:08:11+02:00 Add pattern composers, implements #82 by @yaxu in: [#83](https://codeberg.org/uzu/strudel/pulls/83) +- 2022-04-21T20:57:45+02:00 Tidy up a couple of old files by @Ghost in: [#84](https://codeberg.org/uzu/strudel/pulls/84) +- 2022-04-21T15:21:29+02:00 add `striate()` by @yaxu in: [#76](https://codeberg.org/uzu/strudel/pulls/76) +- 2022-04-20T20:17:27+02:00 Webaudio in REPL by @froos in: [#77](https://codeberg.org/uzu/strudel/pulls/77) +- 2022-04-19T15:31:34+02:00 Basic webserial support by @yaxu in: [#80](https://codeberg.org/uzu/strudel/pulls/80) +- 2022-04-17T19:29:49+02:00 Try to fix appLeft / appRight by @yaxu in: [#75](https://codeberg.org/uzu/strudel/pulls/75) +- 2022-04-16T13:26:57+02:00 More random functions by @yaxu in: [#74](https://codeberg.org/uzu/strudel/pulls/74) +- 2022-04-16T11:13:26+02:00 Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in: [#73](https://codeberg.org/uzu/strudel/pulls/73) +- 2022-04-16T10:26:07+02:00 webaudio package by @froos in: [#26](https://codeberg.org/uzu/strudel/pulls/26) +- 2022-04-15T20:18:06+02:00 More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in: [#70](https://codeberg.org/uzu/strudel/pulls/70) +- 2022-04-15T11:29:51+02:00 First effort at rand() by @yaxu in: [#69](https://codeberg.org/uzu/strudel/pulls/69) +- 2022-04-14T17:41:18+02:00 use new fixed version of osc-js package by @froos in: [#68](https://codeberg.org/uzu/strudel/pulls/68) +- 2022-04-14T00:09:18+02:00 Speech output by @froos in: [#67](https://codeberg.org/uzu/strudel/pulls/67) +- 2022-04-13T23:55:33+02:00 Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in: [#62](https://codeberg.org/uzu/strudel/pulls/62) +- 2022-04-13T17:26:45+02:00 More functions by @yaxu in: [#61](https://codeberg.org/uzu/strudel/pulls/61) +- 2022-04-13T10:04:29+02:00 More functions by @yaxu in: [#56](https://codeberg.org/uzu/strudel/pulls/56) +- 2022-04-12T17:04:04+02:00 OSC and SuperDirt support by @yaxu in: [#27](https://codeberg.org/uzu/strudel/pulls/27) +- 2022-04-12T13:24:14+02:00 Implement `chop()` by @yaxu in: [#50](https://codeberg.org/uzu/strudel/pulls/50) +- 2022-04-12T12:37:32+02:00 First run at squeezeBind, ref #32 by @yaxu in: [#48](https://codeberg.org/uzu/strudel/pulls/48) +- 2022-04-12T00:03:37+02:00 Fix polymeter by @yaxu in: [#44](https://codeberg.org/uzu/strudel/pulls/44) +- 2022-04-11T22:39:44+02:00 Compose by @froos in: [#40](https://codeberg.org/uzu/strudel/pulls/40) +- 2022-04-11T21:49:35+02:00 Update tutorial.mdx by @Ghost in: [#38](https://codeberg.org/uzu/strudel/pulls/38) +- 2022-04-11T08:43:44+02:00 Update tutorial.mdx by @Ghost in: [#37](https://codeberg.org/uzu/strudel/pulls/37) + +## march 2022 + +- 2022-03-28T17:27:22+02:00 Add chunk, chunkBack and iterBack by @yaxu in: [#25](https://codeberg.org/uzu/strudel/pulls/25) +- 2022-03-28T11:48:22+02:00 packaging by @froos in: [#24](https://codeberg.org/uzu/strudel/pulls/24) +- 2022-03-22T12:06:48+01:00 Update package.json by @Ghost in: [#23](https://codeberg.org/uzu/strudel/pulls/23) +- 2022-03-06T20:35:20+01:00 added _asNumber + interprete numbers as midi by @froos in: [#21](https://codeberg.org/uzu/strudel/pulls/21) + +## february 2022 + +- 2022-02-28T00:32:29+01:00 Fix resolveState by @yaxu in: [#22](https://codeberg.org/uzu/strudel/pulls/22) +- 2022-02-27T20:52:52+01:00 Stateful queries and events (WIP) by @yaxu in: [#14](https://codeberg.org/uzu/strudel/pulls/14) +- 2022-02-27T18:04:07+01:00 fix: 💄 Enhance visualisation of the Tutorial on mobile by @Ghost in: [#19](https://codeberg.org/uzu/strudel/pulls/19) +- 2022-02-26T21:16:34+01:00 test: 📦 Add missing dependency and a CI check, to prevent oversights ;p by @Ghost in: [#17](https://codeberg.org/uzu/strudel/pulls/17) +- 2022-02-26T13:29:19+01:00 higher latencyHint by @froos in: [#16](https://codeberg.org/uzu/strudel/pulls/16) +- 2022-02-23T22:15:11+01:00 add apply and layer, and missing div/mul methods by @yaxu in: [#15](https://codeberg.org/uzu/strudel/pulls/15) +- 2022-02-21T01:02:07+01:00 Add continuous signals (sine, cosine, saw, etc) by @yaxu in: [#13](https://codeberg.org/uzu/strudel/pulls/13) +- 2022-02-19T21:30:04+01:00 Added mask() and struct() by @yaxu in: [#11](https://codeberg.org/uzu/strudel/pulls/11) +- 2022-02-19T16:07:07+01:00 Failing test for `when` WIP by @yaxu in: [#10](https://codeberg.org/uzu/strudel/pulls/10) +- 2022-02-10T17:21:31+01:00 Bugfix every, and create more top level functions by @yaxu in: [#9](https://codeberg.org/uzu/strudel/pulls/9) +- 2022-02-10T00:51:21+01:00 timeCat by @yaxu in: [#8](https://codeberg.org/uzu/strudel/pulls/8) +- 2022-02-07T23:07:58+01:00 fixed editor crash by @froos in: [#7](https://codeberg.org/uzu/strudel/pulls/7) +- 2022-02-07T19:08:40+01:00 krill parser + improved repl by @froos in: [#6](https://codeberg.org/uzu/strudel/pulls/6) +- 2022-02-06T00:59:16+01:00 Patternify all the things by @yaxu in: [#5](https://codeberg.org/uzu/strudel/pulls/5) +- 2022-02-05T23:31:37+01:00 update readme for local dev by @Ghost in: [#4](https://codeberg.org/uzu/strudel/pulls/4) +- 2022-02-05T22:36:06+01:00 Fix path by @yaxu in: [#3](https://codeberg.org/uzu/strudel/pulls/3) +- 2022-02-05T21:52:51+01:00 repl + reify functions by @froos in: [#2](https://codeberg.org/uzu/strudel/pulls/2) + +... hello from the bottom of this file! you seem to be interested in the history of strudel, so it's worth noting that the first [commit](https://codeberg.org/uzu/strudel/commit/38b5a0d5cdf28685b2b5e18d460772b70246207b) to the repo was actually on Jan 22, 2022, 09:24 PM GMT+1 by @yaxu which could be seen as strudel's birthday 🎂 From 62bf9abed336c4638bc5a2f760e74ec1b2f942e1 Mon Sep 17 00:00:00 2001 From: Aria Date: Mon, 19 Jan 2026 09:52:49 -0800 Subject: [PATCH 068/200] Add retrig option for LFOs --- packages/core/controls.mjs | 4 +++- packages/superdough/modulators.mjs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 834d963c..a5a1c0a1 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -3046,6 +3046,7 @@ registerSubControls('lfo', [ ['skew', 'sk'], ['curve', 'cu'], ['sync', 's'], + ['retrig', 'rt'], ['fxi'], ]); registerSubControls('env', [ @@ -3132,13 +3133,14 @@ Pattern.prototype.modulate = function (type, config, idPat) { * @param {string | Pattern} [config.control] Node to modulate. Aliases: c * @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc * @param {number | Pattern} [config.rate] Modulation rate. Aliases: r + * @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s * @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr * @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da * @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc * @param {number | Pattern} [config.shape] Shape index. Aliases: sh * @param {number | Pattern} [config.skew] Skew amount. Aliases: sk * @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: cu - * @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s + * @param {number | Pattern} [config.retrig] If > 0.5, the LFO will retrigger on each event. Aliases: rt * @param {number | Pattern} [config.fxi] FX index to target * @param {string | Pattern} id ID to use for this modulator * @returns Pattern diff --git a/packages/superdough/modulators.mjs b/packages/superdough/modulators.mjs index 05674baa..76fc4c7f 100644 --- a/packages/superdough/modulators.mjs +++ b/packages/superdough/modulators.mjs @@ -98,6 +98,7 @@ export const connectLFO = (id, params, nodeTracker) => { fxi = 'main', depth = 1, depthabs, + retrig = 0, ...filteredParams } = params; const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl); @@ -109,7 +110,7 @@ export const connectLFO = (id, params, nodeTracker) => { const modParams = { ...filteredParams, frequency: sync !== undefined ? sync * cps : rate, - time: cycle / cps, + time: retrig > 0.5 ? 0 : cycle / cps, depth: depthValue, min, max, From 87e723649ead1a899842c30af71d58dd356c30a5 Mon Sep 17 00:00:00 2001 From: Dsm0 Date: Sun, 18 Jan 2026 23:43:11 -0800 Subject: [PATCH 069/200] fixed highlighting issue --- packages/codemirror/highlight.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/codemirror/highlight.mjs b/packages/codemirror/highlight.mjs index 5cc6d3a1..b37390ef 100644 --- a/packages/codemirror/highlight.mjs +++ b/packages/codemirror/highlight.mjs @@ -25,7 +25,6 @@ const miniLocations = StateField.define({ //block-based eval case if (e.value.range) { const stateMiniLocations = getMiniLocationsFromDecorations(locations); - const existingById = new Map(stateMiniLocations.map(({ id, from, to }) => [id, [from, to]])); const normalized = e.value.locations .filter(([from]) => from < tr.newDoc.length) @@ -35,13 +34,12 @@ const miniLocations = StateField.define({ const marks = normalized.map((range) => { const id = range.join(':'); - const useRange = existingById.get(id) || range; return Decoration.mark({ id, // this green is only to verify that the decoration moves when the document is edited // it will be removed later, so the mark is not visible by default attributes: { style: `background-color: #00CA2880` }, - }).range(...useRange); // -> Decoration + }).range(...range); // -> Decoration }); const previousMarks = stateMiniLocations From c96b1c2faf8c31bbe38f31714e2c6432fc9a81e6 Mon Sep 17 00:00:00 2001 From: Dsm0 Date: Mon, 19 Jan 2026 01:15:55 -0800 Subject: [PATCH 070/200] fixed anonymous label casing --- packages/core/repl.mjs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 8d023c72..3ff43e2f 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -473,15 +473,12 @@ export function repl({ const labels = meta.labels || []; + // Check for anonymous labels (labels starting with '$') + const hasAnonymousLabel = labels.some((label) => label.name.startsWith('$')); + // Store code blocks in dictionary using labels as keys - if (labels.length > 0) { - for (let i = 0; i < labels.length; i++) { - // processing transpiler output instead of code is simply to avoid - // extra regex in detecting whether or not an inline widget has been commented out - processLabeledBlock(labels, i, meta.output, options, meta); - } - } else if (pattern !== silence) { - // variable/function declarations that return silence are allowed, + if (hasAnonymousLabel) { + // variable/function declarations that don't return patterns are allowed, // but anonymous pattern blocks pose an issue for block-based evaluation // if an anonymous pattern is evaluated multiple times it will just stack and get louder and louder @@ -490,11 +487,17 @@ export function repl({ // otherwise we'll have no idea of which pattern is being overridden // (we probably need to update the docs on this) - // we could easily enable it, but it would confuse a lot of people + throw new Error( 'anonymous labels disabled for block based evaluation (see https://strudel.cc/blog/#label-notation)', ); + } else if (labels.length > 0) { + for (let i = 0; i < labels.length; i++) { + // processing transpiler output instead of code is simply to avoid + // extra regex in detecting whether or not an inline widget has been commented out + processLabeledBlock(labels, i, meta.output, options, meta); + } } else { // Declaration block (variable/function that returns silence) // Store it so its miniLocations are preserved for highlighting patterns stored in variables From 38b7bcb3d288562424e797f0ccc41111fb5a3773 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 19 Jan 2026 23:00:14 +0100 Subject: [PATCH 071/200] style tweaks --- .../repl/components/button/action-button.jsx | 9 +-- .../components/incrementor/Incrementor.jsx | 2 +- .../components/panel/AudioDeviceSelector.jsx | 4 +- .../panel/AudioEngineTargetSelector.jsx | 6 +- .../src/repl/components/panel/ExportTab.jsx | 7 +-- website/src/repl/components/panel/Forms.jsx | 2 +- .../src/repl/components/panel/PatternsTab.jsx | 2 +- .../src/repl/components/panel/Reference.jsx | 2 +- .../src/repl/components/panel/SelectInput.jsx | 20 ------- .../src/repl/components/panel/SettingsTab.jsx | 57 +++++++++++++++---- .../src/repl/components/panel/SoundsTab.jsx | 2 +- .../src/repl/components/textbox/Textbox.jsx | 14 ----- 12 files changed, 62 insertions(+), 65 deletions(-) delete mode 100644 website/src/repl/components/panel/SelectInput.jsx delete mode 100644 website/src/repl/components/textbox/Textbox.jsx diff --git a/website/src/repl/components/button/action-button.jsx b/website/src/repl/components/button/action-button.jsx index de674b69..846d36c7 100644 --- a/website/src/repl/components/button/action-button.jsx +++ b/website/src/repl/components/button/action-button.jsx @@ -13,16 +13,13 @@ export function SpecialActionButton(props) { const { className, ...buttonProps } = props; return ( - + ); } export function ActionInput({ label, className, ...props }) { return ( -