Compare commits

..

7 Commits

Author SHA1 Message Date
Felix Roos 301fb5e59a Merge remote-tracking branch 'origin/main' into example-fixes 2025-11-12 21:35:03 +01:00
Felix Roos 9ac691c19e fix: naming in draw package readme 2025-11-12 21:26:00 +01:00
Felix Roos 6e7512016c chore: package-lock 2025-11-12 21:25:47 +01:00
Felix Roos 3bbd808783 fix: use published web package for headless-repl 2025-11-12 21:25:40 +01:00
Felix Roos 7b245abd1a fix: headless-repl stop button 2025-11-12 21:25:18 +01:00
Felix Roos 9e5c9c7539 fix: update web example 2025-11-12 21:25:08 +01:00
Felix Roos d14194db6a fix: codemirror-repl example 2025-11-12 21:24:56 +01:00
39 changed files with 498 additions and 1650 deletions
-88
View File
@@ -1,88 +0,0 @@
# 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)
+1 -1
View File
@@ -1,4 +1,4 @@
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
<script src="https://unpkg.com/@strudel/web@1.2.6"></script>
<button id="play">PLAY</button>
<script>
initStrudel({
+8 -8
View File
@@ -12,14 +12,14 @@
"vite": "^6.0.11"
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
"@strudel/codemirror": "1.2.6",
"@strudel/core": "1.2.5",
"@strudel/draw": "1.2.5",
"@strudel/mini": "1.2.5",
"@strudel/soundfonts": "1.2.6",
"@strudel/tonal": "1.2.5",
"@strudel/transpiler": "1.2.5",
"@strudel/webaudio": "1.2.6"
},
"engines": {
"node": ">=18.0.0"
+5 -8
View File
@@ -1,6 +1,6 @@
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('https://strudel.b-cdn.net/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
"<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
// make it 22 edo
@@ -28,13 +28,12 @@ samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-dru
.mask("<0@4 1@12>/4")
.reset("<x@15 [x(3,8) x*[4 8]]>")
// wait for it...
).fast(2/3)
).fast(4/3)
//.crush(6) // remove "//" if you dare`;
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('https://strudel.b-cdn.net/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
addVoicings('hip', {
m11: ['2M 3m 4P 7m'],
@@ -61,16 +60,14 @@ stack(
s("rim*4").end(.05).bank('RolandTR808').speed(.8).room(.2)
)
)
.late("[0 .05]*2").late(12)
.late("[0 .05]*2").late(12).fast(2)
`;
export const funk42 = `// froos - how to funk in 42 lines of code
// adapted from "how to funk in two minutes" by marc rebillet https://www.youtube.com/watch?v=3vBwRfQbXkg
// thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('https://strudel.b-cdn.net/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
setcps(.5)
+1 -1
View File
@@ -13,7 +13,7 @@
<button id="c">C</button>
<button id="stop">stop</button>
<script type="module">
import { initStrudel } from '@strudel/web';
import { initStrudel, hush } from '@strudel/web';
initStrudel({
prebake: () => samples('github:tidalcycles/dirt-samples'),
});
+1 -1
View File
@@ -13,7 +13,7 @@
"vite": "^6.0.11"
},
"dependencies": {
"@strudel/web": "workspace:*"
"@strudel/web": "1.2.6"
},
"engines": {
"node": ">=18.0.0"
+6 -51
View File
@@ -1294,8 +1294,6 @@ 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');
@@ -1304,8 +1302,6 @@ 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');
@@ -1314,23 +1310,8 @@ 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
@@ -1380,19 +1361,6 @@ 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
*
@@ -1439,20 +1407,7 @@ export const { hpsync } = registerControl('hpsync');
* @name hpdepth
* @param {number | Pattern} depth depth of modulation
*/
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');
export const { hpdepth } = registerControl('hpdepth');
/**
* Shape of the LFO for the highpass filter
@@ -1865,12 +1820,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").scale("F:minor").s('supersaw').octave("<0 1 2 3>")
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
* @superDirtOnly
*/
export const { octave, oct } = registerControl('octave', 'oct');
export const { octave } = registerControl('octave');
// ['ophatdecay'],
// TODO: example
@@ -1878,7 +1833,6 @@ export const { octave, oct } = registerControl('octave', 'oct');
* 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(
@@ -1886,7 +1840,7 @@ export const { octave, oct } = registerControl('octave', 'oct');
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
* )
*/
export const { orbit } = registerControl('orbit', 'o');
export const { orbit } = registerControl('orbit');
// 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
@@ -2310,6 +2264,7 @@ export const { tsdelay } = registerControl('tsdelay');
export const { real } = registerControl('real');
export const { imag } = registerControl('imag');
export const { enhance } = registerControl('enhance');
export const { partials } = registerControl('partials');
export const { comb } = registerControl('comb');
export const { smear } = registerControl('smear');
export const { scram } = registerControl('scram');
+2 -65
View File
@@ -25,7 +25,7 @@ import {
stringifyValues,
} from './util.mjs';
import drawLine from './drawLine.mjs';
import { errorLogger, logger } from './logger.mjs';
import { 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) {
errorLogger(err, 'query');
logger(`[query]: ${err.message}`, 'error');
return [];
}
}
@@ -3624,66 +3624,3 @@ for (const name of distAlgoNames) {
return this.distort(argsPat);
};
}
/**
* Turns a list of patterns into a single pattern which outputs list-values
*
* @name parray
* @returns Pattern
*/
export const parray = (pats) => {
const pack = (...xs) => xs;
let acc = pure(curry(pack, null, pats.length));
for (const p of pats) acc = acc.appBoth(reify(p));
return acc;
};
const _ensureListPattern = (list) => {
if (Array.isArray(list)) {
return parray(list);
}
return reify(list);
};
/**
* Scale the magnitude of the harmonics of one of the core synths ('sine', 'tri', 'saw', ..)
*
* Can also be used to create a new synth via `s('user').partials(...)`
*
* @name partials
* @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")
* .partials([1, 0, 1, 0, 0, 1])
* @example
* s("saw").seg(8).n(irand(12)).scale("G#:minor")
* .partials(binaryL(irand(256).add("1")))
*/
Pattern.prototype.partials = function (list) {
return this.withValue((v) => (l) => ({ ...v, partials: l })).appLeft(_ensureListPattern(list));
};
// Also create a top-level function
export const partials = (list) => {
return _ensureListPattern(list).as('partials');
};
/**
* Rotates the harmonics of one of the core synths ('sine', 'tri', 'saw', 'user', ..) by a list of phases
*
* @name phases
* @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
* s("saw").seg(8).n(irand(12)).scale("G#1:minor")
* .partials(partials([1, 1, 1]))
* .superimpose(x => x.phases([0.5, 0.5, 0.5]))
*/
Pattern.prototype.phases = function (list) {
return this.withValue((v) => (l) => ({ ...v, phases: l })).appLeft(_ensureListPattern(list));
};
// Also create a top-level function
export const phases = (list) => {
return _ensureListPattern(list).as('phases');
};
+7 -17
View File
@@ -152,9 +152,9 @@ export function repl({
// allows muting a pattern x with x_ or _x
return silence;
}
if (id.includes('$')) {
if (id === '$') {
// allows adding anonymous patterns with $:
id = `${id}${anonymousIndex}`;
id = `$${anonymousIndex}`;
anonymousIndex++;
}
pPatterns[id] = this;
@@ -215,19 +215,8 @@ 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)) {
// 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);
}
patterns.push(value.withState((state) => state.setControls({ id: key })));
}
if (eachTransform) {
// Explicit lambda so only element (not index and array) are passed
@@ -238,13 +227,14 @@ export function repl({
pattern = eachTransform(pattern);
}
if (allTransforms.length) {
for (const transform of allTransforms) {
pattern = transform(pattern);
for (let i in allTransforms) {
pattern = allTransforms[i](pattern);
}
}
if (!isPattern(pattern)) {
pattern = silence;
const message = `got "${typeof evaluated}" instead of pattern`;
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
}
logger(`[eval] code updated`);
pattern = await setPattern(pattern, autostart);
+3 -48
View File
@@ -228,7 +228,7 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
export const run = (n) => saw.range(0, n).round().segment(n);
/**
* Creates a binary pattern from a number.
* Creates a pattern from a binary number.
*
* @name binary
* @param {number} n - input number to convert to binary
@@ -242,7 +242,7 @@ export const binary = (n) => {
};
/**
* Creates a binary pattern from a number, padded to n bits long.
* Creates a pattern from a binary number, padded to n bits long.
*
* @name binaryN
* @param {number} n - input number to convert to binary
@@ -258,51 +258,6 @@ export const binaryN = (n, nBits = 16) => {
return reify(n).segment(nBits).brshift(bitPos).band(pure(1));
};
/**
* Creates a binary list pattern from a number.
*
* @name binaryL
* @param {number} n - input number to convert to binary
* s("saw").seg(8)
* .partials(binaryL(irand(4096).add(1)))
*/
export const binaryL = (n) => {
const nBits = reify(n).log2(0).floor().add(1);
return binaryNL(n, nBits);
};
/**
* Creates a binary list pattern from a number, padded to n bits long.
*
* @name binaryNL
* @param {number} n - input number to convert to binary
* @param {number} nBits - pattern length, defaults to 16
*/
export const binaryNL = (n, nBits = 16) => {
return reify(n)
.withValue((v) => (bits) => {
const bList = [];
for (let i = bits - 1; i >= 0; i--) {
bList.push((v >> i) & 1);
}
return bList;
})
.appLeft(reify(nBits));
};
/**
* Creates a list of random numbers of the given length
*
* @name randL
* @param {number} n Number of random numbers to sample
* @example
* s("saw").seg(16).n(irand(12)).scale("F1:minor")
* .partials(randL(8))
*/
export const randL = (n) => {
return signal((t) => (nVal) => timeToRands(t, nVal).map(Math.abs)).appLeft(reify(n));
};
export const randrun = (n) => {
return signal((t) => {
// Without adding 0.5, the first cycle is always 0,1,2,3,...
@@ -524,7 +479,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
* @example
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example
* wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note()
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
* @example
* // The probability can itself be a pattern
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel/canvas
# @strudel/draw
Helpers for drawing with the Canvas API and Strudel
## Install
```sh
npm i @strudel/canvas --save
npm i @strudel/draw --save
```
-1
View File
@@ -35,7 +35,6 @@ export async function initHydra(options = {}) {
hydra.synth.s0.init({ src: canvas });
}
}
return hydra;
}
export function clearHydra() {
+3 -1
View File
@@ -9,7 +9,6 @@ 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) => {
@@ -55,6 +54,7 @@ async function getBanks(directory, flat = false) {
banks[bank].push(subDir);
return subDir;
});
banks._base = `http://localhost:5432`;
return { banks, files };
}
@@ -134,6 +134,8 @@ 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();
+1 -1
View File
@@ -89,7 +89,7 @@ superdough({ s: 'bd', delay: 0.5 }, 0, 1);
- `decay`: seconds of decay phase
- `sustain`: gain of sustain phase
- `release`: seconds of release phase
- `deadline`: seconds from audio context initialization before playing the sound (getAudioContextCurrentTime() = immediate)
- `deadline`: seconds until the sound should play (0 = immediate)
- `duration`: seconds the sound should last. optional for one shot samples, required for synth sounds
### registerSynthSounds()
+9 -44
View File
@@ -191,7 +191,8 @@ 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, cycle) {
export function createFilter(context, start, end, params, cps) {
let {
frequency,
anchor,
@@ -201,14 +202,12 @@ export function createFilter(context, start, end, params, cps, cycle) {
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 });
@@ -239,25 +238,8 @@ export function createFilter(context, start, end, params, cps, cycle) {
if (sync != null) {
rate = cps * sync;
}
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
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,
};
getParamLfo(context, frequencyParam, start, end, lfoValues);
}
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate };
getParamLfo(context, frequencyParam, start, end, lfoValues);
return filter;
}
@@ -280,15 +262,7 @@ export function drywet(dry, wet, wetAmount = 0) {
let mix = ac.createGain();
dry_gain.connect(mix);
wet_gain.connect(mix);
return {
node: mix,
onended: () => {
dry_gain.disconnect(mix);
wet_gain.disconnect(mix);
dry.disconnect(dry_gain);
wet.disconnect(wet_gain);
},
};
return mix;
}
let curves = ['linear', 'exponential'];
@@ -323,10 +297,6 @@ 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;
}
@@ -380,9 +350,9 @@ const mod = (freq, range = 1, type = 'sine') => {
}
osc.start();
const g = gainNode(range);
const g = new GainNode(ctx, { gain: range });
osc.connect(g); // -range, range
return { node: g, stop: (t) => osc.stop(t), osc: osc };
return { node: g, stop: (t) => osc.stop(t) };
};
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
const carrfreq = frequencyparam.value;
@@ -434,11 +404,6 @@ export function applyFM(param, value, begin) {
modulator.connect(envGain);
envGain.connect(param);
}
fmmod.osc.onended = () => {
envGain.disconnect();
modulator.disconnect();
fmmod.osc.disconnect();
};
}
return { stop };
}
@@ -540,7 +505,7 @@ export const getDistortion = (distort, postgain, algorithm) => {
};
export const getFrequencyFromValue = (value, defaultNote = 36) => {
let { note, freq, octave = 0 } = value;
let { note, freq } = value;
note = note || defaultNote;
if (typeof note === 'string') {
note = noteToMidi(note); // e.g. c3 => 48
@@ -549,7 +514,7 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
if (!freq && typeof note === 'number') {
freq = midiToFreq(note); // + 48);
}
freq *= Math.pow(2, octave);
return Number(freq);
};
+1 -2
View File
@@ -65,9 +65,8 @@ 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,
node: noiseMix,
stop: (time) => noiseOscillator?.stop(time),
};
}
-2
View File
@@ -104,8 +104,6 @@ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt,
player.start();
context.oncomplete = function (event) {
callback(event.renderedBuffer);
filter.disconnect();
player.disconnect();
};
context.startRendering();
+2 -2
View File
@@ -1,4 +1,4 @@
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
import { getCommonSampleInfo } from './util.mjs';
import { registerSound, registerWaveTable } from './index.mjs';
import { getAudioContext } from './audioContext.mjs';
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
@@ -211,7 +211,7 @@ export async function fetchSampleMap(url) {
// not a browser
return;
}
const base = getBaseURL(url);
const base = url.split('/').slice(0, -1).join('/');
if (typeof fetch === 'undefined') {
// skip fetch when in node / testing
return;
+9 -15
View File
@@ -263,8 +263,8 @@ let audioReady;
export async function initAudioOnFirstClick(options) {
if (!audioReady) {
audioReady = new Promise((resolve) => {
document.addEventListener('mousedown', async function listener() {
document.removeEventListener('mousedown', listener);
document.addEventListener('click', async function listener() {
document.removeEventListener('click', listener);
await initAudio(options);
resolve();
});
@@ -481,7 +481,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
for (let i = 0; i <= activeSoundSources.size - maxPolyphony; i++) {
const ch = activeSoundSources.entries().next();
const source = ch.value[1].deref();
const source = ch.value[1];
const chainID = ch.value[0];
const endTime = t + 0.25;
source?.node?.gain?.linearRampToValueAtTime(0, endTime);
@@ -513,7 +513,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
if (soundHandle) {
sourceNode = soundHandle.node;
activeSoundSources.set(chainID, new WeakRef(soundHandle)); // allow GC
activeSoundSources.set(chainID, soundHandle);
}
} else {
throw new Error(`sound ${s} not found! Is it loaded?`);
@@ -553,14 +553,13 @@ 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, cycle);
let lp = () => createFilter(ac, t, end, lpParams, cps);
chain.push(lp());
if (ftype === '24db') {
chain.push(lp());
@@ -582,14 +581,13 @@ 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, cycle);
let hp = () => createFilter(ac, t, end, hpParams, cps);
chain.push(hp());
if (ftype === '24db') {
chain.push(hp());
@@ -611,14 +609,13 @@ 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, cycle);
let bp = () => createFilter(ac, t, end, bpParams, cps);
chain.push(bp());
if (ftype === '24db') {
chain.push(bp());
@@ -668,7 +665,6 @@ 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);
}
@@ -696,8 +692,7 @@ 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);
const send = orbitBus.sendDelay(post, delay);
audioNodes.push(send);
orbitBus.sendDelay(post, delay);
}
// reverb
if (room > 0) {
@@ -713,8 +708,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
roomIR = await loadBuffer(url, ac, ir, 0);
}
orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
const send = orbitBus.sendReverb(post, room);
audioNodes.push(send);
orbitBus.sendReverb(post, room);
}
if (djf != null) {
+2 -2
View File
@@ -78,11 +78,11 @@ export class Orbit {
return this.reverbNode;
}
sendReverb(node, amount) {
return effectSend(node, this.reverbNode, amount);
effectSend(node, this.reverbNode, amount);
}
sendDelay(node, amount) {
return effectSend(node, this.delayNode, amount);
effectSend(node, this.delayNode, amount);
}
duck(t, onsettime = 0, attacktime = 0.1, depth = 1) {
+25 -51
View File
@@ -15,10 +15,9 @@ import {
noises,
webAudioTimeout,
} from './helpers.mjs';
import { logger } from './logger.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const waveforms = ['triangle', 'square', 'sawtooth', 'sine', 'user'];
const waveforms = ['triangle', 'square', 'sawtooth', 'sine'];
const waveformAliases = [
['tri', 'triangle'],
['sqr', 'square'],
@@ -48,17 +47,19 @@ 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);
let sound = getOscillator(s, t, value, () => {
const { duration } = value;
o.onended = () => {
o.disconnect();
g.disconnect();
onended();
});
let { node: o, stop, triggerRelease } = sound;
const { duration } = value;
};
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
@@ -413,13 +414,9 @@ export function registerSynthSounds() {
waveformAliases.forEach(([alias, actual]) => soundMap.set({ ...soundMap.get(), [alias]: soundMap.get()[actual] }));
}
const PI2 = 2 * Math.PI;
export function waveformN(partials, phases, type) {
const isList = typeof partials === 'object';
partials = isList ? partials : new Float32Array(partials).fill(1);
const len = partials.length;
const real = new Float32Array(len + 1);
const imag = new Float32Array(len + 1);
export function waveformN(partials, type) {
const real = new Float32Array(partials + 1);
const imag = new Float32Array(partials + 1);
const ac = getAudioContext();
const osc = ac.createOscillator();
@@ -427,29 +424,20 @@ export function waveformN(partials, phases, type) {
sawtooth: (n) => [0, -1 / n],
square: (n) => [0, n % 2 === 0 ? 0 : 1 / n],
triangle: (n) => [n % 2 === 0 ? 0 : 1 / (n * n), 0],
user: (_n) => [0, 1],
};
if (!terms[type]) {
throw new Error(`unknown wave type ${type}`);
}
for (let n = 0; n < len; n++) {
const mag = partials[n];
const [r, i] = terms[type](n + 1); // we skip n === 0 as this is dc offset
const phase = phases?.[n] ?? 0;
// Scale by `partials`
let R = r * mag;
let I = i * mag;
// Apply rotation by the phase
if (phase !== 0) {
const c = Math.cos(PI2 * phase);
const s = Math.sin(PI2 * phase);
R = c * R - s * I;
I = s * R + c * I;
}
real[n + 1] = R;
imag[n + 1] = I;
real[0] = 0; // dc offset
imag[0] = 0;
let n = 1;
while (n <= partials) {
const [r, i] = terms[type](n);
real[n] = r;
imag[n] = i;
n++;
}
const wave = ac.createPeriodicWave(real, imag);
@@ -458,28 +446,21 @@ export function waveformN(partials, phases, type) {
}
// expects one of waveforms as s
export function getOscillator(s, t, value, onended) {
const { duration, noise = 0 } = value;
const partials = value.partials ?? value.n;
export function getOscillator(s, t, value) {
let { n: partials, duration, noise = 0 } = value;
let o;
if (s === 'user' && !partials) {
logger(
`[superdough] Synth 'user' was selected, but partials not specified. Defaulting to triangle. Use pat.partials to setup custom waveform`,
);
s = 'triangle';
}
s = s === 'user' && !partials ? 'triangle' : s;
// If no partials are given, use stock waveforms
if (!partials || partials?.length === 0 || s === 'sine') {
if (!partials || s === 'sine') {
o = getAudioContext().createOscillator();
o.type = s || 'triangle';
}
// generate custom waveform if partials are given
else {
o = waveformN(partials, value.phases, s);
o = waveformN(partials, s);
}
// set frequency
o.frequency.value = getFrequencyFromValue(value);
o.start(t);
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
@@ -492,13 +473,6 @@ export function getOscillator(s, t, value, onended) {
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,13 +114,3 @@ 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('/');
}
};
+5 -17
View File
@@ -45,8 +45,7 @@ if (typeof GainNode !== 'undefined') {
throw new Error('vowel: unknown vowel ' + letter);
}
const { gains, qs, freqs } = vowelFormant[letter];
this.makeupGain = ac.createGain();
this.audioNodes = [];
const makeupGain = ac.createGain();
for (let i = 0; i < 5; i++) {
const gain = ac.createGain();
gain.gain.value = gains[i];
@@ -54,25 +53,14 @@ if (typeof GainNode !== 'undefined') {
filter.type = 'bandpass';
filter.Q.value = qs[i];
filter.frequency.value = freqs[i];
super.connect(filter);
this.connect(filter);
filter.connect(gain);
this.audioNodes.push(filter);
gain.connect(this.makeupGain);
this.audioNodes.push(gain);
gain.connect(makeupGain);
}
this.makeupGain.gain.value = 8; // how much makeup gain to add?
makeupGain.gain.value = 8; // how much makeup gain to add?
this.connect = (target) => makeupGain.connect(target);
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) {
+2 -3
View File
@@ -1,5 +1,5 @@
import { getAudioContext, registerSound } from './index.mjs';
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
import { getCommonSampleInfo } from './util.mjs';
import {
applyFM,
applyParameterModulators,
@@ -190,7 +190,6 @@ 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;
@@ -201,7 +200,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
}
return fetch(url)
.then((res) => res.json())
.then((json) => _processTables(json, base, frameLen, options))
.then((json) => _processTables(json, url, frameLen, options))
.catch((error) => {
console.error(error);
throw new Error(`error loading "${url}"`);
+173 -237
View File
@@ -6,57 +6,51 @@ import OLAProcessor from './ola-processor';
import FFT from './fft.js';
import { getDistortionAlgorithm } from './helpers.mjs';
const blockSize = 128;
const PI = Math.PI;
const TWO_PI = 2 * PI;
const INVSR = 1 / sampleRate;
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const mod = (n, m) => ((n % m) + m) % m;
const lerp = (a, b, n) => n * (b - a) + a;
const pv = (arr, n) => arr[n] ?? arr[0];
const frac = (x) => x - Math.floor(x);
const ffloor = (x) => x | 0; // fast floor for non-negative
// Fast integer ops for non-negative values
const ffloor = (x) => x | 0;
const fround = (x) => ffloor(x + 0.5);
const fceil = (x) => ffloor(x + 1);
const ffrac = (x) => x - ffloor(x);
const fast_tanh = (x) => {
const x2 = x ** 2;
return (x * (27.0 + x2)) / (27.0 + 9.0 * x2);
};
// Optimized per-voice detuner which precomputes constants
const getDetuner = (unison, detune) => {
const getUnisonDetune = (unison, detune, voiceIndex) => {
if (unison < 2) {
return (_voiceIdx) => 0;
return 0;
}
const scale = detune / (unison - 1);
const center = detune * 0.5;
return (voiceIdx) => voiceIdx * scale - center;
return lerp(-detune * 0.5, detune * 0.5, voiceIndex / (unison - 1));
};
const applySemitoneDetuneToFrequency = (frequency, detune) => {
return frequency * Math.pow(2, detune / 12);
};
// Restrict phase to the range [0, maxPhase) via wrapping
function wrapPhase(phase, maxPhase = 1) {
if (phase >= maxPhase) {
phase -= maxPhase;
} else if (phase < 0) {
phase += maxPhase;
}
return phase;
}
const blockSize = 128;
// Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
function polyBlep(phase, dt) {
dt = Math.min(dt, 1 - dt);
const invdt = 1 / dt;
// Start of cycle
if (phase < dt) {
phase *= invdt;
return 2 * phase - phase ** 2 - 1;
phase /= dt;
// 2 * (phase - phase^2/2 - 0.5)
return phase + phase - phase * phase - 1;
}
// End of cycle
else if (phase > 1 - dt) {
phase = (phase - 1) * invdt;
return phase ** 2 + 2 * phase + 1;
phase = (phase - 1) / dt;
// 2 * (phase^2/2 + phase + 0.5)
return phase * phase + phase + phase + 1;
}
// 0 otherwise
else {
return 0;
@@ -72,7 +66,7 @@ const waveshapes = {
return phase / skew;
},
sine(phase) {
return Math.sin(TWO_PI * phase) * 0.5 + 0.5;
return Math.sin(Math.PI * 2 * phase) * 0.5 + 0.5;
},
ramp(phase) {
return phase;
@@ -106,6 +100,12 @@ const waveshapes = {
return v - polyBlep(phase, dt);
},
};
function getParamValue(block, param) {
if (param.length > 1) {
return param[block];
}
return param[0];
}
const waveShapeNames = Object.keys(waveshapes);
class LFOProcessor extends AudioWorkletProcessor {
@@ -165,9 +165,9 @@ class LFOProcessor extends AudioWorkletProcessor {
const blockSize = output[0].length ?? 0;
if (this.phase == null) {
this.phase = ffrac(time * frequency + phaseoffset);
this.phase = mod(time * frequency + phaseoffset, 1);
}
const dt = frequency * INVSR;
const dt = frequency / sampleRate;
for (let n = 0; n < blockSize; n++) {
for (let i = 0; i < output.length; i++) {
let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
@@ -293,8 +293,8 @@ class TwoPoleFilter {
// Out of bound values can produce NaNs
resonance = clamp(resonance, 0, 1);
cutoff = clamp(cutoff, 0, sampleRate / 2 - 1);
const c = clamp(2 * Math.sin(cutoff * PI * INVSR), 0, 1.14);
const r = Math.pow(0.5, 8 * resonance + 1);
const c = clamp(2 * Math.sin(cutoff * (_PI / sampleRate)), 0, 1.14);
const r = Math.pow(0.5, (resonance + 0.125) / 0.125);
const mrc = 1 - r * c;
this.s0 = mrc * this.s0 - c * this.s1 + c * s; // bpf
this.s1 = mrc * this.s1 + c * this.s0; // lpf
@@ -353,6 +353,11 @@ class DJFProcessor extends AudioWorkletProcessor {
}
registerProcessor('djf-processor', DJFProcessor);
function fast_tanh(x) {
const x2 = x * x;
return (x * (27.0 + x2)) / (27.0 + 9.0 * x2);
}
const _PI = 3.14159265359;
//adapted from https://github.com/TheBouteillacBear/webaudioworklet-wasm?tab=MIT-1-ov-file
class LadderProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
@@ -390,7 +395,7 @@ class LadderProcessor extends AudioWorkletProcessor {
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
let cutoff = parameters.frequency[0];
cutoff = cutoff * TWO_PI * INVSR;
cutoff = (cutoff * 2 * _PI) / sampleRate;
cutoff = cutoff > 1 ? 1 : cutoff;
const k = Math.min(8, resonance * 0.13);
@@ -503,7 +508,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
name: 'voices',
defaultValue: 5,
min: 1,
automationRate: 'k-rate',
},
];
}
@@ -515,37 +519,40 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
// this.port.postMessage({ type: 'onended' });
return false;
}
const output = outputs[0];
const voices = params.voices[0]; // k-rate
for (let i = 0; i < output[0].length; i++) {
const detune = pv(params.detune, i);
const voices = pv(params.voices, i);
const freqspread = pv(params.freqspread, i);
const panspread = pv(params.panspread, i) * 0.5 + 0.5;
let gainL = Math.sqrt(1 - panspread);
let gainR = Math.sqrt(panspread);
const gain1 = Math.sqrt(1 - panspread);
const gain2 = Math.sqrt(panspread);
let freq = pv(params.frequency, i);
// Main detuning
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
const detuner = getDetuner(voices, freqspread);
for (let n = 0; n < voices; n++) {
const isOdd = (n & 1) == 1;
let gainL = gain1;
let gainR = gain2;
// invert right and left gain
if (isOdd) {
gainL = gain2;
gainR = gain1;
}
// Individual voice detuning
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
const freqVoice = applySemitoneDetuneToFrequency(freq, getUnisonDetune(voices, freqspread, n));
// We must wrap this here because it is passed into sawblep below which
// has domain [0, 1]
const dt = frac(freqVoice * INVSR);
const dt = mod(freqVoice / sampleRate, 1);
this.phase[n] = this.phase[n] ?? Math.random();
const v = waveshapes.sawblep(this.phase[n], dt);
output[0][i] += v * gainL;
output[1][i] += v * gainR;
output[0][i] = output[0][i] + v * gainL;
output[1][i] = output[1][i] + v * gainR;
let pn = this.phase[n] + dt;
if (pn >= 1.0) pn -= 1.0;
this.phase[n] = pn;
// invert right and left gain
const tmp = gainL;
gainL = gainR;
gainR = tmp;
this.phase[n] = wrapPhase(this.phase[n] + dt);
}
}
return true;
@@ -557,16 +564,12 @@ registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
// Phase Vocoder sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
const BUFFERED_BLOCK_SIZE = 2048;
const hannCache = new Map();
function genHannWindow(length) {
if (!hannCache.has(length)) {
const win = new Float32Array(length);
for (let i = 0; i < length; i++) {
win[i] = 0.5 * (1 - Math.cos((TWO_PI * i) / length));
}
hannCache.set(length, win);
let win = new Float32Array(length);
for (var i = 0; i < length; i++) {
win[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / length));
}
return hannCache.get(length);
return win;
}
class PhaseVocoderProcessor extends OLAProcessor {
@@ -584,10 +587,11 @@ class PhaseVocoderProcessor extends OLAProcessor {
blockSize: BUFFERED_BLOCK_SIZE,
};
super(options);
this.timeCursor = 0;
this.fftSize = this.blockSize;
this.invfftSize = 1 / this.fftSize;
this.hannWindow = genHannWindow(this.fftSize);
this.timeCursor = 0;
this.hannWindow = genHannWindow(this.blockSize);
// prepare FFT and pre-allocate buffers
this.fft = new FFT(this.fftSize);
this.freqComplexBuffer = this.fft.createComplexArray();
@@ -600,43 +604,52 @@ class PhaseVocoderProcessor extends OLAProcessor {
processOLA(inputs, outputs, parameters) {
// no automation, take last value
let pitchFactor = parameters.pitchFactor[parameters.pitchFactor.length - 1];
if (pitchFactor < 0) {
pitchFactor = pitchFactor * 0.25;
}
pitchFactor = Math.max(0, pitchFactor + 1);
for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < inputs[i].length; j++) {
const input = inputs[i][j];
const output = outputs[i][j];
for (var i = 0; i < this.nbInputs; i++) {
for (var j = 0; j < inputs[i].length; j++) {
// big assumption here: output is symetric to input
var input = inputs[i][j];
var output = outputs[i][j];
this.applyHannWindow(input);
this.fft.realTransform(this.freqComplexBuffer, input);
this.computeMagnitudes();
this.findPeaks();
this.shiftPeaks(pitchFactor);
this.fft.completeSpectrum(this.freqComplexBufferShifted);
this.fft.inverseTransform(this.timeComplexBuffer, this.freqComplexBufferShifted);
this.fft.fromComplexArray(this.timeComplexBuffer, output);
this.applyHannWindow(output);
}
}
this.timeCursor += this.hopSize;
}
/** Apply Hann window in-place */
applyHannWindow(input) {
for (let i = 0; i < this.blockSize; i++) {
input[i] *= this.hannWindow[i] * 1.62;
for (var i = 0; i < this.blockSize; i++) {
input[i] = input[i] * this.hannWindow[i] * 1.62;
}
}
/** Compute squared magnitudes for peak finding **/
computeMagnitudes() {
let i = 0,
var i = 0,
j = 0;
while (i < this.magnitudes.length) {
const real = this.freqComplexBuffer[j];
const imag = this.freqComplexBuffer[j + 1];
let real = this.freqComplexBuffer[j];
let imag = this.freqComplexBuffer[j + 1];
// no need to sqrt for peak finding
this.magnitudes[i] = real ** 2 + imag ** 2;
i += 1;
@@ -647,10 +660,12 @@ class PhaseVocoderProcessor extends OLAProcessor {
/** Find peaks in spectrum magnitudes **/
findPeaks() {
this.nbPeaks = 0;
let i = 2;
const end = this.magnitudes.length - 2;
var i = 2;
let end = this.magnitudes.length - 2;
while (i < end) {
const mag = this.magnitudes[i];
let mag = this.magnitudes[i];
if (this.magnitudes[i - 1] >= mag || this.magnitudes[i - 2] >= mag) {
i++;
continue;
@@ -659,6 +674,7 @@ class PhaseVocoderProcessor extends OLAProcessor {
i++;
continue;
}
this.peakIndexes[this.nbPeaks] = i;
this.nbPeaks++;
i += 2;
@@ -669,44 +685,53 @@ class PhaseVocoderProcessor extends OLAProcessor {
shiftPeaks(pitchFactor) {
// zero-fill new spectrum
this.freqComplexBufferShifted.fill(0);
for (let i = 0; i < this.nbPeaks; i++) {
const peakIndex = this.peakIndexes[i];
const peakIndexShifted = fround(peakIndex * pitchFactor);
for (var i = 0; i < this.nbPeaks; i++) {
let peakIndex = this.peakIndexes[i];
let peakIndexShifted = Math.round(peakIndex * pitchFactor);
if (peakIndexShifted > this.magnitudes.length) {
break;
}
// find region of influence
let startIndex = 0;
let endIndex = this.fftSize;
var startIndex = 0;
var endIndex = this.fftSize;
if (i > 0) {
startIndex = peakIndex - fround((peakIndex - this.peakIndexes[i - 1]) / 2);
let peakIndexBefore = this.peakIndexes[i - 1];
startIndex = peakIndex - Math.floor((peakIndex - peakIndexBefore) / 2);
}
if (i < this.nbPeaks - 1) {
endIndex = peakIndex + fceil((this.peakIndexes[i + 1] - peakIndex) / 2);
let peakIndexAfter = this.peakIndexes[i + 1];
endIndex = peakIndex + Math.ceil((peakIndexAfter - peakIndex) / 2);
}
// shift whole region of influence around peak to shifted peak
const startOffset = startIndex - peakIndex;
const endOffset = endIndex - peakIndex;
const omegaDelta = TWO_PI * this.invfftSize * (peakIndexShifted - peakIndex);
const phaseShiftReal = Math.cos(omegaDelta * this.timeCursor);
const phaseShiftImag = Math.sin(omegaDelta * this.timeCursor);
for (let j = startOffset; j < endOffset; j++) {
const binIndex = peakIndex + j;
const binIndexShifted = peakIndexShifted + j;
let startOffset = startIndex - peakIndex;
let endOffset = endIndex - peakIndex;
for (var j = startOffset; j < endOffset; j++) {
let binIndex = peakIndex + j;
let binIndexShifted = peakIndexShifted + j;
if (binIndexShifted >= this.magnitudes.length) {
break;
}
// apply phase correction
const indexReal = 2 * binIndex;
const indexImag = indexReal + 1;
const valueReal = this.freqComplexBuffer[indexReal];
const valueImag = this.freqComplexBuffer[indexImag];
let omegaDelta = (2 * Math.PI * (binIndexShifted - binIndex)) / this.fftSize;
let phaseShiftReal = Math.cos(omegaDelta * this.timeCursor);
let phaseShiftImag = Math.sin(omegaDelta * this.timeCursor);
const valueShiftedReal = valueReal * phaseShiftReal - valueImag * phaseShiftImag;
const valueShiftedImag = valueReal * phaseShiftImag + valueImag * phaseShiftReal;
let indexReal = binIndex * 2;
let indexImag = indexReal + 1;
let valueReal = this.freqComplexBuffer[indexReal];
let valueImag = this.freqComplexBuffer[indexImag];
const indexShiftedReal = 2 * binIndexShifted;
const indexShiftedImag = indexShiftedReal + 1;
let valueShiftedReal = valueReal * phaseShiftReal - valueImag * phaseShiftImag;
let valueShiftedImag = valueReal * phaseShiftImag + valueImag * phaseShiftReal;
let indexShiftedReal = binIndexShifted * 2;
let indexShiftedImag = indexShiftedReal + 1;
this.freqComplexBufferShifted[indexShiftedReal] += valueShiftedReal;
this.freqComplexBufferShifted[indexShiftedImag] += valueShiftedImag;
}
@@ -720,10 +745,11 @@ registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor);
class PulseOscillatorProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.phi = -PI; // phase
this.pi = _PI;
this.phi = -this.pi; // phase
this.Y0 = 0; // feedback memories
this.Y1 = 0;
this.PW = PI; // pulse width
this.PW = this.pi; // pulse width
this.B = 2.3; // feedback coefficient
this.dphif = 0; // filtered phase increment
this.envf = 0; // filtered envelope
@@ -780,11 +806,11 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
dphi;
for (let i = 0; i < (output[0].length ?? 0); i++) {
const pw = (1 - clamp(pv(params.pulsewidth, i), -0.99, 0.99)) * PI;
const detune = pv(params.detune, i);
const freq = applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100);
const pw = (1 - clamp(getParamValue(i, params.pulsewidth), -0.99, 0.99)) * this.pi;
const detune = getParamValue(i, params.detune);
const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100);
dphi = freq * TWO_PI * INVSR; // phase increment
dphi = freq * (this.pi / (sampleRate * 0.5)); // phase increment
this.dphif += 0.1 * (dphi - this.dphif);
env *= 0.9998; // exponential decay envelope
@@ -796,7 +822,7 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
// Waveform generation (half-Tomisawa oscillators)
this.phi += this.dphif; // phase increment
if (this.phi >= PI) this.phi -= TWO_PI; // phase wrapping
if (this.phi >= this.pi) this.phi -= 2 * this.pi; // phase wrapping
// First half-Tomisawa generator
let out0 = Math.cos(this.phi + this.B * this.Y0); // self-phase modulation
@@ -826,23 +852,24 @@ const chyx = {
/*bit reverse*/ br: function (x, size = 8) {
if (size > 32) {
throw new Error('br() Size cannot be greater than 32');
} else {
let result = 0;
for (let idx = 0; idx < size - 0; idx++) {
result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1)));
}
return result;
}
let result = 0;
for (let idx = 0; idx < size; idx++) {
result |= chyx.bitC(x, 1 << idx, 1 << (size - (idx + 1)));
}
return result;
},
/*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) {
return Math.sin((x * PI) / 128);
return Math.sin(x / (128 / Math.PI));
},
/*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) {
return Math.cos((x * PI) / 128);
return Math.cos(x / (128 / Math.PI));
},
/*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) {
return Math.tan((x * PI) / 128);
return Math.tan(x / (128 / Math.PI));
},
/*converts t into a string composed of its bits; regexes that*/ regG: function (t, X) {
/*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) {
return X.test(t.toString(2));
},
};
@@ -850,7 +877,7 @@ const chyx = {
// Create shortened Math functions
let mathParams, byteBeatHelperFuncs;
function getByteBeatFunc(codetext) {
if (mathParams == null) {
if ((mathParams || byteBeatHelperFuncs) == null) {
mathParams = Object.getOwnPropertyNames(Math);
byteBeatHelperFuncs = mathParams.map((k) => Math[k]);
const chyxNames = Object.getOwnPropertyNames(chyx);
@@ -883,7 +910,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor {
this.func = getByteBeatFunc(codeText);
};
this.initialOffset = 0;
this.initialOffset = null;
this.t = null;
this.func = null;
}
@@ -930,19 +957,18 @@ class ByteBeatProcessor extends AudioWorkletProcessor {
this.t = params.begin[0] * sampleRate;
}
const output = outputs[0];
const scale = 256 * INVSR;
for (let i = 0; i < output[0].length; i++) {
const detune = pv(params.detune, i);
const freq = applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100);
const local_t = scale * freq * this.t + this.initialOffset;
const detune = getParamValue(i, params.detune);
const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100);
let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset;
const funcValue = this.func(local_t);
const signal = (funcValue & 255) / 127.5 - 1;
//prevent speaker blowout via clipping if threshold exceeds
const out = clamp(signal * 0.2, -0.4, 0.4);
let signal = (funcValue & 255) / 127.5 - 1;
const out = signal * 0.2;
for (let c = 0; c < output.length; c++) {
output[c][i] = out;
//prevent speaker blowout via clipping if threshold exceeds
output[c][i] = clamp(out, -0.4, 0.4);
}
this.t++;
this.t = this.t + 1;
}
return true; // keep the audio processing going
@@ -951,102 +977,6 @@ class ByteBeatProcessor extends AudioWorkletProcessor {
registerProcessor('byte-beat-processor', ByteBeatProcessor);
class EnvelopeProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [
{ name: 'begin', defaultValue: 0 },
{ name: 'end', defaultValue: 0 },
{ name: 'attack', defaultValue: 0.005, minValue: 0 },
{ name: 'decay', defaultValue: 0.14, minValue: 0 },
{ name: 'sustain', defaultValue: 0, minValue: 0, maxValue: 1 },
{ name: 'release', defaultValue: 0.1, minValue: 0 },
{ name: 'attackCurve', defaultValue: 0, minValue: -1, maxValue: 1 },
{ name: 'decayCurve', defaultValue: 0, minValue: -1, maxValue: 1 },
{ name: 'releaseCurve', defaultValue: 0, minValue: -1, maxValue: 1 },
{ name: 'peak', defaultValue: 1 },
{ name: 'retrigger', defaultValue: 1, minValue: 0, maxValue: 1 },
];
}
constructor() {
super();
this.val = 0;
this.segIdx = 0;
this.state = 0;
this.beginTime = 0;
this.endTime = 0;
this.attackStart = 0;
}
_warp(phase, curvature, strength = 8) {
if (phase === 0 || phase === 1) return phase; // fast exit
if (curvature > 0) {
// snappier
const exp = 1 + strength * curvature;
return 1 - Math.pow(1 - phase, exp);
} else {
// more calm
const exp = 1 - strength * curvature;
return Math.pow(phase, exp);
}
}
_advance(start, target, time, curvature) {
if (time === 0 || start === target) {
this.val = target;
} else {
// We compute our progress through this section of the envelope in time
// as a `phase` value, which is warped by the curvature, and then used
// to compute the value of the envelope at that time
const phase = Math.min(1, (currentTime - this.beginTime) / time);
const phaseWarped = this._warp(phase, curvature);
this.val = start + (target - start) * phaseWarped;
}
}
process(_inputs, outputs, params) {
const out = outputs[0][0];
if (!out) return true;
const begin = pv(params.begin, 0);
const retrigger = pv(params.retrigger, 0) >= 0.5; // convert to bool
if (begin !== this.beginTime && (this.state === 0 || retrigger)) {
// triggered
this.beginTime = begin;
this.state = 1;
this.endTime = pv(params.end, 0);
this.attackStart = this.val;
}
const susTime = this.endTime - this.beginTime;
for (let i = 0; i < out.length; i++) {
const attack = pv(params.attack, i);
const decay = pv(params.decay, i);
const sustain = pv(params.sustain, i);
const release = pv(params.release, i);
const aCurve = pv(params.attackCurve, i);
const dCurve = pv(params.decayCurve, i);
const rCurve = pv(params.releaseCurve, i);
const peak = pv(params.peak, i);
const states = [
{ time: Number.POSITIVE_INFINITY, start: 0, target: 0 }, // idle
{ time: attack, start: this.attackStart, target: 1, curve: aCurve },
{ time: attack + decay, start: 1, target: sustain, curve: dCurve },
{ time: susTime, start: sustain, target: sustain },
{ time: susTime + release, start: sustain, target: 0, curve: rCurve },
];
let { time, start, target, curve } = states[this.state];
this._advance(start, target, time, curve);
while (currentTime - this.beginTime >= time) {
this.state = (this.state + 1) % states.length;
time = states[this.state].time;
}
out[i] = this.val * peak;
}
return true;
}
}
registerProcessor('envelope-processor', EnvelopeProcessor);
export const WarpMode = Object.freeze({
NONE: 0,
ASYM: 1,
@@ -1126,7 +1056,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
{ name: 'position', defaultValue: 0, min: 0, max: 1 },
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
{ name: 'warpMode', defaultValue: 0 },
{ name: 'voices', defaultValue: 1, min: 1, automationRate: 'k-rate' },
{ name: 'voices', defaultValue: 1, min: 1 },
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
{ name: 'phaserand', defaultValue: 0, min: 0, max: 1 },
];
@@ -1137,6 +1067,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
this.frameLen = 0;
this.numFrames = 0;
this.phase = [];
this.invSR = 1 / sampleRate;
this.port.onmessage = (e) => {
const { type, payload } = e.data || {};
@@ -1173,7 +1104,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
_toBits(amt, min = 2, max = 12) {
const b = max + (min - max) * amt;
return { b, n: fround(Math.pow(2, b)) };
return { b, n: Math.round(Math.pow(2, b)) };
}
_warpPhase(phase, amt, mode) {
@@ -1199,7 +1130,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
return amt < 0.5 ? this._warpPhase(phase, 1 - 2 * amt, 3) : this._warpPhase(phase, 2 * amt - 1, 2);
}
case WarpMode.SYNC: {
const syncRatio = Math.pow(16, amt ** 2);
const syncRatio = Math.pow(16, amt * amt);
return (phase * syncRatio) % 1;
}
case WarpMode.QUANT: {
@@ -1208,8 +1139,8 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
}
case WarpMode.FOLD: {
const K = 7;
const k = 1 + Math.max(1, fround(K * amt));
return Math.abs(ffrac(k * phase) - 0.5) * 2;
const k = 1 + Math.max(1, Math.round(K * amt));
return Math.abs(frac(k * phase) - 0.5) * 2;
}
case WarpMode.PWM: {
const w = clamp(0.5 + 0.49 * (2 * amt - 1), 0, 1);
@@ -1219,12 +1150,12 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
case WarpMode.ORBIT: {
const depth = 0.5 * amt;
const n = 3;
return frac(phase + depth * Math.sin(TWO_PI * n * phase));
return frac(phase + depth * Math.sin(2 * Math.PI * n * phase));
}
case WarpMode.SPIN: {
const depth = 0.5 * amt;
const { n } = this._toBits(amt, 1, 6);
return frac(phase + depth * Math.sin(TWO_PI * n * phase));
return frac(phase + depth * Math.sin(2 * Math.PI * n * phase));
}
case WarpMode.CHAOS: {
const r = 3.7 + 0.3 * amt;
@@ -1235,7 +1166,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const isPrime = (n) => {
if (n < 2) return false;
if (n % 2 === 0) return n === 2;
for (let d = 3; d ** 2 <= n; d += 2) if (n % d === 0) return false;
for (let d = 3; d * d <= n; d += 2) if (n % d === 0) return false;
return true;
};
let { n } = this._toBits(amt, 3);
@@ -1244,12 +1175,18 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
}
case WarpMode.BINARY: {
let { b } = this._toBits(amt, 3);
b = fround(b);
b = Math.round(b);
const n = 1 << b;
const idx = ffloor(phase * n);
const ridx = bitReverse(idx, b);
return ridx / n;
}
case WarpMode.MODULAR: {
const { n } = this._toBits(amt);
const depth = 0.5 * amt;
const jump = frac(phase * n) / n;
return frac(phase + depth * jump);
}
case WarpMode.BROWNIAN: {
const disp = 0.25 * amt * brownian(64 * phase, 4);
return frac(phase + disp);
@@ -1272,7 +1209,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
case WarpMode.LOGISTIC: {
let x = phase;
const r = 3.6 + 0.4 * amt;
const iters = 1 + fround(2 * amt);
const iters = 1 + Math.round(2 * amt);
for (let i = 0; i < iters; i++) x = r * x * (1 - x);
return clamp(x, 0, 1);
}
@@ -1285,7 +1222,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
return (y - y0) / (y1 - y0);
}
case WarpMode.FRACTAL: {
const d = 0.5 * Math.sin(TWO_PI * phase) * amt;
const d = 0.5 * Math.sin(2 * Math.PI * phase) * amt;
return frac(phase + d);
}
case WarpMode.FLIP: {
@@ -1332,16 +1269,16 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
if (outR !== outL) outR.set(outL);
return true;
}
const voices = parameters.voices[0]; // k-rate
for (let i = 0; i < outL.length; i++) {
const detune = pv(parameters.detune, i);
const freqspread = pv(parameters.freqspread, i);
const tablePos = clamp(pv(parameters.position, i), 0, 1);
const idx = tablePos * (this.numFrames - 1);
const fIdx = idx | 0;
const interpT = idx - fIdx;
const frac = idx - fIdx;
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
const warpMode = pv(parameters.warpMode, i);
const voices = pv(parameters.voices, i);
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0;
const gain1 = Math.sqrt(0.5 - 0.5 * panspread);
@@ -1349,7 +1286,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
let f = pv(parameters.frequency, i);
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
const normalizer = 1 / Math.sqrt(voices);
const detuner = getDetuner(voices, freqspread);
for (let n = 0; n < voices; n++) {
const isOdd = (n & 1) == 1;
let gainL = gain1;
@@ -1359,8 +1295,8 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
gainL = gain2;
gainR = gain1;
}
const fVoice = applySemitoneDetuneToFrequency(f, detuner(n)); // voice detune
const dPhase = fVoice * INVSR;
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, freqspread, n)); // voice detune
const dPhase = fVoice * this.invSR;
const level = this._chooseMip(dPhase);
const table = this.tables[level];
@@ -1369,13 +1305,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 = lerp(s0, s1, interpT);
let s = s0 + (s1 - s0) * frac;
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
s = -s;
}
outL[i] += s * gainL * normalizer;
outR[i] += s * gainR * normalizer;
this.phase[n] = frac(this.phase[n] + dPhase);
this.phase[n] = wrapPhase(this.phase[n] + dPhase);
}
}
return true;
+2 -11
View File
@@ -5,16 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Note, Interval, Scale } from '@tonaljs/tonal';
import {
_mod,
errorLogger,
getAccidentalsOffset,
isNote,
logger,
noteToMidi,
register,
removeUndefineds,
} from '@strudel/core';
import { register, _mod, logger, isNote, noteToMidi, removeUndefineds, getAccidentalsOffset } from '@strudel/core';
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
@@ -303,7 +294,7 @@ export const scale = register(
}
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
} catch (err) {
errorLogger(err, 'tonal');
logger(`[tonal] ${err.message}`, 'error');
return; // will be removed
}
}
+132 -20
View File
@@ -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)
@@ -85,29 +85,29 @@ importers:
examples/codemirror-repl:
dependencies:
'@strudel/codemirror':
specifier: workspace:*
version: link:../../packages/codemirror
specifier: 1.2.6
version: 1.2.6(@codemirror/lint@6.8.4)
'@strudel/core':
specifier: workspace:*
version: link:../../packages/core
specifier: 1.2.5
version: 1.2.5
'@strudel/draw':
specifier: workspace:*
version: link:../../packages/draw
specifier: 1.2.5
version: 1.2.5
'@strudel/mini':
specifier: workspace:*
version: link:../../packages/mini
specifier: 1.2.5
version: 1.2.5
'@strudel/soundfonts':
specifier: workspace:*
version: link:../../packages/soundfonts
specifier: 1.2.6
version: 1.2.6
'@strudel/tonal':
specifier: workspace:*
version: link:../../packages/tonal
specifier: 1.2.5
version: 1.2.5
'@strudel/transpiler':
specifier: workspace:*
version: link:../../packages/transpiler
specifier: 1.2.5
version: 1.2.5
'@strudel/webaudio':
specifier: workspace:*
version: link:../../packages/webaudio
specifier: 1.2.6
version: 1.2.6
devDependencies:
vite:
specifier: ^6.0.11
@@ -116,8 +116,8 @@ importers:
examples/headless-repl:
dependencies:
'@strudel/web':
specifier: workspace:*
version: link:../../packages/web
specifier: 1.2.6
version: 1.2.6
devDependencies:
vite:
specifier: ^6.0.11
@@ -2544,6 +2544,33 @@ packages:
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
'@strudel/codemirror@1.2.6':
resolution: {integrity: sha512-zjKbnbnOY40dlIAQu1poq28rcngYliLsuDwXXVnJsaXzCZSVuXEOMq91LqY4IGz/2QUOIDGm663ziz6Nb4IXeg==}
'@strudel/core@1.2.5':
resolution: {integrity: sha512-XO6ICbfuGfvaQ4sZmfIFsPDcjFez/+AVpNgJnmks5gtMMQNXpr0reuee61tvMiu2k5QYKzxWgzDl2FF9pJeIgA==}
'@strudel/draw@1.2.5':
resolution: {integrity: sha512-ZI4WPxUsVsxgDoeZMHo6VfFf+S1tsj8ubKBeSLcf6lH29JqPp7Nz6tPmbomOV1fzazZ7+xpkC7z772MdtgWy2g==}
'@strudel/mini@1.2.5':
resolution: {integrity: sha512-GmTWSBt49C3ogBy0hjM3bmf6K12PaJ1JJLsEvQu7j5J+lUd2n7GmLYR1UL6fntY4yVqT8ZwMhfUf6vIgUCTNbw==}
'@strudel/soundfonts@1.2.6':
resolution: {integrity: sha512-F7eBhohkCXCMo8WiufSAhD3JCfL5GTgCh9RlOJVT+1swiFeIss1vUAWJR3WjonB6moL8BiuNpcYOqO6i6cFy4A==}
'@strudel/tonal@1.2.5':
resolution: {integrity: sha512-mX3uW0nWH8Sa52HTLwnxlR5BrejFX77abeaiMbp+kQCMrX9I+XRdf8rEr7gdeInB6f/y2TzTVFpnsNsB5jLdoA==}
'@strudel/transpiler@1.2.5':
resolution: {integrity: sha512-mBZWfQBzknvnIYsjjzR8D67A+uIZ1iZ60U2WoXT21q5luXKz2c95RcTpqdx3oOGpLK9K00lDzcKjhQDJtj4OKw==}
'@strudel/web@1.2.6':
resolution: {integrity: sha512-e6VltuX90zZz8JVe1FtN23DCaXA9p8zGDakp5OuUb5kZ5hC6cySFkkOsHdGC6dJ0EAsZSCKXMihRxgk9GhBwKA==}
'@strudel/webaudio@1.2.6':
resolution: {integrity: sha512-4LnyNvyeAXYNmXQGGw2f0chIkDr9Rbwo+SK6Y0p8C3W8juDiOfKq2ihSVph95yaBBPRYFJ5Kttj1kLknRHYsog==}
'@supabase/auth-js@2.67.3':
resolution: {integrity: sha512-NJDaW8yXs49xMvWVOkSIr8j46jf+tYHV0wHhrwOaLLMZSFO4g6kKAf+MfzQ2RaD06OCUkUHIzctLAxjTgEVpzw==}
@@ -7094,6 +7121,9 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
superdough@1.2.6:
resolution: {integrity: sha512-zHEFfG+N4zhxJ9lUDf8p4Oj4sdGGQ5SrOPksdc0cKv5Yn0hzlNlMehEa5TkeIfDCiR27Y4H81ks7S8/CGSByhQ==}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -7102,6 +7132,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
supradough@1.2.4:
resolution: {integrity: sha512-/v3cL9fq05QCQVwbttLHNZcjyyFFz5wdKvg3j6V6moRXpDlQG89l8dQtUIpQvHsxYI5sXNod9rh5bf+Y4NMGeA==}
tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
@@ -10019,6 +10052,79 @@ snapshots:
'@sinclair/typebox@0.27.8': {}
'@strudel/codemirror@1.2.6(@codemirror/lint@6.8.4)':
dependencies:
'@codemirror/autocomplete': 6.18.4
'@codemirror/commands': 6.8.0
'@codemirror/lang-javascript': 6.2.2
'@codemirror/language': 6.10.8
'@codemirror/search': 6.5.8
'@codemirror/state': 6.5.1
'@codemirror/view': 6.36.2
'@lezer/highlight': 1.2.1
'@nanostores/persistent': 0.10.2(nanostores@0.11.3)
'@replit/codemirror-emacs': 6.1.0(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)
'@replit/codemirror-vim': 6.3.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)
'@replit/codemirror-vscode-keymap': 6.0.2(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)
'@strudel/core': 1.2.5
'@strudel/draw': 1.2.5
'@strudel/tonal': 1.2.5
'@strudel/transpiler': 1.2.5
'@tonaljs/tonal': 4.10.0
nanostores: 0.11.3
superdough: 1.2.6
transitivePeerDependencies:
- '@codemirror/lint'
'@strudel/core@1.2.5':
dependencies:
fraction.js: 5.2.1
'@strudel/draw@1.2.5':
dependencies:
'@strudel/core': 1.2.5
'@strudel/mini@1.2.5':
dependencies:
'@strudel/core': 1.2.5
'@strudel/soundfonts@1.2.6':
dependencies:
'@strudel/core': 1.2.5
'@strudel/webaudio': 1.2.6
sfumato: 0.1.2
soundfont2: 0.5.0
'@strudel/tonal@1.2.5':
dependencies:
'@strudel/core': 1.2.5
'@tonaljs/tonal': 4.10.0
chord-voicings: 0.0.1
webmidi: 3.1.12
'@strudel/transpiler@1.2.5':
dependencies:
'@strudel/core': 1.2.5
'@strudel/mini': 1.2.5
acorn: 8.14.0
escodegen: 2.1.0
estree-walker: 3.0.3
'@strudel/web@1.2.6':
dependencies:
'@strudel/core': 1.2.5
'@strudel/mini': 1.2.5
'@strudel/tonal': 1.2.5
'@strudel/transpiler': 1.2.5
'@strudel/webaudio': 1.2.6
'@strudel/webaudio@1.2.6':
dependencies:
'@strudel/core': 1.2.5
'@strudel/draw': 1.2.5
superdough: 1.2.6
supradough: 1.2.4
'@supabase/auth-js@2.67.3':
dependencies:
'@supabase/node-fetch': 2.6.15
@@ -10554,7 +10660,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
@@ -15665,12 +15771,18 @@ snapshots:
pirates: 4.0.6
ts-interface-checker: 0.1.13
superdough@1.2.6:
dependencies:
nanostores: 0.11.3
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
supports-preserve-symlinks-flag@1.0.0: {}
supradough@1.2.4: {}
tabbable@6.2.0: {}
tailwindcss@3.4.17:
+60 -718
View File
@@ -1391,75 +1391,6 @@ 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 ]",
@@ -4855,75 +4786,6 @@ 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 ]",
@@ -6107,144 +5969,6 @@ 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 ]",
@@ -6433,75 +6157,6 @@ 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 ]",
@@ -6544,75 +6199,6 @@ 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 ]",
@@ -7302,18 +6888,18 @@ exports[`runs examples > example "nrpv" example index 0 1`] = `
exports[`runs examples > example "octave" example index 0 1`] = `
[
"[ 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 ]",
"[ 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 ]",
]
`;
@@ -7633,112 +7219,6 @@ exports[`runs examples > example "panchor" example index 0 1`] = `
]
`;
exports[`runs examples > example "partials" example index 0 1`] = `
[
"[ 0/1 → 1/16 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
"[ 1/16 → 1/8 | s:user note:G#4 partials:[1 0 1 0 0 1] ]",
"[ 1/8 → 3/16 | s:user note:F#4 partials:[1 0 1 0 0 1] ]",
"[ 3/16 → 1/4 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 1/4 → 5/16 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 5/16 → 3/8 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 3/8 → 7/16 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 7/16 → 1/2 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 1/2 → 9/16 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 9/16 → 5/8 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 5/8 → 11/16 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 11/16 → 3/4 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 3/4 → 13/16 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 13/16 → 7/8 | s:user note:G#4 partials:[1 0 1 0 0 1] ]",
"[ 7/8 → 15/16 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 15/16 → 1/1 | s:user note:F#4 partials:[1 0 1 0 0 1] ]",
"[ 1/1 → 17/16 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 17/16 → 9/8 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 9/8 → 19/16 | s:user note:F#4 partials:[1 0 1 0 0 1] ]",
"[ 19/16 → 5/4 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
"[ 5/4 → 21/16 | s:user note:F#4 partials:[1 0 1 0 0 1] ]",
"[ 21/16 → 11/8 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
"[ 11/8 → 23/16 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 23/16 → 3/2 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 3/2 → 25/16 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 25/16 → 13/8 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 13/8 → 27/16 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 27/16 → 7/4 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 7/4 → 29/16 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 29/16 → 15/8 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 15/8 → 31/16 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 31/16 → 2/1 | s:user note:G#4 partials:[1 0 1 0 0 1] ]",
"[ 2/1 → 33/16 | s:user note:A4 partials:[1 0 1 0 0 1] ]",
"[ 33/16 → 17/8 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 17/8 → 35/16 | s:user note:A4 partials:[1 0 1 0 0 1] ]",
"[ 35/16 → 9/4 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
"[ 9/4 → 37/16 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 37/16 → 19/8 | s:user note:F#4 partials:[1 0 1 0 0 1] ]",
"[ 19/8 → 39/16 | s:user note:G#4 partials:[1 0 1 0 0 1] ]",
"[ 39/16 → 5/2 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 5/2 → 41/16 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 41/16 → 21/8 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 21/8 → 43/16 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 43/16 → 11/4 | s:user note:E4 partials:[1 0 1 0 0 1] ]",
"[ 11/4 → 45/16 | s:user note:F#4 partials:[1 0 1 0 0 1] ]",
"[ 45/16 → 23/8 | s:user note:A4 partials:[1 0 1 0 0 1] ]",
"[ 23/8 → 47/16 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
"[ 47/16 → 3/1 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 3/1 → 49/16 | s:user note:B3 partials:[1 0 1 0 0 1] ]",
"[ 49/16 → 25/8 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
"[ 25/8 → 51/16 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 51/16 → 13/4 | s:user note:A4 partials:[1 0 1 0 0 1] ]",
"[ 13/4 → 53/16 | s:user note:A4 partials:[1 0 1 0 0 1] ]",
"[ 53/16 → 27/8 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
"[ 27/8 → 55/16 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 55/16 → 7/2 | s:user note:G#4 partials:[1 0 1 0 0 1] ]",
"[ 7/2 → 57/16 | s:user note:D4 partials:[1 0 1 0 0 1] ]",
"[ 57/16 → 29/8 | s:user note:G#4 partials:[1 0 1 0 0 1] ]",
"[ 29/8 → 59/16 | s:user note:A4 partials:[1 0 1 0 0 1] ]",
"[ 59/16 → 15/4 | s:user note:A4 partials:[1 0 1 0 0 1] ]",
"[ 15/4 → 61/16 | s:user note:G#4 partials:[1 0 1 0 0 1] ]",
"[ 61/16 → 31/8 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
"[ 31/8 → 63/16 | s:user note:F#4 partials:[1 0 1 0 0 1] ]",
"[ 63/16 → 4/1 | s:user note:C#4 partials:[1 0 1 0 0 1] ]",
]
`;
exports[`runs examples > example "partials" example index 1 1`] = `
[
"[ 0/1 → 1/8 | s:saw note:G#3 partials:[1] ]",
"[ 1/8 → 1/4 | s:saw note:A#4 partials:[1 0 1 1 0 0 0 0] ]",
"[ 1/4 → 3/8 | s:saw note:D#4 partials:[1 0 1 1 1 1 1] ]",
"[ 3/8 → 1/2 | s:saw note:D#4 partials:[1 1 0 0 1 1 1] ]",
"[ 1/2 → 5/8 | s:saw note:C#4 partials:[1 0 0 0 0 1 1] ]",
"[ 5/8 → 3/4 | s:saw note:A#3 partials:[1 0 0 0 1 1] ]",
"[ 3/4 → 7/8 | s:saw note:B3 partials:[1 1 0 0 1 1] ]",
"[ 7/8 → 1/1 | s:saw note:D#4 partials:[1 1 0 0 1 1 0] ]",
"[ 1/1 → 9/8 | s:saw note:F#4 partials:[1 0 0 0 0 1 1 0] ]",
"[ 9/8 → 5/4 | s:saw note:A#4 partials:[1 0 1 0 1 1 0 1] ]",
"[ 5/4 → 11/8 | s:saw note:A#4 partials:[1 0 1 1 1 0 1 1] ]",
"[ 11/8 → 3/2 | s:saw note:B3 partials:[1 0 1 1 1 1] ]",
"[ 3/2 → 13/8 | s:saw note:G#4 partials:[1 0 0 1 1 1 0 0] ]",
"[ 13/8 → 7/4 | s:saw note:E4 partials:[1 0 0 0 0 0 0 0] ]",
"[ 7/4 → 15/8 | s:saw note:B3 partials:[1 1 0 1 0 1] ]",
"[ 15/8 → 2/1 | s:saw note:G#4 partials:[1 0 0 1 1 0 0 1] ]",
"[ 2/1 → 17/8 | s:saw note:D#5 partials:[1 1 1 1 0 1 1 0] ]",
"[ 17/8 → 9/4 | s:saw note:C#5 partials:[1 1 1 0 1 0 0 0] ]",
"[ 9/4 → 19/8 | s:saw note:D#4 partials:[1 0 1 1 0 0 1] ]",
"[ 19/8 → 5/2 | s:saw note:C#5 partials:[1 1 0 1 0 1 1 1] ]",
"[ 5/2 → 21/8 | s:saw note:E4 partials:[1 1 1 0 1 1 0] ]",
"[ 21/8 → 11/4 | s:saw note:A#3 partials:[1 0 1 0 1 0] ]",
"[ 11/4 → 23/8 | s:saw note:G#4 partials:[1 0 1 0 0 0 1 1] ]",
"[ 23/8 → 3/1 | s:saw note:G#3 partials:[1 0 1] ]",
"[ 3/1 → 25/8 | s:saw note:B3 partials:[1 1 1 0 0 0] ]",
"[ 25/8 → 13/4 | s:saw note:D#4 partials:[1 0 1 1 0 0 0] ]",
"[ 13/4 → 27/8 | s:saw note:D#5 partials:[1 1 1 1 1 1 1 1] ]",
"[ 27/8 → 7/2 | s:saw note:D#4 partials:[1 1 0 1 0 0 1] ]",
"[ 7/2 → 29/8 | s:saw note:D#4 partials:[1 1 0 1 0 0 1] ]",
"[ 29/8 → 15/4 | s:saw note:D#5 partials:[1 1 1 1 0 0 0 1] ]",
"[ 15/4 → 31/8 | s:saw note:B4 partials:[1 1 0 1 0 0 0 0] ]",
"[ 31/8 → 4/1 | s:saw note:A#4 partials:[1 0 1 1 1 1 0 1] ]",
]
`;
exports[`runs examples > example "pattack" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:c pattack:0 ]",
@@ -7992,75 +7472,6 @@ exports[`runs examples > example "phasersweep" example index 0 1`] = `
]
`;
exports[`runs examples > example "phases" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:saw note:G#1 partials:{partials:1} ]",
"[ 0/1 → 1/8 | s:saw note:G#1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 1/8 → 1/4 | s:saw note:A#2 partials:{partials:1} ]",
"[ 1/8 → 1/4 | s:saw note:A#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 1/4 → 3/8 | s:saw note:D#2 partials:{partials:1} ]",
"[ 1/4 → 3/8 | s:saw note:D#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 3/8 → 1/2 | s:saw note:D#2 partials:{partials:1} ]",
"[ 3/8 → 1/2 | s:saw note:D#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 1/2 → 5/8 | s:saw note:C#2 partials:{partials:1} ]",
"[ 1/2 → 5/8 | s:saw note:C#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 5/8 → 3/4 | s:saw note:A#1 partials:{partials:1} ]",
"[ 5/8 → 3/4 | s:saw note:A#1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 3/4 → 7/8 | s:saw note:B1 partials:{partials:1} ]",
"[ 3/4 → 7/8 | s:saw note:B1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 7/8 → 1/1 | s:saw note:D#2 partials:{partials:1} ]",
"[ 7/8 → 1/1 | s:saw note:D#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 1/1 → 9/8 | s:saw note:F#2 partials:{partials:1} ]",
"[ 1/1 → 9/8 | s:saw note:F#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 9/8 → 5/4 | s:saw note:A#2 partials:{partials:1} ]",
"[ 9/8 → 5/4 | s:saw note:A#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 5/4 → 11/8 | s:saw note:A#2 partials:{partials:1} ]",
"[ 5/4 → 11/8 | s:saw note:A#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 11/8 → 3/2 | s:saw note:B1 partials:{partials:1} ]",
"[ 11/8 → 3/2 | s:saw note:B1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 3/2 → 13/8 | s:saw note:G#2 partials:{partials:1} ]",
"[ 3/2 → 13/8 | s:saw note:G#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 13/8 → 7/4 | s:saw note:E2 partials:{partials:1} ]",
"[ 13/8 → 7/4 | s:saw note:E2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 7/4 → 15/8 | s:saw note:B1 partials:{partials:1} ]",
"[ 7/4 → 15/8 | s:saw note:B1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 15/8 → 2/1 | s:saw note:G#2 partials:{partials:1} ]",
"[ 15/8 → 2/1 | s:saw note:G#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 2/1 → 17/8 | s:saw note:D#3 partials:{partials:1} ]",
"[ 2/1 → 17/8 | s:saw note:D#3 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 17/8 → 9/4 | s:saw note:C#3 partials:{partials:1} ]",
"[ 17/8 → 9/4 | s:saw note:C#3 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 9/4 → 19/8 | s:saw note:D#2 partials:{partials:1} ]",
"[ 9/4 → 19/8 | s:saw note:D#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 19/8 → 5/2 | s:saw note:C#3 partials:{partials:1} ]",
"[ 19/8 → 5/2 | s:saw note:C#3 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 5/2 → 21/8 | s:saw note:E2 partials:{partials:1} ]",
"[ 5/2 → 21/8 | s:saw note:E2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 21/8 → 11/4 | s:saw note:A#1 partials:{partials:1} ]",
"[ 21/8 → 11/4 | s:saw note:A#1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 11/4 → 23/8 | s:saw note:G#2 partials:{partials:1} ]",
"[ 11/4 → 23/8 | s:saw note:G#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 23/8 → 3/1 | s:saw note:G#1 partials:{partials:1} ]",
"[ 23/8 → 3/1 | s:saw note:G#1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 3/1 → 25/8 | s:saw note:B1 partials:{partials:1} ]",
"[ 3/1 → 25/8 | s:saw note:B1 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 25/8 → 13/4 | s:saw note:D#2 partials:{partials:1} ]",
"[ 25/8 → 13/4 | s:saw note:D#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 13/4 → 27/8 | s:saw note:D#3 partials:{partials:1} ]",
"[ 13/4 → 27/8 | s:saw note:D#3 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 27/8 → 7/2 | s:saw note:D#2 partials:{partials:1} ]",
"[ 27/8 → 7/2 | s:saw note:D#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 7/2 → 29/8 | s:saw note:D#2 partials:{partials:1} ]",
"[ 7/2 → 29/8 | s:saw note:D#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 29/8 → 15/4 | s:saw note:D#3 partials:{partials:1} ]",
"[ 29/8 → 15/4 | s:saw note:D#3 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 15/4 → 31/8 | s:saw note:B2 partials:{partials:1} ]",
"[ 15/4 → 31/8 | s:saw note:B2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
"[ 31/8 → 4/1 | s:saw note:A#2 partials:{partials:1} ]",
"[ 31/8 → 4/1 | s:saw note:A#2 partials:{partials:1} phases:[0.5 0.5 0.5] ]",
]
`;
exports[`runs examples > example "pianoroll" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
@@ -8782,75 +8193,6 @@ exports[`runs examples > example "rand" example index 0 1`] = `
]
`;
exports[`runs examples > example "randL" example index 0 1`] = `
[
"[ 0/1 → 1/16 | s:saw note:F1 partials:[0 0 0 0 0 0 0 0] ]",
"[ 1/16 → 1/8 | s:saw note:Bb2 partials:[0.8426077850162983 0.0886186733841896 0.9342893119901419 0.06848422810435295 0.6248505394905806 0.5372848063707352 0.8283301629126072 0.7969411127269268] ]",
"[ 1/8 → 3/16 | s:saw note:G2 partials:[0.6852155700325966 0.17723728902637959 0.8521428182721138 0.5022399611771107 0.9780306946486235 0.3186125475913286 0.1851645242422819 0.8495976086705923] ]",
"[ 3/16 → 1/4 | s:saw note:Ab1 partials:[0.20066574029624462 0.17195586115121841 0.2159541044384241 0.17005567252635956 0.6841662060469389 0.07906394638121128 0.004815371707081795 0.027107616886496544] ]",
"[ 1/4 → 5/16 | s:saw note:C2 partials:[0.36975969187915325 0.18273563869297504 0.012781793251633644 0.5423613861203194 0.12467948533594608 0.7188410349190235 0.856887087225914 0.8818203993141651] ]",
"[ 5/16 → 3/8 | s:saw note:Eb2 partials:[0.5675661638379097 0.15932588279247284 0.31376867927610874 0.32359412126243114 0.3281281068921089 0.011714376509189606 0.4433113746345043 0.68459103256464] ]",
"[ 3/8 → 7/16 | s:saw note:C2 partials:[0.40139251574873924 0.15799915604293346 0.9604704882949591 0.03873417526483536 0.4157217647880316 0.27756719291210175 0.8800551909953356 0.5692523363977671] ]",
"[ 7/16 → 1/2 | s:saw note:Bb1 partials:[0.3015887886285782 0.22311350144445896 0.47645803540945053 0.001545613631606102 0.53841289319098 0.330710094422102 0.2747743520885706 0.9471044968813658] ]",
"[ 1/2 → 9/16 | s:saw note:Bb1 partials:[0.2604806162416935 0.3654713351279497 0.9900747090578079 0.22572625242173672 0.9328999016433954 0.5273839123547077 0.7047769222408533 0.7311522178351879] ]",
"[ 9/16 → 5/8 | s:saw note:G1 partials:[0.16399178467690945 0.9559339117258787 0.21830322034657001 0.4903192054480314 0.2504696864634752 0.7302105724811554 0.6580934692174196 0.9855979979038239] ]",
"[ 5/8 → 11/16 | s:saw note:G1 partials:[0.1356358677148819 0.5403102282434702 0.07321739941835403 0.9646544624119997 0.7707359045743942 0.46888123638927937 0.9489036612212658 0.366748945787549] ]",
"[ 11/16 → 3/4 | s:saw note:C2 partials:[0.408811716362834 0.9613851886242628 0.7683626655489206 0.5862949229776859 0.9568102769553661 0.7154356613755226 0.6283384170383215 0.2193678840994835] ]",
"[ 3/4 → 13/16 | s:saw note:Ab1 partials:[0.19582648016512394 0.0024610888212919235 0.08272589556872845 0.09592138417065144 0.9678201265633106 0.3249557167291641 0.9463537875562906 0.20766610652208328] ]",
"[ 13/16 → 7/8 | s:saw note:Ab2 partials:[0.7516226731240749 0.15610851533710957 0.9039078876376152 0.8602268267422915 0.928601048886776 0.4479671400040388 0.5080656576901674 0.562442347407341] ]",
"[ 7/8 → 15/16 | s:saw note:C2 partials:[0.3976310808211565 0.13476407527923584 0.963376859202981 0.6579397786408663 0.5203975513577461 0.865439185872674 0.5583905670791864 0.2736878804862499] ]",
"[ 15/16 → 1/1 | s:saw note:G2 partials:[0.7029578909277916 0.6351367030292749 0.26143294386565685 0.6411824338138103 0.5676082745194435 0.4803342465311289 0.5140306428074837 0.9477859679609537] ]",
"[ 1/1 → 17/16 | s:saw note:Eb2 partials:[0.5195421651005745 0.9349692352116108 0.1718774326145649 0.9601866770535707 0.2802200373262167 0.8732441551983356 0.01810968853533268 0.42737805284559727] ]",
"[ 17/16 → 9/8 | s:saw note:Eb2 partials:[0.5486328881233931 0.7088070474565029 0.07877279818058014 0.9422610364854336 0.7696988433599472 0.8289324026554823 0.5925844628363848 0.3920764606446028] ]",
"[ 9/8 → 19/16 | s:saw note:G2 partials:[0.6724895145744085 0.21410975232720375 0.5229047331959009 0.20957534946501255 0.7402758821845055 0.2681974694132805 0.4059371296316385 0.9422509074211121] ]",
"[ 19/16 → 5/4 | s:saw note:F1 partials:[0.08174665085971355 0.2762963864952326 0.5428560189902782 0.29452037811279297 0.17946280725300312 0.366960596293211 0.26327736489474773 0.5002023074775934] ]",
"[ 5/4 → 21/16 | s:saw note:G2 partials:[0.7287282031029463 0.9341024849563837 0.5098182689398527 0.5608645845204592 0.6812942810356617 0.5015129633247852 0.4602120481431484 0.927369873970747] ]",
"[ 21/16 → 11/8 | s:saw note:F1 partials:[0.08104278706014156 0.5200720727443695 0.5363391060382128 0.9824271816760302 0.10652091167867184 0.23473932035267353 0.7656102329492569 0.4850195776671171] ]",
"[ 11/8 → 23/16 | s:saw note:Ab1 partials:[0.18280375190079212 0.40527783520519733 0.30504362285137177 0.7622128054499626 0.842598931863904 0.053796686232089996 0.5124214049428701 0.9178454764187336] ]",
"[ 23/16 → 3/2 | s:saw note:Eb2 partials:[0.5081270858645439 0.1421387754380703 0.8852288406342268 0.9066353775560856 0.7700740322470665 0.35437702015042305 0.20032598450779915 0.963155921548605] ]",
"[ 3/2 → 25/16 | s:saw note:F2 partials:[0.6084080748260021 0.4424846563488245 0.755185678601265 0.04328125901520252 0.04244415462017059 0.3994515985250473 0.787989066913724 0.3180440980941057] ]",
"[ 25/16 → 13/8 | s:saw note:Ab1 partials:[0.17092766426503658 0.7808954659849405 0.8234915658831596 0.8643151633441448 0.9781719036400318 0.5102015696465969 0.018259897828102112 0.25844234600663185] ]",
"[ 13/8 → 27/16 | s:saw note:Db2 partials:[0.49675471149384975 0.2965749856084585 0.09848833456635475 0.19172007404267788 0.22677889838814735 0.9841996673494577 0.11156083643436432 0.6079028844833374] ]",
"[ 27/16 → 7/4 | s:saw note:Bb1 partials:[0.29589061066508293 0.13011129200458527 0.8021425940096378 0.38040103763341904 0.33274981752038 0.29745868407189846 0.9898250997066498 0.743482418358326] ]",
"[ 7/4 → 29/16 | s:saw note:Ab1 partials:[0.20473789609968662 0.7480021081864834 0.30757393687963486 0.5827204789966345 0.22094514779746532 0.6370698790997267 0.16573002003133297 0.0638319905847311] ]",
"[ 29/16 → 15/8 | s:saw note:Db2 partials:[0.4695742893964052 0.6690364442765713 0.6216684486716986 0.41342394426465034 0.9508822970092297 0.8403679896146059 0.91759910620749 0.6591395419090986] ]",
"[ 15/8 → 31/16 | s:saw note:F2 partials:[0.5945571791380644 0.3962489552795887 0.563431927934289 0.4742323160171509 0.4185752831399441 0.9703940469771624 0.5282467231154442 0.6896266285330057] ]",
"[ 31/16 → 2/1 | s:saw note:Bb2 partials:[0.8672592639923096 0.3438429981470108 0.9795673936605453 0.46547594852745533 0.9770395755767822 0.36851615831255913 0.7725539114326239 0.876962523907423] ]",
"[ 2/1 → 33/16 | s:saw note:C3 partials:[0.9595271199941635 0.5427457969635725 0.45864437520504 0.8057199101895094 0.18388565629720688 0.17221237532794476 0.8838487900793552 0.4976818822324276] ]",
"[ 33/16 → 17/8 | s:saw note:Bb1 partials:[0.3324784208089113 0.6005446836352348 0.1564352661371231 0.765217661857605 0.12899602390825748 0.20880493707954884 0.9156261626631021 0.5550615340471268] ]",
"[ 17/8 → 35/16 | s:saw note:Bb2 partials:[0.9033902939409018 0.22919894196093082 0.8388008493930101 0.9108077362179756 0.32246968522667885 0.16122018359601498 0.31932324543595314 0.18524732999503613] ]",
"[ 35/16 → 9/4 | s:saw note:F1 partials:[0.037000780925154686 0.30360451713204384 0.7516817897558212 0.17065968923270702 0.24669718369841576 0.978821286931634 0.9620356522500515 0.12417634017765522] ]",
"[ 9/4 → 37/16 | s:saw note:C2 partials:[0.34548251144587994 0.30419893004000187 0.10205957666039467 0.9906709585338831 0.3156223688274622 0.3745057098567486 0.7668170686811209 0.9379972033202648] ]",
"[ 37/16 → 19/8 | s:saw note:F2 partials:[0.6649543270468712 0.41613837145268917 0.9078915324062109 0.2232209537178278 0.2918607946485281 0.4931973237544298 0.7035325299948454 0.29280414804816246] ]",
"[ 19/8 → 39/16 | s:saw note:Bb2 partials:[0.8365066405385733 0.5369812995195389 0.2263860274106264 0.21759207546710968 0.2172116208821535 0.09454222768545151 0.8075542915612459 0.9017798062413931] ]",
"[ 39/16 → 5/2 | s:saw note:G1 partials:[0.14231421053409576 0.6801821701228619 0.7958894111216068 0.9494249671697617 0.179213372990489 0.22531690262258053 0.7458387855440378 0.3595987129956484] ]",
"[ 5/2 → 41/16 | s:saw note:Db2 partials:[0.45861607417464256 0.6807645913213491 0.07009582966566086 0.1450389064848423 0.2579921595752239 0.612881500273943 0.5503941811621189 0.7027011960744858] ]",
"[ 41/16 → 21/8 | s:saw note:F2 partials:[0.6068673655390739 0.8489279896020889 0.027898555621504784 0.90520747192204 0.18605150654911995 0.30939464271068573 0.16843407973647118 0.5127317048609257] ]",
"[ 21/8 → 43/16 | s:saw note:G1 partials:[0.16019348427653313 0.47758268006145954 0.9749126061797142 0.8025561925023794 0.3600110989063978 0.324309878051281 0.5655391272157431 0.13364790193736553] ]",
"[ 43/16 → 11/4 | s:saw note:Eb2 partials:[0.538035349920392 0.4103843830525875 0.6443832442164421 0.7532152868807316 0.32128027081489563 0.29064710810780525 0.5972099266946316 0.8252892810851336] ]",
"[ 11/4 → 45/16 | s:saw note:F2 partials:[0.6332327704876661 0.7342763151973486 0.16935866326093674 0.4754706546664238 0.528122790157795 0.21934260986745358 0.7476693484932184 0.2995396424084902] ]",
"[ 45/16 → 23/8 | s:saw note:Bb2 partials:[0.8912940509617329 0.5256196465343237 0.25639201514422894 0.46860250271856785 0.08611978217959404 0.6175916157662868 0.990638293325901 0.11189854890108109] ]",
"[ 23/8 → 47/16 | s:saw note:F1 partials:[0.01625417172908783 0.28409438766539097 0.3075305689126253 0.40343056432902813 0.5556836389005184 0.6926821582019329 0.5635769125074148 0.017873913049697876] ]",
"[ 47/16 → 3/1 | s:saw note:Bb1 partials:[0.2830589488148689 0.3613663297146559 0.6216250211000443 0.6083405166864395 0.27202711440622807 0.771099541336298 0.3156145606189966 0.736228458583355] ]",
"[ 3/1 → 49/16 | s:saw note:Ab1 partials:[0.21728911064565182 0.7605195846408606 0.11240843310952187 0.4372697602957487 0.15630115941166878 0.1653979979455471 0.5303416550159454 0.07218753732740879] ]",
"[ 49/16 → 25/8 | s:saw note:F1 partials:[0.012518879026174545 0.6565517522394657 0.783835593611002 0.2481316588819027 0.27786846831440926 0.6153149046003819 0.39914070069789886 0.2175555843859911] ]",
"[ 25/8 → 51/16 | s:saw note:C2 partials:[0.34324387833476067 0.14098095521330833 0.012934491038322449 0.827436288818717 0.5283997394144535 0.8942463714629412 0.4924008697271347 0.9298017006367445] ]",
"[ 51/16 → 13/4 | s:saw note:Bb2 partials:[0.9057559575885534 0.5731389932334423 0.06561482697725296 0.02156665176153183 0.4685337021946907 0.000788738951086998 0.6995994579046965 0.9893404543399811] ]",
"[ 13/4 → 53/16 | s:saw note:C3 partials:[0.9923650715500116 0.7797339502722025 0.1992435697466135 0.9005183521658182 0.718992218375206 0.23745290748775005 0.28872333094477654 0.35448253713548183] ]",
"[ 53/16 → 27/8 | s:saw note:Bb1 partials:[0.3235324025154114 0.5165992900729179 0.6231792941689491 0.8034896682947874 0.6663950439542532 0.4207208137959242 0.9748435858637094 0.27676148526370525] ]",
"[ 27/8 → 55/16 | s:saw note:C2 partials:[0.40869180113077164 0.4018078800290823 0.34696186147630215 0.9572948440909386 0.400035472586751 0.3531211093068123 0.7240961641073227 0.8125612027943134] ]",
"[ 55/16 → 7/2 | s:saw note:Ab2 partials:[0.775677714496851 0.0502743236720562 0.3246541116386652 0.503994770348072 0.8252716399729252 0.89872563816607 0.060319963842630386 0.25497629307210445] ]",
"[ 7/2 → 57/16 | s:saw note:C2 partials:[0.40994881466031075 0.6455810181796551 0.35704658553004265 0.2732649203389883 0.31587288342416286 0.23243548162281513 0.22569947130978107 0.17241661623120308] ]",
"[ 57/16 → 29/8 | s:saw note:Ab2 partials:[0.8133465722203255 0.8327706772834063 0.32914630882442 0.23131784796714783 0.7526696771383286 0.1361286472529173 0.11441296897828579 0.36366880126297474] ]",
"[ 29/8 → 59/16 | s:saw note:C3 partials:[0.9391485787928104 0.3380728308111429 0.7723016366362572 0.998674126341939 0.7193406894803047 0.6963680125772953 0.9260678570717573 0.7829763628542423] ]",
"[ 59/16 → 15/4 | s:saw note:C3 partials:[0.9731374885886908 0.0494034755975008 0.990720022469759 0.9819309785962105 0.22877203114330769 0.7265191469341516 0.4922411348670721 0.7549834884703159] ]",
"[ 15/4 → 61/16 | s:saw note:Ab2 partials:[0.8121673222631216 0.25548945367336273 0.5759696904569864 0.6668333616107702 0.9233786761760712 0.11143362522125244 0.9734930321574211 0.369325939565897] ]",
"[ 61/16 → 31/8 | s:saw note:G1 partials:[0.1008925698697567 0.7558664139360189 0.1523460578173399 0.6523381881415844 0.9690635204315186 0.7343240566551685 0.9544064309448004 0.9150135722011328] ]",
"[ 31/8 → 63/16 | s:saw note:G2 partials:[0.7361665386706591 0.7657648548483849 0.21474426984786987 0.8228576295077801 0.5647660344839096 0.08148551359772682 0.9714624118059874 0.13992334716022015] ]",
"[ 63/16 → 4/1 | s:saw note:Bb1 partials:[0.3056422360241413 0.8881660811603069 0.7683485243469477 0.08641545102000237 0.5427133180201054 0.20803124643862247 0.5347504671663046 0.5977730695158243] ]",
]
`;
exports[`runs examples > example "range" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:hh cutoff:2250 ]",
@@ -12898,54 +12240,54 @@ exports[`runs examples > example "wchooseCycles" example index 0 1`] = `
exports[`runs examples > example "wchooseCycles" example index 1 1`] = `
[
"[ 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 ]",
"[ 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 ]",
]
`;
-1
View File
@@ -1,5 +1,4 @@
{
"name": "dbpatch",
"dependencies": {
"csv": "^6.3.11"
},
-14
View File
@@ -1,14 +0,0 @@
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);
});
/*
*/
+7 -73
View File
@@ -48,94 +48,28 @@ You can also use the `crackle` type to play some subtle noise crackles. You can
### Additive Synthesis
Periodic waveforms are composed of several [harmonics](https://en.wikipedia.org/wiki/Harmonic) above a fundamental frequency, lying at integer multiples. These overtones combine to give a sound its unique timbral quality.
For the basic waveforms, we offer you control over these harmonics with the `partials` and `phases` functions.
#### Partials
`partials` refers to the magnitude of each harmonic relative to the fundamental frequency. They can thus be used to spectrally filter these waveforms and tame some of their harshness:
To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:
<MiniRepl
client:idle
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
.sound("sawtooth")
.partials([1, 1, "<1 0>", "<1 0>", "<1 0>", "<1 0>", "<1 0>"])
.n("<32 16 8 4>")
._scope()`}
/>
`partials` can also be used to construct _new_ waveforms not present in our basic set with the 'user' sound source:
When the `n` control is used on a basic waveform, it defines the number of harmonic partials the sound is getting.
You can also set `n` directly in mini notation with `sound`:
<MiniRepl
client:idle
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
.sound("user")
.partials([1, 0, 0.3, 0, 0.1, 0, 0, 0.3])
.sound("sawtooth:<32 16 8 4>")
._scope()`}
/>
We may algorithmically construct lists of magnitudes with Javascript code like:
<MiniRepl
client:idle
tune={`const numHarmonics = 22;
note("c2 <eb2 <g2 g1>>".fast(2))
.sound("saw")
.partials(new Array(numHarmonics).fill(1))
._scope()`}
/>
which acts as a spectral filter. Or:
<MiniRepl
client:idle
tune={`note("c2 <eb2 <g2 g1>>").fast(2)
.sound("user")
.partials(new Array(50).fill(0)
.map((_, idx) => ((-1) ** (idx + 1)) / (idx + 1))
)
._scope()`}
/>
which recovers a familiar waveform.
`partials` is also compatible with pattern functions designed to produce lists, like `randL` or `binaryL`:
<MiniRepl
client:idle
tune={`note("c2 <eb2 <g2 g1>>").fast(2)
.sound("user")
.partials(randL(10))
._scope()`}
/>
and with lists _of_ patterns:
<MiniRepl
client:idle
tune={`note("c2 <eb2 <g2 g1>>".fast(4))
.sound("user")
.partials([1, 0, "0 1", "0 1 0.3", rand])
._scope()`}
/>
Note that the first value in the `partials` array controls the magnitude of the fundamental harmonic rather than the DC offset, which is fixed at 0.
#### Phases
Earlier, we mentioned that periodic waveforms can be broken into a set of harmonics above a fundamental frequency. Each harmonic has two defining properties: its magnitude (how loud it is) and its phase, which determines where in its cycle that sine wave starts when the waveform is built.
These phases too can be declared in Strudel and can give your sounds interesting depth.
<MiniRepl
client:idle
tune={`s("saw").seg(16).n(irand(12)).scale("F1:minor")
.penv(48).panchor(0).pdec(0.05)
.delay(0.25).room(0.25)
.compressor(-20).vib(0.3)
.partials(randL(200))
.phases(randL(200))`}
/>
Note for tidal users: `n` in tidal is synonymous to `note` for synths only.
In strudel, this is not the case, where `n` will always change timbre, be it though different samples or different waveforms.
## Vibrato
@@ -8,34 +8,3 @@ 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>}
/>
);
}
@@ -1,29 +0,0 @@
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,8 +7,6 @@ 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 (
@@ -115,7 +113,6 @@ export function SettingsTab({ started }) {
isTabIndentationEnabled,
isMultiCursorEnabled,
patternAutoStart,
includePrebakeScriptInShare,
} = useSettings();
const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@@ -207,15 +204,6 @@ 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}
@@ -325,7 +313,8 @@ export function SettingsTab({ started }) {
</FormItem>
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
<FormItem label="Reset Settings">
<SpecialActionButton
<button
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
onClick={() => {
confirmDialog('Sure?').then((r) => {
if (r) {
@@ -336,7 +325,7 @@ export function SettingsTab({ started }) {
}}
>
restore default settings
</SpecialActionButton>
</button>
</FormItem>
</div>
);
+15 -34
View File
@@ -14,10 +14,6 @@ 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);
@@ -60,7 +56,9 @@ export function SoundsTab() {
// holds mutable ref to current triggered sound
const trigRef = useRef();
const numRef = useRef(0);
// Used to cycle through sound previews on banks with multiple sounds
let soundPreviewIdx = 0;
// stop current sound on mouseup
useEvent('mouseup', () => {
@@ -68,14 +66,6 @@ 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)} />
@@ -125,34 +115,25 @@ export function SoundsTab() {
const params = {
note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined,
s: name,
n: numRef.current,
n: soundPreviewIdx,
clip: 1,
release: 0.5,
sustain: 1,
duration: 0.5,
};
soundPreviewIdx++;
const onended = () => trigRef.current?.node?.disconnect();
// 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);
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);
}
} catch (err) {
console.warn('Failed to trigger sound:', err);
}
}}
>
-2
View File
@@ -3,8 +3,6 @@ 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;
+5 -15
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 { evaluate, transpiler } from '@strudel/transpiler';
import { transpiler } from '@strudel/transpiler';
import {
getAudioContextCurrentTime,
webaudioOutput,
@@ -63,10 +63,11 @@ async function getModule(name) {
const initialCode = `// LOADING`;
export function useReplContext() {
const { isSyncEnabled, audioEngineTarget, prebakeScript, includePrebakeScriptInShare } = useSettings();
const { isSyncEnabled, audioEngineTarget } = 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();
@@ -83,12 +84,7 @@ export function useReplContext() {
pattern: silence,
drawTime,
drawContext,
prebake: async () =>
Promise.all([modulesLoading, presets]).then(() => {
if (prebakeScript?.length) {
return evaluate(prebakeScript ?? '');
}
}),
prebake: async () => Promise.all([modulesLoading, presets]),
onUpdateState: (state) => {
setReplState({ ...state });
},
@@ -218,13 +214,7 @@ export function useReplContext() {
editorRef.current.repl.evaluate(code);
};
const handleShare = async () => {
let code = replState.code;
if (includePrebakeScriptInShare) {
code = prebakeScript + '\n' + code;
}
shareCode(code);
};
const handleShare = async () => shareCode(replState.code);
const context = {
started,
pending,
+6 -5
View File
@@ -1,10 +1,11 @@
import { code2hash, evalScope, hash2code, logger } from '@strudel/core';
import { 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';
@@ -108,8 +109,9 @@ export function confirmDialog(msg) {
});
}
let lastShared;
//RIP due to SPAM
// let lastShared;
// export async function shareCode(codeToShare) {
// // const codeToShare = activeCode || code;
// if (lastShared === codeToShare) {
@@ -144,10 +146,9 @@ export function confirmDialog(msg) {
// });
// }
export async function shareCode(codeToShare) {
export async function shareCode() {
try {
const hash = '#' + code2hash(codeToShare);
const shareUrl = window.location.origin + window.location.pathname + hash;
const shareUrl = window.location.href;
if (isTauri()) {
await writeText(shareUrl);
} else {
-5
View File
@@ -45,13 +45,11 @@ 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;
@@ -98,7 +96,6 @@ export function useSettings() {
isPanelOpen: parseBoolean(state.isPanelOpen),
userPatterns: userPatterns,
multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
includePrebakeScriptInShare: parseBoolean(state.includePrebakeScriptInShare),
patternAutoStart: isUdels()
? false
: state.patternAutoStart === undefined
@@ -111,8 +108,6 @@ 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) =>