Merge branch 'main' into glossing/fmsss

This commit is contained in:
Aria
2025-12-03 10:42:35 -06:00
32 changed files with 1065 additions and 210 deletions
+88
View File
@@ -0,0 +1,88 @@
# changelog
NOTE: you can generate this with `node warm.js`. it might still not be perfectly sorted...
## 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)
- 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-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-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-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-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: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-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-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)
+51 -5
View File
@@ -1366,6 +1366,8 @@ export const { fanchor } = registerControl('fanchor');
*
* @name lprate
* @param {number | Pattern} rate rate in hertz
* @example
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lprate("<4 8 2 1>")
*/
export const { lprate } = registerControl('lprate');
@@ -1374,6 +1376,8 @@ export const { lprate } = registerControl('lprate');
*
* @name lpsync
* @param {number | Pattern} rate rate in cycles
* @example
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpsync("<4 8 2 1>")
*/
export const { lpsync } = registerControl('lpsync');
@@ -1382,8 +1386,23 @@ export const { lpsync } = registerControl('lpsync');
*
* @name lpdepth
* @param {number | Pattern} depth depth of modulation
* @example
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepth("<1 .5 1.8 0>")
*/
export const { lpdepth } = registerControl('lpdepth');
/**
* Depth of the LFO for the lowpass filter, in HZ
*
* @name lpdepthfrequency
* @synonyms
* lpdethfreq
* @param {number | Pattern} depth depth of modulation
* @example
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
*/
export const { lpdepthfrequency } = registerControl('lpdepthfrequency', 'lpdepthfreq');
/**
* Shape of the LFO for the lowpass filter
@@ -1433,6 +1452,19 @@ export const { bpsync } = registerControl('bpsync');
*/
export const { bpdepth } = registerControl('bpdepth');
/**
* Depth of the LFO for the bandpass filter, in HZ
*
* @name bpdepthfrequency
* @synonyms
* bpdethfreq
* @param {number | Pattern} depth depth of modulation
* @example
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
*/
export const { bpdepthfrequency } = registerControl('bpdepthfrequency', 'bpdepthfreq');
/**
* Shape of the LFO for the bandpass filter
*
@@ -1479,7 +1511,20 @@ export const { hpsync } = registerControl('hpsync');
* @name hpdepth
* @param {number | Pattern} depth depth of modulation
*/
export const { hpdepth } = registerControl('hpdepth');
export const { hpdepth, hpdepthfreq } = registerControl('hpdepth');
/**
* Depth of the LFO for the hipass filter, in hz
*
* @name hpdepthfrequency
* @synonyms
* hpdethfreq
* @param {number | Pattern} depth depth of modulation
* @example
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
*/
export const { hpdepthfrequency } = registerControl('hpdepthfrequency', 'hpdepthfreq');
/**
* Shape of the LFO for the highpass filter
@@ -1892,12 +1937,12 @@ export const { nudge } = registerControl('nudge');
* Sets the default octave of a synth.
*
* @name octave
* @synonyms oct
* @param {number | Pattern} octave octave number
* @example
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
* @superDirtOnly
* n("0,4,7").scale("F:minor").s('supersaw').octave("<0 1 2 3>")
*/
export const { octave } = registerControl('octave');
export const { octave, oct } = registerControl('octave', 'oct');
// ['ophatdecay'],
// TODO: example
@@ -1905,6 +1950,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
* @synonyms o
* @param {number | Pattern} number
* @example
* stack(
@@ -1912,7 +1958,7 @@ export const { octave } = registerControl('octave');
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
* )
*/
export const { orbit } = registerControl('orbit');
export const { orbit } = registerControl('orbit', 'o');
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
export const { overgain } = registerControl('overgain');
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
+20 -3
View File
@@ -25,7 +25,7 @@ import {
stringifyValues,
} from './util.mjs';
import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs';
import { errorLogger, logger } from './logger.mjs';
let stringParser;
@@ -414,7 +414,7 @@ export class Pattern {
try {
return this.query(new State(new TimeSpan(begin, end), controls));
} catch (err) {
logger(`[query]: ${err.message}`, 'error');
errorLogger(err, 'query');
return [];
}
}
@@ -2234,7 +2234,7 @@ export const brak = register('brak', function (pat) {
});
/**
* Reverse all haps in a pattern
* Reverse all cycles in a pattern. See also `revv` for reversing a whole pattern.
*
* @name rev
* @memberof Pattern
@@ -2266,6 +2266,23 @@ export const rev = register(
true,
);
/**
* Reverse a whole pattern. See also `rev` for reversing each cycle.
*
* @name revv
* @memberof Pattern
* @returns Pattern
* @example
* // This is the same as `<[g e] [d c]>`. If `rev()` is used, you get
* // the same as `<[d c] [g e]>`, where each cycle reverses, but the order of
* // cycles stays the same.
* note("<[c d] [e g]>").revv()
*/
export const revv = register('revv', function (pat) {
const negateSpan = (span) => new TimeSpan(Fraction(0).sub(span.end), Fraction(0).sub(span.begin));
return pat.withQuerySpan(negateSpan).withHapSpan(negateSpan);
});
/** 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.
+17 -7
View File
@@ -152,9 +152,9 @@ export function repl({
// allows muting a pattern x with x_ or _x
return silence;
}
if (id === '$') {
if (id.includes('$')) {
// allows adding anonymous patterns with $:
id = `$${anonymousIndex}`;
id = `${id}${anonymousIndex}`;
anonymousIndex++;
}
pPatterns[id] = this;
@@ -215,8 +215,19 @@ export function repl({
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
if (Object.keys(pPatterns).length) {
let patterns = [];
let soloActive = false;
for (const [key, value] of Object.entries(pPatterns)) {
patterns.push(value.withState((state) => state.setControls({ id: key })));
// 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
@@ -227,14 +238,13 @@ export function repl({
pattern = eachTransform(pattern);
}
if (allTransforms.length) {
for (let i in allTransforms) {
pattern = allTransforms[i](pattern);
for (const transform of allTransforms) {
pattern = transform(pattern);
}
}
if (!isPattern(pattern)) {
const message = `got "${typeof evaluated}" instead of pattern`;
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
pattern = silence;
}
logger(`[eval] code updated`);
pattern = await setPattern(pattern, autostart);
+1 -1
View File
@@ -524,7 +524,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
* @example
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
* wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note()
* @example
* // The probability can itself be a pattern
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
+12
View File
@@ -586,6 +586,18 @@ describe('Pattern', () => {
.map((a) => a.value),
).toStrictEqual(['c', 'b', 'a']);
});
it('Does not reverse the order of cycles', () => {
expect(fastcat('a', 'b', 'c', 'd').slow(2).rev().fast(2).sortHapsByPart().firstCycle()).toStrictEqual(
fastcat('b', 'a', 'd', 'c').firstCycle(),
);
});
});
describe('revv()', () => {
it('Does reverse the order of cycles', () => {
expect(fastcat('a', 'b', 'c', 'd').slow(2).revv().fast(2).sortHapsByPart().firstCycle()).toStrictEqual(
fastcat('d', 'c', 'b', 'a').firstCycle(),
);
});
});
describe('sequence()', () => {
it('Can work like fastcat', () => {
+1
View File
@@ -35,6 +35,7 @@ export async function initHydra(options = {}) {
hydra.synth.s0.init({ src: canvas });
}
}
return hydra;
}
export function clearHydra() {
+1 -3
View File
@@ -9,6 +9,7 @@ import readline from 'readline';
import os from 'os';
const LOG = !!process.env.LOG || false;
const PORT = process.env.PORT || 5432;
const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg'];
const isAudioFile = (f) => {
@@ -54,7 +55,6 @@ async function getBanks(directory, flat = false) {
banks[bank].push(subDir);
return subDir;
});
banks._base = `http://localhost:5432`;
return { banks, files };
}
@@ -134,8 +134,6 @@ const server = http.createServer(async (req, res) => {
readStream.pipe(res);
});
// eslint-disable-next-line
const PORT = process.env.PORT || 5432;
const IP_ADDRESS = '0.0.0.0';
let IP;
const networkInterfaces = os.networkInterfaces();
+3 -2
View File
@@ -6,6 +6,7 @@ import {
getADSRValues,
getPitchEnvelope,
getVibratoOscillator,
onceEnded,
} from '@strudel/webaudio';
import gm from './gm.mjs';
@@ -170,12 +171,12 @@ export function registerSoundfonts() {
bufferSource.stop(envEnd);
const stop = (releaseTime) => {};
bufferSource.onended = () => {
onceEnded(bufferSource, () => {
bufferSource.disconnect();
vibratoOscillator?.stop();
node.disconnect();
onended();
};
});
return { node, stop };
},
{ type: 'soundfont', prebake: true, fonts },
+96 -25
View File
@@ -191,8 +191,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
return { lfo, disconnect: () => lfo?.disconnect() };
}
export function createFilter(context, start, end, params, cps) {
export function createFilter(context, start, end, params, cps, cycle) {
let {
frequency,
anchor,
@@ -202,12 +201,14 @@ export function createFilter(context, start, end, params, cps) {
q = 1,
drive = 0.69,
depth,
depthfrequency,
dcoffset = -0.5,
skew,
shape,
rate,
sync,
} = params;
let frequencyParam, filter;
if (model === 'ladder') {
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
@@ -238,9 +239,27 @@ export function createFilter(context, start, end, params, cps) {
if (sync != null) {
rate = cps * sync;
}
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate };
getParamLfo(context, frequencyParam, start, end, lfoValues);
return filter;
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
let lfo;
if (hasLFO) {
depth = depth ?? 1;
const time = cycle / cps;
const modDepth = depthfrequency ?? (depth ?? 1) * frequency;
const lfoValues = {
depth: modDepth,
dcoffset,
skew,
shape,
frequency: rate ?? cps,
min: -frequency + 30,
max: 20000 - frequency,
time,
curve: 1,
};
lfo = getParamLfo(context, frequencyParam, start, end, lfoValues);
}
return { filter, lfo };
}
// stays 1 until .5, then fades out
@@ -262,7 +281,15 @@ export function drywet(dry, wet, wetAmount = 0) {
let mix = ac.createGain();
dry_gain.connect(mix);
wet_gain.connect(mix);
return mix;
return {
node: mix,
onended: () => {
dry_gain.disconnect(mix);
wet_gain.disconnect(mix);
dry.disconnect(dry_gain);
wet.disconnect(wet_gain);
},
};
}
let curves = ['linear', 'exponential'];
@@ -297,6 +324,10 @@ export function getVibratoOscillator(param, value, t) {
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(param);
vibratoOscillator.onended = () => {
gain.disconnect(param);
vibratoOscillator.disconnect(gain);
};
vibratoOscillator.start(t);
return vibratoOscillator;
}
@@ -318,20 +349,11 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
constantNode.connect(zeroGain);
// Schedule the `onComplete` callback to occur at `stopTime`
constantNode.onended = () => {
// Ensure garbage collection
try {
zeroGain.disconnect();
} catch {
// pass
}
try {
constantNode.disconnect();
} catch {
// pass
}
onceEnded(constantNode, () => {
releaseAudioNode(zeroGain);
releaseAudioNode(constantNode);
onComplete();
};
});
constantNode.start(startTime);
constantNode.stop(stopTime);
return constantNode;
@@ -429,6 +451,11 @@ export function applyFM(param, value, begin) {
}
io[0].connect(io[1]);
}
fmmod.osc.onended = () => {
envGain.disconnect();
modulator.disconnect();
fmmod.osc.disconnect();
};
}
return {
stop: (t) => toStop.forEach((m) => m?.stop(t)),
@@ -532,7 +559,7 @@ export const getDistortion = (distort, postgain, algorithm) => {
};
export const getFrequencyFromValue = (value, defaultNote = 36) => {
let { note, freq } = value;
let { note, freq, octave = 0 } = value;
note = note || defaultNote;
if (typeof note === 'string') {
note = noteToMidi(note); // e.g. c3 => 48
@@ -541,16 +568,60 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
if (!freq && typeof note === 'number') {
freq = midiToFreq(note); // + 48);
}
freq *= Math.pow(2, octave);
return Number(freq);
};
export const destroyAudioWorkletNode = (node) => {
if (node == null) {
return;
// This helper should be used instead of the `node.onended = callback` pattern
// It adds a mechanism to help minimize gc retention
export const onceEnded = (node, callback) => {
let onended = callback;
node.onended = function cleanup() {
onended && onended();
onended = null;
this.onended = null;
};
};
export const releaseAudioNode = (node) => {
if (node == null) return;
// check we received an AudioNode
if (!(node instanceof AudioNode)) {
throw new Error('releaseAudioNode can only release an AudioNode');
}
// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect
node.disconnect();
node.parameters.get('end')?.setValueAtTime(0, 0);
// make sure all AudioScheduledSourceNodes are in a stopped state
// https://developer.mozilla.org/en-US/docs/Web/API/AudioScheduledSourceNode
if (node instanceof AudioScheduledSourceNode) {
if (node.onended && node.onended.name !== 'cleanup') {
logger(
`[superdough] Deprecation warning: it seems your code path is setting 'node.onended = callback' instead of using the onceEnded helper`,
);
}
try {
node.stop();
} catch (e) {
// At the stage, `start` was not called on the node
// but an `onended` callback releasing resources may exist
// and we want it to fire :
// - we force a start/stop cycle so that `onended` gets called
// - we `lock` the node so that no-one can start it
node.start(node.context.currentTime + 5); // will never happen
node.stop();
}
}
// https://www.w3.org/TR/webaudio-1.1/#AudioNode-actively-processing
// An AudioWorkletNode is actively processing when its AudioWorkletProcessor's [[callable process]]
// returns true and either its active source flag is true or
// any AudioNode connected to one of its inputs is actively processing.
if (node instanceof AudioWorkletNode) {
node.parameters.get('end')?.setValueAtTime(0, 0);
}
};
export const cleanupNode = (node, time) => {
+2 -1
View File
@@ -65,8 +65,9 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) {
export function getNoiseMix(inputNode, wet, t) {
const noiseOscillator = getNoiseOscillator('pink', t);
const noiseMix = drywet(inputNode, noiseOscillator.node, wet);
noiseOscillator.node.onended = () => noiseMix.onended();
return {
node: noiseMix,
node: noiseMix.node,
stop: (time) => noiseOscillator?.stop(time),
};
}
+20 -11
View File
@@ -1,7 +1,14 @@
import { getCommonSampleInfo } from './util.mjs';
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
import { registerSound, registerWaveTable } from './index.mjs';
import { getAudioContext } from './audioContext.mjs';
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
import {
getADSRValues,
getParamADSR,
getPitchEnvelope,
getVibratoOscillator,
onceEnded,
releaseAudioNode,
} from './helpers.mjs';
import { logger } from './logger.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer>
@@ -211,7 +218,7 @@ export async function fetchSampleMap(url) {
// not a browser
return;
}
const base = url.split('/').slice(0, -1).join('/');
const base = getBaseURL(url);
if (typeof fetch === 'undefined') {
// skip fetch when in node / testing
return;
@@ -286,17 +293,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl);
// asny stuff above took too long?
if (ac.currentTime > t) {
logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight');
// console.warn('sample still loading:', s, n);
return;
}
if (!bufferSource) {
logger(`[sampler] could not load "${s}:${n}"`, 'error');
return;
}
// async stuff above took too long?
if (ac.currentTime > t) {
logger(`[sampler] loading sound "${s}:${n}" took too long`, 'highlight');
// AudioBufferSourceNode will never be used. discard it
releaseAudioNode(bufferSource);
return;
}
// vibrato
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
@@ -319,13 +328,13 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
node.connect(out);
bufferSource.onended = function () {
onceEnded(bufferSource, function () {
bufferSource.disconnect();
vibratoOscillator?.stop();
node.disconnect();
out.disconnect();
onended();
};
});
let envEnd = holdEnd + release + 0.01;
bufferSource.stop(envEnd);
const stop = (endTime) => {
+38 -20
View File
@@ -263,8 +263,8 @@ let audioReady;
export async function initAudioOnFirstClick(options) {
if (!audioReady) {
audioReady = new Promise((resolve) => {
document.addEventListener('click', async function listener() {
document.removeEventListener('click', listener);
document.addEventListener('mousedown', async function listener() {
document.removeEventListener('mousedown', listener);
await initAudio(options);
resolve();
});
@@ -274,7 +274,7 @@ export async function initAudioOnFirstClick(options) {
}
let controller;
function getSuperdoughAudioController() {
export function getSuperdoughAudioController() {
if (controller == null) {
controller = new SuperdoughAudioController(getAudioContext());
}
@@ -287,7 +287,7 @@ export function connectToDestination(input, channels) {
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
const ac = getAudioContext();
const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
const lfo = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
//filters
const numStages = 2; //num of filters in series
@@ -300,14 +300,14 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
filter.frequency.value = centerFrequency + fOffset;
filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9);
lfoGain.connect(filter.detune);
lfo.connect(filter.detune);
fOffset += 282;
if (i > 0) {
filterChain[i - 1].connect(filter);
}
filterChain.push(filter);
}
return filterChain[filterChain.length - 1];
return { phaser: filterChain[filterChain.length - 1], lfo };
}
function getFilterType(ftype) {
@@ -413,7 +413,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
release = 0,
//phaser
phaserrate: phaser,
phaserrate,
phaserdepth = getDefaultValue('phaserdepth'),
phasersweep,
phasercenter,
@@ -553,16 +553,21 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
rate: 'lprate',
sync: 'lpsync',
depth: 'lpdepth',
depthfrequency: 'lpdepthfrequency',
shape: 'lpshape',
dcoffset: 'lpdc',
skew: 'lpskew',
};
const lpParams = pickAndRename(value, lpMap);
lpParams.type = 'lowpass';
let lp = () => createFilter(ac, t, end, lpParams, cps);
chain.push(lp());
const lp = () => createFilter(ac, t, end, lpParams, cps, cycle);
const { filter: lpf1, lfo: lfo1 } = lp();
chain.push(lpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') {
chain.push(lp());
const { filter: lpf2, lfo: lfo2 } = lp();
chain.push(lpf2);
lfo2 && audioNodes.push(lfo2);
}
}
@@ -581,16 +586,21 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
rate: 'hprate',
sync: 'hpsync',
depth: 'hpdepth',
depthfrequency: 'hpdepthfrequency',
shape: 'hpshape',
dcoffset: 'hpdc',
skew: 'hpskew',
};
const hpParams = pickAndRename(value, hpMap);
hpParams.type = 'highpass';
let hp = () => createFilter(ac, t, end, hpParams, cps);
chain.push(hp());
const hp = () => createFilter(ac, t, end, hpParams, cps, cycle);
const { filter: hpf1, lfo: lfo1 } = hp();
chain.push(hpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') {
chain.push(hp());
const { filter: hpf2, lfo: lfo2 } = hp();
chain.push(hpf2);
lfo2 && audioNodes.push(lfo2);
}
}
@@ -609,16 +619,21 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
rate: 'bprate',
sync: 'bpsync',
depth: 'bpdepth',
depthfrequency: 'bpdepthfrequency',
shape: 'bpshape',
dcoffset: 'bpdc',
skew: 'bpskew',
};
const bpParams = pickAndRename(value, bpMap);
bpParams.type = 'bandpass';
let bp = () => createFilter(ac, t, end, bpParams, cps);
chain.push(bp());
const bp = () => createFilter(ac, t, end, bpParams, cps, cycle);
const { filter: bpf1, lfo: lfo1 } = bp();
chain.push(bpf1);
lfo1 && audioNodes.push(lfo1);
if (ftype === '24db') {
chain.push(bp());
const { filter: bpf2, lfo: lfo2 } = bp();
chain.push(bpf2);
lfo2 && audioNodes.push(lfo2);
}
}
@@ -665,6 +680,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
curve: 1.5,
});
lfo.connect(amGain.gain);
audioNodes.push(lfo);
chain.push(amGain);
}
@@ -680,9 +696,10 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
chain.push(panner);
}
// phaser
if (phaser !== undefined && phaserdepth > 0) {
const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep);
chain.push(phaserFX);
if (phaserrate !== undefined && phaserdepth > 0) {
const { phaser, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
audioNodes.push(lfo);
chain.push(phaser);
}
// last gain
@@ -692,7 +709,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
// delay
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
orbitBus.getDelay(delaytime, delayfeedback, t);
orbitBus.sendDelay(post, delay);
const send = orbitBus.sendDelay(post, delay);
audioNodes.push(send);
}
// reverb
if (room > 0) {
+1 -1
View File
@@ -82,7 +82,7 @@ export class Orbit {
}
sendDelay(node, amount) {
effectSend(node, this.delayNode, amount);
return effectSend(node, this.delayNode, amount);
}
duck(t, onsettime = 0, attacktime = 0.1, depth = 1) {
+19 -18
View File
@@ -3,7 +3,6 @@ import { registerSound, soundMap } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import {
applyFM,
destroyAudioWorkletNode,
gainNode,
getADSRValues,
getFrequencyFromValue,
@@ -13,6 +12,7 @@ import {
getVibratoOscillator,
getWorklet,
noises,
releaseAudioNode,
webAudioTimeout,
} from './helpers.mjs';
import { logger } from './logger.mjs';
@@ -48,19 +48,17 @@ export function registerSynthSounds() {
[0.001, 0.05, 0.6, 0.01],
);
let sound = getOscillator(s, t, value);
let { node: o, stop, triggerRelease } = sound;
// turn down
const g = gainNode(0.3);
const { duration } = value;
o.onended = () => {
o.disconnect();
let sound = getOscillator(s, t, value, () => {
g.disconnect();
onended();
};
});
let { node: o, stop, triggerRelease } = sound;
const { duration } = value;
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
@@ -194,8 +192,7 @@ export function registerSynthSounds() {
let timeoutNode = webAudioTimeout(
ac,
() => {
destroyAudioWorkletNode(o);
envGain.disconnect();
releaseAudioNode(o);
onended();
fm?.stop();
vibratoOscillator?.stop();
@@ -272,8 +269,7 @@ export function registerSynthSounds() {
let timeoutNode = webAudioTimeout(
ac,
() => {
destroyAudioWorkletNode(o);
envGain.disconnect();
releaseAudioNode(o);
onended();
},
begin,
@@ -346,9 +342,8 @@ export function registerSynthSounds() {
let timeoutNode = webAudioTimeout(
ac,
() => {
destroyAudioWorkletNode(o);
destroyAudioWorkletNode(lfo);
envGain.disconnect();
releaseAudioNode(o);
releaseAudioNode(lfo);
onended();
fm?.stop();
vibratoOscillator?.stop();
@@ -460,7 +455,7 @@ export function waveformN(partials, phases, type) {
}
// expects one of waveforms as s
export function getOscillator(s, t, value) {
export function getOscillator(s, t, value, onended) {
const { duration, noise = 0 } = value;
const partials = value.partials ?? value.n;
let o;
@@ -482,7 +477,6 @@ export function getOscillator(s, t, value) {
}
// set frequency
o.frequency.value = getFrequencyFromValue(value);
o.start(t);
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
@@ -495,6 +489,13 @@ export function getOscillator(s, t, value) {
noiseMix = getNoiseMix(o, noise, t);
}
o.onended = () => {
noiseMix || o.disconnect();
noiseMix?.node.disconnect();
onended();
};
o.start(t);
return {
node: noiseMix?.node || o,
stop: (time) => {
+10
View File
@@ -114,3 +114,13 @@ export function getCommonSampleInfo(hapValue, bank) {
export const pickAndRename = (source, map) => {
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
};
export const getBaseURL = (url) => {
try {
// For real URLs
return new URL('.', new URL(url)).href.replace(/\/$/, ''); // removes trailing slash
} catch {
// For pseudo URLS
return url.split('/').slice(0, -1).join('/');
}
};
+17 -5
View File
@@ -45,7 +45,8 @@ if (typeof GainNode !== 'undefined') {
throw new Error('vowel: unknown vowel ' + letter);
}
const { gains, qs, freqs } = vowelFormant[letter];
const makeupGain = ac.createGain();
this.makeupGain = ac.createGain();
this.audioNodes = [];
for (let i = 0; i < 5; i++) {
const gain = ac.createGain();
gain.gain.value = gains[i];
@@ -53,14 +54,25 @@ if (typeof GainNode !== 'undefined') {
filter.type = 'bandpass';
filter.Q.value = qs[i];
filter.frequency.value = freqs[i];
this.connect(filter);
super.connect(filter);
filter.connect(gain);
gain.connect(makeupGain);
this.audioNodes.push(filter);
gain.connect(this.makeupGain);
this.audioNodes.push(gain);
}
makeupGain.gain.value = 8; // how much makeup gain to add?
this.connect = (target) => makeupGain.connect(target);
this.makeupGain.gain.value = 8; // how much makeup gain to add?
return this;
}
connect(target) {
this.makeupGain.connect(target);
}
disconnect() {
this.makeupGain.disconnect();
this.audioNodes.forEach((n) => n.disconnect());
super.disconnect();
this.makeupGain = null;
this.audioNodes = null;
}
}
AudioContext.prototype.createVowelFilter = function (letter) {
+5 -5
View File
@@ -1,15 +1,15 @@
import { getAudioContext, registerSound } from './index.mjs';
import { getCommonSampleInfo } from './util.mjs';
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
import {
applyFM,
applyParameterModulators,
destroyAudioWorkletNode,
getADSRValues,
getFrequencyFromValue,
getParamADSR,
getPitchEnvelope,
getVibratoOscillator,
getWorklet,
releaseAudioNode,
webAudioTimeout,
} from './helpers.mjs';
import { logger } from './logger.mjs';
@@ -190,6 +190,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
if (url.startsWith('local:')) {
url = `http://localhost:5432`;
}
const base = getBaseURL(url);
if (typeof fetch !== 'function') {
// not a browser
return;
@@ -200,7 +201,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
}
return fetch(url)
.then((res) => res.json())
.then((json) => _processTables(json, url, frameLen, options))
.then((json) => _processTables(json, base, frameLen, options))
.catch((error) => {
console.error(error);
throw new Error(`error loading "${url}"`);
@@ -318,10 +319,9 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
const timeoutNode = webAudioTimeout(
ac,
() => {
destroyAudioWorkletNode(source);
releaseAudioNode(source);
vibratoOscillator?.stop();
fm?.stop();
node.disconnect();
wtPosModulators?.disconnect();
wtWarpModulators?.disconnect();
onended();
+11 -9
View File
@@ -508,13 +508,14 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
];
}
process(_input, outputs, params) {
if (currentTime <= params.begin[0]) {
return true;
}
if (currentTime >= params.end[0]) {
// this.port.postMessage({ type: 'onended' });
// should terminate
return false;
}
if (currentTime <= params.begin[0]) {
// keep alive
return true;
}
const output = outputs[0];
const voices = params.voices[0]; // k-rate
for (let i = 0; i < output[0].length; i++) {
@@ -532,7 +533,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
// We must wrap this here because it is passed into sawblep below which
// has domain [0, 1]
const dt = ffrac(freqVoice * INVSR);
const dt = frac(freqVoice * INVSR);
this.phase[n] = this.phase[n] ?? Math.random();
const v = waveshapes.sawblep(this.phase[n], dt);
@@ -543,8 +544,9 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
if (pn >= 1.0) pn -= 1.0;
this.phase[n] = pn;
// invert right and left gain
const tmp = gainL;
gainL = gainR;
gainR = gainL;
gainR = tmp;
}
}
return true;
@@ -1338,7 +1340,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const tablePos = clamp(pv(parameters.position, i), 0, 1);
const idx = tablePos * (this.numFrames - 1);
const fIdx = idx | 0;
const frac = idx - fIdx;
const interpT = idx - fIdx;
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
const warpMode = pv(parameters.warpMode, i);
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
@@ -1368,13 +1370,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
const s0 = this._sampleFrame(table[fIdx], ph);
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
let s = s0 + (s1 - s0) * frac;
let s = lerp(s0, s1, interpT);
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
s = -s;
}
outL[i] += s * gainL * normalizer;
outR[i] += s * gainR * normalizer;
this.phase[n] = ffrac(this.phase[n] + dPhase);
this.phase[n] = frac(this.phase[n] + dPhase);
}
}
return true;
+4 -3
View File
@@ -3,6 +3,7 @@ import { midiToFreq, noteToMidi } from './util.mjs';
import { registerSound } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import { buildSamples } from './zzfx_fork.mjs';
import { onceEnded, releaseAudioNode } from './helpers.mjs';
export const getZZFX = (value, t) => {
let {
@@ -83,10 +84,10 @@ export function registerZZFXSounds() {
wave,
(t, value, onended) => {
const { node: o } = getZZFX({ s: wave, ...value }, t);
o.onended = () => {
o.disconnect();
onceEnded(o, () => {
releaseAudioNode(o);
onended();
};
});
return {
node: o,
stop: () => {},
+11 -2
View File
@@ -5,7 +5,16 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Note, Interval, Scale } from '@tonaljs/tonal';
import { register, _mod, logger, isNote, noteToMidi, removeUndefineds, getAccidentalsOffset } from '@strudel/core';
import {
_mod,
errorLogger,
getAccidentalsOffset,
isNote,
logger,
noteToMidi,
register,
removeUndefineds,
} from '@strudel/core';
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
@@ -294,7 +303,7 @@ export const scale = register(
}
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
} catch (err) {
logger(`[tonal] ${err.message}`, 'error');
errorLogger(err, 'tonal');
return; // will be removed
}
}
+487 -60
View File
@@ -1391,6 +1391,75 @@ exports[`runs examples > example "bpdecay" example index 0 1`] = `
]
`;
exports[`runs examples > example "bpdepthfrequency" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
]
`;
exports[`runs examples > example "bpenv" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]",
@@ -4823,6 +4892,75 @@ exports[`runs examples > example "hpdecay" example index 0 1`] = `
]
`;
exports[`runs examples > example "hpdepthfrequency" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
]
`;
exports[`runs examples > example "hpenv" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]",
@@ -6006,6 +6144,144 @@ exports[`runs examples > example "lpdecay" example index 0 1`] = `
]
`;
exports[`runs examples > example "lpdepth" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
]
`;
exports[`runs examples > example "lpdepthfrequency" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
]
`;
exports[`runs examples > example "lpenv" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]",
@@ -6194,6 +6470,75 @@ exports[`runs examples > example "lpq" example index 0 1`] = `
]
`;
exports[`runs examples > example "lprate" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lprate:1 ]",
]
`;
exports[`runs examples > example "lprelease" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c2 s:sawtooth clip:0.5 cutoff:300 lpenv:4 lprelease:0.5 release:0.5 ]",
@@ -6236,6 +6581,75 @@ exports[`runs examples > example "lpsustain" example index 0 1`] = `
]
`;
exports[`runs examples > example "lpsync" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
]
`;
exports[`runs examples > example "lrate" example index 0 1`] = `
[
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]",
@@ -6925,18 +7339,18 @@ exports[`runs examples > example "nrpv" example index 0 1`] = `
exports[`runs examples > example "octave" example index 0 1`] = `
[
"[ 0/1 → 1/1 | n:0 s:supersquare octave:3 ]",
"[ 0/1 → 1/1 | n:4 s:supersquare octave:3 ]",
"[ 0/1 → 1/1 | n:7 s:supersquare octave:3 ]",
"[ 1/1 → 2/1 | n:0 s:supersquare octave:4 ]",
"[ 1/1 → 2/1 | n:4 s:supersquare octave:4 ]",
"[ 1/1 → 2/1 | n:7 s:supersquare octave:4 ]",
"[ 2/1 → 3/1 | n:0 s:supersquare octave:5 ]",
"[ 2/1 → 3/1 | n:4 s:supersquare octave:5 ]",
"[ 2/1 → 3/1 | n:7 s:supersquare octave:5 ]",
"[ 3/1 → 4/1 | n:0 s:supersquare octave:6 ]",
"[ 3/1 → 4/1 | n:4 s:supersquare octave:6 ]",
"[ 3/1 → 4/1 | n:7 s:supersquare octave:6 ]",
"[ 0/1 → 1/1 | note:F3 s:supersaw octave:0 ]",
"[ 0/1 → 1/1 | note:C4 s:supersaw octave:0 ]",
"[ 0/1 → 1/1 | note:F4 s:supersaw octave:0 ]",
"[ 1/1 → 2/1 | note:F3 s:supersaw octave:1 ]",
"[ 1/1 → 2/1 | note:C4 s:supersaw octave:1 ]",
"[ 1/1 → 2/1 | note:F4 s:supersaw octave:1 ]",
"[ 2/1 → 3/1 | note:F3 s:supersaw octave:2 ]",
"[ 2/1 → 3/1 | note:C4 s:supersaw octave:2 ]",
"[ 2/1 → 3/1 | note:F4 s:supersaw octave:2 ]",
"[ 3/1 → 4/1 | note:F3 s:supersaw octave:3 ]",
"[ 3/1 → 4/1 | note:C4 s:supersaw octave:3 ]",
"[ 3/1 → 4/1 | note:F4 s:supersaw octave:3 ]",
]
`;
@@ -8879,6 +9293,19 @@ exports[`runs examples > example "rev" example index 0 1`] = `
]
`;
exports[`runs examples > example "revv" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:g ]",
"[ 1/2 → 1/1 | note:e ]",
"[ 1/1 → 3/2 | note:d ]",
"[ 3/2 → 2/1 | note:c ]",
"[ 2/1 → 5/2 | note:g ]",
"[ 5/2 → 3/1 | note:e ]",
"[ 3/1 → 7/2 | note:d ]",
"[ 7/2 → 4/1 | note:c ]",
]
`;
exports[`runs examples > example "ribbon" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:d ]",
@@ -12521,54 +12948,54 @@ exports[`runs examples > example "wchooseCycles" example index 0 1`] = `
exports[`runs examples > example "wchooseCycles" example index 1 1`] = `
[
"[ 0/1 → 1/12 | s:bd ]",
"[ 1/12 → 1/6 | s:bd ]",
"[ 1/6 → 1/4 | s:bd ]",
"[ 1/4 → 1/3 | s:bd ]",
"[ 1/3 → 5/12 | s:bd ]",
"[ 5/12 → 1/2 | s:bd ]",
"[ 1/2 → 7/12 | s:sd ]",
"[ 7/12 → 2/3 | s:sd ]",
"[ 2/3 → 3/4 | s:sd ]",
"[ 3/4 → 5/6 | s:bd ]",
"[ 5/6 → 11/12 | s:bd ]",
"[ 11/12 → 1/1 | s:bd ]",
"[ 1/1 → 13/12 | s:bd ]",
"[ 13/12 → 7/6 | s:bd ]",
"[ 7/6 → 5/4 | s:bd ]",
"[ 5/4 → 4/3 | s:bd ]",
"[ 4/3 → 17/12 | s:bd ]",
"[ 17/12 → 3/2 | s:bd ]",
"[ 3/2 → 19/12 | s:hh ]",
"[ 19/12 → 5/3 | s:hh ]",
"[ 5/3 → 7/4 | s:hh ]",
"[ 7/4 → 11/6 | s:bd ]",
"[ 11/6 → 23/12 | s:bd ]",
"[ 23/12 → 2/1 | s:bd ]",
"[ 2/1 → 25/12 | s:hh ]",
"[ 25/12 → 13/6 | s:hh ]",
"[ 13/6 → 9/4 | s:hh ]",
"[ 9/4 → 7/3 | s:hh ]",
"[ 7/3 → 29/12 | s:hh ]",
"[ 29/12 → 5/2 | s:hh ]",
"[ 5/2 → 31/12 | s:bd ]",
"[ 31/12 → 8/3 | s:bd ]",
"[ 8/3 → 11/4 | s:bd ]",
"[ 11/4 → 17/6 | s:bd ]",
"[ 17/6 → 35/12 | s:bd ]",
"[ 35/12 → 3/1 | s:bd ]",
"[ 3/1 → 37/12 | s:bd ]",
"[ 37/12 → 19/6 | s:bd ]",
"[ 19/6 → 13/4 | s:bd ]",
"[ 13/4 → 10/3 | s:sd ]",
"[ 10/3 → 41/12 | s:sd ]",
"[ 41/12 → 7/2 | s:sd ]",
"[ 7/2 → 43/12 | s:hh ]",
"[ 43/12 → 11/3 | s:hh ]",
"[ 11/3 → 15/4 | s:hh ]",
"[ 15/4 → 23/6 | s:bd ]",
"[ 23/6 → 47/12 | s:bd ]",
"[ 47/12 → 4/1 | s:bd ]",
"[ 0/1 → 1/12 | note:c ]",
"[ 1/12 → 1/6 | note:c ]",
"[ 1/6 → 1/4 | note:c ]",
"[ 1/4 → 1/3 | note:c ]",
"[ 1/3 → 5/12 | note:c ]",
"[ 5/12 → 1/2 | note:c ]",
"[ 1/2 → 7/12 | note:f ]",
"[ 7/12 → 2/3 | note:f ]",
"[ 2/3 → 3/4 | note:f ]",
"[ 3/4 → 5/6 | note:c ]",
"[ 5/6 → 11/12 | note:c ]",
"[ 11/12 → 1/1 | note:c ]",
"[ 1/1 → 13/12 | note:c ]",
"[ 13/12 → 7/6 | note:c ]",
"[ 7/6 → 5/4 | note:c ]",
"[ 5/4 → 4/3 | note:c ]",
"[ 4/3 → 17/12 | note:c ]",
"[ 17/12 → 3/2 | note:c ]",
"[ 3/2 → 19/12 | note:a ]",
"[ 19/12 → 5/3 | note:a ]",
"[ 5/3 → 7/4 | note:a ]",
"[ 7/4 → 11/6 | note:c ]",
"[ 11/6 → 23/12 | note:c ]",
"[ 23/12 → 2/1 | note:c ]",
"[ 2/1 → 25/12 | note:a ]",
"[ 25/12 → 13/6 | note:a ]",
"[ 13/6 → 9/4 | note:a ]",
"[ 9/4 → 7/3 | note:a ]",
"[ 7/3 → 29/12 | note:a ]",
"[ 29/12 → 5/2 | note:a ]",
"[ 5/2 → 31/12 | note:c ]",
"[ 31/12 → 8/3 | note:c ]",
"[ 8/3 → 11/4 | note:c ]",
"[ 11/4 → 17/6 | note:c ]",
"[ 17/6 → 35/12 | note:c ]",
"[ 35/12 → 3/1 | note:c ]",
"[ 3/1 → 37/12 | note:c ]",
"[ 37/12 → 19/6 | note:c ]",
"[ 19/6 → 13/4 | note:c ]",
"[ 13/4 → 10/3 | note:f ]",
"[ 10/3 → 41/12 | note:f ]",
"[ 41/12 → 7/2 | note:f ]",
"[ 7/2 → 43/12 | note:a ]",
"[ 43/12 → 11/3 | note:a ]",
"[ 11/3 → 15/4 | note:a ]",
"[ 15/4 → 23/6 | note:c ]",
"[ 23/6 → 47/12 | note:c ]",
"[ 47/12 → 4/1 | note:c ]",
]
`;
+1
View File
@@ -1,4 +1,5 @@
{
"name": "dbpatch",
"dependencies": {
"csv": "^6.3.11"
},
+14
View File
@@ -0,0 +1,14 @@
fetch('https://codeberg.org/api/v1/repos/uzu/strudel/pulls?state=closed&page=1')
.then((res) => res.json())
.then((pulls) => {
const r = pulls
.filter((pull) => pull.merged)
.sort((a, b) => new Date(b.closed_at) - new Date(a.closed_at))
.map((pull) => `${pull.closed_at} ${pull.title} by ${pull.user.login || '?'} in: [#${pull.number}](${pull.url}) `)
.join('\n');
console.log(r);
});
/*
*/
@@ -8,3 +8,34 @@ export function ActionButton({ children, label, labelIsHidden, className, ...but
</button>
);
}
export function SpecialActionButton(props) {
const { className, ...buttonProps } = props;
return (
<ActionButton
{...buttonProps}
className={cx('bg-background p-2 max-w-[300px] rounded-md hover:opacity-50', className)}
/>
);
}
export function ActionInput({ label, className, ...props }) {
return (
<label className={cx('inline-flex items-center cursor-pointer', className)}>
<input {...props} className="sr-only peer" />
<span className="inline-flex items-center peer-hover:opacity-50">{label}</span>
</label>
);
}
export function SpecialActionInput({ className, ...props }) {
return (
<ActionInput
{...props}
className={className}
label={<span className="bg-background p-2 max-w-[300px] rounded-md">{props.label}</span>}
/>
);
}
@@ -0,0 +1,29 @@
import { errorLogger } from '@strudel/core';
import { useSettings, storePrebakeScript } from '../../../settings.mjs';
import { SpecialActionInput } from '../button/action-button';
async function importScript(script) {
const reader = new FileReader();
reader.readAsText(script);
reader.onload = () => {
const text = reader.result;
storePrebakeScript(text);
};
reader.onerror = () => {
errorLogger(new Error('failed to import prebake script'), 'importScript');
};
}
export function ImportPrebakeScriptButton() {
const settings = useSettings();
return (
<SpecialActionInput
type="file"
label="import prebake script"
accept=".strudel"
onChange={(e) => importScript(e.target.files[0])}
/>
);
}
@@ -7,6 +7,8 @@ import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
import { confirmDialog } from '../../util.mjs';
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio';
import { ActionButton, SpecialActionButton } from '../button/action-button.jsx';
import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx';
function Checkbox({ label, value, onChange, disabled = false }) {
return (
@@ -113,6 +115,7 @@ export function SettingsTab({ started }) {
isTabIndentationEnabled,
isMultiCursorEnabled,
patternAutoStart,
includePrebakeScriptInShare,
} = useSettings();
const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@@ -204,6 +207,15 @@ export function SettingsTab({ started }) {
/>
</FormItem>
</div>
<FormItem label="Prebake">
<ImportPrebakeScriptButton />
<Checkbox
label="Include prebake script in share"
onChange={(cbEvent) => settingsMap.setKey('includePrebakeScriptInShare', cbEvent.target.checked)}
value={includePrebakeScriptInShare}
/>
</FormItem>
<FormItem label="Keybindings">
<ButtonGroup
value={keybindings}
@@ -313,8 +325,7 @@ export function SettingsTab({ started }) {
</FormItem>
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
<FormItem label="Reset Settings">
<button
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
<SpecialActionButton
onClick={() => {
confirmDialog('Sure?').then((r) => {
if (r) {
@@ -325,7 +336,7 @@ export function SettingsTab({ started }) {
}}
>
restore default settings
</button>
</SpecialActionButton>
</FormItem>
</div>
);
+34 -15
View File
@@ -14,6 +14,10 @@ import { prebake } from '@src/repl/prebake.mjs';
const getSamples = (samples) =>
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function SoundsTab() {
const sounds = useStore(soundMap);
@@ -56,9 +60,7 @@ export function SoundsTab() {
// holds mutable ref to current triggered sound
const trigRef = useRef();
// Used to cycle through sound previews on banks with multiple sounds
let soundPreviewIdx = 0;
const numRef = useRef(0);
// stop current sound on mouseup
useEvent('mouseup', () => {
@@ -66,6 +68,14 @@ export function SoundsTab() {
trigRef.current = undefined;
ref?.stop?.(getAudioContext().currentTime + 0.01);
});
useEvent('keydown', (e) => {
if (!isNaN(Number(e.key))) {
numRef.current = Number(e.key);
}
});
useEvent('keyup', (e) => {
numRef.current = 0;
});
return (
<div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground">
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
@@ -115,25 +125,34 @@ export function SoundsTab() {
const params = {
note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined,
s: name,
n: soundPreviewIdx,
n: numRef.current,
clip: 1,
release: 0.5,
sustain: 1,
duration: 0.5,
};
soundPreviewIdx++;
const onended = () => trigRef.current?.node?.disconnect();
try {
// Pre-load the sample by calling onTrigger with a future time
// This triggers the loading but schedules playback for later
const time = ctx.currentTime + 0.5; // Give 500ms for loading
const ref = await onTrigger(time, params, onended);
trigRef.current = ref;
if (ref?.node) {
connectToDestination(ref.node);
// Attempt to play the sample and retry every 200ms until 10 attempts have been reached
let errMsg;
for (let attempt = 0; attempt < 10; attempt++) {
try {
// Pre-load the sample by calling onTrigger with a future time
// This triggers the loading but schedules playback for later
const time = ctx.currentTime + 0.05; // Give 50ms for loading
const ref = await onTrigger(time, params, onended);
trigRef.current = ref;
if (ref?.node) {
connectToDestination(ref.node);
break;
}
} catch (err) {
errMsg = err;
}
if (attempt == 9) {
console.warn('Failed to trigger sound after 10 attempts' + (errMsg ? `: ${errMsg}` : ''));
} else {
await wait(200);
}
} catch (err) {
console.warn('Failed to trigger sound:', err);
}
}}
>
+2
View File
@@ -3,6 +3,8 @@ import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@st
import { registerSamplesFromDB } from './idbutils.mjs';
import './piano.mjs';
import './files.mjs';
import { settingsMap } from '@src/settings.mjs';
import { evaluate } from '@strudel/transpiler';
const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
+15 -5
View File
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core';
import { getDrawContext } from '@strudel/draw';
import { transpiler } from '@strudel/transpiler';
import { evaluate, transpiler } from '@strudel/transpiler';
import {
getAudioContextCurrentTime,
webaudioOutput,
@@ -63,11 +63,10 @@ async function getModule(name) {
const initialCode = `// LOADING`;
export function useReplContext() {
const { isSyncEnabled, audioEngineTarget } = useSettings();
const { isSyncEnabled, audioEngineTarget, prebakeScript, includePrebakeScriptInShare } = useSettings();
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
const init = useCallback(() => {
const drawTime = [-2, 2];
const drawContext = getDrawContext();
@@ -84,7 +83,12 @@ export function useReplContext() {
pattern: silence,
drawTime,
drawContext,
prebake: async () => Promise.all([modulesLoading, presets]),
prebake: async () =>
Promise.all([modulesLoading, presets]).then(() => {
if (prebakeScript?.length) {
return evaluate(prebakeScript ?? '');
}
}),
onUpdateState: (state) => {
setReplState({ ...state });
},
@@ -214,7 +218,13 @@ export function useReplContext() {
editorRef.current.repl.evaluate(code);
};
const handleShare = async () => shareCode(replState.code);
const handleShare = async () => {
let code = replState.code;
if (includePrebakeScriptInShare) {
code = prebakeScript + '\n' + code;
}
shareCode(code);
};
const context = {
started,
pending,
+5 -6
View File
@@ -1,11 +1,10 @@
import { evalScope, hash2code, logger } from '@strudel/core';
import { code2hash, evalScope, hash2code, logger } from '@strudel/core';
import { settingPatterns } from '../settings.mjs';
import { setVersionDefaults } from '@strudel/webaudio';
import { getMetadata } from '../metadata_parser';
import { isTauri } from '../tauri.mjs';
import './Repl.css';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs';
@@ -109,9 +108,8 @@ export function confirmDialog(msg) {
});
}
let lastShared;
//RIP due to SPAM
// let lastShared;
// export async function shareCode(codeToShare) {
// // const codeToShare = activeCode || code;
// if (lastShared === codeToShare) {
@@ -146,9 +144,10 @@ let lastShared;
// });
// }
export async function shareCode() {
export async function shareCode(codeToShare) {
try {
const shareUrl = window.location.href;
const hash = '#' + code2hash(codeToShare);
const shareUrl = window.location.origin + window.location.pathname + hash;
if (isTauri()) {
await writeText(shareUrl);
} else {
+5
View File
@@ -45,11 +45,13 @@ export const defaultSettings = {
isPanelOpen: true,
togglePanelTrigger: 'click', //click | hover
userPatterns: '{}',
prebakeScript: '',
audioEngineTarget: audioEngineTargets.webaudio,
isButtonRowHidden: false,
isCSSAnimationDisabled: false,
maxPolyphony: 128,
multiChannelOrbits: false,
includePrebakeScriptInShare: true,
};
let search = null;
@@ -96,6 +98,7 @@ export function useSettings() {
isPanelOpen: parseBoolean(state.isPanelOpen),
userPatterns: userPatterns,
multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
includePrebakeScriptInShare: parseBoolean(state.includePrebakeScriptInShare),
patternAutoStart: isUdels()
? false
: state.patternAutoStart === undefined
@@ -108,6 +111,8 @@ export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool);
export const storePrebakeScript = (script) => settingsMap.setKey('prebakeScript', script);
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
const patternSetting = (key) =>