mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-20 20:47:06 -04:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| efda32eedb | |||
| 358b1e3f56 | |||
| 45d329d3d1 | |||
| 5e99393f5a | |||
| 9650c8eb50 | |||
| 150835aa4d | |||
| 76237016a3 | |||
| 6f4d8ebacf | |||
| 3cecf4a50e | |||
| 16b5faf333 | |||
| bd61c1dc7e | |||
| ceb3aa797d | |||
| 9b3277da1c | |||
| 0b711e879c | |||
| b65cc2128f | |||
| 39e7f70079 | |||
| 7e5a907ce5 | |||
| d7afb8a6c0 | |||
| a278f21f1b | |||
| 36587ae747 | |||
| 6c01c16a0c | |||
| be36898803 | |||
| df047fd48d | |||
| 68840de188 | |||
| 020a6bc75f |
+23
-11
@@ -401,12 +401,13 @@ export const { accelerate } = registerControl('accelerate');
|
|||||||
* Sets the velocity from 0 to 1. Is multiplied together with gain.
|
* Sets the velocity from 0 to 1. Is multiplied together with gain.
|
||||||
*
|
*
|
||||||
* @name velocity
|
* @name velocity
|
||||||
|
* @synonyms vel
|
||||||
* @example
|
* @example
|
||||||
* s("hh*8")
|
* s("hh*8")
|
||||||
* .gain(".4!2 1 .4!2 1 .4 1")
|
* .gain(".4!2 1 .4!2 1 .4 1")
|
||||||
* .velocity(".4 1")
|
* .velocity(".4 1")
|
||||||
*/
|
*/
|
||||||
export const { velocity } = registerControl('velocity');
|
export const { velocity, vel } = registerControl('velocity', 'vel');
|
||||||
/**
|
/**
|
||||||
* Controls the gain by an exponential amount.
|
* Controls the gain by an exponential amount.
|
||||||
*
|
*
|
||||||
@@ -1482,14 +1483,13 @@ export const { lpdepth } = registerControl('lpdepth');
|
|||||||
* Depth of the LFO for the lowpass filter, in HZ
|
* Depth of the LFO for the lowpass filter, in HZ
|
||||||
*
|
*
|
||||||
* @name lpdepthfrequency
|
* @name lpdepthfrequency
|
||||||
* @synonyms
|
* @synonyms lpdepthfreq
|
||||||
* lpdethfreq
|
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
* @example
|
* @example
|
||||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { lpdepthfrequency } = registerControl('lpdepthfrequency', 'lpdepthfreq');
|
export const { lpdepthfrequency, lpdepthfreq } = registerControl('lpdepthfrequency', 'lpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the lowpass filter
|
* Shape of the LFO for the lowpass filter
|
||||||
@@ -1543,14 +1543,13 @@ export const { bpdepth } = registerControl('bpdepth');
|
|||||||
* Depth of the LFO for the bandpass filter, in HZ
|
* Depth of the LFO for the bandpass filter, in HZ
|
||||||
*
|
*
|
||||||
* @name bpdepthfrequency
|
* @name bpdepthfrequency
|
||||||
* @synonyms
|
* @synonyms bpdepthfreq
|
||||||
* bpdethfreq
|
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
* @example
|
* @example
|
||||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { bpdepthfrequency } = registerControl('bpdepthfrequency', 'bpdepthfreq');
|
export const { bpdepthfrequency, bpdepthfreq } = registerControl('bpdepthfrequency', 'bpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the bandpass filter
|
* Shape of the LFO for the bandpass filter
|
||||||
@@ -1598,20 +1597,19 @@ 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
|
* Depth of the LFO for the hipass filter, in hz
|
||||||
*
|
*
|
||||||
* @name hpdepthfrequency
|
* @name hpdepthfrequency
|
||||||
* @synonyms
|
* @synonyms hpdepthfreq
|
||||||
* hpdethfreq
|
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
* @example
|
* @example
|
||||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { hpdepthfrequency } = registerControl('hpdepthfrequency', 'hpdepthfreq');
|
export const { hpdepthfrequency, hpdepthfreq } = registerControl('hpdepthfrequency', 'hpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the highpass filter
|
* Shape of the LFO for the highpass filter
|
||||||
@@ -2788,3 +2786,17 @@ export const scrub = register(
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transient shaper. Gives independent control over the emphasis on transients
|
||||||
|
* and sustains
|
||||||
|
*
|
||||||
|
* @name transient
|
||||||
|
* @param {number | Pattern} attack Emphasis on transients; between -1 (deaccentuate) and 1 (accentuate)
|
||||||
|
* @param {number | Pattern} sustain Emphasis on the sustains; between -1 (deaccentuate) and 1 (accentuate)
|
||||||
|
* @example
|
||||||
|
* s("bd").transient("<-1 -0.5 0 0.5 1>")
|
||||||
|
* @example
|
||||||
|
* s("hh*16").bank("tr909").transient("<-1:1 1:-1>")
|
||||||
|
*/
|
||||||
|
export const { transient } = registerControl(['transient', 'transsustain']);
|
||||||
|
|||||||
@@ -574,7 +574,8 @@ export class Pattern {
|
|||||||
* Returns a new Pattern, which only returns haps that meet the given test.
|
* Returns a new Pattern, which only returns haps that meet the given test.
|
||||||
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
|
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @noAutocomplete
|
* @example
|
||||||
|
* s("bd*8").velocity(rand).filterHaps((h) => (h.whole.begin % 1) < h.value.velocity)
|
||||||
*/
|
*/
|
||||||
filterHaps(hap_test) {
|
filterHaps(hap_test) {
|
||||||
return new Pattern((state) => this.query(state).filter(hap_test));
|
return new Pattern((state) => this.query(state).filter(hap_test));
|
||||||
@@ -585,7 +586,11 @@ export class Pattern {
|
|||||||
* inside haps.
|
* inside haps.
|
||||||
* @param {Function} value_test
|
* @param {Function} value_test
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @noAutocomplete
|
* @example
|
||||||
|
* const drums = s("bd sd bd sd")
|
||||||
|
* kick: drums.filterValues((v) => v.s === 'bd').duck(2)
|
||||||
|
* snare: drums.filterValues((v) => v.s === 'sd')
|
||||||
|
* bass: s("saw!4").note("G#1").lpf(80).lpenv(4).orbit(2)
|
||||||
*/
|
*/
|
||||||
filterValues(value_test) {
|
filterValues(value_test) {
|
||||||
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setSteps(this._steps);
|
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setSteps(this._steps);
|
||||||
@@ -1587,7 +1592,13 @@ export const func = curry((a, b) => reify(b).func(a));
|
|||||||
*
|
*
|
||||||
* @param {string | string[]} name name of the function, or an array of names to be used as synonyms
|
* @param {string | string[]} name name of the function, or an array of names to be used as synonyms
|
||||||
* @param {function} func function with 1 or more params, where last is the current pattern
|
* @param {function} func function with 1 or more params, where last is the current pattern
|
||||||
* @noAutocomplete
|
* @param {bool} patternify defaults to true; if set to false, you will have more control over the arguments to `func` as they will be
|
||||||
|
* in their raw form and it will be up to you to patternify them and/or query them for values
|
||||||
|
* @example
|
||||||
|
* const vlpf = register('vlpf', (freq, pat) => {
|
||||||
|
* return pat.fmap((v) => ({...v, cutoff: freq * (v.velocity ?? 1) }));
|
||||||
|
* })
|
||||||
|
* s("saw").seg(8).velocity(rand).vlpf(800)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) {
|
export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) {
|
||||||
@@ -2676,8 +2687,13 @@ export const hsl = register('hsl', (h, s, l, pat) => {
|
|||||||
/**
|
/**
|
||||||
* Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array).
|
* Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array).
|
||||||
* @name tag
|
* @name tag
|
||||||
* @noAutocomplete
|
|
||||||
* @param {string} tag anything unique
|
* @param {string} tag anything unique
|
||||||
|
* @example
|
||||||
|
* s("saw!16").note("F1")
|
||||||
|
* .lpf(tri.range(40, 80).slow(4)).lpenv(5).lpq(4).lpd(0.15)
|
||||||
|
* .when(rand.late(0.1).gte(0.5), x => x.transpose("12").tag('altered'))
|
||||||
|
* .when(rand.late(0.2).gte(0.5), x => x.s("square").tag('altered'))
|
||||||
|
* .when("<0 1>", x => x.filter((hap) => hap.hasTag('altered')))
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.tag = function (tag) {
|
Pattern.prototype.tag = function (tag) {
|
||||||
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
||||||
@@ -2688,15 +2704,16 @@ Pattern.prototype.tag = function (tag) {
|
|||||||
* @name filter
|
* @name filter
|
||||||
* @param {Function} test function to test Hap
|
* @param {Function} test function to test Hap
|
||||||
* @example
|
* @example
|
||||||
* s("hh!7 oh").filter(hap => hap.value.s==='hh')
|
* s("hh!7 oh").filter(hap => hap.value.s === 'hh')
|
||||||
*/
|
*/
|
||||||
export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test)));
|
export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test)));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters haps by their begin time
|
* Filters haps by their begin time
|
||||||
* @name filterWhen
|
* @name filterWhen
|
||||||
* @noAutocomplete
|
|
||||||
* @param {Function} test function to test Hap.whole.begin
|
* @param {Function} test function to test Hap.whole.begin
|
||||||
|
* @example
|
||||||
|
* oneCycle: s("bd*4").filterWhen((t) => t < 1)
|
||||||
*/
|
*/
|
||||||
export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin)));
|
export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin)));
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
/*
|
|
||||||
audioContext.mjs - Audio Context manager
|
|
||||||
|
|
||||||
Sets up a common and accessible audio context for all superdough operations
|
|
||||||
|
|
||||||
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough/audiocontext.mjs>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let audioContext;
|
let audioContext;
|
||||||
|
|
||||||
export const setDefaultAudioContext = () => {
|
export const setDefaultAudioContext = () => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
import { getNoiseBuffer } from './noise.mjs';
|
import { getNoiseBuffer } from './noise.mjs';
|
||||||
import { getNodeFromPool, markWorkletAsDead } from './nodePools.mjs';
|
|
||||||
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
|
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
|
||||||
|
|
||||||
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||||
@@ -37,16 +36,6 @@ export function getWorklet(ac, processor, params, config) {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPooledWorklet(ac, processor, params, constructorOptions, reInitOptions) {
|
|
||||||
const node = getNodeFromPool(processor, () => new AudioWorkletNode(ac, processor, constructorOptions), params);
|
|
||||||
node.port.postMessage({ type: 'initialize', payload: reInitOptions });
|
|
||||||
node.port.onmessage = (e) => {
|
|
||||||
if (e.data.type === 'died') markWorkletAsDead(node);
|
|
||||||
node.port.onmessage = null;
|
|
||||||
};
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getParamADSR = (
|
export const getParamADSR = (
|
||||||
param,
|
param,
|
||||||
attack,
|
attack,
|
||||||
@@ -135,11 +124,10 @@ export function getLfo(audioContext, begin, end, properties = {}) {
|
|||||||
...props,
|
...props,
|
||||||
};
|
};
|
||||||
|
|
||||||
return getPooledWorklet(audioContext, 'lfo-processor', lfoprops);
|
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCompressor(ac, threshold, ratio, knee, attack, release) {
|
export function getCompressor(ac, threshold, ratio, knee, attack, release) {
|
||||||
const node = getNodeFromPool('compressor', () => new DynamicsCompressorNode(ac, {}));
|
|
||||||
const options = {
|
const options = {
|
||||||
threshold: threshold ?? -3,
|
threshold: threshold ?? -3,
|
||||||
ratio: ratio ?? 10,
|
ratio: ratio ?? 10,
|
||||||
@@ -147,11 +135,7 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) {
|
|||||||
attack: attack ?? 0.005,
|
attack: attack ?? 0.005,
|
||||||
release: release ?? 0.05,
|
release: release ?? 0.05,
|
||||||
};
|
};
|
||||||
const now = ac.currentTime;
|
return new DynamicsCompressorNode(ac, options);
|
||||||
Object.entries(options).forEach(([key, value]) => {
|
|
||||||
node[key].setValueAtTime(value, now);
|
|
||||||
});
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// changes the default values of the envelope based on what parameters the user has defined
|
// changes the default values of the envelope based on what parameters the user has defined
|
||||||
@@ -227,16 +211,13 @@ export function createFilter(context, start, end, params, cps, cycle) {
|
|||||||
|
|
||||||
let frequencyParam, filter;
|
let frequencyParam, filter;
|
||||||
if (model === 'ladder') {
|
if (model === 'ladder') {
|
||||||
filter = getPooledWorklet(context, 'ladder-processor', { frequency, q, drive });
|
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
|
||||||
frequencyParam = filter.parameters.get('frequency');
|
frequencyParam = filter.parameters.get('frequency');
|
||||||
} else {
|
} else {
|
||||||
const factory = () => context.createBiquadFilter();
|
filter = context.createBiquadFilter();
|
||||||
filter = getNodeFromPool('filter', factory);
|
|
||||||
filter.type = type;
|
filter.type = type;
|
||||||
const now = context.currentTime;
|
filter.Q.value = q;
|
||||||
Object.entries({ Q: q, frequency }).forEach(([key, value]) => {
|
filter.frequency.value = frequency;
|
||||||
filter[key].setValueAtTime(value, now);
|
|
||||||
});
|
|
||||||
frequencyParam = filter.frequency;
|
frequencyParam = filter.frequency;
|
||||||
}
|
}
|
||||||
const envelopeValues = [params.attack, params.decay, params.sustain, params.release];
|
const envelopeValues = [params.attack, params.decay, params.sustain, params.release];
|
||||||
@@ -567,9 +548,8 @@ export const getDistortionAlgorithm = (algo) => {
|
|||||||
return distortionAlgorithms[name];
|
return distortionAlgorithms[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDistortion = (fullParams) => {
|
export const getDistortion = (distort, postgain, algorithm) => {
|
||||||
const { algorithm, ...params } = fullParams;
|
return getWorklet(getAudioContext(), 'distort-processor', { distort, postgain }, { processorOptions: { algorithm } });
|
||||||
return getPooledWorklet(getAudioContext(), 'distort-processor', params, {}, { algorithm });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
nodePools.mjs - Helper functions related to pooling and re-using audio nodes
|
|
||||||
|
|
||||||
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough/nodePools.mjs>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const nodePools = new Map();
|
|
||||||
const POOL_KEY = Symbol('nodePoolKey');
|
|
||||||
const IS_WORKLET_DEAD = Symbol('nodePoolIsWorkletDead');
|
|
||||||
const MAX_POOL_SIZE = 64;
|
|
||||||
|
|
||||||
export const isPoolable = (node) => !!node[POOL_KEY];
|
|
||||||
|
|
||||||
export const releaseNodeToPool = (node) => {
|
|
||||||
node.disconnect();
|
|
||||||
if (node instanceof AudioScheduledSourceNode) {
|
|
||||||
// not reusable
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (node[IS_WORKLET_DEAD]) {
|
|
||||||
// Worklet already terminated, don't pool it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const key = node[POOL_KEY];
|
|
||||||
if (key == null) return;
|
|
||||||
const pool = nodePools.get(key) ?? [];
|
|
||||||
if (pool.length < MAX_POOL_SIZE) {
|
|
||||||
pool.push(new WeakRef(node));
|
|
||||||
nodePools.set(key, pool);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const markWorkletAsDead = (worklet) => (worklet[IS_WORKLET_DEAD] = true);
|
|
||||||
|
|
||||||
// Attempt to get node from the pool. If this fails, fall back
|
|
||||||
// to building it with the factory
|
|
||||||
export const getNodeFromPool = (key, factory, params = {}) => {
|
|
||||||
const pool = nodePools.get(key) ?? [];
|
|
||||||
let node;
|
|
||||||
while (pool.length) {
|
|
||||||
const ref = pool.pop();
|
|
||||||
node = ref?.deref();
|
|
||||||
if (node != null && !node[IS_WORKLET_DEAD]) break;
|
|
||||||
}
|
|
||||||
if (node == null || node[IS_WORKLET_DEAD]) {
|
|
||||||
node = factory();
|
|
||||||
}
|
|
||||||
node[POOL_KEY] = key;
|
|
||||||
const paramMap = new Map();
|
|
||||||
if (node instanceof AudioWorkletNode) {
|
|
||||||
for (const [name, param] of node.parameters.entries()) {
|
|
||||||
paramMap.set(name, param);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const name of Object.getOwnPropertyNames(node)) {
|
|
||||||
const value = node[name];
|
|
||||||
if (value instanceof AudioParam) {
|
|
||||||
paramMap.set(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const now = node.context?.currentTime ?? 0;
|
|
||||||
paramMap.forEach((param, name) => {
|
|
||||||
param.cancelScheduledValues(now);
|
|
||||||
// Set values from `params` or restore defaults
|
|
||||||
const target = params[name] !== undefined ? params[name] : param.defaultValue;
|
|
||||||
param.setValueAtTime(target, now);
|
|
||||||
});
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { makeReusable } from './worklets-common.mjs';
|
|
||||||
|
|
||||||
// sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
// sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
||||||
const WEBAUDIO_BLOCK_SIZE = 128;
|
const WEBAUDIO_BLOCK_SIZE = 128;
|
||||||
|
|
||||||
/** Overlap-Add Node */
|
/** Overlap-Add Node */
|
||||||
class OLAProcessor extends makeReusable(AudioWorkletProcessor) {
|
class OLAProcessor extends AudioWorkletProcessor {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
this.started = false;
|
||||||
this.nbInputs = options.numberOfInputs;
|
this.nbInputs = options.numberOfInputs;
|
||||||
this.nbOutputs = options.numberOfOutputs;
|
this.nbOutputs = options.numberOfOutputs;
|
||||||
|
|
||||||
this.blockSize = options.processorOptions.blockSize;
|
this.blockSize = options.processorOptions.blockSize;
|
||||||
// TODO for now, the only support hop size is the size of a web audio block
|
// TODO for now, the only support hop size is the size of a web audio block
|
||||||
this.hopSize = WEBAUDIO_BLOCK_SIZE;
|
this.hopSize = WEBAUDIO_BLOCK_SIZE;
|
||||||
this.nbOverlaps = this.blockSize / this.hopSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
this.nbOverlaps = this.blockSize / this.hopSize;
|
||||||
|
|
||||||
// pre-allocate input buffers (will be reallocated if needed)
|
// pre-allocate input buffers (will be reallocated if needed)
|
||||||
this.inputBuffers = new Array(this.nbInputs);
|
this.inputBuffers = new Array(this.nbInputs);
|
||||||
this.inputBuffersHead = new Array(this.nbInputs);
|
this.inputBuffersHead = new Array(this.nbInputs);
|
||||||
@@ -55,6 +54,7 @@ class OLAProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
|
|
||||||
allocateInputChannels(inputIndex, nbChannels) {
|
allocateInputChannels(inputIndex, nbChannels) {
|
||||||
// allocate input buffers
|
// allocate input buffers
|
||||||
|
|
||||||
this.inputBuffers[inputIndex] = new Array(nbChannels);
|
this.inputBuffers[inputIndex] = new Array(nbChannels);
|
||||||
for (let i = 0; i < nbChannels; i++) {
|
for (let i = 0; i < nbChannels; i++) {
|
||||||
this.inputBuffers[inputIndex][i] = new Float32Array(this.blockSize + WEBAUDIO_BLOCK_SIZE);
|
this.inputBuffers[inputIndex][i] = new Float32Array(this.blockSize + WEBAUDIO_BLOCK_SIZE);
|
||||||
@@ -157,8 +157,15 @@ class OLAProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, params) {
|
process(inputs, outputs, params) {
|
||||||
|
const input = inputs[0];
|
||||||
|
const hasInput = !(input[0] === undefined);
|
||||||
|
if (this.started && !hasInput) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.started = hasInput;
|
||||||
this.reallocateChannelsIfNeeded(inputs, outputs);
|
this.reallocateChannelsIfNeeded(inputs, outputs);
|
||||||
|
|
||||||
this.readInputs(inputs);
|
this.readInputs(inputs);
|
||||||
this.shiftInputBuffers();
|
this.shiftInputBuffers();
|
||||||
this.prepareInputBuffersToSend();
|
this.prepareInputBuffersToSend();
|
||||||
@@ -166,6 +173,7 @@ class OLAProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
this.handleOutputBuffersToRetrieve();
|
this.handleOutputBuffersToRetrieve();
|
||||||
this.writeOutputs(outputs);
|
this.writeOutputs(outputs);
|
||||||
this.shiftOutputBuffers();
|
this.shiftOutputBuffers();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -243,12 +243,6 @@ export async function fetchSampleMap(url) {
|
|||||||
* sd: '808sd/SD0010.WAV'
|
* sd: '808sd/SD0010.WAV'
|
||||||
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
||||||
* s("[bd ~]*2, [~ hh]*2, ~ sd")
|
* s("[bd ~]*2, [~ hh]*2, ~ sd")
|
||||||
* @example
|
|
||||||
* samples('shabda:noise,chimp:2')
|
|
||||||
* s("noise <chimp:0*2 chimp:1>")
|
|
||||||
* @example
|
|
||||||
* samples('shabda/speech/fr-FR/f:chocolat')
|
|
||||||
* s("chocolat*4")
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => {
|
export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => {
|
||||||
|
|||||||
@@ -9,16 +9,7 @@ import './reverb.mjs';
|
|||||||
import './vowel.mjs';
|
import './vowel.mjs';
|
||||||
import { nanFallback, _mod, cycleToSeconds, pickAndRename } from './util.mjs';
|
import { nanFallback, _mod, cycleToSeconds, pickAndRename } from './util.mjs';
|
||||||
import workletsUrl from './worklets.mjs?audioworklet';
|
import workletsUrl from './worklets.mjs?audioworklet';
|
||||||
import {
|
import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs';
|
||||||
createFilter,
|
|
||||||
effectSend,
|
|
||||||
gainNode,
|
|
||||||
getCompressor,
|
|
||||||
getDistortion,
|
|
||||||
getLfo,
|
|
||||||
getPooledWorklet,
|
|
||||||
} from './helpers.mjs';
|
|
||||||
import { getNodeFromPool, isPoolable, releaseNodeToPool } from './nodePools.mjs';
|
|
||||||
import { map } from 'nanostores';
|
import { map } from 'nanostores';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
import { loadBuffer } from './sampler.mjs';
|
import { loadBuffer } from './sampler.mjs';
|
||||||
@@ -299,11 +290,11 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
|
|||||||
const lfo = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
const lfo = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
||||||
|
|
||||||
//filters
|
//filters
|
||||||
const numStages = 2; //num of filters in series
|
const numStages = 1; //num of filters in series
|
||||||
let fOffset = 0;
|
let fOffset = 282; //for backward compat in #1800
|
||||||
const filterChain = [];
|
const filterChain = [];
|
||||||
for (let i = 0; i < numStages; i++) {
|
for (let i = 0; i < numStages; i++) {
|
||||||
const filter = getNodeFromPool('filter', () => ac.createBiquadFilter());
|
const filter = ac.createBiquadFilter();
|
||||||
filter.type = 'notch';
|
filter.type = 'notch';
|
||||||
filter.gain.value = 1;
|
filter.gain.value = 1;
|
||||||
filter.frequency.value = centerFrequency + fOffset;
|
filter.frequency.value = centerFrequency + fOffset;
|
||||||
@@ -311,12 +302,9 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
|
|||||||
|
|
||||||
lfo.connect(filter.detune);
|
lfo.connect(filter.detune);
|
||||||
fOffset += 282;
|
fOffset += 282;
|
||||||
if (i > 0) {
|
|
||||||
filterChain[i - 1].connect(filter);
|
|
||||||
}
|
|
||||||
filterChain.push(filter);
|
filterChain.push(filter);
|
||||||
}
|
}
|
||||||
return { phaser: filterChain[filterChain.length - 1], lfo };
|
return { filterChain, lfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilterType(ftype) {
|
function getFilterType(ftype) {
|
||||||
@@ -460,6 +448,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
compressorKnee,
|
compressorKnee,
|
||||||
compressorAttack,
|
compressorAttack,
|
||||||
compressorRelease,
|
compressorRelease,
|
||||||
|
transient,
|
||||||
|
transsustain,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
@@ -515,7 +505,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
} else if (getSound(s)) {
|
} else if (getSound(s)) {
|
||||||
const { onTrigger } = getSound(s);
|
const { onTrigger } = getSound(s);
|
||||||
const onEnded = () => {
|
const onEnded = () => {
|
||||||
audioNodes.forEach((n) => (isPoolable(n) ? releaseNodeToPool(n) : n?.disconnect()));
|
audioNodes.forEach((n) => n?.disconnect());
|
||||||
activeSoundSources.delete(chainID);
|
activeSoundSources.delete(chainID);
|
||||||
};
|
};
|
||||||
const soundHandle = await onTrigger(t, value, onEnded, cps);
|
const soundHandle = await onTrigger(t, value, onEnded, cps);
|
||||||
@@ -539,9 +529,23 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
}
|
}
|
||||||
const chain = []; // audio nodes that will be connected to each other sequentially
|
const chain = []; // audio nodes that will be connected to each other sequentially
|
||||||
chain.push(sourceNode);
|
chain.push(sourceNode);
|
||||||
stretch !== undefined &&
|
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
||||||
|
|
||||||
|
transient !== undefined &&
|
||||||
chain.push(
|
chain.push(
|
||||||
getPooledWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch, begin: t, end: endWithRelease }),
|
getWorklet(
|
||||||
|
ac,
|
||||||
|
'transient-processor',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
processorOptions: {
|
||||||
|
attack: transient,
|
||||||
|
sustain: transsustain,
|
||||||
|
begin: t,
|
||||||
|
end: endWithRelease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// gain stage
|
// gain stage
|
||||||
@@ -655,13 +659,10 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// effects
|
// effects
|
||||||
coarse !== undefined &&
|
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
|
||||||
chain.push(getPooledWorklet(ac, 'coarse-processor', { coarse, begin: t, end: endWithRelease }));
|
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
|
||||||
crush !== undefined && chain.push(getPooledWorklet(ac, 'crush-processor', { crush, begin: t, end: endWithRelease }));
|
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
|
||||||
shape !== undefined &&
|
distort !== undefined && chain.push(getDistortion(distort, distortvol, distorttype));
|
||||||
chain.push(getPooledWorklet(ac, 'shape-processor', { shape, postgain: shapevol, begin: t, end: endWithRelease }));
|
|
||||||
distort !== undefined &&
|
|
||||||
chain.push(getDistortion({ distort, postgain: distortvol, algorithm: distorttype, begin: t, end: endWithRelease }));
|
|
||||||
|
|
||||||
if (tremolosync != null) {
|
if (tremolosync != null) {
|
||||||
tremolo = cps * tremolosync;
|
tremolo = cps * tremolosync;
|
||||||
@@ -712,9 +713,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
}
|
}
|
||||||
// phaser
|
// phaser
|
||||||
if (phaserrate !== undefined && phaserdepth > 0) {
|
if (phaserrate !== undefined && phaserdepth > 0) {
|
||||||
const { phaser, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
|
const { filterChain, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
|
||||||
audioNodes.push(lfo);
|
audioNodes.push(lfo);
|
||||||
chain.push(phaser);
|
chain.push(...filterChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last gain
|
// last gain
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
/*
|
|
||||||
superdoughoutput.mjs - Output controller for superdough
|
|
||||||
|
|
||||||
Handles setting up and mixing to the outputs as well as all global (orbit) effects
|
|
||||||
|
|
||||||
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough/superdoughoutput.mjs>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { effectSend, getWorklet, webAudioTimeout } from './helpers.mjs';
|
import { effectSend, getWorklet, webAudioTimeout } from './helpers.mjs';
|
||||||
import { errorLogger } from './logger.mjs';
|
import { errorLogger } from './logger.mjs';
|
||||||
import { clamp } from './util.mjs';
|
import { clamp } from './util.mjs';
|
||||||
|
|||||||
@@ -9,15 +9,14 @@ import {
|
|||||||
getLfo,
|
getLfo,
|
||||||
getParamADSR,
|
getParamADSR,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getPooledWorklet,
|
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
|
getWorklet,
|
||||||
noises,
|
noises,
|
||||||
releaseAudioNode,
|
releaseAudioNode,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
||||||
import { releaseNodeToPool } from './nodePools.mjs';
|
|
||||||
|
|
||||||
const waveforms = ['triangle', 'square', 'sawtooth', 'sine', 'user'];
|
const waveforms = ['triangle', 'square', 'sawtooth', 'sine', 'user'];
|
||||||
const waveformAliases = [
|
const waveformAliases = [
|
||||||
@@ -165,14 +164,22 @@ export function registerSynthSounds() {
|
|||||||
const end = holdend + release + 0.01;
|
const end = holdend + release + 0.01;
|
||||||
const voices = clamp(unison, 1, 100);
|
const voices = clamp(unison, 1, 100);
|
||||||
let panspread = voices > 1 ? clamp(spread, 0, 1) : 0;
|
let panspread = voices > 1 ? clamp(spread, 0, 1) : 0;
|
||||||
const params = {
|
let o = getWorklet(
|
||||||
frequency,
|
ac,
|
||||||
begin,
|
'supersaw-oscillator',
|
||||||
end,
|
{
|
||||||
freqspread: detune,
|
frequency,
|
||||||
panspread,
|
begin,
|
||||||
};
|
end,
|
||||||
const o = getPooledWorklet(ac, 'supersaw-oscillator', params, { outputChannelCount: [2] }, { voices });
|
freqspread: detune,
|
||||||
|
voices,
|
||||||
|
panspread,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
outputChannelCount: [2],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const gainAdjustment = 1 / Math.sqrt(voices);
|
const gainAdjustment = 1 / Math.sqrt(voices);
|
||||||
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
||||||
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
||||||
@@ -185,7 +192,7 @@ export function registerSynthSounds() {
|
|||||||
let timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
releaseNodeToPool(o);
|
releaseAudioNode(o);
|
||||||
onended();
|
onended();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
@@ -239,7 +246,7 @@ export function registerSynthSounds() {
|
|||||||
const holdend = begin + duration;
|
const holdend = begin + duration;
|
||||||
const end = holdend + release + 0.01;
|
const end = holdend + release + 0.01;
|
||||||
|
|
||||||
let o = getPooledWorklet(
|
let o = getWorklet(
|
||||||
ac,
|
ac,
|
||||||
'byte-beat-processor',
|
'byte-beat-processor',
|
||||||
{
|
{
|
||||||
@@ -306,7 +313,7 @@ export function registerSynthSounds() {
|
|||||||
);
|
);
|
||||||
const holdend = begin + duration;
|
const holdend = begin + duration;
|
||||||
const end = holdend + release + 0.01;
|
const end = holdend + release + 0.01;
|
||||||
let o = getPooledWorklet(
|
let o = getWorklet(
|
||||||
ac,
|
ac,
|
||||||
'pulse-oscillator',
|
'pulse-oscillator',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import {
|
|||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
getParamADSR,
|
getParamADSR,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getPooledWorklet,
|
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
|
getWorklet,
|
||||||
|
releaseAudioNode,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { releaseNodeToPool } from './nodePools.mjs';
|
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
export const Warpmode = Object.freeze({
|
export const Warpmode = Object.freeze({
|
||||||
@@ -225,19 +225,24 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
}
|
}
|
||||||
const endWithRelease = holdEnd + release;
|
const endWithRelease = holdEnd + release;
|
||||||
const envEnd = endWithRelease + 0.01;
|
const envEnd = endWithRelease + 0.01;
|
||||||
payload.voices = Math.max(value.unison ?? 1, 1);
|
const source = getWorklet(
|
||||||
const params = {
|
ac,
|
||||||
begin: t,
|
'wavetable-oscillator-processor',
|
||||||
end: envEnd,
|
{
|
||||||
frequency,
|
begin: t,
|
||||||
freqspread: value.detune,
|
end: envEnd,
|
||||||
position: value.wt,
|
frequency,
|
||||||
warp: value.warp,
|
freqspread: value.detune,
|
||||||
warpMode: warpmode,
|
position: value.wt,
|
||||||
panspread: value.spread,
|
warp: value.warp,
|
||||||
phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0,
|
warpMode: warpmode,
|
||||||
};
|
voices: Math.max(value.unison ?? 1, 1),
|
||||||
const source = getPooledWorklet(ac, 'wavetable-oscillator-processor', params, { outputChannelCount: [2] }, payload);
|
panspread: value.spread,
|
||||||
|
phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0,
|
||||||
|
},
|
||||||
|
{ outputChannelCount: [2] },
|
||||||
|
);
|
||||||
|
source.port.postMessage({ type: 'table', payload });
|
||||||
if (ac.currentTime > t) {
|
if (ac.currentTime > t) {
|
||||||
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
||||||
return;
|
return;
|
||||||
@@ -314,7 +319,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
const timeoutNode = webAudioTimeout(
|
const timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
releaseNodeToPool(source);
|
releaseAudioNode(source);
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
wtPosModulators?.disconnect();
|
wtPosModulators?.disconnect();
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
worklets-common.mjs - Common worklet code
|
|
||||||
|
|
||||||
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough/worklets-common.mjs>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Mixin to make class "reusable" (i.e. have a begin and end and properly handle `initialize` messages)
|
|
||||||
export const makeReusable = (Base) =>
|
|
||||||
class extends Base {
|
|
||||||
static get parameterDescriptors() {
|
|
||||||
return [
|
|
||||||
...(super.parameterDescriptors ?? []),
|
|
||||||
{
|
|
||||||
name: 'begin',
|
|
||||||
defaultValue: 0,
|
|
||||||
min: 0,
|
|
||||||
max: Number.POSITIVE_INFINITY,
|
|
||||||
automationRate: 'k-rate',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'end',
|
|
||||||
defaultValue: 0,
|
|
||||||
min: 0,
|
|
||||||
max: Number.POSITIVE_INFINITY,
|
|
||||||
automationRate: 'k-rate',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(options) {
|
|
||||||
super(options);
|
|
||||||
this.isAlive = true;
|
|
||||||
this.graceSeconds = options?.processorOptions?.graceSeconds ?? 0.5;
|
|
||||||
|
|
||||||
this.port.onmessage = (e) => {
|
|
||||||
const { type, payload } = e.data || {};
|
|
||||||
if (type === 'initialize') {
|
|
||||||
this.initialize(payload);
|
|
||||||
} else if (this.handlePortMessage) {
|
|
||||||
this.handlePortMessage(type, payload, e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(_options) {
|
|
||||||
// defined on subclasses
|
|
||||||
}
|
|
||||||
|
|
||||||
processActive(_inputs, _outputs, _params) {
|
|
||||||
// processing subclasses should do when we're between begin/end
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
process(inputs, outputs, params) {
|
|
||||||
const begin = params.begin[0];
|
|
||||||
const end = params.end[0];
|
|
||||||
if (currentTime >= end + this.graceSeconds) {
|
|
||||||
if (this.isAlive) {
|
|
||||||
this.port.postMessage({ type: 'died' });
|
|
||||||
this.isAlive = false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (currentTime < begin || currentTime >= end) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this.processActive(inputs, outputs, params);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
// coarse, crush, and shape processors adapted from dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js
|
// coarse, crush, and shape processors adapted from dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js
|
||||||
// LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE
|
// LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE
|
||||||
// TOFIX: THIS FILE DOES NOT SUPPORT IMPORTS ON DEPLOYMENT
|
// TOFIX: THIS FILE DOES NOT SUPPORT IMPORTS ON DEPOLYMENT
|
||||||
|
|
||||||
import OLAProcessor from './ola-processor';
|
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';
|
||||||
import { makeReusable } from './worklets-common.mjs';
|
|
||||||
|
|
||||||
const blockSize = 128;
|
const blockSize = 128;
|
||||||
const PI = Math.PI;
|
const PI = Math.PI;
|
||||||
const TWO_PI = 2 * PI;
|
const TWO_PI = 2 * PI;
|
||||||
const INVSR = 1 / sampleRate;
|
const INVSR = 1 / sampleRate;
|
||||||
|
|
||||||
|
const timeToCoeff = (t) => 1 - Math.exp(-INVSR / t);
|
||||||
|
const dbToLin = (db) => Math.pow(10, db / 20);
|
||||||
|
|
||||||
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;
|
||||||
@@ -109,12 +111,12 @@ const waveshapes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const waveShapeNames = Object.keys(waveshapes);
|
const waveShapeNames = Object.keys(waveshapes);
|
||||||
|
class LFOProcessor extends AudioWorkletProcessor {
|
||||||
class LFOProcessor extends makeReusable(AudioWorkletProcessor) {
|
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
...super.parameterDescriptors,
|
{ name: 'begin', defaultValue: 0 },
|
||||||
{ name: 'time', defaultValue: 0 },
|
{ name: 'time', defaultValue: 0 },
|
||||||
|
{ name: 'end', defaultValue: 0 },
|
||||||
{ name: 'frequency', defaultValue: 0.5 },
|
{ name: 'frequency', defaultValue: 0.5 },
|
||||||
{ name: 'skew', defaultValue: 0.5 },
|
{ name: 'skew', defaultValue: 0.5 },
|
||||||
{ name: 'depth', defaultValue: 1 },
|
{ name: 'depth', defaultValue: 1 },
|
||||||
@@ -127,8 +129,9 @@ class LFOProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
constructor() {
|
||||||
this.phase = null;
|
super();
|
||||||
|
this.phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementPhase(dt) {
|
incrementPhase(dt) {
|
||||||
@@ -138,7 +141,15 @@ class LFOProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(_inputs, outputs, parameters) {
|
process(_inputs, outputs, parameters) {
|
||||||
|
const begin = parameters['begin'][0];
|
||||||
|
if (currentTime >= parameters.end[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= begin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
const frequency = parameters['frequency'][0];
|
const frequency = parameters['frequency'][0];
|
||||||
|
|
||||||
@@ -174,14 +185,26 @@ class LFOProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
registerProcessor('lfo-processor', LFOProcessor);
|
registerProcessor('lfo-processor', LFOProcessor);
|
||||||
|
|
||||||
class CoarseProcessor extends makeReusable(AudioWorkletProcessor) {
|
class CoarseProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [...super.parameterDescriptors, { name: 'coarse', defaultValue: 1 }];
|
return [{ name: 'coarse', defaultValue: 1 }];
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, parameters) {
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, parameters) {
|
||||||
const input = inputs[0];
|
const input = inputs[0];
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
|
|
||||||
|
const hasInput = !(input[0] === undefined);
|
||||||
|
if (this.started && !hasInput) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.started = hasInput;
|
||||||
|
|
||||||
let coarse = parameters.coarse[0] ?? 0;
|
let coarse = parameters.coarse[0] ?? 0;
|
||||||
coarse = Math.max(1, coarse);
|
coarse = Math.max(1, coarse);
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
@@ -194,14 +217,26 @@ class CoarseProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
registerProcessor('coarse-processor', CoarseProcessor);
|
registerProcessor('coarse-processor', CoarseProcessor);
|
||||||
|
|
||||||
class CrushProcessor extends makeReusable(AudioWorkletProcessor) {
|
class CrushProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [...super.parameterDescriptors, { name: 'crush', defaultValue: 0 }];
|
return [{ name: 'crush', defaultValue: 0 }];
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, parameters) {
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, parameters) {
|
||||||
const input = inputs[0];
|
const input = inputs[0];
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
|
|
||||||
|
const hasInput = !(input[0] === undefined);
|
||||||
|
if (this.started && !hasInput) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.started = hasInput;
|
||||||
|
|
||||||
let crush = parameters.crush[0] ?? 8;
|
let crush = parameters.crush[0] ?? 8;
|
||||||
crush = Math.max(1, crush);
|
crush = Math.max(1, crush);
|
||||||
|
|
||||||
@@ -216,14 +251,29 @@ class CrushProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
registerProcessor('crush-processor', CrushProcessor);
|
registerProcessor('crush-processor', CrushProcessor);
|
||||||
|
|
||||||
class ShapeProcessor extends makeReusable(AudioWorkletProcessor) {
|
class ShapeProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [...super.parameterDescriptors, { name: 'shape', defaultValue: 0 }, { name: 'postgain', defaultValue: 1 }];
|
return [
|
||||||
|
{ name: 'shape', defaultValue: 0 },
|
||||||
|
{ name: 'postgain', defaultValue: 1 },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, parameters) {
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, parameters) {
|
||||||
const input = inputs[0];
|
const input = inputs[0];
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
|
|
||||||
|
const hasInput = !(input[0] === undefined);
|
||||||
|
if (this.started && !hasInput) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.started = hasInput;
|
||||||
|
|
||||||
let shape = parameters.shape[0];
|
let shape = parameters.shape[0];
|
||||||
shape = shape < 1 ? shape : 1.0 - 4e-10;
|
shape = shape < 1 ? shape : 1.0 - 4e-10;
|
||||||
shape = (2.0 * shape) / (1.0 - shape);
|
shape = (2.0 * shape) / (1.0 - shape);
|
||||||
@@ -307,17 +357,17 @@ class DJFProcessor extends AudioWorkletProcessor {
|
|||||||
registerProcessor('djf-processor', DJFProcessor);
|
registerProcessor('djf-processor', DJFProcessor);
|
||||||
|
|
||||||
//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 makeReusable(AudioWorkletProcessor) {
|
class LadderProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
...super.parameterDescriptors,
|
|
||||||
{ name: 'frequency', defaultValue: 500 },
|
{ name: 'frequency', defaultValue: 500 },
|
||||||
{ name: 'q', defaultValue: 1 },
|
{ name: 'q', defaultValue: 1 },
|
||||||
{ name: 'drive', defaultValue: 0.69 },
|
{ name: 'drive', defaultValue: 0.69 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(_options) {
|
constructor() {
|
||||||
|
super();
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.p0 = [0, 0];
|
this.p0 = [0, 0];
|
||||||
this.p1 = [0, 0];
|
this.p1 = [0, 0];
|
||||||
@@ -328,9 +378,17 @@ class LadderProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
this.p34 = [0, 0];
|
this.p34 = [0, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
const input = inputs[0];
|
const input = inputs[0];
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
|
|
||||||
|
const hasInput = !(input[0] === undefined);
|
||||||
|
if (this.started && !hasInput) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.started = hasInput;
|
||||||
|
|
||||||
const resonance = parameters.q[0];
|
const resonance = parameters.q[0];
|
||||||
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
|
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
|
||||||
|
|
||||||
@@ -363,18 +421,29 @@ class LadderProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
registerProcessor('ladder-processor', LadderProcessor);
|
registerProcessor('ladder-processor', LadderProcessor);
|
||||||
|
|
||||||
class DistortProcessor extends makeReusable(AudioWorkletProcessor) {
|
class DistortProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [...super.parameterDescriptors, { name: 'distort', defaultValue: 0 }, { name: 'postgain', defaultValue: 1 }];
|
return [
|
||||||
|
{ name: 'distort', defaultValue: 0 },
|
||||||
|
{ name: 'postgain', defaultValue: 1 },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(options) {
|
constructor({ processorOptions }) {
|
||||||
this.algorithm = getDistortionAlgorithm(options?.algorithm);
|
super();
|
||||||
|
this.started = false;
|
||||||
|
this.algorithm = getDistortionAlgorithm(processorOptions.algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
const input = inputs[0];
|
const input = inputs[0];
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
|
|
||||||
|
const hasInput = !(input[0] === undefined);
|
||||||
|
if (this.started && !hasInput) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.started = hasInput;
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1);
|
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1);
|
||||||
const shape = Math.expm1(pv(parameters.distort, n));
|
const shape = Math.expm1(pv(parameters.distort, n));
|
||||||
@@ -389,15 +458,33 @@ class DistortProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
registerProcessor('distort-processor', DistortProcessor);
|
registerProcessor('distort-processor', DistortProcessor);
|
||||||
|
|
||||||
// SUPERSAW
|
// SUPERSAW
|
||||||
class SuperSawOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.phase = [];
|
||||||
|
}
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
...super.parameterDescriptors,
|
{
|
||||||
|
name: 'begin',
|
||||||
|
defaultValue: 0,
|
||||||
|
max: Number.POSITIVE_INFINITY,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'end',
|
||||||
|
defaultValue: 0,
|
||||||
|
max: Number.POSITIVE_INFINITY,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'frequency',
|
name: 'frequency',
|
||||||
defaultValue: 440,
|
defaultValue: 440,
|
||||||
min: Number.EPSILON,
|
min: Number.EPSILON,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'panspread',
|
name: 'panspread',
|
||||||
defaultValue: 0.4,
|
defaultValue: 0.4,
|
||||||
@@ -414,14 +501,26 @@ class SuperSawOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'voices',
|
||||||
|
defaultValue: 5,
|
||||||
|
min: 1,
|
||||||
|
automationRate: 'k-rate',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
initialize(options) {
|
process(_input, outputs, params) {
|
||||||
this.phase = [];
|
if (currentTime >= params.end[0]) {
|
||||||
this.voices = options?.voices ?? 5;
|
// should terminate
|
||||||
}
|
return false;
|
||||||
processActive(_input, outputs, params) {
|
}
|
||||||
|
if (currentTime <= params.begin[0]) {
|
||||||
|
// keep alive
|
||||||
|
return true;
|
||||||
|
}
|
||||||
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 freqspread = pv(params.freqspread, i);
|
const freqspread = pv(params.freqspread, i);
|
||||||
@@ -431,8 +530,8 @@ class SuperSawOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
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(this.voices, freqspread);
|
const detuner = getDetuner(voices, freqspread);
|
||||||
for (let n = 0; n < this.voices; n++) {
|
for (let n = 0; n < voices; n++) {
|
||||||
// Individual voice detuning
|
// Individual voice detuning
|
||||||
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
|
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
|
||||||
// We must wrap this here because it is passed into sawblep below which
|
// We must wrap this here because it is passed into sawblep below which
|
||||||
@@ -476,7 +575,12 @@ function genHannWindow(length) {
|
|||||||
|
|
||||||
class PhaseVocoderProcessor extends OLAProcessor {
|
class PhaseVocoderProcessor extends OLAProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [...super.parameterDescriptors, { name: 'pitchFactor', defaultValue: 1.0 }];
|
return [
|
||||||
|
{
|
||||||
|
name: 'pitchFactor',
|
||||||
|
defaultValue: 1.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
@@ -484,13 +588,9 @@ 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.invfftSize = 1 / this.fftSize;
|
||||||
}
|
|
||||||
|
|
||||||
initialize(options) {
|
|
||||||
super.initialize(options);
|
|
||||||
this.timeCursor = 0;
|
|
||||||
this.hannWindow = genHannWindow(this.fftSize);
|
this.hannWindow = genHannWindow(this.fftSize);
|
||||||
// prepare FFT and pre-allocate buffers
|
// prepare FFT and pre-allocate buffers
|
||||||
this.fft = new FFT(this.fftSize);
|
this.fft = new FFT(this.fftSize);
|
||||||
@@ -621,8 +721,9 @@ class PhaseVocoderProcessor extends OLAProcessor {
|
|||||||
registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor);
|
registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor);
|
||||||
|
|
||||||
// Adapted from https://www.musicdsp.org/en/latest/Effects/221-band-limited-pwm-generator.html
|
// Adapted from https://www.musicdsp.org/en/latest/Effects/221-band-limited-pwm-generator.html
|
||||||
class PulseOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
class PulseOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
initialize() {
|
constructor() {
|
||||||
|
super();
|
||||||
this.phi = -PI; // phase
|
this.phi = -PI; // phase
|
||||||
this.Y0 = 0; // feedback memories
|
this.Y0 = 0; // feedback memories
|
||||||
this.Y1 = 0;
|
this.Y1 = 0;
|
||||||
@@ -634,7 +735,20 @@ class PulseOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
|
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
...super.parameterDescriptors,
|
{
|
||||||
|
name: 'begin',
|
||||||
|
defaultValue: 0,
|
||||||
|
max: Number.POSITIVE_INFINITY,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'end',
|
||||||
|
defaultValue: 0,
|
||||||
|
max: Number.POSITIVE_INFINITY,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'frequency',
|
name: 'frequency',
|
||||||
defaultValue: 440,
|
defaultValue: 440,
|
||||||
@@ -655,10 +769,20 @@ class PulseOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, params) {
|
process(inputs, outputs, params) {
|
||||||
|
if (this.disconnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= params.begin[0]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (currentTime >= params.end[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
let env = 1,
|
let env = 1,
|
||||||
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(pv(params.pulsewidth, i), -0.99, 0.99)) * PI;
|
||||||
const detune = pv(params.detune, i);
|
const detune = pv(params.detune, i);
|
||||||
@@ -741,23 +865,28 @@ function getByteBeatFunc(codetext) {
|
|||||||
return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs);
|
return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ByteBeatProcessor extends makeReusable(AudioWorkletProcessor) {
|
class ByteBeatProcessor extends AudioWorkletProcessor {
|
||||||
initialize(options) {
|
constructor() {
|
||||||
let codeText = options?.codeText;
|
super();
|
||||||
const byteBeatStartTime = options?.byteBeatStartTime;
|
this.port.onmessage = (event) => {
|
||||||
if (byteBeatStartTime != null) {
|
let { codeText } = event.data;
|
||||||
this.t = 0;
|
const { byteBeatStartTime } = event.data;
|
||||||
this.initialOffset = Math.floor(byteBeatStartTime);
|
if (byteBeatStartTime != null) {
|
||||||
}
|
this.t = 0;
|
||||||
//Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important
|
this.initialOffset = Math.floor(byteBeatStartTime);
|
||||||
//Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%")))
|
}
|
||||||
codeText = codeText
|
|
||||||
.trim()
|
//Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important
|
||||||
.replace(
|
//Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%")))
|
||||||
/^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/,
|
codeText = codeText
|
||||||
(match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')),
|
.trim()
|
||||||
);
|
.replace(
|
||||||
this.func = getByteBeatFunc(codeText);
|
/^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/,
|
||||||
|
(match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.func = getByteBeatFunc(codeText);
|
||||||
|
};
|
||||||
this.initialOffset = 0;
|
this.initialOffset = 0;
|
||||||
this.t = null;
|
this.t = null;
|
||||||
this.func = null;
|
this.func = null;
|
||||||
@@ -765,7 +894,12 @@ class ByteBeatProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
|
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
...super.parameterDescriptors,
|
{
|
||||||
|
name: 'begin',
|
||||||
|
defaultValue: 0,
|
||||||
|
max: Number.POSITIVE_INFINITY,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'frequency',
|
name: 'frequency',
|
||||||
defaultValue: 440,
|
defaultValue: 440,
|
||||||
@@ -777,10 +911,25 @@ class ByteBeatProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
min: Number.NEGATIVE_INFINITY,
|
min: Number.NEGATIVE_INFINITY,
|
||||||
max: Number.POSITIVE_INFINITY,
|
max: Number.POSITIVE_INFINITY,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'end',
|
||||||
|
defaultValue: 0,
|
||||||
|
max: Number.POSITIVE_INFINITY,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(inputs, outputs, params) {
|
process(inputs, outputs, params) {
|
||||||
|
if (this.disconnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= params.begin[0]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (currentTime >= params.end[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (this.t == null) {
|
if (this.t == null) {
|
||||||
this.t = params.begin[0] * sampleRate;
|
this.t = params.begin[0] * sampleRate;
|
||||||
}
|
}
|
||||||
@@ -806,9 +955,11 @@ class ByteBeatProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
|
|
||||||
registerProcessor('byte-beat-processor', ByteBeatProcessor);
|
registerProcessor('byte-beat-processor', ByteBeatProcessor);
|
||||||
|
|
||||||
class EnvelopeProcessor extends makeReusable(AudioWorkletProcessor) {
|
class EnvelopeProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
|
{ name: 'begin', defaultValue: 0 },
|
||||||
|
{ name: 'end', defaultValue: 0 },
|
||||||
{ name: 'attack', defaultValue: 0.005, minValue: 0 },
|
{ name: 'attack', defaultValue: 0.005, minValue: 0 },
|
||||||
{ name: 'decay', defaultValue: 0.14, minValue: 0 },
|
{ name: 'decay', defaultValue: 0.14, minValue: 0 },
|
||||||
{ name: 'sustain', defaultValue: 0, minValue: 0, maxValue: 1 },
|
{ name: 'sustain', defaultValue: 0, minValue: 0, maxValue: 1 },
|
||||||
@@ -968,36 +1119,56 @@ function brownian(x, oct = 4) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tablesCache = {};
|
const tablesCache = {};
|
||||||
class WavetableOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
...super.parameterDescriptors,
|
{ name: 'begin', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
|
||||||
|
{ name: 'end', defaultValue: 0, min: 0, max: Number.POSITIVE_INFINITY },
|
||||||
{ name: 'frequency', defaultValue: 440, min: Number.EPSILON },
|
{ name: 'frequency', defaultValue: 440, min: Number.EPSILON },
|
||||||
{ name: 'detune', defaultValue: 0 },
|
{ name: 'detune', defaultValue: 0 },
|
||||||
{ name: 'freqspread', defaultValue: 0.18, min: 0 },
|
{ name: 'freqspread', defaultValue: 0.18, min: 0 },
|
||||||
{ 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: '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 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(options) {
|
constructor(options) {
|
||||||
this.table = null;
|
super(options);
|
||||||
this.frameLen = null;
|
this.frameLen = 0;
|
||||||
this.numFrames = null;
|
this.numFrames = 0;
|
||||||
this.phase = [];
|
this.phase = [];
|
||||||
if (options?.key) {
|
|
||||||
const key = options.key;
|
this.port.onmessage = (e) => {
|
||||||
this.frameLen = options.frameLen;
|
const { type, payload } = e.data || {};
|
||||||
if (!tablesCache[key]) {
|
if (type === 'table') {
|
||||||
tablesCache[key] = options.frames;
|
const key = payload.key;
|
||||||
|
this.frameLen = payload.frameLen;
|
||||||
|
if (!tablesCache[key]) {
|
||||||
|
const tables = [payload.frames];
|
||||||
|
let table = tables[0];
|
||||||
|
for (let level = 1; level < 1; level++) {
|
||||||
|
const nextLen = table.length >> 1;
|
||||||
|
const nextTable = table.map((frame) => {
|
||||||
|
const avg = new Float32Array(nextLen);
|
||||||
|
for (let i = 0; i < nextLen; i++) {
|
||||||
|
avg[i] = (frame[2 * i] + frame[2 * i + 1]) / 2;
|
||||||
|
}
|
||||||
|
return avg;
|
||||||
|
});
|
||||||
|
tables.push(nextTable);
|
||||||
|
table = nextTable;
|
||||||
|
if (nextLen <= 32) break;
|
||||||
|
}
|
||||||
|
tablesCache[key] = tables;
|
||||||
|
}
|
||||||
|
this.tables = tablesCache[key];
|
||||||
|
this.numFrames = this.tables[0].length;
|
||||||
}
|
}
|
||||||
this.table = tablesCache[key];
|
};
|
||||||
this.numFrames = this.table.length;
|
|
||||||
}
|
|
||||||
this.voices = options?.voices ?? 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_mirror(x) {
|
_mirror(x) {
|
||||||
@@ -1142,14 +1313,30 @@ class WavetableOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
return a + (b - a) * frac;
|
return a + (b - a) * frac;
|
||||||
}
|
}
|
||||||
|
|
||||||
processActive(_inputs, outputs, parameters) {
|
_chooseMip(dphi) {
|
||||||
|
const approxHarm = clamp(dphi, 1e-6, 64);
|
||||||
|
let level = 0;
|
||||||
|
while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(_inputs, outputs, parameters) {
|
||||||
|
if (currentTime >= parameters.end[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= parameters.begin[0]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const outL = outputs[0][0];
|
const outL = outputs[0][0];
|
||||||
const outR = outputs[0][1] || outputs[0][0];
|
const outR = outputs[0][1] || outputs[0][0];
|
||||||
if (!this.table) {
|
if (!this.tables) {
|
||||||
outL.fill(0);
|
outL.fill(0);
|
||||||
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);
|
||||||
@@ -1160,14 +1347,14 @@ class WavetableOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
|
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
|
||||||
const warpMode = pv(parameters.warpMode, i);
|
const warpMode = pv(parameters.warpMode, i);
|
||||||
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
|
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
|
||||||
const panspread = this.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);
|
||||||
const gain2 = Math.sqrt(0.5 + 0.5 * panspread);
|
const gain2 = Math.sqrt(0.5 + 0.5 * panspread);
|
||||||
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(this.voices);
|
const normalizer = 1 / Math.sqrt(voices);
|
||||||
const detuner = getDetuner(this.voices, freqspread);
|
const detuner = getDetuner(voices, freqspread);
|
||||||
for (let n = 0; n < this.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;
|
||||||
let gainR = gain2;
|
let gainR = gain2;
|
||||||
@@ -1178,12 +1365,14 @@ class WavetableOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
const fVoice = applySemitoneDetuneToFrequency(f, detuner(n)); // voice detune
|
const fVoice = applySemitoneDetuneToFrequency(f, detuner(n)); // voice detune
|
||||||
const dPhase = fVoice * INVSR;
|
const dPhase = fVoice * INVSR;
|
||||||
|
const level = this._chooseMip(dPhase);
|
||||||
|
const table = this.tables[level];
|
||||||
|
|
||||||
// warp phase then sample
|
// warp phase then sample
|
||||||
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
||||||
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||||
const s0 = this._sampleFrame(this.table[fIdx], ph);
|
const s0 = this._sampleFrame(table[fIdx], ph);
|
||||||
const s1 = this._sampleFrame(this.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 = lerp(s0, s1, interpT);
|
||||||
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||||
s = -s;
|
s = -s;
|
||||||
@@ -1198,3 +1387,82 @@ class WavetableOscillatorProcessor extends makeReusable(AudioWorkletProcessor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor);
|
registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor);
|
||||||
|
|
||||||
|
class TransientProcessor extends AudioWorkletProcessor {
|
||||||
|
static get parameterDescriptors() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
this.gainCoeff = timeToCoeff(0.2);
|
||||||
|
this.avgGain = 1;
|
||||||
|
let {
|
||||||
|
attackTime = 0.003,
|
||||||
|
sustainTime = 0.08,
|
||||||
|
attack = 0,
|
||||||
|
sustain = 0,
|
||||||
|
sensitivity = 0.1,
|
||||||
|
mix = 1,
|
||||||
|
begin = 0,
|
||||||
|
end = 0,
|
||||||
|
} = options.processorOptions;
|
||||||
|
attackTime = clamp(attackTime, 0.0005, 0.05);
|
||||||
|
sustainTime = clamp(sustainTime, 0.01, 0.5);
|
||||||
|
this.attackCoeff = timeToCoeff(attackTime);
|
||||||
|
this.sustainCoeff = timeToCoeff(sustainTime);
|
||||||
|
this.attackAmt = clamp(attack, -1, 1);
|
||||||
|
this.sustainAmt = clamp(sustain, -1, 1);
|
||||||
|
this.scaling = 0.5 + 5 * clamp(sensitivity, 0, 1);
|
||||||
|
this.mix = clamp(mix, 0, 1);
|
||||||
|
this.begin = begin;
|
||||||
|
this.end = end;
|
||||||
|
this.attackEnv = new Float32Array(2); // assume stereo
|
||||||
|
this.sustainEnv = new Float32Array(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, _params) {
|
||||||
|
const input = inputs[0];
|
||||||
|
const output = outputs[0];
|
||||||
|
if (currentTime >= this.end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= this.begin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const channels = input.length;
|
||||||
|
if (channels > this.attackEnv.length) {
|
||||||
|
this.attackEnv = new Float32Array(channels);
|
||||||
|
this.sustainEnv = new Float32Array(channels);
|
||||||
|
}
|
||||||
|
let avgGain = this.avgGain;
|
||||||
|
for (let ch = 0; ch < channels; ch++) {
|
||||||
|
let attEnv = this.attackEnv[ch];
|
||||||
|
let susEnv = this.sustainEnv[ch];
|
||||||
|
for (let n = 0; n < blockSize; n++) {
|
||||||
|
const sample = input[ch][n];
|
||||||
|
const x = Math.abs(sample);
|
||||||
|
attEnv = lerp(attEnv, x, this.attackCoeff);
|
||||||
|
susEnv = lerp(susEnv, x, this.sustainCoeff);
|
||||||
|
const peakiness = clamp((this.scaling * (attEnv - susEnv)) / (susEnv + 1e-6), -1.5, 1.5);
|
||||||
|
const attScale = peakiness > 0 ? peakiness : 0;
|
||||||
|
const susScale = peakiness < 0 ? -peakiness : 0;
|
||||||
|
const attackGain = dbToLin(this.attackAmt * attScale * 18);
|
||||||
|
const sustainGain = dbToLin(this.sustainAmt * susScale * 36);
|
||||||
|
const gain = clamp(attackGain * sustainGain, 0, 8);
|
||||||
|
avgGain = lerp(avgGain, gain, this.gainCoeff);
|
||||||
|
const makeup = avgGain > 1e-3 ? 1 / avgGain : 1;
|
||||||
|
const wet = sample * gain * makeup;
|
||||||
|
let y = lerp(sample, wet, this.mix);
|
||||||
|
y /= 1 + Math.abs(y); // soft clip
|
||||||
|
output[ch][n] = y;
|
||||||
|
}
|
||||||
|
this.attackEnv[ch] = attEnv;
|
||||||
|
this.sustainEnv[ch] = susEnv;
|
||||||
|
}
|
||||||
|
this.avgGain = avgGain;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('transient-processor', TransientProcessor);
|
||||||
|
|||||||
@@ -4043,6 +4043,70 @@ exports[`runs examples > example "filter" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "filterHaps" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 1/8 → 1/4 | s:bd velocity:0.6852155700325966 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:bd velocity:0.36975969187915325 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:bd velocity:0.40139251574873924 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:bd velocity:0.5195421651005745 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:bd velocity:0.6724895145744085 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:bd velocity:0.7287282031029463 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:bd velocity:0.6084080748260021 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:bd velocity:0.9595271199941635 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:bd velocity:0.9033902939409018 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:bd velocity:0.34548251144587994 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:bd velocity:0.8365066405385733 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:bd velocity:0.21728911064565182 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:bd velocity:0.34324387833476067 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:bd velocity:0.9923650715500116 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:bd velocity:0.40869180113077164 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:bd velocity:0.9391485787928104 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:bd velocity:0.8121673222631216 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "filterValues" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 1/4 → 1/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 1/2 → 3/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 3/4 → 1/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 1/1 → 5/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 5/4 → 3/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 3/2 → 7/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 7/4 → 2/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 2/1 → 9/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 9/4 → 5/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 5/2 → 11/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 11/4 → 3/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 3/1 → 13/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 13/4 → 7/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 7/2 → 15/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 15/4 → 4/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "filterWhen" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | s:bd ]",
|
||||||
|
"[ 1/4 → 1/2 | s:bd ]",
|
||||||
|
"[ 1/2 → 3/4 | s:bd ]",
|
||||||
|
"[ 3/4 → 1/1 | s:bd ]",
|
||||||
|
"[ 1/1 → 5/4 | s:bd ]",
|
||||||
|
"[ 5/4 → 3/2 | s:bd ]",
|
||||||
|
"[ 3/2 → 7/4 | s:bd ]",
|
||||||
|
"[ 7/4 → 2/1 | s:bd ]",
|
||||||
|
"[ 2/1 → 9/4 | s:bd ]",
|
||||||
|
"[ 9/4 → 5/2 | s:bd ]",
|
||||||
|
"[ 5/2 → 11/4 | s:bd ]",
|
||||||
|
"[ 11/4 → 3/1 | s:bd ]",
|
||||||
|
"[ 3/1 → 13/4 | s:bd ]",
|
||||||
|
"[ 13/4 → 7/2 | s:bd ]",
|
||||||
|
"[ 7/2 → 15/4 | s:bd ]",
|
||||||
|
"[ 15/4 → 4/1 | s:bd ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "firstOf" example index 0 1`] = `
|
exports[`runs examples > example "firstOf" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:g3 ]",
|
"[ 0/1 → 1/4 | note:g3 ]",
|
||||||
@@ -9101,6 +9165,43 @@ exports[`runs examples > example "ratio" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "register" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:saw velocity:0 cutoff:0 ]",
|
||||||
|
"[ 1/8 → 1/4 | s:saw velocity:0.6852155700325966 cutoff:548.1724560260773 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:saw velocity:0.36975969187915325 cutoff:295.8077535033226 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:saw velocity:0.40139251574873924 cutoff:321.1140125989914 ]",
|
||||||
|
"[ 1/2 → 5/8 | s:saw velocity:0.2604806162416935 cutoff:208.3844929933548 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:saw velocity:0.1356358677148819 cutoff:108.50869417190552 ]",
|
||||||
|
"[ 3/4 → 7/8 | s:saw velocity:0.19582648016512394 cutoff:156.66118413209915 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:saw velocity:0.3976310808211565 cutoff:318.1048646569252 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:saw velocity:0.5195421651005745 cutoff:415.6337320804596 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:saw velocity:0.6724895145744085 cutoff:537.9916116595268 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:saw velocity:0.7287282031029463 cutoff:582.982562482357 ]",
|
||||||
|
"[ 11/8 → 3/2 | s:saw velocity:0.18280375190079212 cutoff:146.2430015206337 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:saw velocity:0.6084080748260021 cutoff:486.7264598608017 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:saw velocity:0.49675471149384975 cutoff:397.4037691950798 ]",
|
||||||
|
"[ 7/4 → 15/8 | s:saw velocity:0.20473789609968662 cutoff:163.7903168797493 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:saw velocity:0.5945571791380644 cutoff:475.6457433104515 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:saw velocity:0.9595271199941635 cutoff:767.6216959953308 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:saw velocity:0.9033902939409018 cutoff:722.7122351527214 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:saw velocity:0.34548251144587994 cutoff:276.38600915670395 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:saw velocity:0.8365066405385733 cutoff:669.2053124308586 ]",
|
||||||
|
"[ 5/2 → 21/8 | s:saw velocity:0.45861607417464256 cutoff:366.89285933971405 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:saw velocity:0.16019348427653313 cutoff:128.1547874212265 ]",
|
||||||
|
"[ 11/4 → 23/8 | s:saw velocity:0.6332327704876661 cutoff:506.5862163901329 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:saw velocity:0.01625417172908783 cutoff:13.003337383270264 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:saw velocity:0.21728911064565182 cutoff:173.83128851652145 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:saw velocity:0.34324387833476067 cutoff:274.59510266780853 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:saw velocity:0.9923650715500116 cutoff:793.8920572400093 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:saw velocity:0.40869180113077164 cutoff:326.9534409046173 ]",
|
||||||
|
"[ 7/2 → 29/8 | s:saw velocity:0.40994881466031075 cutoff:327.9590517282486 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:saw velocity:0.9391485787928104 cutoff:751.3188630342484 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:saw velocity:0.8121673222631216 cutoff:649.7338578104973 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:saw velocity:0.7361665386706591 cutoff:588.9332309365273 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "release" example index 0 1`] = `
|
exports[`runs examples > example "release" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c3 release:0 ]",
|
"[ 0/1 → 1/4 | note:c3 release:0 ]",
|
||||||
@@ -9722,42 +9823,6 @@ exports[`runs examples > example "samples" example index 1 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "samples" example index 2 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/2 | s:noise ]",
|
|
||||||
"[ 1/2 → 3/4 | s:chimp n:0 ]",
|
|
||||||
"[ 3/4 → 1/1 | s:chimp n:0 ]",
|
|
||||||
"[ 1/1 → 3/2 | s:noise ]",
|
|
||||||
"[ 3/2 → 2/1 | s:chimp n:1 ]",
|
|
||||||
"[ 2/1 → 5/2 | s:noise ]",
|
|
||||||
"[ 5/2 → 11/4 | s:chimp n:0 ]",
|
|
||||||
"[ 11/4 → 3/1 | s:chimp n:0 ]",
|
|
||||||
"[ 3/1 → 7/2 | s:noise ]",
|
|
||||||
"[ 7/2 → 4/1 | s:chimp n:1 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "samples" example index 3 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/4 | s:chocolat ]",
|
|
||||||
"[ 1/4 → 1/2 | s:chocolat ]",
|
|
||||||
"[ 1/2 → 3/4 | s:chocolat ]",
|
|
||||||
"[ 3/4 → 1/1 | s:chocolat ]",
|
|
||||||
"[ 1/1 → 5/4 | s:chocolat ]",
|
|
||||||
"[ 5/4 → 3/2 | s:chocolat ]",
|
|
||||||
"[ 3/2 → 7/4 | s:chocolat ]",
|
|
||||||
"[ 7/4 → 2/1 | s:chocolat ]",
|
|
||||||
"[ 2/1 → 9/4 | s:chocolat ]",
|
|
||||||
"[ 9/4 → 5/2 | s:chocolat ]",
|
|
||||||
"[ 5/2 → 11/4 | s:chocolat ]",
|
|
||||||
"[ 11/4 → 3/1 | s:chocolat ]",
|
|
||||||
"[ 3/1 → 13/4 | s:chocolat ]",
|
|
||||||
"[ 13/4 → 7/2 | s:chocolat ]",
|
|
||||||
"[ 7/2 → 15/4 | s:chocolat ]",
|
|
||||||
"[ 15/4 → 4/1 | s:chocolat ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "saw" example index 0 1`] = `
|
exports[`runs examples > example "saw" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | note:c3 clip:0 ]",
|
"[ 0/1 → 1/8 | note:c3 clip:0 ]",
|
||||||
@@ -11762,6 +11827,75 @@ exports[`runs examples > example "sysexid" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "tag" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | s:square note:F1 cutoff:40 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/16 → 1/8 | s:square note:F1 cutoff:41.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/8 → 3/16 | s:square note:F1 cutoff:42.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/16 → 1/4 | s:square note:F1 cutoff:43.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/4 → 5/16 | s:square note:F1 cutoff:45 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/16 → 3/8 | s:square note:F1 cutoff:46.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/8 → 7/16 | s:square note:F1 cutoff:47.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/16 → 1/2 | s:square note:F1 cutoff:48.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/2 → 9/16 | s:square note:F1 cutoff:50 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 9/16 → 5/8 | s:square note:F1 cutoff:51.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/8 → 11/16 | s:square note:F1 cutoff:52.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 11/16 → 3/4 | s:square note:F1 cutoff:53.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/4 → 13/16 | s:square note:F1 cutoff:55 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 13/16 → 7/8 | s:square note:F1 cutoff:56.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/8 → 15/16 | s:square note:F1 cutoff:57.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 15/16 → 1/1 | s:square note:F1 cutoff:58.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/1 → 17/16 | s:saw note:F2 cutoff:60 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 17/16 → 9/8 | s:saw note:F2 cutoff:61.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 9/8 → 19/16 | s:saw note:F2 cutoff:62.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 19/16 → 5/4 | s:saw note:F2 cutoff:63.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/4 → 21/16 | s:saw note:F2 cutoff:65 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 21/16 → 11/8 | s:saw note:F2 cutoff:66.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 11/8 → 23/16 | s:saw note:F2 cutoff:67.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 23/16 → 3/2 | s:saw note:F2 cutoff:68.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/2 → 25/16 | s:saw note:F2 cutoff:70 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 25/16 → 13/8 | s:saw note:F2 cutoff:71.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 13/8 → 27/16 | s:saw note:F2 cutoff:72.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 27/16 → 7/4 | s:saw note:F2 cutoff:73.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/4 → 29/16 | s:saw note:F2 cutoff:75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 29/16 → 15/8 | s:saw note:F2 cutoff:76.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 15/8 → 31/16 | s:saw note:F2 cutoff:77.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 31/16 → 2/1 | s:saw note:F2 cutoff:78.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 2/1 → 33/16 | s:saw note:F1 cutoff:80 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 33/16 → 17/8 | s:saw note:F1 cutoff:78.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 17/8 → 35/16 | s:saw note:F1 cutoff:77.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 35/16 → 9/4 | s:saw note:F1 cutoff:76.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 9/4 → 37/16 | s:saw note:F1 cutoff:75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 37/16 → 19/8 | s:saw note:F1 cutoff:73.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 19/8 → 39/16 | s:saw note:F1 cutoff:72.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 39/16 → 5/2 | s:saw note:F1 cutoff:71.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/2 → 41/16 | s:saw note:F1 cutoff:70 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 41/16 → 21/8 | s:saw note:F1 cutoff:68.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 21/8 → 43/16 | s:saw note:F1 cutoff:67.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 43/16 → 11/4 | s:saw note:F1 cutoff:66.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 11/4 → 45/16 | s:saw note:F1 cutoff:65 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 45/16 → 23/8 | s:saw note:F1 cutoff:63.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 23/8 → 47/16 | s:saw note:F1 cutoff:62.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 47/16 → 3/1 | s:saw note:F1 cutoff:61.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/1 → 49/16 | s:square note:F1 cutoff:60 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 49/16 → 25/8 | s:square note:F1 cutoff:58.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 25/8 → 51/16 | s:square note:F1 cutoff:57.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 51/16 → 13/4 | s:square note:F1 cutoff:56.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 13/4 → 53/16 | s:square note:F1 cutoff:55 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 53/16 → 27/8 | s:square note:F1 cutoff:53.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 27/8 → 55/16 | s:square note:F1 cutoff:52.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 55/16 → 7/2 | s:square note:F1 cutoff:51.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/2 → 57/16 | s:square note:F1 cutoff:50 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 57/16 → 29/8 | s:square note:F1 cutoff:48.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 29/8 → 59/16 | s:square note:F1 cutoff:47.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 59/16 → 15/4 | s:square note:F1 cutoff:46.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 15/4 → 61/16 | s:square note:F1 cutoff:45 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 61/16 → 31/8 | s:square note:F1 cutoff:43.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 31/8 → 63/16 | s:square note:F1 cutoff:42.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 63/16 → 4/1 | s:square note:F1 cutoff:41.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "take" example index 0 1`] = `
|
exports[`runs examples > example "take" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/2 | s:bd ]",
|
"[ 0/1 → 1/2 | s:bd ]",
|
||||||
@@ -11874,6 +12008,84 @@ exports[`runs examples > example "tour" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "transient" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | s:bd transient:-1 ]",
|
||||||
|
"[ 1/1 → 2/1 | s:bd transient:-0.5 ]",
|
||||||
|
"[ 2/1 → 3/1 | s:bd transient:0 ]",
|
||||||
|
"[ 3/1 → 4/1 | s:bd transient:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "transient" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/16 → 1/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/8 → 3/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/16 → 1/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/4 → 5/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/16 → 3/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/8 → 7/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 7/16 → 1/2 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/2 → 9/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 9/16 → 5/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/8 → 11/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 11/16 → 3/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/4 → 13/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 13/16 → 7/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 7/8 → 15/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 15/16 → 1/1 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/1 → 17/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 17/16 → 9/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 9/8 → 19/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 19/16 → 5/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 5/4 → 21/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 21/16 → 11/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 11/8 → 23/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 23/16 → 3/2 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 3/2 → 25/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 25/16 → 13/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 13/8 → 27/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 27/16 → 7/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 7/4 → 29/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 29/16 → 15/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 15/8 → 31/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 31/16 → 2/1 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 2/1 → 33/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 33/16 → 17/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 17/8 → 35/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 35/16 → 9/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 9/4 → 37/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 37/16 → 19/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 19/8 → 39/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 39/16 → 5/2 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/2 → 41/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 41/16 → 21/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 21/8 → 43/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 43/16 → 11/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 11/4 → 45/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 45/16 → 23/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 23/8 → 47/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 47/16 → 3/1 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/1 → 49/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 49/16 → 25/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 25/8 → 51/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 51/16 → 13/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 13/4 → 53/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 53/16 → 27/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 27/8 → 55/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 55/16 → 7/2 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 7/2 → 57/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 57/16 → 29/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 29/8 → 59/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 59/16 → 15/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 15/4 → 61/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 61/16 → 31/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 31/8 → 63/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 63/16 → 4/1 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "transpose" example index 0 1`] = `
|
exports[`runs examples > example "transpose" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:C2 ]",
|
"[ 0/1 → 1/4 | note:C2 ]",
|
||||||
|
|||||||
Reference in New Issue
Block a user