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
33 changed files with 473 additions and 1482 deletions
+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> <button id="play">PLAY</button>
<script> <script>
initStrudel({ initStrudel({
+8 -8
View File
@@ -12,14 +12,14 @@
"vite": "^6.0.11" "vite": "^6.0.11"
}, },
"dependencies": { "dependencies": {
"@strudel/codemirror": "workspace:*", "@strudel/codemirror": "1.2.6",
"@strudel/core": "workspace:*", "@strudel/core": "1.2.5",
"@strudel/draw": "workspace:*", "@strudel/draw": "1.2.5",
"@strudel/mini": "workspace:*", "@strudel/mini": "1.2.5",
"@strudel/soundfonts": "workspace:*", "@strudel/soundfonts": "1.2.6",
"@strudel/tonal": "workspace:*", "@strudel/tonal": "1.2.5",
"@strudel/transpiler": "workspace:*", "@strudel/transpiler": "1.2.5",
"@strudel/webaudio": "workspace:*" "@strudel/webaudio": "1.2.6"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "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 export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
samples('github:felixroos/samples') 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" "<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
// make it 22 edo // 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") .mask("<0@4 1@12>/4")
.reset("<x@15 [x(3,8) x*[4 8]]>") .reset("<x@15 [x(3,8) x*[4 8]]>")
// wait for it... // wait for it...
).fast(2/3) ).fast(4/3)
//.crush(6) // remove "//" if you dare`; //.crush(6) // remove "//" if you dare`;
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0 export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
samples('github:felixroos/samples') samples('https://strudel.b-cdn.net/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
addVoicings('hip', { addVoicings('hip', {
m11: ['2M 3m 4P 7m'], m11: ['2M 3m 4P 7m'],
@@ -61,16 +60,14 @@ stack(
s("rim*4").end(.05).bank('RolandTR808').speed(.8).room(.2) 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 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 // 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 // thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
samples('github:felixroos/samples') samples('https://strudel.b-cdn.net/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
setcps(.5) setcps(.5)
+1 -1
View File
@@ -13,7 +13,7 @@
<button id="c">C</button> <button id="c">C</button>
<button id="stop">stop</button> <button id="stop">stop</button>
<script type="module"> <script type="module">
import { initStrudel } from '@strudel/web'; import { initStrudel, hush } from '@strudel/web';
initStrudel({ initStrudel({
prebake: () => samples('github:tidalcycles/dirt-samples'), prebake: () => samples('github:tidalcycles/dirt-samples'),
}); });
+1 -1
View File
@@ -13,7 +13,7 @@
"vite": "^6.0.11" "vite": "^6.0.11"
}, },
"dependencies": { "dependencies": {
"@strudel/web": "workspace:*" "@strudel/web": "1.2.6"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
+6 -51
View File
@@ -1294,8 +1294,6 @@ 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');
@@ -1304,8 +1302,6 @@ 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');
@@ -1314,23 +1310,8 @@ 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
@@ -1380,19 +1361,6 @@ 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
* *
@@ -1439,20 +1407,7 @@ 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, hpdepthfreq } = registerControl('hpdepth'); export const { hpdepth } = 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
@@ -1865,12 +1820,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").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'], // ['ophatdecay'],
// TODO: example // 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. * 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(
@@ -1886,7 +1840,7 @@ export const { octave, oct } = registerControl('octave', 'oct');
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2) * 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 // 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
@@ -2310,6 +2264,7 @@ export const { tsdelay } = registerControl('tsdelay');
export const { real } = registerControl('real'); export const { real } = registerControl('real');
export const { imag } = registerControl('imag'); export const { imag } = registerControl('imag');
export const { enhance } = registerControl('enhance'); export const { enhance } = registerControl('enhance');
export const { partials } = registerControl('partials');
export const { comb } = registerControl('comb'); export const { comb } = registerControl('comb');
export const { smear } = registerControl('smear'); export const { smear } = registerControl('smear');
export const { scram } = registerControl('scram'); export const { scram } = registerControl('scram');
-63
View File
@@ -3624,66 +3624,3 @@ for (const name of distAlgoNames) {
return this.distort(argsPat); 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');
};
+5 -16
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.includes('$')) { if (id === '$') {
// allows adding anonymous patterns with $: // allows adding anonymous patterns with $:
id = `${id}${anonymousIndex}`; id = `$${anonymousIndex}`;
anonymousIndex++; anonymousIndex++;
} }
pPatterns[id] = this; pPatterns[id] = this;
@@ -215,19 +215,8 @@ 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)) {
// handle soloed patterns ex: S$: s("bd!4") patterns.push(value.withState((state) => state.setControls({ id: key })));
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
@@ -238,8 +227,8 @@ export function repl({
pattern = eachTransform(pattern); pattern = eachTransform(pattern);
} }
if (allTransforms.length) { if (allTransforms.length) {
for (const transform of allTransforms) { for (let i in allTransforms) {
pattern = transform(pattern); pattern = allTransforms[i](pattern);
} }
} }
+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); 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 * @name binary
* @param {number} n - input number to convert to 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 * @name binaryN
* @param {number} n - input number to convert to binary * @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)); 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) => { export const randrun = (n) => {
return signal((t) => { return signal((t) => {
// Without adding 0.5, the first cycle is always 0,1,2,3,... // 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 * @example
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) * wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example * @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 * @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()
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel/canvas # @strudel/draw
Helpers for drawing with the Canvas API and Strudel Helpers for drawing with the Canvas API and Strudel
## Install ## Install
```sh ```sh
npm i @strudel/canvas --save npm i @strudel/draw --save
``` ```
+3 -1
View File
@@ -9,7 +9,6 @@ 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) => {
@@ -55,6 +54,7 @@ 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,6 +134,8 @@ 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();
+1 -1
View File
@@ -89,7 +89,7 @@ superdough({ s: 'bd', delay: 0.5 }, 0, 1);
- `decay`: seconds of decay phase - `decay`: seconds of decay phase
- `sustain`: gain of sustain phase - `sustain`: gain of sustain phase
- `release`: seconds of release 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 - `duration`: seconds the sound should last. optional for one shot samples, required for synth sounds
### registerSynthSounds() ### registerSynthSounds()
+8 -43
View File
@@ -191,7 +191,8 @@ 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,
@@ -201,14 +202,12 @@ export function createFilter(context, start, end, params, cps, cycle) {
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 });
@@ -239,25 +238,8 @@ export function createFilter(context, start, end, params, cps, cycle) {
if (sync != null) { if (sync != null) {
rate = cps * sync; rate = cps * sync;
} }
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined); const lfoValues = { depth, dcoffset, skew, shape, frequency: rate };
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); getParamLfo(context, frequencyParam, start, end, lfoValues);
}
return filter; return filter;
} }
@@ -280,15 +262,7 @@ 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 { return mix;
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'];
@@ -323,10 +297,6 @@ 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;
} }
@@ -380,9 +350,9 @@ const mod = (freq, range = 1, type = 'sine') => {
} }
osc.start(); osc.start();
const g = gainNode(range); const g = new GainNode(ctx, { gain: range });
osc.connect(g); // -range, 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 fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
const carrfreq = frequencyparam.value; const carrfreq = frequencyparam.value;
@@ -434,11 +404,6 @@ 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 };
} }
@@ -540,7 +505,7 @@ export const getDistortion = (distort, postgain, algorithm) => {
}; };
export const getFrequencyFromValue = (value, defaultNote = 36) => { export const getFrequencyFromValue = (value, defaultNote = 36) => {
let { note, freq, octave = 0 } = value; let { note, freq } = 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
@@ -549,7 +514,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);
}; };
+1 -2
View File
@@ -65,9 +65,8 @@ 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, node: noiseMix,
stop: (time) => noiseOscillator?.stop(time), stop: (time) => noiseOscillator?.stop(time),
}; };
} }
-2
View File
@@ -104,8 +104,6 @@ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt,
player.start(); player.start();
context.oncomplete = function (event) { context.oncomplete = function (event) {
callback(event.renderedBuffer); callback(event.renderedBuffer);
filter.disconnect();
player.disconnect();
}; };
context.startRendering(); 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 { 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 = getBaseURL(url); const base = url.split('/').slice(0, -1).join('/');
if (typeof fetch === 'undefined') { if (typeof fetch === 'undefined') {
// skip fetch when in node / testing // skip fetch when in node / testing
return; return;
+7 -13
View File
@@ -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 // oldest audio nodes will be destroyed if maximum polyphony is exceeded
for (let i = 0; i <= activeSoundSources.size - maxPolyphony; i++) { for (let i = 0; i <= activeSoundSources.size - maxPolyphony; i++) {
const ch = activeSoundSources.entries().next(); const ch = activeSoundSources.entries().next();
const source = ch.value[1].deref(); const source = ch.value[1];
const chainID = ch.value[0]; const chainID = ch.value[0];
const endTime = t + 0.25; const endTime = t + 0.25;
source?.node?.gain?.linearRampToValueAtTime(0, endTime); 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) { if (soundHandle) {
sourceNode = soundHandle.node; sourceNode = soundHandle.node;
activeSoundSources.set(chainID, new WeakRef(soundHandle)); // allow GC activeSoundSources.set(chainID, soundHandle);
} }
} else { } else {
throw new Error(`sound ${s} not found! Is it loaded?`); 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', 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, cycle); let lp = () => createFilter(ac, t, end, lpParams, cps);
chain.push(lp()); chain.push(lp());
if (ftype === '24db') { if (ftype === '24db') {
chain.push(lp()); chain.push(lp());
@@ -582,14 +581,13 @@ 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, cycle); let hp = () => createFilter(ac, t, end, hpParams, cps);
chain.push(hp()); chain.push(hp());
if (ftype === '24db') { if (ftype === '24db') {
chain.push(hp()); chain.push(hp());
@@ -611,14 +609,13 @@ 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, cycle); let bp = () => createFilter(ac, t, end, bpParams, cps);
chain.push(bp()); chain.push(bp());
if (ftype === '24db') { if (ftype === '24db') {
chain.push(bp()); chain.push(bp());
@@ -668,7 +665,6 @@ 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);
} }
@@ -696,8 +692,7 @@ 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);
const send = orbitBus.sendDelay(post, delay); orbitBus.sendDelay(post, delay);
audioNodes.push(send);
} }
// reverb // reverb
if (room > 0) { 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); roomIR = await loadBuffer(url, ac, ir, 0);
} }
orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin); orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
const send = orbitBus.sendReverb(post, room); orbitBus.sendReverb(post, room);
audioNodes.push(send);
} }
if (djf != null) { if (djf != null) {
+4 -4
View File
@@ -78,14 +78,14 @@ export class Orbit {
return this.reverbNode; return this.reverbNode;
} }
sendReverb(node, amount) { sendReverb(node, amount) {
return effectSend(node, this.reverbNode, amount); effectSend(node, this.reverbNode, amount);
} }
sendDelay(node, amount) { sendDelay(node, amount) {
return effectSend(node, this.delayNode, amount); effectSend(node, this.delayNode, amount);
} }
duck(t, onsettime = 0, attacktime = 0.16, depth = 0.8) { duck(t, onsettime = 0, attacktime = 0.1, depth = 1) {
const onset = onsettime; const onset = onsettime;
const attack = Math.max(attacktime, 0.002); const attack = Math.max(attacktime, 0.002);
const gainParam = this.output.gain; const gainParam = this.output.gain;
@@ -178,7 +178,7 @@ export class SuperdoughAudioController {
this.output.reset(); this.output.reset();
} }
duck(targetOrbits, t, onsettime = 0, attacktime = 0.16, depth = 0.8) { duck(targetOrbits, t, onsettime = 0, attacktime = 0.1, depth = 1) {
const targetArr = [targetOrbits].flat(); const targetArr = [targetOrbits].flat();
const onsetArr = [onsettime].flat(); const onsetArr = [onsettime].flat();
const attackArr = [attacktime].flat(); const attackArr = [attacktime].flat();
+25 -51
View File
@@ -15,10 +15,9 @@ import {
noises, noises,
webAudioTimeout, webAudioTimeout,
} from './helpers.mjs'; } from './helpers.mjs';
import { logger } from './logger.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const waveforms = ['triangle', 'square', 'sawtooth', 'sine', 'user']; const waveforms = ['triangle', 'square', 'sawtooth', 'sine'];
const waveformAliases = [ const waveformAliases = [
['tri', 'triangle'], ['tri', 'triangle'],
['sqr', 'square'], ['sqr', 'square'],
@@ -48,17 +47,19 @@ 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);
let sound = getOscillator(s, t, value, () => { const { duration } = 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);
@@ -413,13 +414,9 @@ export function registerSynthSounds() {
waveformAliases.forEach(([alias, actual]) => soundMap.set({ ...soundMap.get(), [alias]: soundMap.get()[actual] })); waveformAliases.forEach(([alias, actual]) => soundMap.set({ ...soundMap.get(), [alias]: soundMap.get()[actual] }));
} }
const PI2 = 2 * Math.PI; export function waveformN(partials, type) {
export function waveformN(partials, phases, type) { const real = new Float32Array(partials + 1);
const isList = typeof partials === 'object'; const imag = new Float32Array(partials + 1);
partials = isList ? partials : new Float32Array(partials).fill(1);
const len = partials.length;
const real = new Float32Array(len + 1);
const imag = new Float32Array(len + 1);
const ac = getAudioContext(); const ac = getAudioContext();
const osc = ac.createOscillator(); const osc = ac.createOscillator();
@@ -427,29 +424,20 @@ export function waveformN(partials, phases, type) {
sawtooth: (n) => [0, -1 / n], sawtooth: (n) => [0, -1 / n],
square: (n) => [0, n % 2 === 0 ? 0 : 1 / n], square: (n) => [0, n % 2 === 0 ? 0 : 1 / n],
triangle: (n) => [n % 2 === 0 ? 0 : 1 / (n * n), 0], triangle: (n) => [n % 2 === 0 ? 0 : 1 / (n * n), 0],
user: (_n) => [0, 1],
}; };
if (!terms[type]) { if (!terms[type]) {
throw new Error(`unknown wave type ${type}`); throw new Error(`unknown wave type ${type}`);
} }
for (let n = 0; n < len; n++) { real[0] = 0; // dc offset
const mag = partials[n]; imag[0] = 0;
const [r, i] = terms[type](n + 1); // we skip n === 0 as this is dc offset let n = 1;
const phase = phases?.[n] ?? 0; while (n <= partials) {
// Scale by `partials` const [r, i] = terms[type](n);
let R = r * mag; real[n] = r;
let I = i * mag; imag[n] = i;
// Apply rotation by the phase n++;
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;
} }
const wave = ac.createPeriodicWave(real, imag); const wave = ac.createPeriodicWave(real, imag);
@@ -458,28 +446,21 @@ export function waveformN(partials, phases, type) {
} }
// expects one of waveforms as s // expects one of waveforms as s
export function getOscillator(s, t, value, onended) { export function getOscillator(s, t, value) {
const { duration, noise = 0 } = value; let { n: partials, duration, noise = 0 } = value;
const partials = value.partials ?? value.n;
let o; 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 no partials are given, use stock waveforms
if (!partials || partials?.length === 0 || s === 'sine') { if (!partials || s === 'sine') {
o = getAudioContext().createOscillator(); o = getAudioContext().createOscillator();
o.type = s || 'triangle'; o.type = s || 'triangle';
} }
// generate custom waveform if partials are given // generate custom waveform if partials are given
else { else {
o = waveformN(partials, value.phases, s); o = waveformN(partials, s);
} }
// 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);
@@ -492,13 +473,6 @@ export function getOscillator(s, t, value, onended) {
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,13 +114,3 @@ 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('/');
}
};
+2 -3
View File
@@ -1,5 +1,5 @@
import { getAudioContext, registerSound } from './index.mjs'; import { getAudioContext, registerSound } from './index.mjs';
import { getBaseURL, getCommonSampleInfo } from './util.mjs'; import { getCommonSampleInfo } from './util.mjs';
import { import {
applyFM, applyFM,
applyParameterModulators, applyParameterModulators,
@@ -190,7 +190,6 @@ 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;
@@ -201,7 +200,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, base, frameLen, options)) .then((json) => _processTables(json, url, frameLen, options))
.catch((error) => { .catch((error) => {
console.error(error); console.error(error);
throw new Error(`error loading "${url}"`); throw new Error(`error loading "${url}"`);
+171 -235
View File
@@ -6,57 +6,51 @@ import OLAProcessor from './ola-processor';
import FFT from './fft.js'; import FFT from './fft.js';
import { getDistortionAlgorithm } from './helpers.mjs'; 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 clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const mod = (n, m) => ((n % m) + m) % m; const mod = (n, m) => ((n % m) + m) % m;
const lerp = (a, b, n) => n * (b - a) + a; const lerp = (a, b, n) => n * (b - a) + a;
const pv = (arr, n) => arr[n] ?? arr[0]; const pv = (arr, n) => arr[n] ?? arr[0];
const frac = (x) => x - Math.floor(x); 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 getUnisonDetune = (unison, detune, voiceIndex) => {
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) => {
if (unison < 2) { if (unison < 2) {
return (_voiceIdx) => 0; return 0;
} }
const scale = detune / (unison - 1); return lerp(-detune * 0.5, detune * 0.5, voiceIndex / (unison - 1));
const center = detune * 0.5;
return (voiceIdx) => voiceIdx * scale - center;
}; };
const applySemitoneDetuneToFrequency = (frequency, detune) => { const applySemitoneDetuneToFrequency = (frequency, detune) => {
return frequency * Math.pow(2, detune / 12); 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 // Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517 // referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
function polyBlep(phase, dt) { function polyBlep(phase, dt) {
dt = Math.min(dt, 1 - dt); dt = Math.min(dt, 1 - dt);
const invdt = 1 / dt;
// Start of cycle // Start of cycle
if (phase < dt) { if (phase < dt) {
phase *= invdt; phase /= dt;
return 2 * phase - phase ** 2 - 1; // 2 * (phase - phase^2/2 - 0.5)
return phase + phase - phase * phase - 1;
} }
// End of cycle // End of cycle
else if (phase > 1 - dt) { else if (phase > 1 - dt) {
phase = (phase - 1) * invdt; phase = (phase - 1) / dt;
return phase ** 2 + 2 * phase + 1; // 2 * (phase^2/2 + phase + 0.5)
return phase * phase + phase + phase + 1;
} }
// 0 otherwise // 0 otherwise
else { else {
return 0; return 0;
@@ -72,7 +66,7 @@ const waveshapes = {
return phase / skew; return phase / skew;
}, },
sine(phase) { 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) { ramp(phase) {
return phase; return phase;
@@ -106,6 +100,12 @@ const waveshapes = {
return v - polyBlep(phase, dt); return v - polyBlep(phase, dt);
}, },
}; };
function getParamValue(block, param) {
if (param.length > 1) {
return param[block];
}
return param[0];
}
const waveShapeNames = Object.keys(waveshapes); const waveShapeNames = Object.keys(waveshapes);
class LFOProcessor extends AudioWorkletProcessor { class LFOProcessor extends AudioWorkletProcessor {
@@ -165,9 +165,9 @@ class LFOProcessor extends AudioWorkletProcessor {
const blockSize = output[0].length ?? 0; const blockSize = output[0].length ?? 0;
if (this.phase == null) { 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 n = 0; n < blockSize; n++) {
for (let i = 0; i < output.length; i++) { for (let i = 0; i < output.length; i++) {
let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth; let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
@@ -293,8 +293,8 @@ class TwoPoleFilter {
// Out of bound values can produce NaNs // Out of bound values can produce NaNs
resonance = clamp(resonance, 0, 1); resonance = clamp(resonance, 0, 1);
cutoff = clamp(cutoff, 0, sampleRate / 2 - 1); cutoff = clamp(cutoff, 0, sampleRate / 2 - 1);
const c = clamp(2 * Math.sin(cutoff * PI * INVSR), 0, 1.14); const c = clamp(2 * Math.sin(cutoff * (_PI / sampleRate)), 0, 1.14);
const r = Math.pow(0.5, 8 * resonance + 1); const r = Math.pow(0.5, (resonance + 0.125) / 0.125);
const mrc = 1 - r * c; const mrc = 1 - r * c;
this.s0 = mrc * this.s0 - c * this.s1 + c * s; // bpf this.s0 = mrc * this.s0 - c * this.s1 + c * s; // bpf
this.s1 = mrc * this.s1 + c * this.s0; // lpf this.s1 = mrc * this.s1 + c * this.s0; // lpf
@@ -353,6 +353,11 @@ class DJFProcessor extends AudioWorkletProcessor {
} }
registerProcessor('djf-processor', DJFProcessor); 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 //adapted from https://github.com/TheBouteillacBear/webaudioworklet-wasm?tab=MIT-1-ov-file
class LadderProcessor extends AudioWorkletProcessor { class LadderProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() { static get parameterDescriptors() {
@@ -390,7 +395,7 @@ class LadderProcessor extends AudioWorkletProcessor {
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000); const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
let cutoff = parameters.frequency[0]; let cutoff = parameters.frequency[0];
cutoff = cutoff * TWO_PI * INVSR; cutoff = (cutoff * 2 * _PI) / sampleRate;
cutoff = cutoff > 1 ? 1 : cutoff; cutoff = cutoff > 1 ? 1 : cutoff;
const k = Math.min(8, resonance * 0.13); const k = Math.min(8, resonance * 0.13);
@@ -503,7 +508,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
name: 'voices', name: 'voices',
defaultValue: 5, defaultValue: 5,
min: 1, min: 1,
automationRate: 'k-rate',
}, },
]; ];
} }
@@ -515,37 +519,40 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
// this.port.postMessage({ type: 'onended' }); // this.port.postMessage({ type: 'onended' });
return false; return false;
} }
const output = outputs[0]; const output = outputs[0];
const voices = params.voices[0]; // k-rate
for (let i = 0; i < output[0].length; i++) { for (let i = 0; i < output[0].length; i++) {
const detune = pv(params.detune, i); const detune = pv(params.detune, i);
const voices = pv(params.voices, i);
const freqspread = pv(params.freqspread, i); const freqspread = pv(params.freqspread, i);
const panspread = pv(params.panspread, i) * 0.5 + 0.5; const panspread = pv(params.panspread, i) * 0.5 + 0.5;
let gainL = Math.sqrt(1 - panspread); const gain1 = Math.sqrt(1 - panspread);
let gainR = Math.sqrt(panspread); const gain2 = Math.sqrt(panspread);
let freq = pv(params.frequency, i); let freq = pv(params.frequency, i);
// Main detuning // Main detuning
freq = applySemitoneDetuneToFrequency(freq, detune / 100); freq = applySemitoneDetuneToFrequency(freq, detune / 100);
const detuner = getDetuner(voices, freqspread);
for (let n = 0; n < voices; n++) { 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 // 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 // We must wrap this here because it is passed into sawblep below which
// has domain [0, 1] // has domain [0, 1]
const dt = frac(freqVoice * INVSR); const dt = mod(freqVoice / sampleRate, 1);
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);
output[0][i] += v * gainL; output[0][i] = output[0][i] + v * gainL;
output[1][i] += v * gainR; output[1][i] = output[1][i] + v * gainR;
let pn = this.phase[n] + dt; this.phase[n] = wrapPhase(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;
} }
} }
return true; 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 // Phase Vocoder sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
const BUFFERED_BLOCK_SIZE = 2048; const BUFFERED_BLOCK_SIZE = 2048;
const hannCache = new Map();
function genHannWindow(length) { function genHannWindow(length) {
if (!hannCache.has(length)) { let win = new Float32Array(length);
const win = new Float32Array(length); for (var i = 0; i < length; i++) {
for (let i = 0; i < length; i++) { win[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / length));
win[i] = 0.5 * (1 - Math.cos((TWO_PI * i) / length));
} }
hannCache.set(length, win); return win;
}
return hannCache.get(length);
} }
class PhaseVocoderProcessor extends OLAProcessor { class PhaseVocoderProcessor extends OLAProcessor {
@@ -584,10 +587,11 @@ class PhaseVocoderProcessor extends OLAProcessor {
blockSize: BUFFERED_BLOCK_SIZE, blockSize: BUFFERED_BLOCK_SIZE,
}; };
super(options); super(options);
this.timeCursor = 0;
this.fftSize = this.blockSize; this.fftSize = this.blockSize;
this.invfftSize = 1 / this.fftSize; this.timeCursor = 0;
this.hannWindow = genHannWindow(this.fftSize);
this.hannWindow = genHannWindow(this.blockSize);
// prepare FFT and pre-allocate buffers // prepare FFT and pre-allocate buffers
this.fft = new FFT(this.fftSize); this.fft = new FFT(this.fftSize);
this.freqComplexBuffer = this.fft.createComplexArray(); this.freqComplexBuffer = this.fft.createComplexArray();
@@ -600,43 +604,52 @@ class PhaseVocoderProcessor extends OLAProcessor {
processOLA(inputs, outputs, parameters) { processOLA(inputs, outputs, parameters) {
// no automation, take last value // no automation, take last value
let pitchFactor = parameters.pitchFactor[parameters.pitchFactor.length - 1]; let pitchFactor = parameters.pitchFactor[parameters.pitchFactor.length - 1];
if (pitchFactor < 0) { if (pitchFactor < 0) {
pitchFactor = pitchFactor * 0.25; pitchFactor = pitchFactor * 0.25;
} }
pitchFactor = Math.max(0, pitchFactor + 1); pitchFactor = Math.max(0, pitchFactor + 1);
for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < inputs[i].length; j++) { for (var i = 0; i < this.nbInputs; i++) {
const input = inputs[i][j]; for (var j = 0; j < inputs[i].length; j++) {
const output = outputs[i][j]; // big assumption here: output is symetric to input
var input = inputs[i][j];
var output = outputs[i][j];
this.applyHannWindow(input); this.applyHannWindow(input);
this.fft.realTransform(this.freqComplexBuffer, input); this.fft.realTransform(this.freqComplexBuffer, input);
this.computeMagnitudes(); this.computeMagnitudes();
this.findPeaks(); this.findPeaks();
this.shiftPeaks(pitchFactor); this.shiftPeaks(pitchFactor);
this.fft.completeSpectrum(this.freqComplexBufferShifted); this.fft.completeSpectrum(this.freqComplexBufferShifted);
this.fft.inverseTransform(this.timeComplexBuffer, this.freqComplexBufferShifted); this.fft.inverseTransform(this.timeComplexBuffer, this.freqComplexBufferShifted);
this.fft.fromComplexArray(this.timeComplexBuffer, output); this.fft.fromComplexArray(this.timeComplexBuffer, output);
this.applyHannWindow(output); this.applyHannWindow(output);
} }
} }
this.timeCursor += this.hopSize; this.timeCursor += this.hopSize;
} }
/** Apply Hann window in-place */ /** Apply Hann window in-place */
applyHannWindow(input) { applyHannWindow(input) {
for (let i = 0; i < this.blockSize; i++) { for (var i = 0; i < this.blockSize; i++) {
input[i] *= this.hannWindow[i] * 1.62; input[i] = input[i] * this.hannWindow[i] * 1.62;
} }
} }
/** Compute squared magnitudes for peak finding **/ /** Compute squared magnitudes for peak finding **/
computeMagnitudes() { computeMagnitudes() {
let i = 0, var i = 0,
j = 0; j = 0;
while (i < this.magnitudes.length) { while (i < this.magnitudes.length) {
const real = this.freqComplexBuffer[j]; let real = this.freqComplexBuffer[j];
const imag = this.freqComplexBuffer[j + 1]; let imag = this.freqComplexBuffer[j + 1];
// no need to sqrt for peak finding // no need to sqrt for peak finding
this.magnitudes[i] = real ** 2 + imag ** 2; this.magnitudes[i] = real ** 2 + imag ** 2;
i += 1; i += 1;
@@ -647,10 +660,12 @@ class PhaseVocoderProcessor extends OLAProcessor {
/** Find peaks in spectrum magnitudes **/ /** Find peaks in spectrum magnitudes **/
findPeaks() { findPeaks() {
this.nbPeaks = 0; this.nbPeaks = 0;
let i = 2; var i = 2;
const end = this.magnitudes.length - 2; let end = this.magnitudes.length - 2;
while (i < end) { while (i < end) {
const mag = this.magnitudes[i]; let mag = this.magnitudes[i];
if (this.magnitudes[i - 1] >= mag || this.magnitudes[i - 2] >= mag) { if (this.magnitudes[i - 1] >= mag || this.magnitudes[i - 2] >= mag) {
i++; i++;
continue; continue;
@@ -659,6 +674,7 @@ class PhaseVocoderProcessor extends OLAProcessor {
i++; i++;
continue; continue;
} }
this.peakIndexes[this.nbPeaks] = i; this.peakIndexes[this.nbPeaks] = i;
this.nbPeaks++; this.nbPeaks++;
i += 2; i += 2;
@@ -669,44 +685,53 @@ class PhaseVocoderProcessor extends OLAProcessor {
shiftPeaks(pitchFactor) { shiftPeaks(pitchFactor) {
// zero-fill new spectrum // zero-fill new spectrum
this.freqComplexBufferShifted.fill(0); this.freqComplexBufferShifted.fill(0);
for (let i = 0; i < this.nbPeaks; i++) {
const peakIndex = this.peakIndexes[i]; for (var i = 0; i < this.nbPeaks; i++) {
const peakIndexShifted = fround(peakIndex * pitchFactor); let peakIndex = this.peakIndexes[i];
let peakIndexShifted = Math.round(peakIndex * pitchFactor);
if (peakIndexShifted > this.magnitudes.length) { if (peakIndexShifted > this.magnitudes.length) {
break; break;
} }
// find region of influence // find region of influence
let startIndex = 0; var startIndex = 0;
let endIndex = this.fftSize; var endIndex = this.fftSize;
if (i > 0) { 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) { 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 // shift whole region of influence around peak to shifted peak
const startOffset = startIndex - peakIndex; let startOffset = startIndex - peakIndex;
const endOffset = endIndex - peakIndex; let endOffset = endIndex - peakIndex;
const omegaDelta = TWO_PI * this.invfftSize * (peakIndexShifted - peakIndex); for (var j = startOffset; j < endOffset; j++) {
const phaseShiftReal = Math.cos(omegaDelta * this.timeCursor); let binIndex = peakIndex + j;
const phaseShiftImag = Math.sin(omegaDelta * this.timeCursor); let binIndexShifted = peakIndexShifted + j;
for (let j = startOffset; j < endOffset; j++) {
const binIndex = peakIndex + j;
const binIndexShifted = peakIndexShifted + j;
if (binIndexShifted >= this.magnitudes.length) { if (binIndexShifted >= this.magnitudes.length) {
break; break;
} }
// apply phase correction // apply phase correction
const indexReal = 2 * binIndex; let omegaDelta = (2 * Math.PI * (binIndexShifted - binIndex)) / this.fftSize;
const indexImag = indexReal + 1; let phaseShiftReal = Math.cos(omegaDelta * this.timeCursor);
const valueReal = this.freqComplexBuffer[indexReal]; let phaseShiftImag = Math.sin(omegaDelta * this.timeCursor);
const valueImag = this.freqComplexBuffer[indexImag];
const valueShiftedReal = valueReal * phaseShiftReal - valueImag * phaseShiftImag; let indexReal = binIndex * 2;
const valueShiftedImag = valueReal * phaseShiftImag + valueImag * phaseShiftReal; let indexImag = indexReal + 1;
let valueReal = this.freqComplexBuffer[indexReal];
let valueImag = this.freqComplexBuffer[indexImag];
const indexShiftedReal = 2 * binIndexShifted; let valueShiftedReal = valueReal * phaseShiftReal - valueImag * phaseShiftImag;
const indexShiftedImag = indexShiftedReal + 1; let valueShiftedImag = valueReal * phaseShiftImag + valueImag * phaseShiftReal;
let indexShiftedReal = binIndexShifted * 2;
let indexShiftedImag = indexShiftedReal + 1;
this.freqComplexBufferShifted[indexShiftedReal] += valueShiftedReal; this.freqComplexBufferShifted[indexShiftedReal] += valueShiftedReal;
this.freqComplexBufferShifted[indexShiftedImag] += valueShiftedImag; this.freqComplexBufferShifted[indexShiftedImag] += valueShiftedImag;
} }
@@ -720,10 +745,11 @@ registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor);
class PulseOscillatorProcessor extends AudioWorkletProcessor { class PulseOscillatorProcessor extends AudioWorkletProcessor {
constructor() { constructor() {
super(); super();
this.phi = -PI; // phase this.pi = _PI;
this.phi = -this.pi; // phase
this.Y0 = 0; // feedback memories this.Y0 = 0; // feedback memories
this.Y1 = 0; this.Y1 = 0;
this.PW = PI; // pulse width this.PW = this.pi; // pulse width
this.B = 2.3; // feedback coefficient this.B = 2.3; // feedback coefficient
this.dphif = 0; // filtered phase increment this.dphif = 0; // filtered phase increment
this.envf = 0; // filtered envelope this.envf = 0; // filtered envelope
@@ -780,11 +806,11 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
dphi; dphi;
for (let i = 0; i < (output[0].length ?? 0); i++) { for (let i = 0; i < (output[0].length ?? 0); i++) {
const pw = (1 - clamp(pv(params.pulsewidth, i), -0.99, 0.99)) * PI; const pw = (1 - clamp(getParamValue(i, params.pulsewidth), -0.99, 0.99)) * this.pi;
const detune = pv(params.detune, i); const detune = getParamValue(i, params.detune);
const freq = applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100); 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); this.dphif += 0.1 * (dphi - this.dphif);
env *= 0.9998; // exponential decay envelope env *= 0.9998; // exponential decay envelope
@@ -796,7 +822,7 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
// Waveform generation (half-Tomisawa oscillators) // Waveform generation (half-Tomisawa oscillators)
this.phi += this.dphif; // phase increment 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 // First half-Tomisawa generator
let out0 = Math.cos(this.phi + this.B * this.Y0); // self-phase modulation 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) { /*bit reverse*/ br: function (x, size = 8) {
if (size > 32) { if (size > 32) {
throw new Error('br() Size cannot be greater than 32'); throw new Error('br() Size cannot be greater than 32');
} } else {
let result = 0; let result = 0;
for (let idx = 0; idx < size; idx++) { for (let idx = 0; idx < size - 0; idx++) {
result |= chyx.bitC(x, 1 << idx, 1 << (size - (idx + 1))); result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1)));
} }
return result; return result;
}
}, },
/*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) { /*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) { /*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) { /*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)); return X.test(t.toString(2));
}, },
}; };
@@ -850,7 +877,7 @@ const chyx = {
// Create shortened Math functions // Create shortened Math functions
let mathParams, byteBeatHelperFuncs; let mathParams, byteBeatHelperFuncs;
function getByteBeatFunc(codetext) { function getByteBeatFunc(codetext) {
if (mathParams == null) { if ((mathParams || byteBeatHelperFuncs) == null) {
mathParams = Object.getOwnPropertyNames(Math); mathParams = Object.getOwnPropertyNames(Math);
byteBeatHelperFuncs = mathParams.map((k) => Math[k]); byteBeatHelperFuncs = mathParams.map((k) => Math[k]);
const chyxNames = Object.getOwnPropertyNames(chyx); const chyxNames = Object.getOwnPropertyNames(chyx);
@@ -883,7 +910,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor {
this.func = getByteBeatFunc(codeText); this.func = getByteBeatFunc(codeText);
}; };
this.initialOffset = 0; this.initialOffset = null;
this.t = null; this.t = null;
this.func = null; this.func = null;
} }
@@ -930,19 +957,18 @@ class ByteBeatProcessor extends AudioWorkletProcessor {
this.t = params.begin[0] * sampleRate; this.t = params.begin[0] * sampleRate;
} }
const output = outputs[0]; const output = outputs[0];
const scale = 256 * INVSR;
for (let i = 0; i < output[0].length; i++) { for (let i = 0; i < output[0].length; i++) {
const detune = pv(params.detune, i); const detune = getParamValue(i, params.detune);
const freq = applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100);
const local_t = scale * freq * this.t + this.initialOffset; let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset;
const funcValue = this.func(local_t); const funcValue = this.func(local_t);
const signal = (funcValue & 255) / 127.5 - 1; let signal = (funcValue & 255) / 127.5 - 1;
//prevent speaker blowout via clipping if threshold exceeds const out = signal * 0.2;
const out = clamp(signal * 0.2, -0.4, 0.4);
for (let c = 0; c < output.length; c++) { 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 return true; // keep the audio processing going
@@ -951,102 +977,6 @@ class ByteBeatProcessor extends AudioWorkletProcessor {
registerProcessor('byte-beat-processor', ByteBeatProcessor); 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({ export const WarpMode = Object.freeze({
NONE: 0, NONE: 0,
ASYM: 1, ASYM: 1,
@@ -1126,7 +1056,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
{ name: 'position', defaultValue: 0, min: 0, max: 1 }, { name: 'position', defaultValue: 0, min: 0, max: 1 },
{ name: 'warp', defaultValue: 0, min: 0, max: 1 }, { name: 'warp', defaultValue: 0, min: 0, max: 1 },
{ name: 'warpMode', defaultValue: 0 }, { 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: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
{ name: 'phaserand', defaultValue: 0, min: 0, max: 1 }, { name: 'phaserand', defaultValue: 0, min: 0, max: 1 },
]; ];
@@ -1137,6 +1067,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
this.frameLen = 0; this.frameLen = 0;
this.numFrames = 0; this.numFrames = 0;
this.phase = []; this.phase = [];
this.invSR = 1 / sampleRate;
this.port.onmessage = (e) => { this.port.onmessage = (e) => {
const { type, payload } = e.data || {}; const { type, payload } = e.data || {};
@@ -1173,7 +1104,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
_toBits(amt, min = 2, max = 12) { _toBits(amt, min = 2, max = 12) {
const b = max + (min - max) * amt; 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) { _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); return amt < 0.5 ? this._warpPhase(phase, 1 - 2 * amt, 3) : this._warpPhase(phase, 2 * amt - 1, 2);
} }
case WarpMode.SYNC: { case WarpMode.SYNC: {
const syncRatio = Math.pow(16, amt ** 2); const syncRatio = Math.pow(16, amt * amt);
return (phase * syncRatio) % 1; return (phase * syncRatio) % 1;
} }
case WarpMode.QUANT: { case WarpMode.QUANT: {
@@ -1208,8 +1139,8 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
} }
case WarpMode.FOLD: { case WarpMode.FOLD: {
const K = 7; const K = 7;
const k = 1 + Math.max(1, fround(K * amt)); const k = 1 + Math.max(1, Math.round(K * amt));
return Math.abs(ffrac(k * phase) - 0.5) * 2; return Math.abs(frac(k * phase) - 0.5) * 2;
} }
case WarpMode.PWM: { case WarpMode.PWM: {
const w = clamp(0.5 + 0.49 * (2 * amt - 1), 0, 1); const w = clamp(0.5 + 0.49 * (2 * amt - 1), 0, 1);
@@ -1219,12 +1150,12 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
case WarpMode.ORBIT: { case WarpMode.ORBIT: {
const depth = 0.5 * amt; const depth = 0.5 * amt;
const n = 3; 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: { case WarpMode.SPIN: {
const depth = 0.5 * amt; const depth = 0.5 * amt;
const { n } = this._toBits(amt, 1, 6); 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: { case WarpMode.CHAOS: {
const r = 3.7 + 0.3 * amt; const r = 3.7 + 0.3 * amt;
@@ -1235,7 +1166,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
const isPrime = (n) => { const isPrime = (n) => {
if (n < 2) return false; if (n < 2) return false;
if (n % 2 === 0) return n === 2; 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; return true;
}; };
let { n } = this._toBits(amt, 3); let { n } = this._toBits(amt, 3);
@@ -1244,12 +1175,18 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
} }
case WarpMode.BINARY: { case WarpMode.BINARY: {
let { b } = this._toBits(amt, 3); let { b } = this._toBits(amt, 3);
b = fround(b); b = Math.round(b);
const n = 1 << b; const n = 1 << b;
const idx = ffloor(phase * n); const idx = ffloor(phase * n);
const ridx = bitReverse(idx, b); const ridx = bitReverse(idx, b);
return ridx / n; 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: { case WarpMode.BROWNIAN: {
const disp = 0.25 * amt * brownian(64 * phase, 4); const disp = 0.25 * amt * brownian(64 * phase, 4);
return frac(phase + disp); return frac(phase + disp);
@@ -1272,7 +1209,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
case WarpMode.LOGISTIC: { case WarpMode.LOGISTIC: {
let x = phase; let x = phase;
const r = 3.6 + 0.4 * amt; 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); for (let i = 0; i < iters; i++) x = r * x * (1 - x);
return clamp(x, 0, 1); return clamp(x, 0, 1);
} }
@@ -1285,7 +1222,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
return (y - y0) / (y1 - y0); return (y - y0) / (y1 - y0);
} }
case WarpMode.FRACTAL: { 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); return frac(phase + d);
} }
case WarpMode.FLIP: { case WarpMode.FLIP: {
@@ -1332,16 +1269,16 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
if (outR !== outL) outR.set(outL); if (outR !== outL) outR.set(outL);
return true; return true;
} }
const voices = parameters.voices[0]; // k-rate
for (let i = 0; i < outL.length; i++) { for (let i = 0; i < outL.length; i++) {
const detune = pv(parameters.detune, i); const detune = pv(parameters.detune, i);
const freqspread = pv(parameters.freqspread, i); const freqspread = pv(parameters.freqspread, i);
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 interpT = idx - fIdx; const frac = 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 voices = pv(parameters.voices, i);
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1); const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0; const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0;
const gain1 = Math.sqrt(0.5 - 0.5 * panspread); const gain1 = Math.sqrt(0.5 - 0.5 * panspread);
@@ -1349,7 +1286,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
let f = pv(parameters.frequency, i); let f = pv(parameters.frequency, i);
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
const normalizer = 1 / Math.sqrt(voices); const normalizer = 1 / Math.sqrt(voices);
const detuner = getDetuner(voices, freqspread);
for (let n = 0; n < voices; n++) { for (let n = 0; n < voices; n++) {
const isOdd = (n & 1) == 1; const isOdd = (n & 1) == 1;
let gainL = gain1; let gainL = gain1;
@@ -1359,8 +1295,8 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
gainL = gain2; gainL = gain2;
gainR = gain1; gainR = gain1;
} }
const fVoice = applySemitoneDetuneToFrequency(f, detuner(n)); // voice detune const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, freqspread, n)); // voice detune
const dPhase = fVoice * INVSR; const dPhase = fVoice * this.invSR;
const level = this._chooseMip(dPhase); const level = this._chooseMip(dPhase);
const table = this.tables[level]; const table = this.tables[level];
@@ -1369,13 +1305,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 = lerp(s0, s1, interpT); let s = s0 + (s1 - s0) * frac;
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] = frac(this.phase[n] + dPhase); this.phase[n] = wrapPhase(this.phase[n] + dPhase);
} }
} }
return true; return true;
+132 -20
View File
@@ -41,7 +41,7 @@ importers:
version: 2.2.7 version: 2.2.7
'@vitest/coverage-v8': '@vitest/coverage-v8':
specifier: 3.0.4 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': '@vitest/ui':
specifier: ^3.0.4 specifier: ^3.0.4
version: 3.0.4(vitest@3.0.4) version: 3.0.4(vitest@3.0.4)
@@ -85,29 +85,29 @@ importers:
examples/codemirror-repl: examples/codemirror-repl:
dependencies: dependencies:
'@strudel/codemirror': '@strudel/codemirror':
specifier: workspace:* specifier: 1.2.6
version: link:../../packages/codemirror version: 1.2.6(@codemirror/lint@6.8.4)
'@strudel/core': '@strudel/core':
specifier: workspace:* specifier: 1.2.5
version: link:../../packages/core version: 1.2.5
'@strudel/draw': '@strudel/draw':
specifier: workspace:* specifier: 1.2.5
version: link:../../packages/draw version: 1.2.5
'@strudel/mini': '@strudel/mini':
specifier: workspace:* specifier: 1.2.5
version: link:../../packages/mini version: 1.2.5
'@strudel/soundfonts': '@strudel/soundfonts':
specifier: workspace:* specifier: 1.2.6
version: link:../../packages/soundfonts version: 1.2.6
'@strudel/tonal': '@strudel/tonal':
specifier: workspace:* specifier: 1.2.5
version: link:../../packages/tonal version: 1.2.5
'@strudel/transpiler': '@strudel/transpiler':
specifier: workspace:* specifier: 1.2.5
version: link:../../packages/transpiler version: 1.2.5
'@strudel/webaudio': '@strudel/webaudio':
specifier: workspace:* specifier: 1.2.6
version: link:../../packages/webaudio version: 1.2.6
devDependencies: devDependencies:
vite: vite:
specifier: ^6.0.11 specifier: ^6.0.11
@@ -116,8 +116,8 @@ importers:
examples/headless-repl: examples/headless-repl:
dependencies: dependencies:
'@strudel/web': '@strudel/web':
specifier: workspace:* specifier: 1.2.6
version: link:../../packages/web version: 1.2.6
devDependencies: devDependencies:
vite: vite:
specifier: ^6.0.11 specifier: ^6.0.11
@@ -2544,6 +2544,33 @@ packages:
'@sinclair/typebox@0.27.8': '@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} 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': '@supabase/auth-js@2.67.3':
resolution: {integrity: sha512-NJDaW8yXs49xMvWVOkSIr8j46jf+tYHV0wHhrwOaLLMZSFO4g6kKAf+MfzQ2RaD06OCUkUHIzctLAxjTgEVpzw==} resolution: {integrity: sha512-NJDaW8yXs49xMvWVOkSIr8j46jf+tYHV0wHhrwOaLLMZSFO4g6kKAf+MfzQ2RaD06OCUkUHIzctLAxjTgEVpzw==}
@@ -7094,6 +7121,9 @@ packages:
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
hasBin: true hasBin: true
superdough@1.2.6:
resolution: {integrity: sha512-zHEFfG+N4zhxJ9lUDf8p4Oj4sdGGQ5SrOPksdc0cKv5Yn0hzlNlMehEa5TkeIfDCiR27Y4H81ks7S8/CGSByhQ==}
supports-color@7.2.0: supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -7102,6 +7132,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
supradough@1.2.4:
resolution: {integrity: sha512-/v3cL9fq05QCQVwbttLHNZcjyyFFz5wdKvg3j6V6moRXpDlQG89l8dQtUIpQvHsxYI5sXNod9rh5bf+Y4NMGeA==}
tabbable@6.2.0: tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
@@ -10019,6 +10052,79 @@ snapshots:
'@sinclair/typebox@0.27.8': {} '@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': '@supabase/auth-js@2.67.3':
dependencies: dependencies:
'@supabase/node-fetch': 2.6.15 '@supabase/node-fetch': 2.6.15
@@ -10554,7 +10660,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - 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: dependencies:
'@ampproject/remapping': 2.3.0 '@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2 '@bcoe/v8-coverage': 1.0.2
@@ -15665,12 +15771,18 @@ snapshots:
pirates: 4.0.6 pirates: 4.0.6
ts-interface-checker: 0.1.13 ts-interface-checker: 0.1.13
superdough@1.2.6:
dependencies:
nanostores: 0.11.3
supports-color@7.2.0: supports-color@7.2.0:
dependencies: dependencies:
has-flag: 4.0.0 has-flag: 4.0.0
supports-preserve-symlinks-flag@1.0.0: {} supports-preserve-symlinks-flag@1.0.0: {}
supradough@1.2.4: {}
tabbable@6.2.0: {} tabbable@6.2.0: {}
tailwindcss@3.4.17: 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`] = ` 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 ]",
@@ -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`] = ` 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 ]",
@@ -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`] = ` 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 ]",
@@ -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`] = ` 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 ]",
@@ -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`] = ` 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 ]",
@@ -7302,18 +6888,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 | note:F3 s:supersaw octave:0 ]", "[ 0/1 → 1/1 | n:0 s:supersquare octave:3 ]",
"[ 0/1 → 1/1 | note:C4 s:supersaw octave:0 ]", "[ 0/1 → 1/1 | n:4 s:supersquare octave:3 ]",
"[ 0/1 → 1/1 | note:F4 s:supersaw octave:0 ]", "[ 0/1 → 1/1 | n:7 s:supersquare octave:3 ]",
"[ 1/1 → 2/1 | note:F3 s:supersaw octave:1 ]", "[ 1/1 → 2/1 | n:0 s:supersquare octave:4 ]",
"[ 1/1 → 2/1 | note:C4 s:supersaw octave:1 ]", "[ 1/1 → 2/1 | n:4 s:supersquare octave:4 ]",
"[ 1/1 → 2/1 | note:F4 s:supersaw octave:1 ]", "[ 1/1 → 2/1 | n:7 s:supersquare octave:4 ]",
"[ 2/1 → 3/1 | note:F3 s:supersaw octave:2 ]", "[ 2/1 → 3/1 | n:0 s:supersquare octave:5 ]",
"[ 2/1 → 3/1 | note:C4 s:supersaw octave:2 ]", "[ 2/1 → 3/1 | n:4 s:supersquare octave:5 ]",
"[ 2/1 → 3/1 | note:F4 s:supersaw octave:2 ]", "[ 2/1 → 3/1 | n:7 s:supersquare octave:5 ]",
"[ 3/1 → 4/1 | note:F3 s:supersaw octave:3 ]", "[ 3/1 → 4/1 | n:0 s:supersquare octave:6 ]",
"[ 3/1 → 4/1 | note:C4 s:supersaw octave:3 ]", "[ 3/1 → 4/1 | n:4 s:supersquare octave:6 ]",
"[ 3/1 → 4/1 | note:F4 s:supersaw octave:3 ]", "[ 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`] = ` exports[`runs examples > example "pattack" example index 0 1`] = `
[ [
"[ 0/1 → 1/2 | note:c pattack:0 ]", "[ 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`] = ` exports[`runs examples > example "pianoroll" example index 0 1`] = `
[ [
"[ 0/1 → 1/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]", "[ 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`] = ` exports[`runs examples > example "range" example index 0 1`] = `
[ [
"[ 0/1 → 1/8 | s:hh cutoff:2250 ]", "[ 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`] = ` exports[`runs examples > example "wchooseCycles" example index 1 1`] = `
[ [
"[ 0/1 → 1/12 | note:c ]", "[ 0/1 → 1/12 | s:bd ]",
"[ 1/12 → 1/6 | note:c ]", "[ 1/12 → 1/6 | s:bd ]",
"[ 1/6 → 1/4 | note:c ]", "[ 1/6 → 1/4 | s:bd ]",
"[ 1/4 → 1/3 | note:c ]", "[ 1/4 → 1/3 | s:bd ]",
"[ 1/3 → 5/12 | note:c ]", "[ 1/3 → 5/12 | s:bd ]",
"[ 5/12 → 1/2 | note:c ]", "[ 5/12 → 1/2 | s:bd ]",
"[ 1/2 → 7/12 | note:f ]", "[ 1/2 → 7/12 | s:sd ]",
"[ 7/12 → 2/3 | note:f ]", "[ 7/12 → 2/3 | s:sd ]",
"[ 2/3 → 3/4 | note:f ]", "[ 2/3 → 3/4 | s:sd ]",
"[ 3/4 → 5/6 | note:c ]", "[ 3/4 → 5/6 | s:bd ]",
"[ 5/6 → 11/12 | note:c ]", "[ 5/6 → 11/12 | s:bd ]",
"[ 11/12 → 1/1 | note:c ]", "[ 11/12 → 1/1 | s:bd ]",
"[ 1/1 → 13/12 | note:c ]", "[ 1/1 → 13/12 | s:bd ]",
"[ 13/12 → 7/6 | note:c ]", "[ 13/12 → 7/6 | s:bd ]",
"[ 7/6 → 5/4 | note:c ]", "[ 7/6 → 5/4 | s:bd ]",
"[ 5/4 → 4/3 | note:c ]", "[ 5/4 → 4/3 | s:bd ]",
"[ 4/3 → 17/12 | note:c ]", "[ 4/3 → 17/12 | s:bd ]",
"[ 17/12 → 3/2 | note:c ]", "[ 17/12 → 3/2 | s:bd ]",
"[ 3/2 → 19/12 | note:a ]", "[ 3/2 → 19/12 | s:hh ]",
"[ 19/12 → 5/3 | note:a ]", "[ 19/12 → 5/3 | s:hh ]",
"[ 5/3 → 7/4 | note:a ]", "[ 5/3 → 7/4 | s:hh ]",
"[ 7/4 → 11/6 | note:c ]", "[ 7/4 → 11/6 | s:bd ]",
"[ 11/6 → 23/12 | note:c ]", "[ 11/6 → 23/12 | s:bd ]",
"[ 23/12 → 2/1 | note:c ]", "[ 23/12 → 2/1 | s:bd ]",
"[ 2/1 → 25/12 | note:a ]", "[ 2/1 → 25/12 | s:hh ]",
"[ 25/12 → 13/6 | note:a ]", "[ 25/12 → 13/6 | s:hh ]",
"[ 13/6 → 9/4 | note:a ]", "[ 13/6 → 9/4 | s:hh ]",
"[ 9/4 → 7/3 | note:a ]", "[ 9/4 → 7/3 | s:hh ]",
"[ 7/3 → 29/12 | note:a ]", "[ 7/3 → 29/12 | s:hh ]",
"[ 29/12 → 5/2 | note:a ]", "[ 29/12 → 5/2 | s:hh ]",
"[ 5/2 → 31/12 | note:c ]", "[ 5/2 → 31/12 | s:bd ]",
"[ 31/12 → 8/3 | note:c ]", "[ 31/12 → 8/3 | s:bd ]",
"[ 8/3 → 11/4 | note:c ]", "[ 8/3 → 11/4 | s:bd ]",
"[ 11/4 → 17/6 | note:c ]", "[ 11/4 → 17/6 | s:bd ]",
"[ 17/6 → 35/12 | note:c ]", "[ 17/6 → 35/12 | s:bd ]",
"[ 35/12 → 3/1 | note:c ]", "[ 35/12 → 3/1 | s:bd ]",
"[ 3/1 → 37/12 | note:c ]", "[ 3/1 → 37/12 | s:bd ]",
"[ 37/12 → 19/6 | note:c ]", "[ 37/12 → 19/6 | s:bd ]",
"[ 19/6 → 13/4 | note:c ]", "[ 19/6 → 13/4 | s:bd ]",
"[ 13/4 → 10/3 | note:f ]", "[ 13/4 → 10/3 | s:sd ]",
"[ 10/3 → 41/12 | note:f ]", "[ 10/3 → 41/12 | s:sd ]",
"[ 41/12 → 7/2 | note:f ]", "[ 41/12 → 7/2 | s:sd ]",
"[ 7/2 → 43/12 | note:a ]", "[ 7/2 → 43/12 | s:hh ]",
"[ 43/12 → 11/3 | note:a ]", "[ 43/12 → 11/3 | s:hh ]",
"[ 11/3 → 15/4 | note:a ]", "[ 11/3 → 15/4 | s:hh ]",
"[ 15/4 → 23/6 | note:c ]", "[ 15/4 → 23/6 | s:bd ]",
"[ 23/6 → 47/12 | note:c ]", "[ 23/6 → 47/12 | s:bd ]",
"[ 47/12 → 4/1 | note:c ]", "[ 47/12 → 4/1 | s:bd ]",
] ]
`; `;
+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 ### 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. To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:
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:
<MiniRepl <MiniRepl
client:idle client:idle
tune={`note("c2 <eb2 <g2 g1>>".fast(2)) tune={`note("c2 <eb2 <g2 g1>>".fast(2))
.sound("sawtooth") .sound("sawtooth")
.partials([1, 1, "<1 0>", "<1 0>", "<1 0>", "<1 0>", "<1 0>"]) .n("<32 16 8 4>")
._scope()`} ._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 <MiniRepl
client:idle client:idle
tune={`note("c2 <eb2 <g2 g1>>".fast(2)) tune={`note("c2 <eb2 <g2 g1>>".fast(2))
.sound("user") .sound("sawtooth:<32 16 8 4>")
.partials([1, 0, 0.3, 0, 0.1, 0, 0, 0.3])
._scope()`} ._scope()`}
/> />
We may algorithmically construct lists of magnitudes with Javascript code like: 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.
<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))`}
/>
## Vibrato ## Vibrato
@@ -8,34 +8,3 @@ 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>}
/>
);
}
@@ -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 { 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 (
@@ -115,7 +113,6 @@ 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;
@@ -207,15 +204,6 @@ 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}
@@ -325,7 +313,8 @@ 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">
<SpecialActionButton <button
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) {
@@ -336,7 +325,7 @@ export function SettingsTab({ started }) {
}} }}
> >
restore default settings restore default settings
</SpecialActionButton> </button>
</FormItem> </FormItem>
</div> </div>
); );
@@ -126,7 +126,7 @@ export function SoundsTab() {
try { try {
// Pre-load the sample by calling onTrigger with a future time // Pre-load the sample by calling onTrigger with a future time
// This triggers the loading but schedules playback for later // This triggers the loading but schedules playback for later
const time = ctx.currentTime + 0.05; const time = ctx.currentTime + 0.5; // Give 500ms for loading
const ref = await onTrigger(time, params, onended); const ref = await onTrigger(time, params, onended);
trigRef.current = ref; trigRef.current = ref;
if (ref?.node) { if (ref?.node) {
-2
View File
@@ -3,8 +3,6 @@ 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;
+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 { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core';
import { getDrawContext } from '@strudel/draw'; import { getDrawContext } from '@strudel/draw';
import { evaluate, transpiler } from '@strudel/transpiler'; import { transpiler } from '@strudel/transpiler';
import { import {
getAudioContextCurrentTime, getAudioContextCurrentTime,
webaudioOutput, webaudioOutput,
@@ -63,10 +63,11 @@ async function getModule(name) {
const initialCode = `// LOADING`; const initialCode = `// LOADING`;
export function useReplContext() { export function useReplContext() {
const { isSyncEnabled, audioEngineTarget, prebakeScript, includePrebakeScriptInShare } = useSettings(); const { isSyncEnabled, audioEngineTarget } = 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();
@@ -83,12 +84,7 @@ export function useReplContext() {
pattern: silence, pattern: silence,
drawTime, drawTime,
drawContext, drawContext,
prebake: async () => prebake: async () => Promise.all([modulesLoading, presets]),
Promise.all([modulesLoading, presets]).then(() => {
if (prebakeScript?.length) {
return evaluate(prebakeScript ?? '');
}
}),
onUpdateState: (state) => { onUpdateState: (state) => {
setReplState({ ...state }); setReplState({ ...state });
}, },
@@ -218,13 +214,7 @@ export function useReplContext() {
editorRef.current.repl.evaluate(code); editorRef.current.repl.evaluate(code);
}; };
const handleShare = async () => { const handleShare = async () => shareCode(replState.code);
let code = replState.code;
if (includePrebakeScriptInShare) {
code = prebakeScript + '\n' + code;
}
shareCode(code);
};
const context = { const context = {
started, started,
pending, 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 { 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';
@@ -108,8 +109,9 @@ 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) {
@@ -144,10 +146,9 @@ export function confirmDialog(msg) {
// }); // });
// } // }
export async function shareCode(codeToShare) { export async function shareCode() {
try { try {
const hash = '#' + code2hash(codeToShare); const shareUrl = window.location.href;
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,13 +45,11 @@ 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;
@@ -98,7 +96,6 @@ 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
@@ -111,8 +108,6 @@ 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) =>