diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs
index 044cfa96d..6ca21b6db 100644
--- a/packages/core/controls.mjs
+++ b/packages/core/controls.mjs
@@ -2108,3 +2108,5 @@ export const scrub = register(
},
false,
);
+
+export const { FXrelease, FXrel, FXr, fxr } = registerControl('FXrelease', 'FXrel', 'FXr', 'fxr');
diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs
index 458e7d9a2..c4e83704e 100644
--- a/packages/core/repl.mjs
+++ b/packages/core/repl.mjs
@@ -5,7 +5,7 @@ import { errorLogger, logger } from './logger.mjs';
import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs';
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
-import { SalatRepl } from "@kabelsalat/web";
+import { SalatRepl } from '@kabelsalat/web';
export function repl({
defaultOutput,
diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs
index 1df5c8776..625f40756 100644
--- a/packages/core/test/pattern.test.mjs
+++ b/packages/core/test/pattern.test.mjs
@@ -796,7 +796,7 @@ describe('Pattern', () => {
});
});
describe('apply', () => {
- it('Can apply a function', () => {
+ (it('Can apply a function', () => {
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
}),
it('Can apply a pattern of functions', () => {
@@ -804,7 +804,7 @@ describe('Pattern', () => {
expect(sequence('a', 'b').apply(fast(2), fast(3)).firstCycle()).toStrictEqual(
sequence('a', 'b').fast(2, 3).firstCycle(),
);
- });
+ }));
});
describe('layer', () => {
it('Can layer up multiple functions', () => {
diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs
index 40fa96dac..43e27a072 100644
--- a/packages/superdough/superdough.mjs
+++ b/packages/superdough/superdough.mjs
@@ -636,6 +636,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
analyze, // analyser wet
fft = getDefaultValue('fft'), // fftSize 0 - 10
FX,
+ FXrelease,
} = value;
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
@@ -655,7 +656,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
delay = applyGainCurve(delay);
const end = t + hapDuration;
- const endWithRelease = end + release;
+ const fullRelease = Math.max(release, FXrelease ?? 0);
+ const endWithRelease = end + fullRelease;
+ value.release = fullRelease;
const chainID = Math.round(Math.random() * 1000000);
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
@@ -687,14 +690,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
sourceNode = source(t, value, hapDuration, cps);
} else if (getSound(s)) {
const { onTrigger } = getSound(s);
- const onEnded = () => {
- // audioNodes.forEach((n) => n?.disconnect());
- debugger;
- earlyNodes.forEach((n) => n?.disconnect());
- disconnectNodeWhenQuiet(ac, finalDryNode, audioNodes);
- activeSoundSources.delete(chainID);
- };
- const soundHandle = await onTrigger(t, value, onEnded);
+ const soundHandle = await onTrigger(t, value, () => void 0);
if (soundHandle) {
sourceNode = soundHandle.node;
@@ -881,9 +877,11 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
earlyNodes = [...chain];
chain.push(workletNode);
let workletSrc = fx.workletSrc;
- workletSrc = workletSrc.replace(/\bpat\[(\d+)\]/g, (_, i) => fx.workletInputs[i]);
- workletSrc = workletSrc.replaceAll('sFreq', getFrequencyFromValue(value));
- workletSrc = workletSrc.replaceAll('sGate', `cc('strudel-gate-${chainID}')`);
+ workletSrc = workletSrc
+ .replace(/\bpat\[(\d+)\]/g, (_, i) => fx.workletInputs[i])
+ .replaceAll('sFreq', getFrequencyFromValue(value))
+ .replaceAll('sGate', `cc('strudel-gate-${chainID}')`);
+ /* global compileKabel */
const { src, ugens, registers } = compileKabel(workletSrc);
workletNode.port.postMessage({ src, schema: { ugens, registers }, start: t, end });
}
@@ -934,6 +932,13 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
audioNodes.push(dryReverb, reverbNode, wetReverb);
}
}
+ const useFXRelease = FXrelease !== undefined && FXrelease > release;
+ if (useFXRelease) {
+ const releaseNode = gainNode(1);
+ releaseNode.gain.setValueAtTime(1, end + release);
+ releaseNode.gain.linearRampToValueAtTime(0, endWithRelease);
+ chain.push(releaseNode);
+ }
// last gain
const post = new GainNode(ac, { gain: postgain });
@@ -986,8 +991,20 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
// connect chain elements together
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
audioNodes.push(...chain);
- earlyNodes = earlyNodes.length > 0 ? earlyNodes : audioNodes;
- audioNodes = audioNodes.filter((n) => earlyNodes.includes(n));
+ // earlyNodes = [];
+ earlyNodes = earlyNodes.length > 0 && !useFXRelease ? earlyNodes : audioNodes;
+ audioNodes = audioNodes.filter((n) => !earlyNodes.includes(n));
+ webAudioTimeout(
+ ac,
+ () => {
+ // audioNodes.forEach((n) => n?.disconnect());
+ // earlyNodes.forEach((n) => n?.disconnect());
+ disconnectNodeWhenQuiet(ac, finalDryNode, audioNodes);
+ activeSoundSources.delete(chainID);
+ },
+ 0,
+ endWithRelease,
+ );
};
export const superdoughTrigger = (t, hap, ct, cps) => {
diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs
index ac670edf2..48fa8a9ef 100644
--- a/packages/superdough/synth.mjs
+++ b/packages/superdough/synth.mjs
@@ -1,4 +1,4 @@
-import { clamp, getFrequencyFromValue} from './util.mjs';
+import { clamp, getFrequencyFromValue } from './util.mjs';
import { registerSound, getAudioContext, soundMap, getLfo } from './superdough.mjs';
import {
applyFM,
@@ -13,14 +13,6 @@ import {
} from './helpers.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
-function destroyAudioWorkletNode(node) {
- if (node == null) {
- return;
- }
- node.disconnect();
- node.parameters.get('end')?.setValueAtTime(0, 0);
-}
-
const waveforms = ['triangle', 'square', 'sawtooth', 'sine'];
const waveformAliases = [
['tri', 'triangle'],
@@ -44,7 +36,7 @@ export function registerSynthSounds() {
[...waveforms].forEach((s) => {
registerSound(
s,
- (t, value, onended) => {
+ (t, value) => {
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
@@ -52,31 +44,20 @@ export function registerSynthSounds() {
);
let sound = getOscillator(s, t, value);
- let { node: o, stop, triggerRelease } = sound;
+ let { node: o } = sound;
// turn down
const g = gainNode(0.3);
const { duration } = value;
- o.onended = () => {
- o.disconnect();
- g.disconnect();
- onended();
- };
-
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
const holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
const envEnd = holdEnd + release + 0.01;
- triggerRelease?.(envEnd);
- stop(envEnd);
return {
node,
- stop: (endTime) => {
- stop(endTime);
- },
};
},
{ type: 'synth', prebake: true },
@@ -85,7 +66,7 @@ export function registerSynthSounds() {
registerSound(
'sbd',
- (t, value, onended) => {
+ (t, value) => {
const { duration, decay = 0.5, pdecay = 0.5, penv = 36, clip } = value;
const ctx = getAudioContext();
const attackhold = 0.02;
@@ -115,16 +96,6 @@ export function registerSynthSounds() {
const mix = gainNode(mixGain);
- o.onended = () => {
- o.disconnect();
- g.disconnect();
- sat.disconnect();
- noise.node.disconnect();
- noiseGain.disconnect();
- mix.disconnect();
- onended();
- };
-
const node = o.connect(sat).connect(g).connect(mix);
noise.node.connect(noiseGain).connect(mix);
@@ -138,14 +109,8 @@ export function registerSynthSounds() {
mix.gain.setValueAtTime(mixGain, end - 0.01);
mix.gain.linearRampToValueAtTime(0, end);
- o.stop(end);
- noise.stop(end);
-
return {
node,
- stop: (endTime) => {
- o.stop(endTime);
- },
};
},
{ type: 'synth', prebake: true },
@@ -153,7 +118,7 @@ export function registerSynthSounds() {
registerSound(
'supersaw',
- (begin, value, onended) => {
+ (begin, value) => {
const ac = getAudioContext();
let { duration, n, unison = 5, spread = 0.6, detune } = value;
detune = detune ?? n ?? 0.18;
@@ -194,24 +159,8 @@ export function registerSynthSounds() {
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
- let timeoutNode = webAudioTimeout(
- ac,
- () => {
- destroyAudioWorkletNode(o);
- envGain.disconnect();
- onended();
- fm?.stop();
- vibratoOscillator?.stop();
- },
- begin,
- end,
- );
-
return {
node: envGain,
- stop: (time) => {
- timeoutNode.stop(time);
- },
};
},
{ prebake: true, type: 'synth' },
@@ -219,7 +168,7 @@ export function registerSynthSounds() {
registerSound(
'bytebeat',
- (begin, value, onended) => {
+ (begin, value) => {
const defaultBeats = [
'(t%255 >= t/255%255)*255',
'(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400',
@@ -272,22 +221,8 @@ export function registerSynthSounds() {
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
- let timeoutNode = webAudioTimeout(
- ac,
- () => {
- destroyAudioWorkletNode(o);
- envGain.disconnect();
- onended();
- },
- begin,
- end,
- );
-
return {
node: envGain,
- stop: (time) => {
- timeoutNode.stop(time);
- },
};
},
{ prebake: true, type: 'synth' },
@@ -295,7 +230,7 @@ export function registerSynthSounds() {
registerSound(
'pulse',
- (begin, value, onended) => {
+ (begin, value) => {
const ac = getAudioContext();
let { pwrate, pwsweep } = value;
if (pwsweep == null) {
@@ -346,25 +281,9 @@ export function registerSynthSounds() {
lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep });
lfo.connect(o.parameters.get('pulsewidth'));
}
- let timeoutNode = webAudioTimeout(
- ac,
- () => {
- destroyAudioWorkletNode(o);
- destroyAudioWorkletNode(lfo);
- envGain.disconnect();
- onended();
- fm?.stop();
- vibratoOscillator?.stop();
- },
- begin,
- end,
- );
return {
node: envGain,
- stop: (time) => {
- timeoutNode.stop(time);
- },
};
},
{ prebake: true, type: 'synth' },
@@ -373,7 +292,7 @@ export function registerSynthSounds() {
[...noises].forEach((s) => {
registerSound(
s,
- (t, value, onended) => {
+ (t, value) => {
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
@@ -385,31 +304,20 @@ export function registerSynthSounds() {
let { density } = value;
sound = getNoiseOscillator(s, t, density);
- let { node: o, stop, triggerRelease } = sound;
+ let { node: o } = sound;
// turn down
const g = gainNode(0.3);
const { duration } = value;
- o.onended = () => {
- o.disconnect();
- g.disconnect();
- onended();
- };
-
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
const holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
const envEnd = holdEnd + release + 0.01;
- triggerRelease?.(envEnd);
- stop(envEnd);
return {
node,
- stop: (endTime) => {
- stop(endTime);
- },
};
},
{ type: 'synth', prebake: true },
@@ -479,14 +387,5 @@ export function getOscillator(s, t, value) {
return {
node: noiseMix?.node || o,
- stop: (time) => {
- fmModulator.stop(time);
- vibratoOscillator?.stop(time);
- noiseMix?.stop(time);
- o.stop(time);
- },
- triggerRelease: (time) => {
- // envGain?.stop(time);
- },
};
}
diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs
index 5dd967c84..08b78f805 100644
--- a/packages/superdough/worklets.mjs
+++ b/packages/superdough/worklets.mjs
@@ -958,16 +958,23 @@ class MeterProcessor extends AudioWorkletProcessor {
}
registerProcessor('meter-processor', MeterProcessor);
-class GenericProcessor extends AudioWorkletProcessor{
- constructor(){
+class GenericProcessor extends AudioWorkletProcessor {
+ constructor() {
super();
this.playPos = 0;
const channels = 16;
this.outputs = new Array(channels).fill(0);
this.sources = new Array(channels).fill(0);
this.gateEnded = false;
+ this.started = false;
this.port.onmessage = (event) => {
- let { src, schema: { ugens, registers }, gate, start, end } = event.data;
+ let {
+ src,
+ schema: { ugens, registers },
+ gate,
+ start,
+ end,
+ } = event.data;
this.start = start;
this.end = end;
this.registers = new Array(registers).fill(0);
@@ -984,36 +991,35 @@ class GenericProcessor extends AudioWorkletProcessor{
this.nodes[i] = node;
}
this.genSample = new Function(
- "time",
- "nodes",
- "input",
- "r", // registers
- "o", // outputs
- "s", // sources
- this.src
+ 'time',
+ 'nodes',
+ 'input',
+ 'r', // registers
+ 'o', // outputs
+ 's', // sources
+ this.src,
);
};
}
- process(inputs, outputs){
+ process(inputs, outputs) {
+ const input = inputs[0]?.[0];
if (this.genSample === undefined || currentTime < this.start) {
+ // pending
return true;
+ } else if (input === undefined) {
+ // if no input and started, return false and close worklet
+ // else just keep waiting (true)
+ return !this.started;
}
+ this.started = true;
if (!this.gateEnded && currentTime > this.end) {
this.gateNode?.setValue(0);
this.gateEnded = true;
}
const outL = outputs[0][0];
const outR = outputs[0][1] ?? outputs[0][0];
- const input = inputs[0]?.[0];
- for(let n = 0; n < blockSize; n++) {
- this.genSample(
- this.playPos,
- this.nodes,
- input ? input[n] : 0,
- this.registers,
- this.outputs,
- this.sources,
- );
+ for (let n = 0; n < blockSize; n++) {
+ this.genSample(this.playPos, this.nodes, input ? input[n] : 0, this.registers, this.outputs, this.sources);
outL[n] = this.outputs[0];
outR[n] = this.outputs[1];
this.playPos += 1 / sampleRate;
@@ -1021,4 +1027,4 @@ class GenericProcessor extends AudioWorkletProcessor{
return true;
}
}
-registerProcessor("generic-processor", GenericProcessor);
\ No newline at end of file
+registerProcessor('generic-processor', GenericProcessor);
diff --git a/website/src/components/Header/Search.css b/website/src/components/Header/Search.css
index 456ef9f6b..b14146cbd 100644
--- a/website/src/components/Header/Search.css
+++ b/website/src/components/Header/Search.css
@@ -18,8 +18,8 @@
--docsearch-modal-background: var(--background);
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #fff 30%);
--docsearch-key-gradient: var(--foreground);
- --docsearch-key-shadow: inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground),
- 0 1px 2px 1px var(--gutterBackground);
+ --docsearch-key-shadow:
+ inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground), 0 1px 2px 1px var(--gutterBackground);
}
.dark {
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #000 30%);
diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx
index eb79cccf7..201d57dfa 100644
--- a/website/src/pages/learn/samples.mdx
+++ b/website/src/pages/learn/samples.mdx
@@ -381,7 +381,7 @@ Sampler effects are functions that can be used to change the behaviour of sample
### scrub
-{' '}
+
### speed