Compare commits

..

15 Commits

Author SHA1 Message Date
alex 96835be056 tweaks 2025-12-11 15:07:57 +00:00
alex 960f1de9d2 tweaks 2025-12-11 12:16:54 +00:00
alex 12884cba3a Updates relating to LLM, github, etc 2025-12-11 12:09:26 +00:00
Alex McLean 20c513d649 Merge pull request 'fix .as so it doesn't set undefined values' (#1827) from fix-as into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1827
2025-12-10 15:32:43 +01:00
alex e89414e400 fix .as so it doesn't set undefined values 2025-12-10 09:19:12 +00:00
Aria 765de9daf3 Merge pull request '[perf] propagate onceEnded and releaseAudioNode' (#1809) from jeromew/strudel:fix-perf12 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1809
Reviewed-by: Aria <glossing@noreply.codeberg.org>
2025-12-08 19:27:30 +01:00
jeromew 132dea8478 Merge branch 'main' into fix-perf12 2025-12-08 18:54:51 +01:00
jeromew b889d19493 Merge branch 'fix-perf12' of https://codeberg.org/jeromew/strudel into fix-perf12 2025-12-08 17:32:47 +00:00
jeromew 71d36b79ed Improve getOscillator/noiseMix release 2025-12-08 17:31:34 +00:00
Aria d7bf5db8be Merge pull request 'Feat: Add channel support to midi in' (#1775) from glossing/midin-channels into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1775
2025-12-07 21:51:39 +01:00
Aria 4374bdf1bd Merge branch 'main' into fix-perf12 2025-12-07 18:44:38 +01:00
jeromew 1225d04437 codeformat 2025-12-04 15:29:21 +00:00
jeromew f6f507dd29 propagate releaseAudioNode in packages 2025-12-04 15:26:59 +00:00
jeromew c7586e96ff Add comment on end worklet param 2025-12-04 13:20:17 +00:00
jeromew 46291bf85c Difuse the onceEnded mechanism 2025-12-04 13:17:20 +00:00
9 changed files with 87 additions and 58 deletions
+22 -17
View File
@@ -4,20 +4,13 @@ Thanks for wanting to contribute!!! There are many ways you can add value to thi
## Move to codeberg
We are currently in the process of moving from github to codeberg -- not everything is working, please bear with us.
To update your local clone, you can run this command:
```
git remote set-url origin git@codeberg.org:uzu/strudel.git
```
Along with many other live coding projects, we have moved from Microsoft's Github platform to Codeberg for ethical reasons. Please don't fork the project back to github.
## Communication Channels
To get in touch with the contributors, either
To get in touch with the community, either
- [join the Tidal Discord Channel](https://discord.com/invite/HGEdXmRkzT) and go to the #strudel channel
- [join the Uzulang Discord Server](https://discord.com/invite/HGEdXmRkzT) and go to the strudel channels (Uzulangs are a family of live coding languages inspired by each other, including TidalCycles as well as Strudel)
- Find related discussions on the [tidal club forum](https://club.tidalcycles.org/)
## Ask a Question
@@ -36,22 +29,35 @@ Use one of the Communication Channels listed above and drop us a line or two!
## Share Music
If you made some music with strudel, you can give back some love and share what you've done!
Your creation could also be part of the random selection in the REPL if you want.
Use one of the Communication Channels listed above.
(There used to be a random selection of contributed patterns in the REPL, but that is unfortunately disabled for now due to abuse)
## Improve the Docs
If you find some weak spots in the [docs](https://strudel.cc/workshop/getting-started/),
you can edit each file directly on codeburg. (we are currently fixing the "Edit this page" links in the right sidebar)
If you find some weak spots in the [docs](https://strudel.cc/workshop/getting-started/), you can edit each file directly on codeberg. There are "Edit this page" links in the right sidebar that take you to the right place.
## Propose a Feature
If you want a specific feature that is not part of strudel yet, feel free to use one of the communication channels above.
Maybe you even want to help with the implementation of that feature!
If you want a specific feature that is not part of strudel yet, feel free to use one of the communication channels above. Please bear in mind that this is a free/open source project, oriented around collaborative discussion. Maybe you even want to help with the implementation of that feature!
## Contribute a feature
Pull requests welcome! Consider starting with discussion or proof-of-concept first, rather than do loads of work on something and then find it doesn't fit the collective goals of the project, or something like that.
At the time of writing we have a PR backlog, and generally prioritise bugfixes and contributions that have arisen through community discussion.
### AI/LLM policy
Strudel is a project handmade by humans, with thought and nuance.
If you have used LLMs (so called 'AI'), please detail that in the pull request. We are still developing our response to the onslaught of LLM technology, but for practical and legal reasons are currently not accepting wholly LLM-generated code. We are also not accepting PRs that add LLM features to strudel itself.
There are #llm-chat and #llm-share channels on our discord. Please do not discuss or share LLM-related things outside of those channels.
## Report a Bug
If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://codeberg.org/uzu/strudel/issues).
Please check that it has not been reported before.
## Fix a Bug
@@ -118,8 +124,7 @@ There are also eslint extensions / plugins for most editors.
## Running all CI Checks
When opening a PR, the CI runner will automatically check the code style and eslint, as well as run all tests.
You can run the same check with `pnpm check`
When opening a PR, the CI runner will (once approved by a human) check the code style and eslint, as well as run all tests. You can run the same check with `pnpm check`.
## Package Workflow
+7 -2
View File
@@ -2753,8 +2753,13 @@ export const as = register('as', (mapping, pat) => {
mapping = Array.isArray(mapping) ? mapping : [mapping];
return pat.fmap((v) => {
v = Array.isArray(v) ? v : [v];
v = Object.fromEntries(mapping.map((prop, i) => [getControlName(prop), v[i]]));
return v;
const entries = [];
for (let i = 0; i < mapping.length; ++i) {
if (v[i] !== undefined) {
entries.push([getControlName(mapping[i]), v[i]]);
}
}
return Object.fromEntries(entries);
});
});
+3 -3
View File
@@ -7,6 +7,7 @@ import {
getPitchEnvelope,
getVibratoOscillator,
onceEnded,
releaseAudioNode,
} from '@strudel/webaudio';
import gm from './gm.mjs';
@@ -172,9 +173,8 @@ export function registerSoundfonts() {
bufferSource.stop(envEnd);
const stop = (releaseTime) => {};
onceEnded(bufferSource, () => {
bufferSource.disconnect();
vibratoOscillator?.stop();
node.disconnect();
releaseAudioNode(bufferSource);
releaseAudioNode(vibratoOscillator);
onended();
});
return { node, stop };
+13 -8
View File
@@ -189,7 +189,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve);
}
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
return { lfo, disconnect: () => lfo?.disconnect() };
return lfo;
}
export function createFilter(context, start, end, params, cps, cycle) {
let {
@@ -283,9 +283,12 @@ export function drywet(dry, wet, wetAmount = 0) {
wet_gain.connect(mix);
return {
node: mix,
onended: () => {
dry_gain.disconnect(mix);
wet_gain.disconnect(mix);
teardown: () => {
releaseAudioNode(dry_gain);
releaseAudioNode(wet_gain);
// it is not the responsability of drywet
// to call `releaseAudioNode` on
// the 2 external args dry and wet
dry.disconnect(dry_gain);
wet.disconnect(wet_gain);
},
@@ -324,10 +327,10 @@ export function getVibratoOscillator(param, value, t) {
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(param);
vibratoOscillator.onended = () => {
gain.disconnect(param);
vibratoOscillator.disconnect(gain);
};
onceEnded(vibratoOscillator, () => {
releaseAudioNode(gain);
releaseAudioNode(vibratoOscillator);
});
vibratoOscillator.start(t);
return vibratoOscillator;
}
@@ -613,6 +616,8 @@ export const releaseAudioNode = (node) => {
// returns true and either its active source flag is true or
// any AudioNode connected to one of its inputs is actively processing.
if (node instanceof AudioWorkletNode) {
// while `end` is not native to the web audio API, it is common practice in superdough
// to use that param in the worklets to trigger returning false from the processor
node.parameters.get('end')?.setValueAtTime(0, 0);
}
};
+5 -2
View File
@@ -1,4 +1,4 @@
import { drywet } from './helpers.mjs';
import { drywet, onceEnded, releaseAudioNode } from './helpers.mjs';
import { getAudioContext } from './audioContext.mjs';
let noiseCache = {};
@@ -65,9 +65,12 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) {
export function getNoiseMix(inputNode, wet, t) {
const noiseOscillator = getNoiseOscillator('pink', t);
const noiseMix = drywet(inputNode, noiseOscillator.node, wet);
noiseOscillator.node.onended = () => noiseMix.onended();
onceEnded(noiseOscillator.node, () => {
releaseAudioNode(noiseOscillator.node);
});
return {
node: noiseMix.node,
stop: (time) => noiseOscillator?.stop(time),
teardown: noiseMix.teardown,
};
}
+4 -4
View File
@@ -323,10 +323,10 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
node.connect(out);
onceEnded(bufferSource, function () {
bufferSource.disconnect();
vibratoOscillator?.stop();
node.disconnect();
out.disconnect();
releaseAudioNode(bufferSource);
releaseAudioNode(vibratoOscillator);
releaseAudioNode(node);
releaseAudioNode(out);
onended();
});
let envEnd = holdEnd + release + 0.01;
+11 -2
View File
@@ -9,7 +9,16 @@ import './reverb.mjs';
import './vowel.mjs';
import { nanFallback, _mod, cycleToSeconds, pickAndRename } from './util.mjs';
import workletsUrl from './worklets.mjs?audioworklet';
import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs';
import {
createFilter,
gainNode,
getCompressor,
getDistortion,
getLfo,
getWorklet,
effectSend,
releaseAudioNode,
} from './helpers.mjs';
import { map } from 'nanostores';
import { logger } from './logger.mjs';
import { loadBuffer } from './sampler.mjs';
@@ -505,7 +514,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
} else if (getSound(s)) {
const { onTrigger } = getSound(s);
const onEnded = () => {
audioNodes.forEach((n) => n?.disconnect());
audioNodes.forEach((n) => releaseAudioNode(n));
activeSoundSources.delete(chainID);
};
const soundHandle = await onTrigger(t, value, onEnded, cps);
+19 -17
View File
@@ -12,6 +12,7 @@ import {
getVibratoOscillator,
getWorklet,
noises,
onceEnded,
releaseAudioNode,
webAudioTimeout,
} from './helpers.mjs';
@@ -52,7 +53,7 @@ export function registerSynthSounds() {
const g = gainNode(0.3);
let sound = getOscillator(s, t, value, () => {
g.disconnect();
releaseAudioNode(g);
onended();
});
@@ -110,15 +111,15 @@ export function registerSynthSounds() {
const mix = gainNode(mixGain);
o.onended = () => {
o.disconnect();
g.disconnect();
sat.disconnect();
noise.node.disconnect();
noiseGain.disconnect();
mix.disconnect();
onceEnded(o, () => {
releaseAudioNode(o);
releaseAudioNode(g);
releaseAudioNode(sat);
releaseAudioNode(noise.node);
releaseAudioNode(noiseGain);
releaseAudioNode(mix);
onended();
};
});
const node = o.connect(sat).connect(g).connect(mix);
noise.node.connect(noiseGain).connect(mix);
@@ -384,11 +385,11 @@ export function registerSynthSounds() {
const { duration } = value;
o.onended = () => {
o.disconnect();
g.disconnect();
onceEnded(o, () => {
releaseAudioNode(o);
releaseAudioNode(g);
onended();
};
});
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
@@ -489,11 +490,12 @@ export function getOscillator(s, t, value, onended) {
noiseMix = getNoiseMix(o, noise, t);
}
o.onended = () => {
noiseMix || o.disconnect();
noiseMix?.node.disconnect();
onceEnded(o, () => {
noiseMix?.teardown();
releaseAudioNode(o);
releaseAudioNode(noiseMix?.node);
onended();
};
});
o.start(t);
return {
+3 -3
View File
@@ -320,10 +320,10 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
ac,
() => {
releaseAudioNode(source);
vibratoOscillator?.stop();
releaseAudioNode(vibratoOscillator);
fm?.stop();
wtPosModulators?.disconnect();
wtWarpModulators?.disconnect();
releaseAudioNode(wtPosModulators);
releaseAudioNode(wtWarpModulators);
onended();
},
t,