mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-24 13:59:07 -04:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebaf7624f9 | |||
| 882bcc513f | |||
| aec33710b7 | |||
| 7953fa61e4 |
@@ -2846,6 +2846,7 @@ registerSubControls('lfo', [
|
|||||||
['skew', 'sk'],
|
['skew', 'sk'],
|
||||||
['curve'],
|
['curve'],
|
||||||
['sync', 's'],
|
['sync', 's'],
|
||||||
|
['fxi'],
|
||||||
]);
|
]);
|
||||||
registerSubControls('env', [
|
registerSubControls('env', [
|
||||||
['control', 'c'],
|
['control', 'c'],
|
||||||
@@ -2859,6 +2860,7 @@ registerSubControls('env', [
|
|||||||
['acurve', 'ac'],
|
['acurve', 'ac'],
|
||||||
['dcurve', 'dc'],
|
['dcurve', 'dc'],
|
||||||
['rcurve', 'rc'],
|
['rcurve', 'rc'],
|
||||||
|
['fxi'],
|
||||||
]);
|
]);
|
||||||
registerSubControls('bmod', [
|
registerSubControls('bmod', [
|
||||||
['bus', 'b'],
|
['bus', 'b'],
|
||||||
@@ -2867,6 +2869,7 @@ registerSubControls('bmod', [
|
|||||||
['depth', 'dep', 'dr'],
|
['depth', 'dep', 'dr'],
|
||||||
['depthabs', 'da'],
|
['depthabs', 'da'],
|
||||||
['dc'],
|
['dc'],
|
||||||
|
['fxi'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Pattern.prototype.modulate = function (type, config, id) {
|
Pattern.prototype.modulate = function (type, config, id) {
|
||||||
@@ -2932,6 +2935,7 @@ Pattern.prototype.modulate = function (type, config, id) {
|
|||||||
* @param {number | Pattern} [config.skew] Skew amount. Aliases: sk
|
* @param {number | Pattern} [config.skew] Skew amount. Aliases: sk
|
||||||
* @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: c
|
* @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: c
|
||||||
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
|
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
|
||||||
|
* @param {number | Pattern} [config.fxi] FX index to target
|
||||||
* @param {string | Pattern} id ID to use for this modulator
|
* @param {string | Pattern} id ID to use for this modulator
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
*
|
*
|
||||||
@@ -2985,6 +2989,7 @@ export const lfo = (config) => pure({}).lfo(config);
|
|||||||
* @param {number | Pattern} [config.acurve] Snappiness of attack curve (-1 = relaxed, 1 = snappy). Aliases: ac
|
* @param {number | Pattern} [config.acurve] Snappiness of attack curve (-1 = relaxed, 1 = snappy). Aliases: ac
|
||||||
* @param {number | Pattern} [config.dcurve] Snappiness of decay curve (-1 = relaxed, 1 = snappy). Aliases: dc
|
* @param {number | Pattern} [config.dcurve] Snappiness of decay curve (-1 = relaxed, 1 = snappy). Aliases: dc
|
||||||
* @param {number | Pattern} [config.rcurve] Snappiness of release curve (-1 = relaxed, 1 = snappy). Aliases: rc
|
* @param {number | Pattern} [config.rcurve] Snappiness of release curve (-1 = relaxed, 1 = snappy). Aliases: rc
|
||||||
|
* @param {number | Pattern} [config.fxi] FX index to target
|
||||||
* @param {string | Pattern} id ID to use for this modulator
|
* @param {string | Pattern} id ID to use for this modulator
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
*
|
*
|
||||||
@@ -3039,6 +3044,7 @@ export const env = (config) => pure({}).env(config);
|
|||||||
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
||||||
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
||||||
* @param {number | Pattern} [config.dc] DC offset prior to application
|
* @param {number | Pattern} [config.dc] DC offset prior to application
|
||||||
|
* @param {number | Pattern} [config.fxi] FX index to target
|
||||||
* @param {string | Pattern} id ID to use for this modulator
|
* @param {string | Pattern} id ID to use for this modulator
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
*
|
*
|
||||||
@@ -3065,3 +3071,5 @@ export const bmod = (config) => pure({}).bmod(config);
|
|||||||
* s("hh*16").bank("tr909").transient("<-1:1 1:-1>")
|
* s("hh*16").bank("tr909").transient("<-1:1 1:-1>")
|
||||||
*/
|
*/
|
||||||
export const { transient } = registerControl(['transient', 'transsustain']);
|
export const { transient } = registerControl(['transient', 'transsustain']);
|
||||||
|
|
||||||
|
export const { FXrelease, FXrel, FXr, fxr } = registerControl('FXrelease', 'FXrel', 'FXr', 'fxr');
|
||||||
|
|||||||
@@ -3721,3 +3721,35 @@ Pattern.prototype.phases = function (list) {
|
|||||||
export const phases = (list) => {
|
export const phases = (list) => {
|
||||||
return _ensureListPattern(list).as('phases');
|
return _ensureListPattern(list).as('phases');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes an FX chain. Can be called by chaining .FX(<fx1>).FX(<fx2>)..
|
||||||
|
* calls and/or in a single .FX(<fx1>, <fx2>, ..) call. The <fx1>, .. are _patterns_ which
|
||||||
|
* establish the controls of the given effect. See examples.
|
||||||
|
* @name FX
|
||||||
|
* @memberof Pattern
|
||||||
|
* @returns Pattern
|
||||||
|
* @example
|
||||||
|
* $: s("[sbd <hh [bd | lt | oh]>]*4").dec(.4)
|
||||||
|
* .FX(
|
||||||
|
* phaser(0.5).gain(2),
|
||||||
|
* bpf(800),
|
||||||
|
* distort(1.3),
|
||||||
|
* room(0.2),
|
||||||
|
* delay(0.5).gain(1.25),
|
||||||
|
* distort(0.3),
|
||||||
|
* ).fxr(1.7) // sets release time of effects (like delay)
|
||||||
|
* @example
|
||||||
|
* $: s("saw").fm(0.5)
|
||||||
|
* .delay(0.3) // outer effects are applied *last*
|
||||||
|
* .FX(coarse(4)) // first coarse
|
||||||
|
* .FX(lpf(500).lpe(4).lpa(1).lpd(2)) // then lpf
|
||||||
|
* .FX(distort(1)) // then distort
|
||||||
|
*/
|
||||||
|
Pattern.prototype.FX = function (...effects) {
|
||||||
|
effects = effects.map(reify);
|
||||||
|
return this.withValue((v) => (vEff) => {
|
||||||
|
const currFX = v.FX ?? [];
|
||||||
|
return { ...v, FX: currFX.concat(vEff) };
|
||||||
|
}).appLeft(parray(effects));
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,19 +5,18 @@ if (typeof DelayNode !== 'undefined') {
|
|||||||
wet = Math.abs(wet);
|
wet = Math.abs(wet);
|
||||||
this.delayTime.value = time;
|
this.delayTime.value = time;
|
||||||
|
|
||||||
const feedbackGain = ac.createGain();
|
this.feedbackGain = ac.createGain();
|
||||||
feedbackGain.gain.value = Math.min(Math.abs(feedback), 0.995);
|
this.feedbackGain.gain.value = Math.min(Math.abs(feedback), 0.995);
|
||||||
this.feedback = feedbackGain.gain;
|
this.feedback = this.feedbackGain.gain;
|
||||||
|
|
||||||
const delayGain = ac.createGain();
|
this.delayGain = ac.createGain();
|
||||||
delayGain.gain.value = wet;
|
this.delayGain.gain.value = wet;
|
||||||
this.delayGain = delayGain;
|
|
||||||
|
|
||||||
this.connect(feedbackGain);
|
this.connect(this.feedbackGain);
|
||||||
this.connect(delayGain);
|
this.connect(this.delayGain);
|
||||||
feedbackGain.connect(this);
|
this.feedbackGain.connect(this);
|
||||||
|
|
||||||
this.connect = (target) => delayGain.connect(target);
|
this.connect = (target) => this.delayGain.connect(target);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
start(t) {
|
start(t) {
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ const getTargetParamsForControl = (control, nodes, subControl) => {
|
|||||||
const lookupKey = subControl ? `${control}_${subControl}` : control;
|
const lookupKey = subControl ? `${control}_${subControl}` : control;
|
||||||
const targetInfo = getControlData(lookupKey) ?? getControlData(control);
|
const targetInfo = getControlData(lookupKey) ?? getControlData(control);
|
||||||
if (!targetInfo) {
|
if (!targetInfo) {
|
||||||
errorLogger(new Error(`Could not find control data for target '${control}'`), 'superdough');
|
errorLogger(
|
||||||
|
new Error(`Could not find control data for target '${control}'. It may not be modulatable.`),
|
||||||
|
'superdough',
|
||||||
|
);
|
||||||
return { targetParams: [], paramName: control };
|
return { targetParams: [], paramName: control };
|
||||||
}
|
}
|
||||||
const paramName = targetInfo.param;
|
const paramName = targetInfo.param;
|
||||||
@@ -85,8 +88,19 @@ const getTargetParamsForControl = (control, nodes, subControl) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const connectLFO = (id, params, nodeTracker) => {
|
export const connectLFO = (id, params, nodeTracker) => {
|
||||||
const { rate = 1, sync, cps, cycle, control = 'lfo', subControl, depth = 1, depthabs, ...filteredParams } = params;
|
const {
|
||||||
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker, subControl);
|
rate = 1,
|
||||||
|
sync,
|
||||||
|
cps,
|
||||||
|
cycle,
|
||||||
|
control = 'lfo',
|
||||||
|
subControl,
|
||||||
|
fxi = 'main',
|
||||||
|
depth = 1,
|
||||||
|
depthabs,
|
||||||
|
...filteredParams
|
||||||
|
} = params;
|
||||||
|
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl);
|
||||||
if (!targetParams.length) return;
|
if (!targetParams.length) return;
|
||||||
let currentValue = targetParams[0].value;
|
let currentValue = targetParams[0].value;
|
||||||
currentValue = currentValue === 0 ? 1 : currentValue;
|
currentValue = currentValue === 0 ? 1 : currentValue;
|
||||||
@@ -101,14 +115,14 @@ export const connectLFO = (id, params, nodeTracker) => {
|
|||||||
max,
|
max,
|
||||||
};
|
};
|
||||||
const lfoNode = getLfo(getAudioContext(), modParams);
|
const lfoNode = getLfo(getAudioContext(), modParams);
|
||||||
nodeTracker[`lfo_${id}`] = [lfoNode];
|
nodeTracker.main[`lfo_${id}`] = [lfoNode];
|
||||||
targetParams.forEach((t) => lfoNode.connect(t));
|
targetParams.forEach((t) => lfoNode.connect(t));
|
||||||
return lfoNode;
|
return lfoNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const connectEnvelope = (id, params, nodeTracker) => {
|
export const connectEnvelope = (id, params, nodeTracker) => {
|
||||||
const { control, subControl, acurve, dcurve, rcurve, depth = 1, depthabs, ...filteredParams } = params;
|
const { control, subControl, acurve, dcurve, rcurve, depth = 1, depthabs, fxi = 'main', ...filteredParams } = params;
|
||||||
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker, subControl);
|
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl);
|
||||||
if (!targetParams.length) return;
|
if (!targetParams.length) return;
|
||||||
let currentValue = targetParams[0].value;
|
let currentValue = targetParams[0].value;
|
||||||
currentValue = currentValue === 0 ? 1 : currentValue;
|
currentValue = currentValue === 0 ? 1 : currentValue;
|
||||||
@@ -123,15 +137,15 @@ export const connectEnvelope = (id, params, nodeTracker) => {
|
|||||||
decayCurve: dcurve,
|
decayCurve: dcurve,
|
||||||
releaseCurve: rcurve,
|
releaseCurve: rcurve,
|
||||||
});
|
});
|
||||||
nodeTracker[`env_${id}`] = [envNode];
|
nodeTracker.main[`env_${id}`] = [envNode];
|
||||||
targetParams.forEach((t) => envNode.connect(t));
|
targetParams.forEach((t) => envNode.connect(t));
|
||||||
return envNode;
|
return envNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const connectBusModulator = (params, nodeTracker, controller) => {
|
export const connectBusModulator = (params, nodeTracker, controller) => {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const { control, subControl, depth = 1, depthabs } = params;
|
const { control, subControl, depth = 1, depthabs, fxi = 'main' } = params;
|
||||||
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker, subControl);
|
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl);
|
||||||
if (!targetParams.length) return { toCleanup: [] };
|
if (!targetParams.length) return { toCleanup: [] };
|
||||||
const signal = controller.getBus(params.bus);
|
const signal = controller.getBus(params.bus);
|
||||||
const dc = new ConstantSourceNode(ac, { offset: params.dc ?? 0 });
|
const dc = new ConstantSourceNode(ac, { offset: params.dc ?? 0 });
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { releaseAudioNode } from './helpers.mjs';
|
||||||
|
|
||||||
var reverbGen = {};
|
var reverbGen = {};
|
||||||
|
|
||||||
/** Generates a reverb impulse response.
|
/** Generates a reverb impulse response.
|
||||||
@@ -104,8 +106,8 @@ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt,
|
|||||||
player.start();
|
player.start();
|
||||||
context.oncomplete = function (event) {
|
context.oncomplete = function (event) {
|
||||||
callback(event.renderedBuffer);
|
callback(event.renderedBuffer);
|
||||||
filter.disconnect();
|
releaseAudioNode(filter);
|
||||||
player.disconnect();
|
releaseAudioNode(player);
|
||||||
};
|
};
|
||||||
context.startRendering();
|
context.startRendering();
|
||||||
|
|
||||||
|
|||||||
+420
-330
@@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
import './feedbackdelay.mjs';
|
import './feedbackdelay.mjs';
|
||||||
import './reverb.mjs';
|
import './reverb.mjs';
|
||||||
import './vowel.mjs';
|
import './vowel.mjs';
|
||||||
import { nanFallback, _mod, cycleToSeconds, pickAndRename } from './util.mjs';
|
import { clamp, nanFallback, _mod, cycleToSeconds, pickAndRename } from './util.mjs';
|
||||||
import workletsUrl from './worklets.mjs?audioworklet';
|
import workletsUrl from './worklets.mjs?audioworklet';
|
||||||
import {
|
import {
|
||||||
createFilter,
|
createFilter,
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
getLfo,
|
getLfo,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
releaseAudioNode,
|
releaseAudioNode,
|
||||||
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { map } from 'nanostores';
|
import { map } from 'nanostores';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
@@ -193,6 +194,9 @@ let defaultDefaultValues = {
|
|||||||
i: 1,
|
i: 1,
|
||||||
velocity: 1,
|
velocity: 1,
|
||||||
fft: 8,
|
fft: 8,
|
||||||
|
tremolodepth: 1,
|
||||||
|
tremolophase: 0,
|
||||||
|
release: 0.01,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultDefaultDefaultValues = Object.freeze({ ...defaultDefaultValues });
|
const defaultDefaultDefaultValues = Object.freeze({ ...defaultDefaultValues });
|
||||||
@@ -402,8 +406,37 @@ function mapChannelNumbers(channels) {
|
|||||||
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Chain {
|
||||||
|
constructor(head) {
|
||||||
|
this.audioNodes = [head];
|
||||||
|
this.tails = [head];
|
||||||
|
}
|
||||||
|
connect(...nodes) {
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
this.tails.forEach((tail) => {
|
||||||
|
tail.connect(node);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.tails = nodes;
|
||||||
|
this.audioNodes.push(...nodes);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
connectOne(idx, node) {
|
||||||
|
this.tails[idx].connect(node);
|
||||||
|
this.tails[idx] = node;
|
||||||
|
this.audioNodes.push(node);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
releaseNodes() {
|
||||||
|
this.audioNodes.forEach((n) => releaseAudioNode(n));
|
||||||
|
this.audioNodes = [];
|
||||||
|
this.tails = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
|
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
|
||||||
let nodes = {};
|
// mapping from main FX and numbered FX chains to nodes
|
||||||
|
const nodes = { main: {} };
|
||||||
// new: t is always expected to be the absolute target onset time
|
// new: t is always expected to be the absolute target onset time
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const audioController = getSuperdoughAudioController();
|
const audioController = getSuperdoughAudioController();
|
||||||
@@ -432,44 +465,17 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
}
|
}
|
||||||
// destructure
|
// destructure
|
||||||
let {
|
let {
|
||||||
tremolo,
|
|
||||||
tremolosync,
|
|
||||||
tremolodepth = 1,
|
|
||||||
tremoloskew,
|
|
||||||
tremolophase = 0,
|
|
||||||
tremoloshape,
|
|
||||||
s = getDefaultValue('s'),
|
s = getDefaultValue('s'),
|
||||||
bank,
|
bank,
|
||||||
source,
|
source,
|
||||||
gain = getDefaultValue('gain'),
|
|
||||||
postgain = getDefaultValue('postgain'),
|
postgain = getDefaultValue('postgain'),
|
||||||
density = getDefaultValue('density'),
|
|
||||||
duckorbit,
|
duckorbit,
|
||||||
duckonset,
|
duckonset,
|
||||||
duckattack,
|
duckattack,
|
||||||
duckdepth,
|
duckdepth,
|
||||||
djf,
|
djf,
|
||||||
// filters
|
release = getDefaultValue('release'),
|
||||||
fanchor = getDefaultValue('fanchor'),
|
|
||||||
release = 0,
|
|
||||||
|
|
||||||
//phaser
|
|
||||||
phaserrate,
|
|
||||||
phaserdepth = getDefaultValue('phaserdepth'),
|
|
||||||
phasersweep,
|
|
||||||
phasercenter,
|
|
||||||
//
|
|
||||||
coarse,
|
|
||||||
|
|
||||||
crush,
|
|
||||||
dry,
|
dry,
|
||||||
shape,
|
|
||||||
shapevol = getDefaultValue('shapevol'),
|
|
||||||
distort,
|
|
||||||
distortvol = getDefaultValue('distortvol'),
|
|
||||||
distorttype = getDefaultValue('distorttype'),
|
|
||||||
pan,
|
|
||||||
vowel,
|
|
||||||
delay = getDefaultValue('delay'),
|
delay = getDefaultValue('delay'),
|
||||||
delayfeedback = getDefaultValue('delayfeedback'),
|
delayfeedback = getDefaultValue('delayfeedback'),
|
||||||
delaysync = getDefaultValue('delaysync'),
|
delaysync = getDefaultValue('delaysync'),
|
||||||
@@ -486,16 +492,10 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
irspeed,
|
irspeed,
|
||||||
irbegin,
|
irbegin,
|
||||||
i = getDefaultValue('i'),
|
i = getDefaultValue('i'),
|
||||||
velocity = getDefaultValue('velocity'),
|
|
||||||
analyze, // analyser wet
|
analyze, // analyser wet
|
||||||
fft = getDefaultValue('fft'), // fftSize 0 - 10
|
fft = getDefaultValue('fft'), // fftSize 0 - 10
|
||||||
compressor: compressorThreshold,
|
FX = [],
|
||||||
compressorRatio,
|
FXrelease,
|
||||||
compressorKnee,
|
|
||||||
compressorAttack,
|
|
||||||
compressorRelease,
|
|
||||||
transient,
|
|
||||||
transsustain,
|
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
@@ -510,18 +510,13 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
audioController.duck(duckorbit, t, duckonset, duckattack, duckdepth);
|
audioController.duck(duckorbit, t, duckonset, duckattack, duckdepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
gain = applyGainCurve(nanFallback(gain, 1));
|
|
||||||
postgain = applyGainCurve(postgain);
|
postgain = applyGainCurve(postgain);
|
||||||
shapevol = applyGainCurve(shapevol);
|
|
||||||
distortvol = applyGainCurve(distortvol);
|
|
||||||
delay = applyGainCurve(delay);
|
delay = applyGainCurve(delay);
|
||||||
velocity = applyGainCurve(velocity);
|
|
||||||
tremolodepth = applyGainCurve(tremolodepth);
|
|
||||||
busgain = applyGainCurve(busgain);
|
busgain = applyGainCurve(busgain);
|
||||||
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
|
|
||||||
|
|
||||||
const end = t + hapDuration;
|
const end = t + hapDuration;
|
||||||
const endWithRelease = end + release;
|
const fullRelease = Math.max(release, FXrelease ?? 0);
|
||||||
|
const endWithRelease = end + fullRelease;
|
||||||
const chainID = Math.round(Math.random() * 1000000);
|
const chainID = Math.round(Math.random() * 1000000);
|
||||||
|
|
||||||
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
|
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
|
||||||
@@ -535,8 +530,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
activeSoundSources.delete(chainID);
|
activeSoundSources.delete(chainID);
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioNodes = [];
|
|
||||||
|
|
||||||
if (['-', '~', '_'].includes(s)) {
|
if (['-', '~', '_'].includes(s)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -549,19 +542,27 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
let sourceNode;
|
let sourceNode;
|
||||||
if (source) {
|
if (source) {
|
||||||
sourceNode = source(t, value, hapDuration, cps);
|
sourceNode = source(t, value, hapDuration, cps);
|
||||||
nodes['source'] = [sourceNode];
|
nodes.main['source'] = [sourceNode];
|
||||||
} else if (getSound(s)) {
|
} else if (getSound(s)) {
|
||||||
const { onTrigger } = getSound(s);
|
const { onTrigger } = getSound(s);
|
||||||
const onEnded = () => {
|
// We have to use onEnded because some sources (e.g. `sampler`) have
|
||||||
audioNodes.forEach((n) => releaseAudioNode(n));
|
// an internal duration which is longer than `value.duration`
|
||||||
activeSoundSources.delete(chainID);
|
const onEnded = () =>
|
||||||
};
|
webAudioTimeout(
|
||||||
|
ac,
|
||||||
|
() => {
|
||||||
|
chain.releaseNodes();
|
||||||
|
activeSoundSources.delete(chainID);
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
endWithRelease,
|
||||||
|
);
|
||||||
const soundHandle = await onTrigger(t, value, onEnded, cps);
|
const soundHandle = await onTrigger(t, value, onEnded, cps);
|
||||||
|
|
||||||
if (soundHandle) {
|
if (soundHandle) {
|
||||||
sourceNode = soundHandle.node;
|
sourceNode = soundHandle.node;
|
||||||
activeSoundSources.set(chainID, new WeakRef(soundHandle)); // allow GC
|
activeSoundSources.set(chainID, new WeakRef(soundHandle)); // allow GC
|
||||||
nodes = { ...nodes, ...soundHandle.nodes };
|
nodes.main = { ...nodes.main, ...soundHandle.nodes };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`sound ${s} not found! Is it loaded?`);
|
throw new Error(`sound ${s} not found! Is it loaded?`);
|
||||||
@@ -576,253 +577,340 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
logger('[webaudio] skip hap: still loading', ac.currentTime - t);
|
logger('[webaudio] skip hap: still loading', ac.currentTime - t);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const chain = []; // audio nodes that will be connected to each other sequentially
|
|
||||||
chain.push(sourceNode);
|
|
||||||
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
|
||||||
|
|
||||||
if (transient !== undefined) {
|
const chain = new Chain(sourceNode); // connection manager which tracks audio nodes for releasing
|
||||||
const transProcessor = getWorklet(
|
FX = [...FX, value]; // run through the FX chain and then run through all FX outside of it as well
|
||||||
ac,
|
for (let [idx, fx] of Object.entries(FX)) {
|
||||||
'transient-processor',
|
const key = idx == FX.length - 1 ? 'main' : idx;
|
||||||
{},
|
nodes[key] ??= {};
|
||||||
{
|
const fxNodes = nodes[key];
|
||||||
processorOptions: {
|
let {
|
||||||
attack: transient,
|
gain = getDefaultValue('gain'),
|
||||||
sustain: transsustain,
|
velocity = getDefaultValue('velocity'),
|
||||||
begin: t,
|
shapevol = getDefaultValue('shapevol'),
|
||||||
end: endWithRelease,
|
distorttype = getDefaultValue('distorttype'),
|
||||||
|
distortvol = getDefaultValue('distortvol'),
|
||||||
|
tremolodepth = getDefaultValue('tremolodepth'),
|
||||||
|
phaserdepth = getDefaultValue('phaserdepth'),
|
||||||
|
delay = getDefaultValue('delay'),
|
||||||
|
delayfeedback = getDefaultValue('delayfeedback'),
|
||||||
|
delaysync = getDefaultValue('delaysync'),
|
||||||
|
delaytime,
|
||||||
|
stretch = getDefaultValue('stretch'),
|
||||||
|
i = getDefaultValue('i'),
|
||||||
|
} = fx;
|
||||||
|
gain = applyGainCurve(nanFallback(gain, 1));
|
||||||
|
shapevol = applyGainCurve(shapevol);
|
||||||
|
distortvol = applyGainCurve(distortvol);
|
||||||
|
velocity = applyGainCurve(velocity);
|
||||||
|
tremolodepth = applyGainCurve(tremolodepth);
|
||||||
|
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
|
||||||
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
|
|
||||||
|
stretch !== undefined && chain.connect(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
||||||
|
|
||||||
|
if (fx.transient !== undefined) {
|
||||||
|
const transProcessor = getWorklet(
|
||||||
|
ac,
|
||||||
|
'transient-processor',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
processorOptions: {
|
||||||
|
attack: fx.transient,
|
||||||
|
sustain: fx.transsustain,
|
||||||
|
begin: t,
|
||||||
|
end: endWithRelease,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
);
|
||||||
);
|
chain.connect(transProcessor);
|
||||||
chain.push(transProcessor);
|
fxNodes['transient'] = transProcessor;
|
||||||
nodes['transient'] = transProcessor;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// gain stage
|
// gain stage
|
||||||
const initialGain = gainNode(gain);
|
const initialGain = gainNode(gain);
|
||||||
nodes['gain'] = [initialGain];
|
fxNodes['gain'] = [initialGain];
|
||||||
chain.push(initialGain);
|
chain.connect(initialGain);
|
||||||
|
|
||||||
// filter
|
// filter
|
||||||
const ftype = getFilterType(value.ftype);
|
const ftype = getFilterType(value.ftype);
|
||||||
|
|
||||||
const filt = (params) => createFilter(ac, t, end, params, cps, cycle);
|
const filt = (params) => createFilter(ac, t, end, params, cps, cycle);
|
||||||
if (value.cutoff !== undefined) {
|
if (fx.cutoff !== undefined) {
|
||||||
const lpMap = {
|
const lpMap = {
|
||||||
frequency: 'cutoff',
|
frequency: 'cutoff',
|
||||||
q: 'resonance',
|
q: 'resonance',
|
||||||
attack: 'lpattack',
|
attack: 'lpattack',
|
||||||
decay: 'lpdecay',
|
decay: 'lpdecay',
|
||||||
sustain: 'lpsustain',
|
sustain: 'lpsustain',
|
||||||
release: 'lprelease',
|
release: 'lprelease',
|
||||||
env: 'lpenv',
|
env: 'lpenv',
|
||||||
anchor: 'fanchor',
|
anchor: 'fanchor',
|
||||||
model: 'ftype',
|
model: 'ftype',
|
||||||
drive: 'drive',
|
drive: 'drive',
|
||||||
rate: 'lprate',
|
rate: 'lprate',
|
||||||
sync: 'lpsync',
|
sync: 'lpsync',
|
||||||
depth: 'lpdepth',
|
depth: 'lpdepth',
|
||||||
depthfrequency: 'lpdepthfrequency',
|
depthfrequency: 'lpdepthfrequency',
|
||||||
shape: 'lpshape',
|
shape: 'lpshape',
|
||||||
dcoffset: 'lpdc',
|
dcoffset: 'lpdc',
|
||||||
skew: 'lpskew',
|
skew: 'lpskew',
|
||||||
};
|
};
|
||||||
const lpParams = pickAndRename(value, lpMap);
|
const lpParams = pickAndRename(fx, lpMap);
|
||||||
lpParams.type = 'lowpass';
|
lpParams.type = 'lowpass';
|
||||||
const { filter: lpf1, lfo: lfo1 } = filt(lpParams);
|
const { filter: lpf1, lfo: lfo1 } = filt(lpParams);
|
||||||
nodes['lpf'] = [lpf1];
|
fxNodes['lpf'] = [lpf1];
|
||||||
nodes['lpf_lfo'] = [lfo1];
|
fxNodes['lpf_lfo'] = [lfo1];
|
||||||
chain.push(lpf1);
|
chain.connect(lpf1);
|
||||||
lfo1 && audioNodes.push(lfo1);
|
lfo1 && chain.audioNodes.push(lfo1);
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
const { filter: lpf2, lfo: lfo2 } = filt(lpParams);
|
const { filter: lpf2, lfo: lfo2 } = filt(lpParams);
|
||||||
nodes['lpf'].push(lpf2);
|
fxNodes['lpf'].push(lpf2);
|
||||||
nodes['lpf_lfo'].push(lfo2);
|
fxNodes['lpf_lfo'].push(lfo2);
|
||||||
chain.push(lpf2);
|
chain.connect(lpf2);
|
||||||
lfo2 && audioNodes.push(lfo2);
|
lfo2 && chain.audioNodes.push(lfo2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fx.hcutoff !== undefined) {
|
||||||
|
const hpMap = {
|
||||||
|
frequency: 'hcutoff',
|
||||||
|
q: 'hresonance',
|
||||||
|
attack: 'hpattack',
|
||||||
|
decay: 'hpdecay',
|
||||||
|
sustain: 'hpsustain',
|
||||||
|
release: 'hprelease',
|
||||||
|
env: 'hpenv',
|
||||||
|
anchor: 'fanchor',
|
||||||
|
model: 'ftype',
|
||||||
|
drive: 'drive',
|
||||||
|
rate: 'hprate',
|
||||||
|
sync: 'hpsync',
|
||||||
|
depth: 'hpdepth',
|
||||||
|
depthfrequency: 'hpdepthfrequency',
|
||||||
|
shape: 'hpshape',
|
||||||
|
dcoffset: 'hpdc',
|
||||||
|
skew: 'hpskew',
|
||||||
|
};
|
||||||
|
const hpParams = pickAndRename(fx, hpMap);
|
||||||
|
hpParams.type = 'highpass';
|
||||||
|
const { filter: hpf1, lfo: lfo1 } = filt(hpParams);
|
||||||
|
fxNodes['hpf'] = [hpf1];
|
||||||
|
fxNodes['hpf_lfo'] = [lfo1];
|
||||||
|
lfo1 && chain.audioNodes.push(lfo1);
|
||||||
|
chain.connect(hpf1);
|
||||||
|
if (ftype === '24db') {
|
||||||
|
const { filter: hpf2, lfo: lfo2 } = filt(hpParams);
|
||||||
|
fxNodes['hpf'].push(hpf2);
|
||||||
|
fxNodes['hpf_lfo'].push(lfo2);
|
||||||
|
chain.connect(hpf2);
|
||||||
|
lfo2 && chain.audioNodes.push(lfo2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fx.bandf !== undefined) {
|
||||||
|
const bpMap = {
|
||||||
|
frequency: 'bandf',
|
||||||
|
q: 'bandq',
|
||||||
|
attack: 'bpattack',
|
||||||
|
decay: 'bpdecay',
|
||||||
|
sustain: 'bpsustain',
|
||||||
|
release: 'bprelease',
|
||||||
|
env: 'bpenv',
|
||||||
|
anchor: 'fanchor',
|
||||||
|
model: 'ftype',
|
||||||
|
drive: 'drive',
|
||||||
|
rate: 'bprate',
|
||||||
|
sync: 'bpsync',
|
||||||
|
depth: 'bpdepth',
|
||||||
|
depthfrequency: 'bpdepthfrequency',
|
||||||
|
shape: 'bpshape',
|
||||||
|
dcoffset: 'bpdc',
|
||||||
|
skew: 'bpskew',
|
||||||
|
};
|
||||||
|
const bpParams = pickAndRename(fx, bpMap);
|
||||||
|
bpParams.type = 'bandpass';
|
||||||
|
const { filter: bpf1, lfo: lfo1 } = filt(bpParams);
|
||||||
|
fxNodes['bpf'] = [bpf1];
|
||||||
|
fxNodes['bpf_lfo'] = [lfo1];
|
||||||
|
chain.connect(bpf1);
|
||||||
|
lfo1 && chain.audioNodes.push(lfo1);
|
||||||
|
if (ftype === '24db') {
|
||||||
|
const { filter: bpf2, lfo: lfo2 } = filt(bpParams);
|
||||||
|
fxNodes['bpf'].push(bpf2);
|
||||||
|
fxNodes['bpf_lfo'].push(lfo2);
|
||||||
|
chain.connect(bpf2);
|
||||||
|
lfo2 && chain.audioNodes.push(lfo2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fx.vowel !== undefined) {
|
||||||
|
const vowelNode = ac.createVowelFilter(fx.vowel);
|
||||||
|
fxNodes['vowel'] = vowelNode.filters;
|
||||||
|
chain.connect(vowelNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// effects
|
||||||
|
if (fx.coarse !== undefined) {
|
||||||
|
const coarseNode = getWorklet(ac, 'coarse-processor', { coarse: fx.coarse });
|
||||||
|
fxNodes['coarse'] = [coarseNode];
|
||||||
|
chain.connect(coarseNode);
|
||||||
|
}
|
||||||
|
if (fx.crush !== undefined) {
|
||||||
|
const crushNode = getWorklet(ac, 'crush-processor', { crush: fx.crush });
|
||||||
|
fxNodes['crush'] = [crushNode];
|
||||||
|
chain.connect(crushNode);
|
||||||
|
}
|
||||||
|
if (fx.shape !== undefined) {
|
||||||
|
const shapeNode = getWorklet(ac, 'shape-processor', { shape: fx.shape, postgain: shapevol });
|
||||||
|
fxNodes['shape'] = [shapeNode];
|
||||||
|
chain.connect(shapeNode);
|
||||||
|
}
|
||||||
|
if (fx.distort !== undefined) {
|
||||||
|
const distortNode = getDistortion(fx.distort, distortvol, distorttype);
|
||||||
|
fxNodes['distort'] = [distortNode];
|
||||||
|
chain.connect(distortNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tremolo = fx.tremolo;
|
||||||
|
if (fx.tremolosync != null) {
|
||||||
|
tremolo = cps * fx.tremolosync;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tremolo !== undefined) {
|
||||||
|
// Allow clipping of modulator for more dynamic possiblities, and to prevent speaker overload
|
||||||
|
// EX: a triangle waveform will clip like this /-\ when the depth is above 1
|
||||||
|
const gain = Math.max(1 - tremolodepth, 0);
|
||||||
|
const amGain = new GainNode(ac, { gain });
|
||||||
|
|
||||||
|
const time = cycle / cps;
|
||||||
|
const lfo = getLfo(ac, {
|
||||||
|
skew: fx.tremoloskew ?? (fx.tremoloshape != null ? 0.5 : 1),
|
||||||
|
frequency: tremolo,
|
||||||
|
depth: tremolodepth,
|
||||||
|
time,
|
||||||
|
dcoffset: 0,
|
||||||
|
shape: fx.tremoloshape,
|
||||||
|
phaseoffset: fx.tremolophase,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
curve: 1.5,
|
||||||
|
begin: t,
|
||||||
|
end: endWithRelease,
|
||||||
|
});
|
||||||
|
fxNodes['tremolo'] = [lfo];
|
||||||
|
fxNodes['tremolo_gain'] = [amGain];
|
||||||
|
lfo.connect(amGain.gain);
|
||||||
|
chain.audioNodes.push(lfo);
|
||||||
|
chain.connect(amGain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fx.compressor !== undefined) {
|
||||||
|
const compressorNode = getCompressor(
|
||||||
|
ac,
|
||||||
|
fx.compressor,
|
||||||
|
fx.compressorRatio,
|
||||||
|
fx.compressorKnee,
|
||||||
|
fx.compressorAttack,
|
||||||
|
fx.compressorRelease,
|
||||||
|
);
|
||||||
|
fxNodes['compressor'] = [compressorNode];
|
||||||
|
chain.connect(compressorNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// panning
|
||||||
|
if (fx.pan !== undefined) {
|
||||||
|
const panner = ac.createStereoPanner();
|
||||||
|
panner.pan.value = 2 * fx.pan - 1;
|
||||||
|
chain.connect(panner);
|
||||||
|
}
|
||||||
|
// phaser
|
||||||
|
if (fx.phaserrate !== undefined && phaserdepth > 0) {
|
||||||
|
const { filterChain, lfo } = getPhaser(
|
||||||
|
t,
|
||||||
|
endWithRelease,
|
||||||
|
fx.phaserrate,
|
||||||
|
phaserdepth,
|
||||||
|
fx.phasercenter,
|
||||||
|
fx.phasersweep,
|
||||||
|
);
|
||||||
|
fxNodes['phaser'] = [...filterChain];
|
||||||
|
fxNodes['phaser_lfo'] = [lfo];
|
||||||
|
filterChain.forEach((f) => chain.connect(f));
|
||||||
|
chain.audioNodes.push(lfo);
|
||||||
|
}
|
||||||
|
// delay
|
||||||
|
if (key !== 'main' && delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||||
|
const dry = gainNode(1);
|
||||||
|
delayfeedback = clamp(delayfeedback, 0, 0.98);
|
||||||
|
const delayNode = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||||
|
const wetDelay = gainNode(delay);
|
||||||
|
const dryDelay = gainNode(fx.dry ?? 1);
|
||||||
|
const sum = new GainNode(ac, { gain: 1, channelCount: 2, channelCountMode: 'explicit' });
|
||||||
|
chain
|
||||||
|
.connect(dry)
|
||||||
|
.connect(dryDelay, delayNode)
|
||||||
|
.connectOne(1, wetDelay) // connect delayNode -> wetDelay
|
||||||
|
.connect(sum);
|
||||||
|
chain.audioNodes.push(delayNode.feedbackGain, delayNode.delayGain);
|
||||||
|
fxNodes['delay'] = [delayNode];
|
||||||
|
fxNodes['delay_mix'] = [wetDelay];
|
||||||
|
}
|
||||||
|
// reverb
|
||||||
|
if (key !== 'main' && fx.room > 0) {
|
||||||
|
let roomIR;
|
||||||
|
if (fx.ir !== undefined) {
|
||||||
|
let url;
|
||||||
|
let sample = getSound(fx.ir);
|
||||||
|
if (Array.isArray(sample)) {
|
||||||
|
url = sample.data.samples[fx.i % sample.data.samples.length];
|
||||||
|
} else if (typeof sample === 'object') {
|
||||||
|
url = Object.values(sample.data.samples).flat()[i % Object.values(sample.data.samples).length];
|
||||||
|
}
|
||||||
|
roomIR = await loadBuffer(url, ac, fx.ir, 0);
|
||||||
|
}
|
||||||
|
const dry = gainNode(1);
|
||||||
|
const reverbNode = ac.createReverb(
|
||||||
|
fx.roomsize,
|
||||||
|
fx.roomfade,
|
||||||
|
fx.roomlp,
|
||||||
|
fx.roomdim,
|
||||||
|
roomIR,
|
||||||
|
fx.irspeed,
|
||||||
|
fx.irbegin,
|
||||||
|
);
|
||||||
|
const wetReverb = gainNode(fx.room);
|
||||||
|
const dryReverb = gainNode(fx.dry ?? 1);
|
||||||
|
const sum = new GainNode(ac, { gain: 1, channelCount: 2, channelCountMode: 'explicit' });
|
||||||
|
chain
|
||||||
|
.connect(dry)
|
||||||
|
.connect(dryReverb, reverbNode)
|
||||||
|
.connectOne(1, wetReverb) // connect reverbNode -> wetReverb
|
||||||
|
.connect(sum);
|
||||||
|
fxNodes['room'] = [reverbNode];
|
||||||
|
fxNodes['room_mix'] = [wetReverb];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.hcutoff !== undefined) {
|
if (FXrelease !== undefined && FXrelease > release) {
|
||||||
const hpMap = {
|
const releaseNode = gainNode(1);
|
||||||
frequency: 'hcutoff',
|
releaseNode.gain.setValueAtTime(1, end + release);
|
||||||
q: 'hresonance',
|
releaseNode.gain.linearRampToValueAtTime(0, endWithRelease);
|
||||||
attack: 'hpattack',
|
chain.connect(releaseNode);
|
||||||
decay: 'hpdecay',
|
|
||||||
sustain: 'hpsustain',
|
|
||||||
release: 'hprelease',
|
|
||||||
env: 'hpenv',
|
|
||||||
anchor: 'fanchor',
|
|
||||||
model: 'ftype',
|
|
||||||
drive: 'drive',
|
|
||||||
rate: 'hprate',
|
|
||||||
sync: 'hpsync',
|
|
||||||
depth: 'hpdepth',
|
|
||||||
depthfrequency: 'hpdepthfrequency',
|
|
||||||
shape: 'hpshape',
|
|
||||||
dcoffset: 'hpdc',
|
|
||||||
skew: 'hpskew',
|
|
||||||
};
|
|
||||||
const hpParams = pickAndRename(value, hpMap);
|
|
||||||
hpParams.type = 'highpass';
|
|
||||||
const { filter: hpf1, lfo: lfo1 } = filt(hpParams);
|
|
||||||
nodes['hpf'] = [hpf1];
|
|
||||||
nodes['hpf_lfo'] = [lfo1];
|
|
||||||
lfo1 && audioNodes.push(lfo1);
|
|
||||||
chain.push(hpf1);
|
|
||||||
if (ftype === '24db') {
|
|
||||||
const { filter: hpf2, lfo: lfo2 } = filt(hpParams);
|
|
||||||
nodes['hpf'].push(hpf2);
|
|
||||||
nodes['hpf_lfo'].push(lfo2);
|
|
||||||
chain.push(hpf2);
|
|
||||||
lfo2 && audioNodes.push(lfo2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.bandf !== undefined) {
|
|
||||||
const bpMap = {
|
|
||||||
frequency: 'bandf',
|
|
||||||
q: 'bandq',
|
|
||||||
attack: 'bpattack',
|
|
||||||
decay: 'bpdecay',
|
|
||||||
sustain: 'bpsustain',
|
|
||||||
release: 'bprelease',
|
|
||||||
env: 'bpenv',
|
|
||||||
anchor: 'fanchor',
|
|
||||||
model: 'ftype',
|
|
||||||
drive: 'drive',
|
|
||||||
rate: 'bprate',
|
|
||||||
sync: 'bpsync',
|
|
||||||
depth: 'bpdepth',
|
|
||||||
depthfrequency: 'bpdepthfrequency',
|
|
||||||
shape: 'bpshape',
|
|
||||||
dcoffset: 'bpdc',
|
|
||||||
skew: 'bpskew',
|
|
||||||
};
|
|
||||||
const bpParams = pickAndRename(value, bpMap);
|
|
||||||
bpParams.type = 'bandpass';
|
|
||||||
const { filter: bpf1, lfo: lfo1 } = filt(bpParams);
|
|
||||||
nodes['bpf'] = [bpf1];
|
|
||||||
nodes['bpf_lfo'] = [lfo1];
|
|
||||||
chain.push(bpf1);
|
|
||||||
lfo1 && audioNodes.push(lfo1);
|
|
||||||
if (ftype === '24db') {
|
|
||||||
const { filter: bpf2, lfo: lfo2 } = filt(bpParams);
|
|
||||||
nodes['bpf'].push(bpf2);
|
|
||||||
nodes['bpf_lfo'].push(lfo2);
|
|
||||||
chain.push(bpf2);
|
|
||||||
lfo2 && audioNodes.push(lfo2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vowel !== undefined) {
|
|
||||||
const vowelFilter = ac.createVowelFilter(vowel);
|
|
||||||
nodes['vowel'] = [vowelFilter];
|
|
||||||
chain.push(vowelFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// effects
|
|
||||||
if (coarse !== undefined) {
|
|
||||||
const coarseNode = getWorklet(ac, 'coarse-processor', { coarse });
|
|
||||||
nodes['coarse'] = [coarseNode];
|
|
||||||
chain.push(coarseNode);
|
|
||||||
}
|
|
||||||
if (crush !== undefined) {
|
|
||||||
const crushNode = getWorklet(ac, 'crush-processor', { crush });
|
|
||||||
nodes['crush'] = [crushNode];
|
|
||||||
chain.push(crushNode);
|
|
||||||
}
|
|
||||||
if (shape !== undefined) {
|
|
||||||
const shapeNode = getWorklet(ac, 'shape-processor', { shape, postgain: shapevol });
|
|
||||||
nodes['shape'] = [shapeNode];
|
|
||||||
chain.push(shapeNode);
|
|
||||||
}
|
|
||||||
if (distort !== undefined) {
|
|
||||||
const distortNode = getDistortion(distort, distortvol, distorttype);
|
|
||||||
nodes['distort'] = [distortNode];
|
|
||||||
chain.push(distortNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tremolosync != null) {
|
|
||||||
tremolo = cps * tremolosync;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.wtPosSynced != null) {
|
|
||||||
value.wtPosRate /= cps;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.wtWarpSynced != null) {
|
|
||||||
value.wtWarpRate /= cps;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tremolo !== undefined) {
|
|
||||||
// Allow clipping of modulator for more dynamic possiblities, and to prevent speaker overload
|
|
||||||
// EX: a triangle waveform will clip like this /-\ when the depth is above 1
|
|
||||||
const gain = Math.max(1 - tremolodepth, 0);
|
|
||||||
const amGain = new GainNode(ac, { gain });
|
|
||||||
|
|
||||||
const time = cycle / cps;
|
|
||||||
const lfo = getLfo(ac, {
|
|
||||||
skew: tremoloskew ?? (tremoloshape != null ? 0.5 : 1),
|
|
||||||
frequency: tremolo,
|
|
||||||
depth: tremolodepth,
|
|
||||||
time,
|
|
||||||
dcoffset: 0,
|
|
||||||
shape: tremoloshape,
|
|
||||||
phaseoffset: tremolophase,
|
|
||||||
min: 0,
|
|
||||||
max: 1,
|
|
||||||
curve: 1.5,
|
|
||||||
begin: t,
|
|
||||||
end: endWithRelease,
|
|
||||||
});
|
|
||||||
nodes['tremolo'] = [lfo];
|
|
||||||
nodes['tremolo_gain'] = [amGain];
|
|
||||||
lfo.connect(amGain.gain);
|
|
||||||
audioNodes.push(lfo);
|
|
||||||
chain.push(amGain);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compressorThreshold !== undefined) {
|
|
||||||
const compressorNode = getCompressor(
|
|
||||||
ac,
|
|
||||||
compressorThreshold,
|
|
||||||
compressorRatio,
|
|
||||||
compressorKnee,
|
|
||||||
compressorAttack,
|
|
||||||
compressorRelease,
|
|
||||||
);
|
|
||||||
nodes['compressor'] = [compressorNode];
|
|
||||||
chain.push(compressorNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// panning
|
|
||||||
if (pan !== undefined) {
|
|
||||||
const panner = ac.createStereoPanner();
|
|
||||||
panner.pan.value = 2 * pan - 1;
|
|
||||||
chain.push(panner);
|
|
||||||
}
|
|
||||||
// phaser
|
|
||||||
if (phaserrate !== undefined && phaserdepth > 0) {
|
|
||||||
const { filterChain, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
|
|
||||||
nodes['phaser'] = [...filterChain];
|
|
||||||
nodes['phaser_lfo'] = [lfo];
|
|
||||||
chain.push(...filterChain);
|
|
||||||
audioNodes.push(lfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// last gain
|
// last gain
|
||||||
const post = new GainNode(ac, { gain: postgain });
|
const post = new GainNode(ac, { gain: postgain });
|
||||||
nodes['post'] = [post];
|
nodes.main['post'] = [post];
|
||||||
chain.push(post);
|
chain.connect(post);
|
||||||
|
|
||||||
// delay
|
// delay
|
||||||
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||||
const delayNode = orbitBus.getDelay(delaytime, delayfeedback, t);
|
const delayNode = orbitBus.getDelay(delaytime, delayfeedback, t);
|
||||||
nodes['delay'] = [delayNode];
|
nodes.main['delay'] = [delayNode];
|
||||||
const delaySend = orbitBus.sendDelay(post, delay);
|
const delaySend = orbitBus.sendDelay(post, delay);
|
||||||
nodes['delay_mix'] = [delaySend];
|
nodes.main['delay_mix'] = [delaySend];
|
||||||
audioNodes.push(delaySend);
|
chain.audioNodes.push(delaySend);
|
||||||
}
|
}
|
||||||
// reverb
|
// reverb
|
||||||
if (room > 0) {
|
if (room > 0) {
|
||||||
@@ -838,81 +926,83 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
roomIR = await loadBuffer(url, ac, ir, 0);
|
roomIR = await loadBuffer(url, ac, ir, 0);
|
||||||
}
|
}
|
||||||
const roomNode = orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
|
const roomNode = orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
|
||||||
nodes['room'] = [roomNode];
|
nodes.main['room'] = [roomNode];
|
||||||
const reverbSend = orbitBus.sendReverb(post, room);
|
const reverbSend = orbitBus.sendReverb(post, room);
|
||||||
nodes['room_mix'] = [reverbSend];
|
nodes.main['room_mix'] = [reverbSend];
|
||||||
audioNodes.push(reverbSend);
|
chain.audioNodes.push(reverbSend);
|
||||||
}
|
}
|
||||||
if (bus != null) {
|
if (bus != null) {
|
||||||
const busNode = audioController.getBus(bus);
|
const busNode = audioController.getBus(bus);
|
||||||
const busSend = effectSend(post, busNode, busgain);
|
const busSend = effectSend(post, busNode, busgain);
|
||||||
audioNodes.push(busSend);
|
chain.audioNodes.push(busSend);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (djf != null) {
|
if (djf != null) {
|
||||||
const djfNode = orbitBus.getDjf(djf, t);
|
const djfNode = orbitBus.getDjf(djf, t);
|
||||||
nodes['djf'] = [djfNode];
|
nodes.main['djf'] = [djfNode];
|
||||||
}
|
}
|
||||||
|
|
||||||
// analyser
|
// analyser
|
||||||
if (analyze && !(ac instanceof OfflineAudioContext)) {
|
if (analyze && !(ac instanceof OfflineAudioContext)) {
|
||||||
const analyserNode = getAnalyserById(analyze, 2 ** (fft + 5));
|
const analyserNode = getAnalyserById(analyze, 2 ** (fft + 5));
|
||||||
const analyserSend = effectSend(post, analyserNode, 1);
|
const analyserSend = effectSend(post, analyserNode, 1);
|
||||||
audioNodes.push(analyserSend);
|
chain.audioNodes.push(analyserSend);
|
||||||
}
|
}
|
||||||
if (dry != null) {
|
if (dry != null) {
|
||||||
dry = applyGainCurve(dry);
|
dry = applyGainCurve(dry);
|
||||||
const dryGain = new GainNode(ac, { gain: dry });
|
const dryGain = new GainNode(ac, { gain: dry });
|
||||||
chain.push(dryGain);
|
chain.connect(dryGain);
|
||||||
orbitBus.connectToOutput(dryGain);
|
orbitBus.connectToOutput(dryGain);
|
||||||
} else {
|
} else {
|
||||||
orbitBus.connectToOutput(post);
|
orbitBus.connectToOutput(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect chain elements together
|
|
||||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
|
||||||
audioNodes.push(...chain);
|
|
||||||
|
|
||||||
// finally, now that `nodes` is populated, set up modulators
|
// finally, now that `nodes` is populated, set up modulators
|
||||||
if (value.lfo) {
|
FX.forEach((fx, idx) => {
|
||||||
for (const id of value.lfo.__ids) {
|
const key = idx === FX.length - 1 ? 'main' : idx;
|
||||||
const params = value.lfo[id];
|
if (fx.lfo) {
|
||||||
const lfo = connectLFO(
|
for (const id of fx.lfo.__ids) {
|
||||||
id,
|
const params = fx.lfo[id];
|
||||||
{
|
params.fxi ??= key;
|
||||||
...params,
|
const lfo = connectLFO(
|
||||||
cps,
|
id,
|
||||||
cycle,
|
{
|
||||||
begin: t,
|
...params,
|
||||||
end: endWithRelease,
|
cps,
|
||||||
},
|
cycle,
|
||||||
nodes,
|
begin: t,
|
||||||
);
|
end: endWithRelease,
|
||||||
lfo && audioNodes.push(lfo);
|
},
|
||||||
|
nodes,
|
||||||
|
);
|
||||||
|
lfo && chain.audioNodes.push(lfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (fx.env) {
|
||||||
if (value.env) {
|
for (const id of fx.env.__ids) {
|
||||||
for (const id of value.env.__ids) {
|
const params = fx.env[id];
|
||||||
const params = value.env[id];
|
params.fxi ??= key;
|
||||||
const env = connectEnvelope(
|
const env = connectEnvelope(
|
||||||
id,
|
id,
|
||||||
{
|
{
|
||||||
...params,
|
...params,
|
||||||
begin: t,
|
begin: t,
|
||||||
end: endWithRelease,
|
end: endWithRelease,
|
||||||
},
|
},
|
||||||
nodes,
|
nodes,
|
||||||
);
|
);
|
||||||
env && audioNodes.push(env);
|
env && chain.audioNodes.push(env);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (fx.bmod) {
|
||||||
if (value.bmod) {
|
for (const id of fx.bmod.__ids) {
|
||||||
for (const id of value.bmod.__ids) {
|
const params = fx.bmod[id];
|
||||||
const params = value.bmod[id];
|
params.fxi ??= key;
|
||||||
const { toCleanup } = connectBusModulator({ ...params, begin: t, end: endWithRelease }, nodes, controller);
|
const { toCleanup } = connectBusModulator({ ...params, begin: t, end: endWithRelease }, nodes, controller);
|
||||||
audioNodes.push(...toCleanup);
|
chain.audioNodes.push(...toCleanup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const superdoughTrigger = (t, hap, ct, cps) => {
|
export const superdoughTrigger = (t, hap, ct, cps) => {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ const CONTROL_TARGETS = {
|
|||||||
compressorRelease: { node: 'compressor', param: 'release' },
|
compressorRelease: { node: 'compressor', param: 'release' },
|
||||||
|
|
||||||
// PHASER
|
// PHASER
|
||||||
phaserrate: { node: 'phaser_lfo', param: 'rate' },
|
phaserrate: { node: 'phaser_lfo', param: 'frequency' },
|
||||||
phasersweep: { node: 'phaser_lfo', param: 'depth' },
|
phasersweep: { node: 'phaser_lfo', param: 'depth' },
|
||||||
phasercenter: { node: 'phaser', param: 'frequency' },
|
phasercenter: { node: 'phaser', param: 'frequency' },
|
||||||
phaserdepth: { node: 'phaser', param: 'Q' },
|
phaserdepth: { node: 'phaser', param: 'Q' },
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { releaseAudioNode } from './helpers.mjs';
|
||||||
|
|
||||||
// credits to webdirt: https://github.com/dktr0/WebDirt/blob/41342e81d6ad694a2310d491fef7b7e8b0929efe/js-src/Graph.js#L597
|
// credits to webdirt: https://github.com/dktr0/WebDirt/blob/41342e81d6ad694a2310d491fef7b7e8b0929efe/js-src/Graph.js#L597
|
||||||
export var vowelFormant = {
|
export var vowelFormant = {
|
||||||
a: { freqs: [660, 1120, 2750, 3000, 3350], gains: [1, 0.5012, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
|
a: { freqs: [660, 1120, 2750, 3000, 3350], gains: [1, 0.5012, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
|
||||||
@@ -46,7 +48,8 @@ if (typeof GainNode !== 'undefined') {
|
|||||||
}
|
}
|
||||||
const { gains, qs, freqs } = vowelFormant[letter];
|
const { gains, qs, freqs } = vowelFormant[letter];
|
||||||
this.makeupGain = ac.createGain();
|
this.makeupGain = ac.createGain();
|
||||||
this.audioNodes = [];
|
this.filters = [];
|
||||||
|
this.gains = [];
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
const gain = ac.createGain();
|
const gain = ac.createGain();
|
||||||
gain.gain.value = gains[i];
|
gain.gain.value = gains[i];
|
||||||
@@ -56,9 +59,9 @@ if (typeof GainNode !== 'undefined') {
|
|||||||
filter.frequency.value = freqs[i];
|
filter.frequency.value = freqs[i];
|
||||||
super.connect(filter);
|
super.connect(filter);
|
||||||
filter.connect(gain);
|
filter.connect(gain);
|
||||||
this.audioNodes.push(filter);
|
this.filters.push(filter);
|
||||||
gain.connect(this.makeupGain);
|
gain.connect(this.makeupGain);
|
||||||
this.audioNodes.push(gain);
|
this.gains.push(gain);
|
||||||
}
|
}
|
||||||
this.makeupGain.gain.value = 8; // how much makeup gain to add?
|
this.makeupGain.gain.value = 8; // how much makeup gain to add?
|
||||||
return this;
|
return this;
|
||||||
@@ -67,11 +70,13 @@ if (typeof GainNode !== 'undefined') {
|
|||||||
this.makeupGain.connect(target);
|
this.makeupGain.connect(target);
|
||||||
}
|
}
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.makeupGain.disconnect();
|
releaseAudioNode(this.makeupGain);
|
||||||
this.audioNodes.forEach((n) => n.disconnect());
|
this.filters.forEach(releaseAudioNode);
|
||||||
|
this.gains.forEach(releaseAudioNode);
|
||||||
super.disconnect();
|
super.disconnect();
|
||||||
this.makeupGain = null;
|
this.makeupGain = null;
|
||||||
this.audioNodes = null;
|
this.filters = null;
|
||||||
|
this.gains = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -565,6 +565,52 @@ exports[`runs examples > example "_euclidRot" example index 20 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "FX" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 1/8 → 1/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:bd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 1/2 → 5/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 3/4 → 7/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:lt decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 11/8 → 3/2 | s:oh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 7/4 → 15/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:bd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:bd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 5/2 → 21/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 11/4 → 23/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:bd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:lt decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 7/2 → 29/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:hh decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:sbd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:bd decay:0.4 FX:[{phaserrate:0.5 gain:2} {bandf:800} {distort:1.3} {room:0.2} {delay:0.5 gain:1.25} {distort:0.3}] FXrelease:1.7 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "FX" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | s:saw fmi:0.5 delay:0.3 FX:[{coarse:4} {cutoff:500 lpenv:4 lpattack:1 lpdecay:2} {distort:1}] ]",
|
||||||
|
"[ 1/1 → 2/1 | s:saw fmi:0.5 delay:0.3 FX:[{coarse:4} {cutoff:500 lpenv:4 lpattack:1 lpdecay:2} {distort:1}] ]",
|
||||||
|
"[ 2/1 → 3/1 | s:saw fmi:0.5 delay:0.3 FX:[{coarse:4} {cutoff:500 lpenv:4 lpattack:1 lpdecay:2} {distort:1}] ]",
|
||||||
|
"[ 3/1 → 4/1 | s:saw fmi:0.5 delay:0.3 FX:[{coarse:4} {cutoff:500 lpenv:4 lpattack:1 lpdecay:2} {distort:1}] ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "accelerate" example index 0 1`] = `
|
exports[`runs examples > example "accelerate" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 2/1 | s:sax accelerate:0 ]",
|
"[ 0/1 → 2/1 | s:sax accelerate:0 ]",
|
||||||
|
|||||||
Reference in New Issue
Block a user