Compare commits

...

83 Commits

Author SHA1 Message Date
Felix Roos 3c4ba667c8 cleanup 2025-11-27 21:29:37 +01:00
Felix Roos 7fcb5262b8 Merge remote-tracking branch 'origin/main' into glossing/sounds-tab-preview 2025-11-27 21:14:22 +01:00
Felix Roos b824c45ba4 Merge remote-tracking branch 'origin/main' into glossing/sounds-tab-preview 2025-11-27 21:11:50 +01:00
froos e47e6c87fc Merge pull request '[hydra] return the hydra object when await initHydra(..) is called' (#1784) from jeromew/strudel:return-hydra into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1784
2025-11-27 20:36:44 +01:00
froos 69b5ba89cb Merge pull request 'Use errorLogger for query and tonal errors' (#1782) from glossing/query-error into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1782
2025-11-27 20:36:12 +01:00
froos 33a51761ce Merge pull request '[perf] level 5 connect-leak on vowel' (#1779) from jeromew/strudel:fix-perf7 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1779
2025-11-27 20:27:09 +01:00
Switch Angel AKA Jade Rose d5d6284a90 Merge pull request 'feat: add prebake script import button for loading .strudel files' (#1774) from pre_eval into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1774
2025-11-26 08:23:37 +01:00
Switch Angel AKA Jade Rose fb790de26c Merge branch 'main' into pre_eval 2025-11-26 08:05:05 +01:00
Aria ec6f6a1f1c Merge pull request 'Fix Sampler port trampling' (#1717) from Dayglo/strudel:fix-port-trampling into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1717
Reviewed-by: Aria <glossing@noreply.codeberg.org>
2025-11-26 07:39:27 +01:00
W-A-James a126bc9b86 Fix lint 2025-11-25 22:20:55 -05:00
Aria a5c7fcc136 Add back options code (will resolve in another PR) 2025-11-25 22:16:23 -05:00
Aria d70b758065 Remove base url from sample server and properly handle url schemes 2025-11-25 22:16:11 -05:00
W-A-James 9fdba6374c remove extraneous eslint ignore directive 2025-11-25 22:14:19 -05:00
W-A-James af90baea33 Fix trampling of port env variable 2025-11-25 22:14:15 -05:00
Aria 743e239e52 Merge pull request 'Feat: Hook up octave and fix example' (#1773) from glossing/hook-up-octave into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1773
2025-11-25 20:59:08 +01:00
Aria f44a96a5ce Merge branch 'main' into glossing/hook-up-octave 2025-11-25 20:50:49 +01:00
Aria b94a686394 Merge pull request '[perf] fix connect-leak in tremolo param' (#1780) from jeromew/strudel:fix-perf8 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1780
Reviewed-by: Aria <glossing@noreply.codeberg.org>
2025-11-25 20:48:07 +01:00
Aria 728552b669 Merge branch 'main' into fix-perf8 2025-11-25 20:30:18 +01:00
Aria 8b427bc1dd Add oct synonym 2025-11-25 13:19:39 -06:00
Aria ae47d767ce Merge branch 'main' into glossing/hook-up-octave 2025-11-25 20:02:43 +01:00
jeromew ab64451d65 [hydra] return the hydra object when await initHydra(..) is called 2025-11-25 10:49:30 +00:00
Aria 2bc208d8f5 Use errorLogger for query and tonal errors 2025-11-24 19:05:42 -06:00
Switch Angel AKA Jade Rose cfeff26d42 Merge pull request 'filter modulation improvements!' (#1769) from filter_mod_improvements into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1769
2025-11-24 17:51:19 +01:00
jeromew e60ecba393 Merge branch 'fix-perf8' of https://codeberg.org/jeromew/strudel into fix-perf8 2025-11-24 16:36:14 +00:00
jeromew f0d49e0e22 [perf] fix connect-leak from audioworklet=>param in lfo/tremolo 2025-11-24 16:35:48 +00:00
Jade (Rose) Rowland 7ee6b933cc add tests 2025-11-24 11:33:21 -05:00
jeromew a3947d1ccf [perf] fix connect-leak from audioworklet=>param in lfo/tremolo 2025-11-24 16:33:20 +00:00
Jade (Rose) Rowland e86850b6d8 frequency mod 2025-11-24 11:19:49 -05:00
jeromew 89a6efb69e [perf] level 5 connect-leak on vowel 2025-11-24 16:10:00 +00:00
Jade (Rose) Rowland 31c694b596 Merge branch 'main' into filter_mod_improvements 2025-11-24 10:44:11 -05:00
Jade (Rose) Rowland 7a4babf6e1 format 2025-11-24 10:40:16 -05:00
Jade (Rose) Rowland d5e5ab83ac fix style 2025-11-24 10:39:37 -05:00
Jade (Rose) Rowland 20016fd9aa update hash 2025-11-23 20:25:13 -05:00
Switch Angel AKA Jade Rose 132a124644 Merge pull request 'Add o as a synonym for orbit' (#1766) from jade/orbitsynonym into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1766
Reviewed-by: Aria <glossing@noreply.codeberg.org>
2025-11-24 01:20:52 +01:00
Jade (Rose) Rowland 4caacb0de1 only eval if script 2025-11-23 19:12:28 -05:00
Jade (Rose) Rowland 8325b4cfc4 Merge branch 'main' into jade/orbitsynonym 2025-11-23 19:06:16 -05:00
Jade (Rose) Rowland 3b2df0b725 synonym: 2025-11-23 19:05:57 -05:00
Jade (Rose) Rowland f80fdbc38a Merge branch 'pre_eval' of ssh://codeberg.org/uzu/strudel into pre_eval 2025-11-23 18:59:26 -05:00
Jade (Rose) Rowland 3a7b237765 change variable name 2025-11-23 18:59:16 -05:00
Switch Angel AKA Jade Rose f5af55b63b Merge branch 'main' into pre_eval 2025-11-24 00:47:37 +01:00
Jade (Rose) Rowland d5dfbd7aa4 cleanup 2025-11-23 18:45:13 -05:00
Jade (Rose) Rowland 6221099af8 working 2025-11-23 18:09:38 -05:00
froos 22f1512697 Merge pull request '[perf] fix connect-leak in fm modulation' (#1758) from jeromew/strudel:fix-perf6 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1758
2025-11-23 22:23:24 +01:00
Aria 6c0d550b6b Fix example 2025-11-23 12:25:40 -06:00
Aria cf12e3e1d8 Hook up the octave control 2025-11-23 12:21:55 -06:00
froos 9a05a679aa Merge pull request 'Fix interoperability issue between all and await initHydra()' (#1663) from jeromew/strudel:jeromew-hydra-all into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1663
2025-11-23 09:47:22 +01:00
Switch Angel AKA Jade Rose 321e76166a Merge branch 'main' into filter_mod_improvements 2025-11-23 06:35:22 +01:00
Jade (Rose) Rowland bde17594ce test 2025-11-23 00:33:52 -05:00
Jade (Rose) Rowland 7bbb653963 fixed 2025-11-22 23:42:20 -05:00
Jade (Rose) Rowland 30e672a027 wip 2025-11-22 22:49:23 -05:00
Aria f7f3aa7668 Merge pull request 'Bug fix: Swap l/r gains with temp variable' (#1768) from glossing/stereo-bug into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1768
2025-11-23 04:23:04 +01:00
Aria adc3a9ac6c Swap with temp variable 2025-11-22 20:51:30 -06:00
Switch Angel AKA Jade Rose 2518b2aed2 Merge pull request 'FIX: prevent LFO filter modulation from popping from negative values' (#1767) from lpmod_improvements into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1767
2025-11-23 00:52:27 +01:00
Jade (Rose) Rowland 1d5f3a4f30 prevent filter modulation pops 2025-11-22 18:21:24 -05:00
Jade (Rose) Rowland 2efd56e331 add synonym 2025-11-22 18:10:28 -05:00
Switch Angel AKA Jade Rose 5fa6cb4653 Merge pull request 'prefix "S" for solo' (#1481) from solo into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1481
2025-11-23 00:03:31 +01:00
Jade (Rose) Rowland 19fd0fc649 rm unessecary file 2025-11-22 17:17:41 -05:00
Jade (Rose) Rowland e72e26eb81 add comment 2025-11-22 17:11:17 -05:00
Jade (Rose) Rowland 1c4b05d55f solo search n = 1 2025-11-22 16:40:31 -05:00
jeromew d453c861d3 Merge branch 'fix-perf6' of https://codeberg.org/jeromew/strudel into fix-perf6 2025-11-21 09:01:02 +00:00
jeromew f637b8b0dd [perf] fix connect-leak in fm modulation 2025-11-21 09:00:17 +00:00
Aria 06bf17f444 Merge pull request 'Bug Fix: Use frac due to negative frequencies from FM' (#1759) from glossing/droponelettertosaveears into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1759
2025-11-21 01:56:10 +01:00
Aria 21543956c6 Also for wavetable 2025-11-20 18:30:53 -06:00
Aria 4758effdc6 Use frac due to negative frequencies from FM 2025-11-20 18:22:44 -06:00
froos 6f58e74bea Merge pull request '[perf] fix connect-leak added by #1742 when noise() is not used' (#1757) from jeromew/strudel:fix-perf5 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1757
2025-11-20 19:48:15 +01:00
jeromew 907f03f3bf [perf] fix connect-leak in fm modulation 2025-11-20 17:40:51 +00:00
jeromew 0435711051 [perf] fix connect-leak added by #1742 when noise() is not used 2025-11-20 17:36:00 +00:00
froos 8f6718b9e8 Merge pull request '[perf] fix connect-leak in delay effect' (#1755) from jeromew/strudel:fix-perf4 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1755
2025-11-20 14:50:59 +01:00
froos aa77255d98 Merge pull request '[perf] fix connect leak when .noise() is in the mix' (#1742) from jeromew/strudel:fix-perf3 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1742
2025-11-20 14:49:40 +01:00
jeromew 7267990e20 Fix codeformat 2025-11-20 13:35:37 +00:00
jeromew 387e5db0fe Merge branch 'fix-perf3' of https://codeberg.org/jeromew/strudel into fix-perf3 2025-11-20 13:24:03 +00:00
jeromew d9f63b8dfa [perf] fix connect leak when .noise() is in the mix 2025-11-20 13:21:09 +00:00
jeromew 8ed72b4573 [perf] fix connect-leak in delay effect 2025-11-20 13:05:41 +00:00
Aria 74488f8480 Stray log message 2025-11-19 18:41:56 -06:00
Aria 8144ec46bd Attempt system for sounds tab previews 2025-11-19 18:35:35 -06:00
Felix Roos dd2c808cd7 hotfix: reduce sounds-tab click to play latency 2025-11-19 21:55:35 +01:00
Alex McLean 389a63a850 Merge pull request 'wchooseCycles has now notes in an example' (#1748) from scrappy_fiddler/strudel:documentation into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1748
2025-11-18 23:52:43 +01:00
scrappy_fiddler 99beb4454d update snapshot with note example 2025-11-17 20:49:26 +01:00
scrappy_fiddler 30cc46ea66 wchooseCycles has now notes in an example 2025-11-17 20:04:40 +01:00
jeromew bfa8fa3c75 [perf] fix connect leak when .noise() is in the mix 2025-11-16 09:23:11 +00:00
jeromew 47c85f8540 fix interoperability of all with hydra
Hydra add methods on Array.prototype which breaks the Array enumeration with for...in.
Use for...of instead
cf https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#difference_between_for...of_and_for...in for details on the issue

without this fix, it is impossible to use both `await initHydra()` and `all` in the same strudel code. Strudel errors with "Error: got "undefined" instead of pattern."

example code: https://strudel.cc/#YXdhaXQgaW5pdEh5ZHJhKCkKCmFsbChwaWFub3JvbGwpCgokOiBzKCJiZCIp
2025-10-16 14:06:33 +02:00
Switch Angel AKA Jade Rose f4cf77f5c6 Merge branch 'main' into solo 2025-09-30 08:09:37 +02:00
Felix Roos 7f103e77e3 prefix "S" for solo 2025-07-28 04:05:21 +02:00
26 changed files with 802 additions and 148 deletions
+51 -5
View File
@@ -1294,6 +1294,8 @@ export const { fanchor } = registerControl('fanchor');
* *
* @name lprate * @name lprate
* @param {number | Pattern} rate rate in hertz * @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'); export const { lprate } = registerControl('lprate');
@@ -1302,6 +1304,8 @@ export const { lprate } = registerControl('lprate');
* *
* @name lpsync * @name lpsync
* @param {number | Pattern} rate rate in cycles * @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'); export const { lpsync } = registerControl('lpsync');
@@ -1310,8 +1314,23 @@ export const { lpsync } = registerControl('lpsync');
* *
* @name lpdepth * @name lpdepth
* @param {number | Pattern} depth depth of modulation * @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'); 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 * Shape of the LFO for the lowpass filter
@@ -1361,6 +1380,19 @@ export const { bpsync } = registerControl('bpsync');
*/ */
export const { bpdepth } = registerControl('bpdepth'); 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 * Shape of the LFO for the bandpass filter
* *
@@ -1407,7 +1439,20 @@ export const { hpsync } = registerControl('hpsync');
* @name hpdepth * @name hpdepth
* @param {number | Pattern} depth depth of modulation * @param {number | Pattern} depth depth of modulation
*/ */
export const { hpdepth } = registerControl('hpdepth'); export const { hpdepth, hpdepthfreq } = registerControl('hpdepth');
/**
* Depth of the LFO for the hipass filter, in hz
*
* @name hpdepthfrequency
* @synonyms
* hpdethfreq
* @param {number | Pattern} depth depth of modulation
* @example
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
*/
export const { hpdepthfrequency } = registerControl('hpdepthfrequency', 'hpdepthfreq');
/** /**
* Shape of the LFO for the highpass filter * Shape of the LFO for the highpass filter
@@ -1820,12 +1865,12 @@ export const { nudge } = registerControl('nudge');
* Sets the default octave of a synth. * Sets the default octave of a synth.
* *
* @name octave * @name octave
* @synonyms oct
* @param {number | Pattern} octave octave number * @param {number | Pattern} octave octave number
* @example * @example
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc() * n("0,4,7").scale("F:minor").s('supersaw').octave("<0 1 2 3>")
* @superDirtOnly
*/ */
export const { octave } = registerControl('octave'); export const { octave, oct } = registerControl('octave', 'oct');
// ['ophatdecay'], // ['ophatdecay'],
// TODO: example // TODO: example
@@ -1833,6 +1878,7 @@ export const { octave } = registerControl('octave');
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects. * An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
* *
* @name orbit * @name orbit
* @synonyms o
* @param {number | Pattern} number * @param {number | Pattern} number
* @example * @example
* stack( * stack(
@@ -1840,7 +1886,7 @@ export const { octave } = registerControl('octave');
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2) * s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
* ) * )
*/ */
export const { orbit } = registerControl('orbit'); export const { orbit } = registerControl('orbit', 'o');
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that // 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'); export const { overgain } = registerControl('overgain');
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1 // TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
+2 -2
View File
@@ -25,7 +25,7 @@ import {
stringifyValues, stringifyValues,
} from './util.mjs'; } from './util.mjs';
import drawLine from './drawLine.mjs'; import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs'; import { errorLogger, logger } from './logger.mjs';
let stringParser; let stringParser;
@@ -414,7 +414,7 @@ export class Pattern {
try { try {
return this.query(new State(new TimeSpan(begin, end), controls)); return this.query(new State(new TimeSpan(begin, end), controls));
} catch (err) { } catch (err) {
logger(`[query]: ${err.message}`, 'error'); errorLogger(err, 'query');
return []; return [];
} }
} }
+16 -5
View File
@@ -152,9 +152,9 @@ export function repl({
// allows muting a pattern x with x_ or _x // allows muting a pattern x with x_ or _x
return silence; return silence;
} }
if (id === '$') { if (id.includes('$')) {
// allows adding anonymous patterns with $: // allows adding anonymous patterns with $:
id = `$${anonymousIndex}`; id = `${id}${anonymousIndex}`;
anonymousIndex++; anonymousIndex++;
} }
pPatterns[id] = this; pPatterns[id] = this;
@@ -215,8 +215,19 @@ export function repl({
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions); let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
if (Object.keys(pPatterns).length) { if (Object.keys(pPatterns).length) {
let patterns = []; let patterns = [];
let soloActive = false;
for (const [key, value] of Object.entries(pPatterns)) { for (const [key, value] of Object.entries(pPatterns)) {
patterns.push(value.withState((state) => state.setControls({ id: key }))); // handle soloed patterns ex: S$: s("bd!4")
const isSolod = key.length > 1 && key.startsWith('S');
if (isSolod && soloActive === false) {
// first time we see a soloed pattern, clear existing patterns
patterns = [];
soloActive = true;
}
if (!soloActive || (soloActive && isSolod)) {
const valWithState = value.withState((state) => state.setControls({ id: key }));
patterns.push(valWithState);
}
} }
if (eachTransform) { if (eachTransform) {
// Explicit lambda so only element (not index and array) are passed // Explicit lambda so only element (not index and array) are passed
@@ -227,8 +238,8 @@ export function repl({
pattern = eachTransform(pattern); pattern = eachTransform(pattern);
} }
if (allTransforms.length) { if (allTransforms.length) {
for (let i in allTransforms) { for (const transform of allTransforms) {
pattern = allTransforms[i](pattern); pattern = transform(pattern);
} }
} }
+1 -1
View File
@@ -524,7 +524,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
* @example * @example
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) * wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example * @example
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s() * wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note()
* @example * @example
* // The probability can itself be a pattern * // The probability can itself be a pattern
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s() * wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
+1
View File
@@ -35,6 +35,7 @@ export async function initHydra(options = {}) {
hydra.synth.s0.init({ src: canvas }); hydra.synth.s0.init({ src: canvas });
} }
} }
return hydra;
} }
export function clearHydra() { export function clearHydra() {
+1 -3
View File
@@ -9,6 +9,7 @@ import readline from 'readline';
import os from 'os'; import os from 'os';
const LOG = !!process.env.LOG || false; const LOG = !!process.env.LOG || false;
const PORT = process.env.PORT || 5432;
const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg']; const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg'];
const isAudioFile = (f) => { const isAudioFile = (f) => {
@@ -54,7 +55,6 @@ async function getBanks(directory, flat = false) {
banks[bank].push(subDir); banks[bank].push(subDir);
return subDir; return subDir;
}); });
banks._base = `http://localhost:5432`;
return { banks, files }; return { banks, files };
} }
@@ -134,8 +134,6 @@ const server = http.createServer(async (req, res) => {
readStream.pipe(res); readStream.pipe(res);
}); });
// eslint-disable-next-line
const PORT = process.env.PORT || 5432;
const IP_ADDRESS = '0.0.0.0'; const IP_ADDRESS = '0.0.0.0';
let IP; let IP;
const networkInterfaces = os.networkInterfaces(); const networkInterfaces = os.networkInterfaces();
+44 -9
View File
@@ -191,8 +191,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
const lfo = getParamLfo(audioContext, param, start, end, lfoValues); const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
return { lfo, disconnect: () => lfo?.disconnect() }; return { lfo, disconnect: () => lfo?.disconnect() };
} }
export function createFilter(context, start, end, params, cps, cycle) {
export function createFilter(context, start, end, params, cps) {
let { let {
frequency, frequency,
anchor, anchor,
@@ -202,12 +201,14 @@ export function createFilter(context, start, end, params, cps) {
q = 1, q = 1,
drive = 0.69, drive = 0.69,
depth, depth,
depthfrequency,
dcoffset = -0.5, dcoffset = -0.5,
skew, skew,
shape, shape,
rate, rate,
sync, sync,
} = params; } = params;
let frequencyParam, filter; let frequencyParam, filter;
if (model === 'ladder') { if (model === 'ladder') {
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive }); filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
@@ -238,8 +239,25 @@ export function createFilter(context, start, end, params, cps) {
if (sync != null) { if (sync != null) {
rate = cps * sync; rate = cps * sync;
} }
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate }; const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
getParamLfo(context, frequencyParam, start, end, lfoValues); 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);
}
return filter; return filter;
} }
@@ -262,7 +280,15 @@ export function drywet(dry, wet, wetAmount = 0) {
let mix = ac.createGain(); let mix = ac.createGain();
dry_gain.connect(mix); dry_gain.connect(mix);
wet_gain.connect(mix); wet_gain.connect(mix);
return mix; return {
node: mix,
onended: () => {
dry_gain.disconnect(mix);
wet_gain.disconnect(mix);
dry.disconnect(dry_gain);
wet.disconnect(wet_gain);
},
};
} }
let curves = ['linear', 'exponential']; let curves = ['linear', 'exponential'];
@@ -297,6 +323,10 @@ export function getVibratoOscillator(param, value, t) {
gain.gain.value = vibmod * 100; gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain); vibratoOscillator.connect(gain);
gain.connect(param); gain.connect(param);
vibratoOscillator.onended = () => {
gain.disconnect(param);
vibratoOscillator.disconnect(gain);
};
vibratoOscillator.start(t); vibratoOscillator.start(t);
return vibratoOscillator; return vibratoOscillator;
} }
@@ -350,9 +380,9 @@ const mod = (freq, range = 1, type = 'sine') => {
} }
osc.start(); osc.start();
const g = new GainNode(ctx, { gain: range }); const g = gainNode(range);
osc.connect(g); // -range, range osc.connect(g); // -range, range
return { node: g, stop: (t) => osc.stop(t) }; return { node: g, stop: (t) => osc.stop(t), osc: osc };
}; };
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => { const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
const carrfreq = frequencyparam.value; const carrfreq = frequencyparam.value;
@@ -404,6 +434,11 @@ export function applyFM(param, value, begin) {
modulator.connect(envGain); modulator.connect(envGain);
envGain.connect(param); envGain.connect(param);
} }
fmmod.osc.onended = () => {
envGain.disconnect();
modulator.disconnect();
fmmod.osc.disconnect();
};
} }
return { stop }; return { stop };
} }
@@ -505,7 +540,7 @@ export const getDistortion = (distort, postgain, algorithm) => {
}; };
export const getFrequencyFromValue = (value, defaultNote = 36) => { export const getFrequencyFromValue = (value, defaultNote = 36) => {
let { note, freq } = value; let { note, freq, octave = 0 } = value;
note = note || defaultNote; note = note || defaultNote;
if (typeof note === 'string') { if (typeof note === 'string') {
note = noteToMidi(note); // e.g. c3 => 48 note = noteToMidi(note); // e.g. c3 => 48
@@ -514,7 +549,7 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
if (!freq && typeof note === 'number') { if (!freq && typeof note === 'number') {
freq = midiToFreq(note); // + 48); freq = midiToFreq(note); // + 48);
} }
freq *= Math.pow(2, octave);
return Number(freq); return Number(freq);
}; };
+2 -1
View File
@@ -65,8 +65,9 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) {
export function getNoiseMix(inputNode, wet, t) { export function getNoiseMix(inputNode, wet, t) {
const noiseOscillator = getNoiseOscillator('pink', t); const noiseOscillator = getNoiseOscillator('pink', t);
const noiseMix = drywet(inputNode, noiseOscillator.node, wet); const noiseMix = drywet(inputNode, noiseOscillator.node, wet);
noiseOscillator.node.onended = () => noiseMix.onended();
return { return {
node: noiseMix, node: noiseMix.node,
stop: (time) => noiseOscillator?.stop(time), stop: (time) => noiseOscillator?.stop(time),
}; };
} }
+2 -2
View File
@@ -1,4 +1,4 @@
import { getCommonSampleInfo } from './util.mjs'; import { getBaseURL, getCommonSampleInfo } from './util.mjs';
import { registerSound, registerWaveTable } from './index.mjs'; import { registerSound, registerWaveTable } from './index.mjs';
import { getAudioContext } from './audioContext.mjs'; import { getAudioContext } from './audioContext.mjs';
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
@@ -211,7 +211,7 @@ export async function fetchSampleMap(url) {
// not a browser // not a browser
return; return;
} }
const base = url.split('/').slice(0, -1).join('/'); const base = getBaseURL(url);
if (typeof fetch === 'undefined') { if (typeof fetch === 'undefined') {
// skip fetch when in node / testing // skip fetch when in node / testing
return; return;
+11 -6
View File
@@ -263,8 +263,8 @@ let audioReady;
export async function initAudioOnFirstClick(options) { export async function initAudioOnFirstClick(options) {
if (!audioReady) { if (!audioReady) {
audioReady = new Promise((resolve) => { audioReady = new Promise((resolve) => {
document.addEventListener('click', async function listener() { document.addEventListener('mousedown', async function listener() {
document.removeEventListener('click', listener); document.removeEventListener('mousedown', listener);
await initAudio(options); await initAudio(options);
resolve(); resolve();
}); });
@@ -553,13 +553,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
rate: 'lprate', rate: 'lprate',
sync: 'lpsync', sync: 'lpsync',
depth: 'lpdepth', depth: 'lpdepth',
depthfrequency: 'lpdepthfrequency',
shape: 'lpshape', shape: 'lpshape',
dcoffset: 'lpdc', dcoffset: 'lpdc',
skew: 'lpskew', skew: 'lpskew',
}; };
const lpParams = pickAndRename(value, lpMap); const lpParams = pickAndRename(value, lpMap);
lpParams.type = 'lowpass'; lpParams.type = 'lowpass';
let lp = () => createFilter(ac, t, end, lpParams, cps); let lp = () => createFilter(ac, t, end, lpParams, cps, cycle);
chain.push(lp()); chain.push(lp());
if (ftype === '24db') { if (ftype === '24db') {
chain.push(lp()); chain.push(lp());
@@ -581,13 +582,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
rate: 'hprate', rate: 'hprate',
sync: 'hpsync', sync: 'hpsync',
depth: 'hpdepth', depth: 'hpdepth',
depthfrequency: 'hpdepthfrequency',
shape: 'hpshape', shape: 'hpshape',
dcoffset: 'hpdc', dcoffset: 'hpdc',
skew: 'hpskew', skew: 'hpskew',
}; };
const hpParams = pickAndRename(value, hpMap); const hpParams = pickAndRename(value, hpMap);
hpParams.type = 'highpass'; hpParams.type = 'highpass';
let hp = () => createFilter(ac, t, end, hpParams, cps); let hp = () => createFilter(ac, t, end, hpParams, cps, cycle);
chain.push(hp()); chain.push(hp());
if (ftype === '24db') { if (ftype === '24db') {
chain.push(hp()); chain.push(hp());
@@ -609,13 +611,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
rate: 'bprate', rate: 'bprate',
sync: 'bpsync', sync: 'bpsync',
depth: 'bpdepth', depth: 'bpdepth',
depthfrequency: 'bpdepthfrequency',
shape: 'bpshape', shape: 'bpshape',
dcoffset: 'bpdc', dcoffset: 'bpdc',
skew: 'bpskew', skew: 'bpskew',
}; };
const bpParams = pickAndRename(value, bpMap); const bpParams = pickAndRename(value, bpMap);
bpParams.type = 'bandpass'; bpParams.type = 'bandpass';
let bp = () => createFilter(ac, t, end, bpParams, cps); let bp = () => createFilter(ac, t, end, bpParams, cps, cycle);
chain.push(bp()); chain.push(bp());
if (ftype === '24db') { if (ftype === '24db') {
chain.push(bp()); chain.push(bp());
@@ -665,6 +668,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
curve: 1.5, curve: 1.5,
}); });
lfo.connect(amGain.gain); lfo.connect(amGain.gain);
audioNodes.push(lfo);
chain.push(amGain); chain.push(amGain);
} }
@@ -692,7 +696,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
// delay // delay
if (delay > 0 && delaytime > 0 && delayfeedback > 0) { if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
orbitBus.getDelay(delaytime, delayfeedback, t); orbitBus.getDelay(delaytime, delayfeedback, t);
orbitBus.sendDelay(post, delay); const send = orbitBus.sendDelay(post, delay);
audioNodes.push(send);
} }
// reverb // reverb
if (room > 0) { if (room > 0) {
+1 -1
View File
@@ -82,7 +82,7 @@ export class Orbit {
} }
sendDelay(node, amount) { sendDelay(node, amount) {
effectSend(node, this.delayNode, amount); return effectSend(node, this.delayNode, amount);
} }
duck(t, onsettime = 0, attacktime = 0.1, depth = 1) { duck(t, onsettime = 0, attacktime = 0.1, depth = 1) {
+14 -10
View File
@@ -48,19 +48,17 @@ export function registerSynthSounds() {
[0.001, 0.05, 0.6, 0.01], [0.001, 0.05, 0.6, 0.01],
); );
let sound = getOscillator(s, t, value);
let { node: o, stop, triggerRelease } = sound;
// turn down // turn down
const g = gainNode(0.3); const g = gainNode(0.3);
const { duration } = value; let sound = getOscillator(s, t, value, () => {
o.onended = () => {
o.disconnect();
g.disconnect(); g.disconnect();
onended(); onended();
}; });
let { node: o, stop, triggerRelease } = sound;
const { duration } = value;
const envGain = gainNode(1); const envGain = gainNode(1);
let node = o.connect(g).connect(envGain); let node = o.connect(g).connect(envGain);
@@ -460,7 +458,7 @@ export function waveformN(partials, phases, type) {
} }
// expects one of waveforms as s // expects one of waveforms as s
export function getOscillator(s, t, value) { export function getOscillator(s, t, value, onended) {
const { duration, noise = 0 } = value; const { duration, noise = 0 } = value;
const partials = value.partials ?? value.n; const partials = value.partials ?? value.n;
let o; let o;
@@ -482,7 +480,6 @@ export function getOscillator(s, t, value) {
} }
// set frequency // set frequency
o.frequency.value = getFrequencyFromValue(value); o.frequency.value = getFrequencyFromValue(value);
o.start(t);
let vibratoOscillator = getVibratoOscillator(o.detune, value, t); let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
@@ -495,6 +492,13 @@ export function getOscillator(s, t, value) {
noiseMix = getNoiseMix(o, noise, t); noiseMix = getNoiseMix(o, noise, t);
} }
o.onended = () => {
o.disconnect();
noiseMix?.node.disconnect();
onended();
};
o.start(t);
return { return {
node: noiseMix?.node || o, node: noiseMix?.node || o,
stop: (time) => { stop: (time) => {
+10
View File
@@ -114,3 +114,13 @@ export function getCommonSampleInfo(hapValue, bank) {
export const pickAndRename = (source, map) => { export const pickAndRename = (source, map) => {
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]])); return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
}; };
export const getBaseURL = (url) => {
try {
// For real URLs
return new URL('.', new URL(url)).href.replace(/\/$/, ''); // removes trailing slash
} catch {
// For pseudo URLS
return url.split('/').slice(0, -1).join('/');
}
};
+17 -5
View File
@@ -45,7 +45,8 @@ if (typeof GainNode !== 'undefined') {
throw new Error('vowel: unknown vowel ' + letter); throw new Error('vowel: unknown vowel ' + letter);
} }
const { gains, qs, freqs } = vowelFormant[letter]; const { gains, qs, freqs } = vowelFormant[letter];
const makeupGain = ac.createGain(); this.makeupGain = ac.createGain();
this.audioNodes = [];
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
const gain = ac.createGain(); const gain = ac.createGain();
gain.gain.value = gains[i]; gain.gain.value = gains[i];
@@ -53,14 +54,25 @@ if (typeof GainNode !== 'undefined') {
filter.type = 'bandpass'; filter.type = 'bandpass';
filter.Q.value = qs[i]; filter.Q.value = qs[i];
filter.frequency.value = freqs[i]; filter.frequency.value = freqs[i];
this.connect(filter); super.connect(filter);
filter.connect(gain); filter.connect(gain);
gain.connect(makeupGain); this.audioNodes.push(filter);
gain.connect(this.makeupGain);
this.audioNodes.push(gain);
} }
makeupGain.gain.value = 8; // how much makeup gain to add? this.makeupGain.gain.value = 8; // how much makeup gain to add?
this.connect = (target) => makeupGain.connect(target);
return this; 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) { AudioContext.prototype.createVowelFilter = function (letter) {
+3 -2
View File
@@ -1,5 +1,5 @@
import { getAudioContext, registerSound } from './index.mjs'; import { getAudioContext, registerSound } from './index.mjs';
import { getCommonSampleInfo } from './util.mjs'; import { getBaseURL, getCommonSampleInfo } from './util.mjs';
import { import {
applyFM, applyFM,
applyParameterModulators, applyParameterModulators,
@@ -190,6 +190,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
if (url.startsWith('local:')) { if (url.startsWith('local:')) {
url = `http://localhost:5432`; url = `http://localhost:5432`;
} }
const base = getBaseURL(url);
if (typeof fetch !== 'function') { if (typeof fetch !== 'function') {
// not a browser // not a browser
return; return;
@@ -200,7 +201,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
} }
return fetch(url) return fetch(url)
.then((res) => res.json()) .then((res) => res.json())
.then((json) => _processTables(json, url, frameLen, options)) .then((json) => _processTables(json, base, frameLen, options))
.catch((error) => { .catch((error) => {
console.error(error); console.error(error);
throw new Error(`error loading "${url}"`); throw new Error(`error loading "${url}"`);
+6 -5
View File
@@ -532,7 +532,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n)); const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
// We must wrap this here because it is passed into sawblep below which // We must wrap this here because it is passed into sawblep below which
// has domain [0, 1] // has domain [0, 1]
const dt = ffrac(freqVoice * INVSR); const dt = frac(freqVoice * INVSR);
this.phase[n] = this.phase[n] ?? Math.random(); this.phase[n] = this.phase[n] ?? Math.random();
const v = waveshapes.sawblep(this.phase[n], dt); const v = waveshapes.sawblep(this.phase[n], dt);
@@ -543,8 +543,9 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
if (pn >= 1.0) pn -= 1.0; if (pn >= 1.0) pn -= 1.0;
this.phase[n] = pn; this.phase[n] = pn;
// invert right and left gain // invert right and left gain
const tmp = gainL;
gainL = gainR; gainL = gainR;
gainR = gainL; gainR = tmp;
} }
} }
return true; return true;
@@ -1338,7 +1339,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const tablePos = clamp(pv(parameters.position, i), 0, 1); const tablePos = clamp(pv(parameters.position, i), 0, 1);
const idx = tablePos * (this.numFrames - 1); const idx = tablePos * (this.numFrames - 1);
const fIdx = idx | 0; const fIdx = idx | 0;
const frac = idx - fIdx; const interpT = idx - fIdx;
const warpAmount = clamp(pv(parameters.warp, i), 0, 1); const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
const warpMode = pv(parameters.warpMode, i); const warpMode = pv(parameters.warpMode, i);
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1); const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
@@ -1368,13 +1369,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode); const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
const s0 = this._sampleFrame(table[fIdx], ph); const s0 = this._sampleFrame(table[fIdx], ph);
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph); const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
let s = s0 + (s1 - s0) * frac; let s = lerp(s0, s1, interpT);
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) { if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
s = -s; s = -s;
} }
outL[i] += s * gainL * normalizer; outL[i] += s * gainL * normalizer;
outR[i] += s * gainR * normalizer; outR[i] += s * gainR * normalizer;
this.phase[n] = ffrac(this.phase[n] + dPhase); this.phase[n] = frac(this.phase[n] + dPhase);
} }
} }
return true; return true;
+11 -2
View File
@@ -5,7 +5,16 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Note, Interval, Scale } from '@tonaljs/tonal'; import { Note, Interval, Scale } from '@tonaljs/tonal';
import { register, _mod, logger, isNote, noteToMidi, removeUndefineds, getAccidentalsOffset } from '@strudel/core'; import {
_mod,
errorLogger,
getAccidentalsOffset,
isNote,
logger,
noteToMidi,
register,
removeUndefineds,
} from '@strudel/core';
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs'; import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P'; const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
@@ -294,7 +303,7 @@ export const scale = register(
} }
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset)); if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
} catch (err) { } catch (err) {
logger(`[tonal] ${err.message}`, 'error'); errorLogger(err, 'tonal');
return; // will be removed return; // will be removed
} }
} }
+474 -60
View File
@@ -1391,6 +1391,75 @@ exports[`runs examples > example "bpdecay" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "bpdepthfrequency" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
]
`;
exports[`runs examples > example "bpenv" example index 0 1`] = ` exports[`runs examples > example "bpenv" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]", "[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]",
@@ -4786,6 +4855,75 @@ exports[`runs examples > example "hpdecay" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "hpdepthfrequency" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
]
`;
exports[`runs examples > example "hpenv" example index 0 1`] = ` exports[`runs examples > example "hpenv" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]", "[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]",
@@ -5969,6 +6107,144 @@ exports[`runs examples > example "lpdecay" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "lpdepth" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
]
`;
exports[`runs examples > example "lpdepthfrequency" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
]
`;
exports[`runs examples > example "lpenv" example index 0 1`] = ` exports[`runs examples > example "lpenv" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]", "[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]",
@@ -6157,6 +6433,75 @@ exports[`runs examples > example "lpq" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "lprate" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lprate:4 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lprate:8 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lprate:1 ]",
]
`;
exports[`runs examples > example "lprelease" example index 0 1`] = ` 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 ]", "[ 0/1 → 1/4 | note:c2 s:sawtooth clip:0.5 cutoff:300 lpenv:4 lprelease:0.5 release:0.5 ]",
@@ -6199,6 +6544,75 @@ exports[`runs examples > example "lpsustain" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "lpsync" example index 0 1`] = `
[
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
]
`;
exports[`runs examples > example "lrate" example index 0 1`] = ` exports[`runs examples > example "lrate" example index 0 1`] = `
[ [
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]", "[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]",
@@ -6888,18 +7302,18 @@ exports[`runs examples > example "nrpv" example index 0 1`] = `
exports[`runs examples > example "octave" example index 0 1`] = ` exports[`runs examples > example "octave" example index 0 1`] = `
[ [
"[ 0/1 → 1/1 | n:0 s:supersquare octave:3 ]", "[ 0/1 → 1/1 | note:F3 s:supersaw octave:0 ]",
"[ 0/1 → 1/1 | n:4 s:supersquare octave:3 ]", "[ 0/1 → 1/1 | note:C4 s:supersaw octave:0 ]",
"[ 0/1 → 1/1 | n:7 s:supersquare octave:3 ]", "[ 0/1 → 1/1 | note:F4 s:supersaw octave:0 ]",
"[ 1/1 → 2/1 | n:0 s:supersquare octave:4 ]", "[ 1/1 → 2/1 | note:F3 s:supersaw octave:1 ]",
"[ 1/1 → 2/1 | n:4 s:supersquare octave:4 ]", "[ 1/1 → 2/1 | note:C4 s:supersaw octave:1 ]",
"[ 1/1 → 2/1 | n:7 s:supersquare octave:4 ]", "[ 1/1 → 2/1 | note:F4 s:supersaw octave:1 ]",
"[ 2/1 → 3/1 | n:0 s:supersquare octave:5 ]", "[ 2/1 → 3/1 | note:F3 s:supersaw octave:2 ]",
"[ 2/1 → 3/1 | n:4 s:supersquare octave:5 ]", "[ 2/1 → 3/1 | note:C4 s:supersaw octave:2 ]",
"[ 2/1 → 3/1 | n:7 s:supersquare octave:5 ]", "[ 2/1 → 3/1 | note:F4 s:supersaw octave:2 ]",
"[ 3/1 → 4/1 | n:0 s:supersquare octave:6 ]", "[ 3/1 → 4/1 | note:F3 s:supersaw octave:3 ]",
"[ 3/1 → 4/1 | n:4 s:supersquare octave:6 ]", "[ 3/1 → 4/1 | note:C4 s:supersaw octave:3 ]",
"[ 3/1 → 4/1 | n:7 s:supersquare octave:6 ]", "[ 3/1 → 4/1 | note:F4 s:supersaw octave:3 ]",
] ]
`; `;
@@ -12484,54 +12898,54 @@ exports[`runs examples > example "wchooseCycles" example index 0 1`] = `
exports[`runs examples > example "wchooseCycles" example index 1 1`] = ` exports[`runs examples > example "wchooseCycles" example index 1 1`] = `
[ [
"[ 0/1 → 1/12 | s:bd ]", "[ 0/1 → 1/12 | note:c ]",
"[ 1/12 → 1/6 | s:bd ]", "[ 1/12 → 1/6 | note:c ]",
"[ 1/6 → 1/4 | s:bd ]", "[ 1/6 → 1/4 | note:c ]",
"[ 1/4 → 1/3 | s:bd ]", "[ 1/4 → 1/3 | note:c ]",
"[ 1/3 → 5/12 | s:bd ]", "[ 1/3 → 5/12 | note:c ]",
"[ 5/12 → 1/2 | s:bd ]", "[ 5/12 → 1/2 | note:c ]",
"[ 1/2 → 7/12 | s:sd ]", "[ 1/2 → 7/12 | note:f ]",
"[ 7/12 → 2/3 | s:sd ]", "[ 7/12 → 2/3 | note:f ]",
"[ 2/3 → 3/4 | s:sd ]", "[ 2/3 → 3/4 | note:f ]",
"[ 3/4 → 5/6 | s:bd ]", "[ 3/4 → 5/6 | note:c ]",
"[ 5/6 → 11/12 | s:bd ]", "[ 5/6 → 11/12 | note:c ]",
"[ 11/12 → 1/1 | s:bd ]", "[ 11/12 → 1/1 | note:c ]",
"[ 1/1 → 13/12 | s:bd ]", "[ 1/1 → 13/12 | note:c ]",
"[ 13/12 → 7/6 | s:bd ]", "[ 13/12 → 7/6 | note:c ]",
"[ 7/6 → 5/4 | s:bd ]", "[ 7/6 → 5/4 | note:c ]",
"[ 5/4 → 4/3 | s:bd ]", "[ 5/4 → 4/3 | note:c ]",
"[ 4/3 → 17/12 | s:bd ]", "[ 4/3 → 17/12 | note:c ]",
"[ 17/12 → 3/2 | s:bd ]", "[ 17/12 → 3/2 | note:c ]",
"[ 3/2 → 19/12 | s:hh ]", "[ 3/2 → 19/12 | note:a ]",
"[ 19/12 → 5/3 | s:hh ]", "[ 19/12 → 5/3 | note:a ]",
"[ 5/3 → 7/4 | s:hh ]", "[ 5/3 → 7/4 | note:a ]",
"[ 7/4 → 11/6 | s:bd ]", "[ 7/4 → 11/6 | note:c ]",
"[ 11/6 → 23/12 | s:bd ]", "[ 11/6 → 23/12 | note:c ]",
"[ 23/12 → 2/1 | s:bd ]", "[ 23/12 → 2/1 | note:c ]",
"[ 2/1 → 25/12 | s:hh ]", "[ 2/1 → 25/12 | note:a ]",
"[ 25/12 → 13/6 | s:hh ]", "[ 25/12 → 13/6 | note:a ]",
"[ 13/6 → 9/4 | s:hh ]", "[ 13/6 → 9/4 | note:a ]",
"[ 9/4 → 7/3 | s:hh ]", "[ 9/4 → 7/3 | note:a ]",
"[ 7/3 → 29/12 | s:hh ]", "[ 7/3 → 29/12 | note:a ]",
"[ 29/12 → 5/2 | s:hh ]", "[ 29/12 → 5/2 | note:a ]",
"[ 5/2 → 31/12 | s:bd ]", "[ 5/2 → 31/12 | note:c ]",
"[ 31/12 → 8/3 | s:bd ]", "[ 31/12 → 8/3 | note:c ]",
"[ 8/3 → 11/4 | s:bd ]", "[ 8/3 → 11/4 | note:c ]",
"[ 11/4 → 17/6 | s:bd ]", "[ 11/4 → 17/6 | note:c ]",
"[ 17/6 → 35/12 | s:bd ]", "[ 17/6 → 35/12 | note:c ]",
"[ 35/12 → 3/1 | s:bd ]", "[ 35/12 → 3/1 | note:c ]",
"[ 3/1 → 37/12 | s:bd ]", "[ 3/1 → 37/12 | note:c ]",
"[ 37/12 → 19/6 | s:bd ]", "[ 37/12 → 19/6 | note:c ]",
"[ 19/6 → 13/4 | s:bd ]", "[ 19/6 → 13/4 | note:c ]",
"[ 13/4 → 10/3 | s:sd ]", "[ 13/4 → 10/3 | note:f ]",
"[ 10/3 → 41/12 | s:sd ]", "[ 10/3 → 41/12 | note:f ]",
"[ 41/12 → 7/2 | s:sd ]", "[ 41/12 → 7/2 | note:f ]",
"[ 7/2 → 43/12 | s:hh ]", "[ 7/2 → 43/12 | note:a ]",
"[ 43/12 → 11/3 | s:hh ]", "[ 43/12 → 11/3 | note:a ]",
"[ 11/3 → 15/4 | s:hh ]", "[ 11/3 → 15/4 | note:a ]",
"[ 15/4 → 23/6 | s:bd ]", "[ 15/4 → 23/6 | note:c ]",
"[ 23/6 → 47/12 | s:bd ]", "[ 23/6 → 47/12 | note:c ]",
"[ 47/12 → 4/1 | s:bd ]", "[ 47/12 → 4/1 | note:c ]",
] ]
`; `;
@@ -8,3 +8,34 @@ export function ActionButton({ children, label, labelIsHidden, className, ...but
</button> </button>
); );
} }
export function SpecialActionButton(props) {
const { className, ...buttonProps } = props;
return (
<ActionButton
{...buttonProps}
className={cx('bg-background p-2 max-w-[300px] rounded-md hover:opacity-50', className)}
/>
);
}
export function ActionInput({ label, className, ...props }) {
return (
<label className={cx('inline-flex items-center cursor-pointer', className)}>
<input {...props} className="sr-only peer" />
<span className="inline-flex items-center peer-hover:opacity-50">{label}</span>
</label>
);
}
export function SpecialActionInput({ className, ...props }) {
return (
<ActionInput
{...props}
className={className}
label={<span className="bg-background p-2 max-w-[300px] rounded-md">{props.label}</span>}
/>
);
}
@@ -0,0 +1,29 @@
import { errorLogger } from '@strudel/core';
import { useSettings, storePrebakeScript } from '../../../settings.mjs';
import { SpecialActionInput } from '../button/action-button';
async function importScript(script) {
const reader = new FileReader();
reader.readAsText(script);
reader.onload = () => {
const text = reader.result;
storePrebakeScript(text);
};
reader.onerror = () => {
errorLogger(new Error('failed to import prebake script'), 'importScript');
};
}
export function ImportPrebakeScriptButton() {
const settings = useSettings();
return (
<SpecialActionInput
type="file"
label="import prebake script"
accept=".strudel"
onChange={(e) => importScript(e.target.files[0])}
/>
);
}
@@ -7,6 +7,8 @@ import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx'; import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
import { confirmDialog } from '../../util.mjs'; import { confirmDialog } from '../../util.mjs';
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio'; 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 }) { function Checkbox({ label, value, onChange, disabled = false }) {
return ( return (
@@ -113,6 +115,7 @@ export function SettingsTab({ started }) {
isTabIndentationEnabled, isTabIndentationEnabled,
isMultiCursorEnabled, isMultiCursorEnabled,
patternAutoStart, patternAutoStart,
includePrebakeScriptInShare,
} = useSettings(); } = useSettings();
const shouldAlwaysSync = isUdels(); const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@@ -204,6 +207,15 @@ export function SettingsTab({ started }) {
/> />
</FormItem> </FormItem>
</div> </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"> <FormItem label="Keybindings">
<ButtonGroup <ButtonGroup
value={keybindings} value={keybindings}
@@ -313,8 +325,7 @@ export function SettingsTab({ started }) {
</FormItem> </FormItem>
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem> <FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
<FormItem label="Reset Settings"> <FormItem label="Reset Settings">
<button <SpecialActionButton
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
onClick={() => { onClick={() => {
confirmDialog('Sure?').then((r) => { confirmDialog('Sure?').then((r) => {
if (r) { if (r) {
@@ -325,7 +336,7 @@ export function SettingsTab({ started }) {
}} }}
> >
restore default settings restore default settings
</button> </SpecialActionButton>
</FormItem> </FormItem>
</div> </div>
); );
+34 -15
View File
@@ -14,6 +14,10 @@ import { prebake } from '@src/repl/prebake.mjs';
const getSamples = (samples) => const getSamples = (samples) =>
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1; 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() { export function SoundsTab() {
const sounds = useStore(soundMap); const sounds = useStore(soundMap);
@@ -56,9 +60,7 @@ export function SoundsTab() {
// holds mutable ref to current triggered sound // holds mutable ref to current triggered sound
const trigRef = useRef(); 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 // stop current sound on mouseup
useEvent('mouseup', () => { useEvent('mouseup', () => {
@@ -66,6 +68,14 @@ export function SoundsTab() {
trigRef.current = undefined; trigRef.current = undefined;
ref?.stop?.(getAudioContext().currentTime + 0.01); 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 ( return (
<div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground"> <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)} /> <Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
@@ -115,25 +125,34 @@ export function SoundsTab() {
const params = { const params = {
note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined, note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined,
s: name, s: name,
n: soundPreviewIdx, n: numRef.current,
clip: 1, clip: 1,
release: 0.5, release: 0.5,
sustain: 1, sustain: 1,
duration: 0.5, duration: 0.5,
}; };
soundPreviewIdx++;
const onended = () => trigRef.current?.node?.disconnect(); const onended = () => trigRef.current?.node?.disconnect();
try { // Attempt to play the sample and retry every 200ms until 10 attempts have been reached
// Pre-load the sample by calling onTrigger with a future time let errMsg;
// This triggers the loading but schedules playback for later for (let attempt = 0; attempt < 10; attempt++) {
const time = ctx.currentTime + 0.5; // Give 500ms for loading try {
const ref = await onTrigger(time, params, onended); // Pre-load the sample by calling onTrigger with a future time
trigRef.current = ref; // This triggers the loading but schedules playback for later
if (ref?.node) { const time = ctx.currentTime + 0.05; // Give 50ms for loading
connectToDestination(ref.node); const ref = await onTrigger(time, params, onended);
trigRef.current = ref;
if (ref?.node) {
connectToDestination(ref.node);
break;
}
} catch (err) {
errMsg = err;
}
if (attempt == 9) {
console.warn('Failed to trigger sound after 10 attempts' + (errMsg ? `: ${errMsg}` : ''));
} else {
await wait(200);
} }
} catch (err) {
console.warn('Failed to trigger sound:', err);
} }
}} }}
> >
+2
View File
@@ -3,6 +3,8 @@ import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@st
import { registerSamplesFromDB } from './idbutils.mjs'; import { registerSamplesFromDB } from './idbutils.mjs';
import './piano.mjs'; import './piano.mjs';
import './files.mjs'; import './files.mjs';
import { settingsMap } from '@src/settings.mjs';
import { evaluate } from '@strudel/transpiler';
const { BASE_URL } = import.meta.env; const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
+15 -5
View File
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core'; import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core';
import { getDrawContext } from '@strudel/draw'; import { getDrawContext } from '@strudel/draw';
import { transpiler } from '@strudel/transpiler'; import { evaluate, transpiler } from '@strudel/transpiler';
import { import {
getAudioContextCurrentTime, getAudioContextCurrentTime,
webaudioOutput, webaudioOutput,
@@ -63,11 +63,10 @@ async function getModule(name) {
const initialCode = `// LOADING`; const initialCode = `// LOADING`;
export function useReplContext() { export function useReplContext() {
const { isSyncEnabled, audioEngineTarget } = useSettings(); const { isSyncEnabled, audioEngineTarget, prebakeScript, includePrebakeScriptInShare } = useSettings();
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc; const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput; const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds; const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
const init = useCallback(() => { const init = useCallback(() => {
const drawTime = [-2, 2]; const drawTime = [-2, 2];
const drawContext = getDrawContext(); const drawContext = getDrawContext();
@@ -84,7 +83,12 @@ export function useReplContext() {
pattern: silence, pattern: silence,
drawTime, drawTime,
drawContext, drawContext,
prebake: async () => Promise.all([modulesLoading, presets]), prebake: async () =>
Promise.all([modulesLoading, presets]).then(() => {
if (prebakeScript?.length) {
return evaluate(prebakeScript ?? '');
}
}),
onUpdateState: (state) => { onUpdateState: (state) => {
setReplState({ ...state }); setReplState({ ...state });
}, },
@@ -214,7 +218,13 @@ export function useReplContext() {
editorRef.current.repl.evaluate(code); editorRef.current.repl.evaluate(code);
}; };
const handleShare = async () => shareCode(replState.code); const handleShare = async () => {
let code = replState.code;
if (includePrebakeScriptInShare) {
code = prebakeScript + '\n' + code;
}
shareCode(code);
};
const context = { const context = {
started, started,
pending, pending,
+5 -6
View File
@@ -1,11 +1,10 @@
import { evalScope, hash2code, logger } from '@strudel/core'; import { code2hash, evalScope, hash2code, logger } from '@strudel/core';
import { settingPatterns } from '../settings.mjs'; import { settingPatterns } from '../settings.mjs';
import { setVersionDefaults } from '@strudel/webaudio'; import { setVersionDefaults } from '@strudel/webaudio';
import { getMetadata } from '../metadata_parser'; import { getMetadata } from '../metadata_parser';
import { isTauri } from '../tauri.mjs'; import { isTauri } from '../tauri.mjs';
import './Repl.css'; import './Repl.css';
import { createClient } from '@supabase/supabase-js'; import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs'; import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs';
@@ -109,9 +108,8 @@ export function confirmDialog(msg) {
}); });
} }
let lastShared;
//RIP due to SPAM //RIP due to SPAM
// let lastShared;
// export async function shareCode(codeToShare) { // export async function shareCode(codeToShare) {
// // const codeToShare = activeCode || code; // // const codeToShare = activeCode || code;
// if (lastShared === codeToShare) { // if (lastShared === codeToShare) {
@@ -146,9 +144,10 @@ let lastShared;
// }); // });
// } // }
export async function shareCode() { export async function shareCode(codeToShare) {
try { try {
const shareUrl = window.location.href; const hash = '#' + code2hash(codeToShare);
const shareUrl = window.location.origin + window.location.pathname + hash;
if (isTauri()) { if (isTauri()) {
await writeText(shareUrl); await writeText(shareUrl);
} else { } else {
+5
View File
@@ -45,11 +45,13 @@ export const defaultSettings = {
isPanelOpen: true, isPanelOpen: true,
togglePanelTrigger: 'click', //click | hover togglePanelTrigger: 'click', //click | hover
userPatterns: '{}', userPatterns: '{}',
prebakeScript: '',
audioEngineTarget: audioEngineTargets.webaudio, audioEngineTarget: audioEngineTargets.webaudio,
isButtonRowHidden: false, isButtonRowHidden: false,
isCSSAnimationDisabled: false, isCSSAnimationDisabled: false,
maxPolyphony: 128, maxPolyphony: 128,
multiChannelOrbits: false, multiChannelOrbits: false,
includePrebakeScriptInShare: true,
}; };
let search = null; let search = null;
@@ -96,6 +98,7 @@ export function useSettings() {
isPanelOpen: parseBoolean(state.isPanelOpen), isPanelOpen: parseBoolean(state.isPanelOpen),
userPatterns: userPatterns, userPatterns: userPatterns,
multiChannelOrbits: parseBoolean(state.multiChannelOrbits), multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
includePrebakeScriptInShare: parseBoolean(state.includePrebakeScriptInShare),
patternAutoStart: isUdels() patternAutoStart: isUdels()
? false ? false
: state.patternAutoStart === undefined : state.patternAutoStart === undefined
@@ -108,6 +111,8 @@ export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool); export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', 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); export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
const patternSetting = (key) => const patternSetting = (key) =>