From 48407c309cca95b87d541e3a420659ff2f04abdd Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 17 Nov 2025 23:10:10 +0100 Subject: [PATCH 01/10] add basic dough repl --- packages/codemirror/codemirror.mjs | 6 +- website/src/components/Dough/Dough.astro | 17 +++ website/src/components/Dough/dough-mirror.mjs | 139 ++++++++++++++++++ website/src/components/Dough/dough-repl.mjs | 105 +++++++++++++ website/src/pages/dough/index.astro | 15 ++ 5 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 website/src/components/Dough/Dough.astro create mode 100644 website/src/components/Dough/dough-mirror.mjs create mode 100644 website/src/components/Dough/dough-repl.mjs create mode 100644 website/src/pages/dough/index.astro diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 69fce4b50..ad27fd30f 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -27,7 +27,7 @@ import { updateWidgets, widgetPlugin } from './widget.mjs'; export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands'; -const extensions = { +export const extensions = { isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []), isBracketClosingEnabled: (on) => (on ? closeBrackets() : []), @@ -48,7 +48,7 @@ const extensions = { ] : [], }; -const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); +export const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); export const defaultSettings = { keybindings: 'codemirror', @@ -411,7 +411,7 @@ export class StrudelMirror { } } -function parseBooleans(value) { +export function parseBooleans(value) { return { true: true, false: false }[value] ?? value; } diff --git a/website/src/components/Dough/Dough.astro b/website/src/components/Dough/Dough.astro new file mode 100644 index 000000000..91b50e07b --- /dev/null +++ b/website/src/components/Dough/Dough.astro @@ -0,0 +1,17 @@ +--- +import '../../repl/Repl.css'; +--- + +
+ + + diff --git a/website/src/components/Dough/dough-mirror.mjs b/website/src/components/Dough/dough-mirror.mjs new file mode 100644 index 000000000..f20fd2b4c --- /dev/null +++ b/website/src/components/Dough/dough-mirror.mjs @@ -0,0 +1,139 @@ +import { + initEditor, + codemirrorSettings, + flash, + compartments, + extensions, + parseBooleans, + activateTheme, + updateMiniLocations, + highlightMiniLocations, +} from '@strudel/codemirror'; +import { evalScope } from '@strudel/core'; +import { Framer } from '@strudel/draw'; +import { persistentAtom } from '@nanostores/persistent'; +import { DoughRepl } from './dough-repl.mjs'; + +const initialCode = '$: note("c a f e")'; +export const code = persistentAtom('vanilla-repl-code', initialCode, { + encode: JSON.stringify, + decode: JSON.parse, +}); + +export class DoughMirror { + constructor(options) { + const { root, initialCode = code.get(), bgFill = true } = options; + this.root = root; + this.code = initialCode; + this.repl = new DoughRepl(); + this.prebaked = this.prebake(); + + // init codemirror + this.editor = initEditor({ + root, + initialCode: this.code, + onChange: (v) => { + if (v.docChanged) { + this.code = v.state.doc.toString(); + code.set(this.code); + } + }, + onEvaluate: this.evaluate.bind(this), + onStop: () => this.stop(), + mondo: false, + }); + const settings = codemirrorSettings.get(); + this.setFontSize(settings.fontSize); + this.setFontFamily(settings.fontFamily); + + // init event highlighting + this.framer = new Framer( + () => { + const frameHaps = this.repl.processHaps(); + highlightMiniLocations(this.editor, time, frameHaps); + }, + (err) => console.log('Framer error', err), + ); + } + prebake() { + const modulesLoading = evalScope(import('@strudel/core'), import('@strudel/tonal'), import('@strudel/mini')); + return Promise.all([modulesLoading, this.repl.prebake()]); + } + async evaluate() { + this.framer.start(); + this.flash(); + await this.prebaked; + const { miniLocations } = await this.repl.evaluate(this.code); + updateMiniLocations(this.editor, miniLocations); + } + stop() { + this.repl.stop(); + this.framer.stop(); + highlightMiniLocations(this.editor, 0, []); + } + // added synonym (compared to StrudelMirror) + settings(settings = {}) { + this.updateSettings(settings); + } + + // the rest is copy pasted from StrudelMirror: + + updateSettings(settings = {}) { + settings.fontSize && this.setFontSize(settings.fontSize); + settings.fontFamily && this.setFontFamily(settings.fontFamily); + for (let key in extensions) { + if (key in settings) { + this.reconfigureExtension(key, settings[key]); + } + } + const updated = { ...codemirrorSettings.get(), ...settings }; + // console.log(updated); + codemirrorSettings.set(updated); + } + reconfigureExtension(key, value) { + if (!extensions[key]) { + console.warn(`extension ${key} is not known`); + return; + } + value = parseBooleans(value); + const newValue = extensions[key](value, this); + this.editor.dispatch({ + effects: compartments[key].reconfigure(newValue), + }); + if (key === 'theme') { + activateTheme(value); + } + } + flash(ms) { + flash(this.editor, ms); + } + setFontSize(size) { + this.root.style.fontSize = size + 'px'; + } + setFontFamily(family) { + this.root.style.fontFamily = family; + const scroller = this.root.querySelector('.cm-scroller'); + if (scroller) { + scroller.style.fontFamily = family; + } + } + setCode(code, offset = 0) { + const changes = { + from: 0, + to: this.editor.state.doc.length + offset, + insert: code, + }; + this.editor.dispatch({ changes }); + } + getCursorLocation() { + return this.editor.state.selection.main.head; + } + setCursorLocation(col) { + return this.editor.dispatch({ selection: { anchor: col } }); + } + appendCode(code) { + const cursor = this.getCursorLocation(); + this.setCode(this.code + code); + this.setCursorLocation(cursor); + } +} diff --git a/website/src/components/Dough/dough-repl.mjs b/website/src/components/Dough/dough-repl.mjs new file mode 100644 index 000000000..643647085 --- /dev/null +++ b/website/src/components/Dough/dough-repl.mjs @@ -0,0 +1,105 @@ +// import { Dough, doughsamples } from 'dough-synth'; +import { Dough, doughsamples } from 'https://unpkg.com/dough-synth@0.1.9/dough.js'; +import { Pattern, noteToMidi, evaluate } from '@strudel/core'; +// import doughUrl from 'dough-synth?url'; +import { transpiler } from '@strudel/transpiler'; +//const doughBaseUrl = doughUrl.split('/').slice(0, -1).join('/') + '/'; +const doughBaseUrl = 'https://unpkg.com/dough-synth@0.1.9/'; + +export class DoughRepl { + pattern; + latency = 0.1; + cps = 0.5; + origin; + t0; + lasttime; + strudel; + q = []; + constructor() { + this.ready = this.init(); + } + async init() { + // init dough immediately, so that it can attach the document click event to initAudio immediately + this.dough = new Dough({ + base: doughBaseUrl, + //base: "../", // local dev + onTick: ({ t0, t1 }) => { + if (!this.pattern) { + return; + } + this.origin ??= t0; + this.t0 = t0; + this.lasttime = performance.now(); + const a = (t0 - this.origin) * this.cps; + const b = (t1 - this.origin) * this.cps; + + const haps = this.pattern.queryArc(a, b).filter((hap) => hap.hasOnset()); + if (!haps.length) { + return; + } + haps.forEach((hap) => { + const time = hap.whole.begin.valueOf() / this.cps + this.origin + this.latency; + const duration = hap.duration.valueOf() / this.cps; + const event = { + dough: 'play', + ...hap.value, + time, + duration, + }; + if (event.note && typeof event.note === 'string') { + event.note = noteToMidi(event.note); + } + //console.log("event", JSON.stringify(Object.entries(event))); + this.dough.evaluate(event); + this.q.push({ event, hap }); + }); + }, + }); + // miniAllStrings(); + const setcps = (cps) => (this.cps = cps); + const setcpm = (cpm) => setcps(cpm / 60); + const replScope = { setcps, setcpm }; + Object.assign(globalThis, replScope); + } + async evaluate(code) { + await this.ready; + let patterns = []; + Pattern.prototype.p = function (id) { + if (!id.startsWith('_')) { + patterns.push(this); + } + }; + + let { meta } = await evaluate(code, transpiler, { addReturn: false, wrapAsync: true, emitWidgets: false }); + const { miniLocations } = meta; + + this.pattern = stack(...patterns); + return { miniLocations, pattern: this.pattern }; + } + stop() { + this.pattern = undefined; + this.origin = undefined; + } + prebake() { + return Promise.all([doughsamples('github:eddyflux/crate')]); + } + // tbd: move this to dough-synth + get time() { + return this.t0 + (performance.now() - this.lasttime) / 1000 + this.latency; + } + processHaps() { + const currentHaps = []; + const time = this.time; + this.q = this.q.filter(({ event, hap }) => { + const end = event.time + event.duration; + const isActive = time >= event.time && time <= end; + if (isActive) { + currentHaps.push(hap); + } + return end > time; // delete old events + // we do NOT return !isActive, because a frame might miss an event, which would cause a leak + }); + // console.log(this.q.length); // to check for leaks + return currentHaps; + } +} diff --git a/website/src/pages/dough/index.astro b/website/src/pages/dough/index.astro new file mode 100644 index 000000000..9909fc6da --- /dev/null +++ b/website/src/pages/dough/index.astro @@ -0,0 +1,15 @@ +--- +import HeadCommon from '../../components/HeadCommon.astro'; +import Dough from '../../components/Dough/Dough.astro'; +--- + + + + + Strudel Dough REPL + + + + + + From 3b1c75d3de236d942f0191d54c237c9ed950547e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 17 Nov 2025 23:18:31 +0100 Subject: [PATCH 02/10] fix: linting errors --- website/src/components/Dough/dough-mirror.mjs | 2 +- website/src/components/Dough/dough-repl.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/components/Dough/dough-mirror.mjs b/website/src/components/Dough/dough-mirror.mjs index f20fd2b4c..5cdfeb3f6 100644 --- a/website/src/components/Dough/dough-mirror.mjs +++ b/website/src/components/Dough/dough-mirror.mjs @@ -48,7 +48,7 @@ export class DoughMirror { // init event highlighting this.framer = new Framer( - () => { + (time) => { const frameHaps = this.repl.processHaps(); highlightMiniLocations(this.editor, time, frameHaps); }, diff --git a/website/src/components/Dough/dough-repl.mjs b/website/src/components/Dough/dough-repl.mjs index 643647085..ef28617e8 100644 --- a/website/src/components/Dough/dough-repl.mjs +++ b/website/src/components/Dough/dough-repl.mjs @@ -1,6 +1,6 @@ // import { Dough, doughsamples } from 'dough-synth'; import { Dough, doughsamples } from 'https://unpkg.com/dough-synth@0.1.9/dough.js'; -import { Pattern, noteToMidi, evaluate } from '@strudel/core'; +import { Pattern, noteToMidi, evaluate, stack } from '@strudel/core'; // import doughUrl from 'dough-synth?url'; import { transpiler } from '@strudel/transpiler'; //const doughBaseUrl = doughUrl.split('/').slice(0, -1).join('/') + '/'; From a9752fb2a45104f7101c1b9e3dd653a3dd5fab8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Bj=C3=B6rk?= Date: Sat, 6 Dec 2025 19:54:33 +0100 Subject: [PATCH 03/10] Exposing Vim from codemirror-vim Docs for adding keybindings Only expose Vim from keybindings --- packages/codemirror/index.mjs | 1 + packages/codemirror/keybindings.mjs | 2 ++ website/src/pages/technical-manual/vim.mdx | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/packages/codemirror/index.mjs b/packages/codemirror/index.mjs index 3a5f2a23c..2d5b3de21 100644 --- a/packages/codemirror/index.mjs +++ b/packages/codemirror/index.mjs @@ -4,3 +4,4 @@ export * from './flash.mjs'; export * from './slider.mjs'; export * from './themes.mjs'; export * from './widget.mjs'; +export { Vim } from './keybindings.mjs'; diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs index d92e9c959..24437b7e1 100644 --- a/packages/codemirror/keybindings.mjs +++ b/packages/codemirror/keybindings.mjs @@ -131,6 +131,8 @@ const keymaps = { vscode: vscodeExtension, }; +export { Vim } from '@replit/codemirror-vim'; + export function keybindings(name) { const active = keymaps[name]; const extensions = active ? [Prec.high(active())] : []; diff --git a/website/src/pages/technical-manual/vim.mdx b/website/src/pages/technical-manual/vim.mdx index fa53aa550..6ec41d4a2 100644 --- a/website/src/pages/technical-manual/vim.mdx +++ b/website/src/pages/technical-manual/vim.mdx @@ -35,3 +35,24 @@ Troubleshooting - If :w logs but evaluation doesn't apply, ensure Vim keybindings are active and try again. You can also use Ctrl+Enter as a fallback. - For :q / gc, ensure focus is inside the editor. If an error occurs, reload the page to reset editor state and try again. + +## Adding custom keybindings + +To add custom keybindings the `Vim` object can be used (either from within a pattern or in a prebake script) + +Example: + +```javascript +// Map 'jk' to Escape in normal mode +Vim.map('jk', '', 'insert'); +// Map 'U' to :w (Evaluate the current code) +Vim.map('U', ':w', 'normal'); +// Map 'Q' to :q — Stop/pause playback +Vim.map('Q', ':q', 'normal'); +// Map 'J' to find next '$' (jump to next label) +Vim.map('J', '/\\$', 'normal'); +// Map 'K' to find previous '$' (jump to previous label) +Vim.map('K', '?\\$', 'normal'); +``` + +For more information on how to use the `Vim` object see [CodeMirror Vim](https://github.com/replit/codemirror-vim) From c7a2c2bb9c37a4a334747330260048742c93d683 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Sun, 7 Dec 2025 00:02:24 +0100 Subject: [PATCH 04/10] Say that @license should use SPDX identifier --- website/src/pages/learn/metadata.mdx | 34 +++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/website/src/pages/learn/metadata.mdx b/website/src/pages/learn/metadata.mdx index 61db16f0e..c94257925 100644 --- a/website/src/pages/learn/metadata.mdx +++ b/website/src/pages/learn/metadata.mdx @@ -11,9 +11,9 @@ import { JsDoc } from '../../docs/JsDoc'; You can optionally add some music metadata in your Strudel code, by using tags in code comments: ```js -// @title Hey Hoo -// @by Sam Tagada -// @license CC BY-NC-SA +// @title My Cool Song +// @by John Doe +// @license CC-BY-SA-4.0 ``` Like other comments, those are ignored by Strudel, but it can be used by other tools to retrieve some information about the music. @@ -24,22 +24,22 @@ You can also use comment blocks: ```js /* -@title Hey Hoo -@by Sam Tagada -@license CC BY-NC-SA +@title My Cool Song +@by John Doe +@license CC-BY-SA-4.0 */ ``` Or define multiple tags in one line: ```js -// @title Hey Hoo @by Sam Tagada @license CC BY-NC-SA +// @title My Cool Song @by John Doe @license CC-BY-SA-4.0 ``` The `title` tag has an alternative syntax using quotes (must be defined at the very begining): ```js -// "Hey Hoo" @by Sam Tagada +// "My Cool Song" @by John Doe ``` ## Tags list @@ -48,20 +48,22 @@ Available tags are: - `@title`: music title - `@by`: music author(s), separated by comma, eventually followed with a link in `<>` (ex: `@by John Doe `) -- `@license`: music license(s), e.g. CC BY-NC-SA. Unsure? [Choose a creative commons license here](https://creativecommons.org/choose/) +- `@license`: music license(s), separated by comma. Each license should be specified by using the correct identifier in the [https://spdx.org/licenses/](SPDX License List). Example: CC-BY-SA-4.0. Unsure? [Choose a Creative Commons license here](https://creativecommons.org/choose/). - `@details`: some additional information about the music -- `@url`: web page(s) related to the music (git repo, soundcloud link, etc.) -- `@genre`: music genre(s) (pop, jazz, etc) +- `@url`: web page(s) related to the music (git repository, Soundcloud link, etc.) +- `@genre`: music genre(s) (pop, jazz, etc.) - `@album`: music album name +Note to tool authors: _Never_ trust that a song has filled those fields with syntactically correct values; make sure your software is robust enough it doesn't break if it encounters bad values + ## Multiple values Some of them accepts several values, using the comma or new line separator, or duplicating the tag: ```js /* -@by Sam Tagada - Jimmy +@by John Doe + Jane Doe @genre pop, jazz @url https://example.com @url https://example.org @@ -72,11 +74,11 @@ You can also add optional prefixes and use tags where you want: ```js /* -song @by Sam Tagada -samples @by Jimmy +song @by John Doe +samples @by Jane Doe */ ... -note("a3 c#4 e4 a4") // @by Sandy +note("a3 c#4 e4 a4") // @by Sandy Sue ``` ## Multiline From e286ab2830f5681e1c92ad9f6bfd6913becbf3c5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 28 Dec 2025 13:10:37 +0100 Subject: [PATCH 05/10] no default samples for now + add doughsamples to scope --- website/src/components/Dough/dough-repl.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/src/components/Dough/dough-repl.mjs b/website/src/components/Dough/dough-repl.mjs index ef28617e8..c8b47a3e8 100644 --- a/website/src/components/Dough/dough-repl.mjs +++ b/website/src/components/Dough/dough-repl.mjs @@ -6,6 +6,8 @@ import { transpiler } from '@strudel/transpiler'; //const doughBaseUrl = doughUrl.split('/').slice(0, -1).join('/') + '/'; const doughBaseUrl = 'https://unpkg.com/dough-synth@0.1.9/'; +Object.assign(globalThis, { doughsamples }); + export class DoughRepl { pattern; latency = 0.1; @@ -81,7 +83,9 @@ export class DoughRepl { this.origin = undefined; } prebake() { - return Promise.all([doughsamples('github:eddyflux/crate')]); + return Promise.all([ + // doughsamples('github:eddyflux/crate') + ]); } // tbd: move this to dough-synth get time() { From 53e49952d8942b7d78393e8626ec32a5808006a2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 28 Dec 2025 13:10:57 +0100 Subject: [PATCH 06/10] persist code in hash --- website/src/components/Dough/dough-mirror.mjs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/website/src/components/Dough/dough-mirror.mjs b/website/src/components/Dough/dough-mirror.mjs index 5cdfeb3f6..2626787ed 100644 --- a/website/src/components/Dough/dough-mirror.mjs +++ b/website/src/components/Dough/dough-mirror.mjs @@ -9,12 +9,23 @@ import { updateMiniLocations, highlightMiniLocations, } from '@strudel/codemirror'; -import { evalScope } from '@strudel/core'; +import { evalScope, hash2code } from '@strudel/core'; import { Framer } from '@strudel/draw'; import { persistentAtom } from '@nanostores/persistent'; import { DoughRepl } from './dough-repl.mjs'; -const initialCode = '$: note("c a f e")'; +let initialCode = '$: note("c a f e")'; +if (typeof window !== 'undefined') { + try { + const codeParam = window.location.href.split('#')[1] || ''; + if (codeParam) { + initialCode = hash2code(codeParam); + } + } catch (err) { + console.error('could not init code from url'); + } +} + export const code = persistentAtom('vanilla-repl-code', initialCode, { encode: JSON.stringify, decode: JSON.parse, @@ -64,6 +75,7 @@ export class DoughMirror { this.flash(); await this.prebaked; const { miniLocations } = await this.repl.evaluate(this.code); + window.location.hash = '#' + code2hash(this.code); updateMiniLocations(this.editor, miniLocations); } stop() { From 125eece2237d7fd361f731156a6f47d165ec2486 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 28 Dec 2025 13:20:22 +0100 Subject: [PATCH 07/10] fix: import --- website/src/components/Dough/dough-mirror.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/components/Dough/dough-mirror.mjs b/website/src/components/Dough/dough-mirror.mjs index 2626787ed..b7794d2db 100644 --- a/website/src/components/Dough/dough-mirror.mjs +++ b/website/src/components/Dough/dough-mirror.mjs @@ -9,7 +9,7 @@ import { updateMiniLocations, highlightMiniLocations, } from '@strudel/codemirror'; -import { evalScope, hash2code } from '@strudel/core'; +import { evalScope, hash2code, code2hash } from '@strudel/core'; import { Framer } from '@strudel/draw'; import { persistentAtom } from '@nanostores/persistent'; import { DoughRepl } from './dough-repl.mjs'; From 300a1bbfd51dc763e0adc20ff0a6755a33fe6ff2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 28 Dec 2025 14:07:27 +0100 Subject: [PATCH 08/10] fix: actually load code from url --- website/src/components/Dough/dough-mirror.mjs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/website/src/components/Dough/dough-mirror.mjs b/website/src/components/Dough/dough-mirror.mjs index b7794d2db..7784402e9 100644 --- a/website/src/components/Dough/dough-mirror.mjs +++ b/website/src/components/Dough/dough-mirror.mjs @@ -15,22 +15,24 @@ import { persistentAtom } from '@nanostores/persistent'; import { DoughRepl } from './dough-repl.mjs'; let initialCode = '$: note("c a f e")'; -if (typeof window !== 'undefined') { - try { - const codeParam = window.location.href.split('#')[1] || ''; - if (codeParam) { - initialCode = hash2code(codeParam); - } - } catch (err) { - console.error('could not init code from url'); - } -} export const code = persistentAtom('vanilla-repl-code', initialCode, { encode: JSON.stringify, decode: JSON.parse, }); +if (typeof window !== 'undefined') { + try { + const codeParam = window.location.href.split('#')[1] || ''; + if (codeParam) { + const codeFromHash = hash2code(codeParam); + code.set(codeFromHash); + } + } catch (err) { + console.error('could not init code from url'); + } +} + export class DoughMirror { constructor(options) { const { root, initialCode = code.get(), bgFill = true } = options; From 30fe5b47e38ec768c34186cfd348e3b891c83f4b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 28 Dec 2025 22:42:42 +0100 Subject: [PATCH 09/10] hotfix: fix scope and friends #1847 --- packages/superdough/superdough.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 4fd6164dc..e98a2aa22 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -359,7 +359,9 @@ export let analysers = {}, analysersData = {}; export function getAnalyserById(id, fftSize = 1024, smoothingTimeConstant = 0.5) { - if (!analysers[id] || analysers[id].audioContext != getAudioContext()) { + // below commented out branch is hotfixing https://codeberg.org/uzu/strudel/issues/1847 + // might cause conflicts with exporting... + if (!analysers[id] /* || analysers[id].audioContext != getAudioContext() */) { // make sure this doesn't happen too often as it piles up garbage const analyserNode = getAudioContext().createAnalyser(); analyserNode.fftSize = fftSize; From c0f4f4715026d909cf3339e370a3298027f554a7 Mon Sep 17 00:00:00 2001 From: jeromew Date: Mon, 29 Dec 2025 14:04:40 +0000 Subject: [PATCH 10/10] Fix AudioContext change detection. Use AudioNode.context --- packages/superdough/superdough.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index e98a2aa22..1ce91c835 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -359,9 +359,7 @@ export let analysers = {}, analysersData = {}; export function getAnalyserById(id, fftSize = 1024, smoothingTimeConstant = 0.5) { - // below commented out branch is hotfixing https://codeberg.org/uzu/strudel/issues/1847 - // might cause conflicts with exporting... - if (!analysers[id] /* || analysers[id].audioContext != getAudioContext() */) { + if (!analysers[id] || analysers[id].context != getAudioContext()) { // make sure this doesn't happen too often as it piles up garbage const analyserNode = getAudioContext().createAnalyser(); analyserNode.fftSize = fftSize;