mirror of
https://codeberg.org/uzu/strudel
synced 2026-10-20 13:56:50 -04:00
Working version
This commit is contained in:
@@ -2108,3 +2108,5 @@ export const scrub = register(
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
export const { FXrelease, FXrel, FXr, fxr } = registerControl('FXrelease', 'FXrel', 'FXr', 'fxr');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
registerProcessor('generic-processor', GenericProcessor);
|
||||
|
||||
@@ -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%);
|
||||
|
||||
@@ -381,7 +381,7 @@ Sampler effects are functions that can be used to change the behaviour of sample
|
||||
|
||||
### scrub
|
||||
|
||||
<JsDoc client:idle name="Pattern.scrub" h={0} />{' '}
|
||||
<JsDoc client:idle name="Pattern.scrub" h={0} />
|
||||
|
||||
### speed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user