From c8cf1dc712847d9802b3a527ed337a8e75303943 Mon Sep 17 00:00:00 2001 From: Chandler Abraham Date: Tue, 22 Jul 2025 21:49:40 -0700 Subject: [PATCH 01/20] fix(midi): ensure midin initializes device state correctly The midin function only initialized the refs object for a device on the initial MIDI setup. This caused an error if a second MIDI device was connected in the same session, as its refs object would not exist, leading to a 'cannot read properties of undefined' error when accessed. This commit fixes the issue by ensuring that the refs object for a given MIDI input is initialized every time is called, not just on the first call. --- packages/midi/midi.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index ce7cdb0e2..faa45f32d 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -493,6 +493,9 @@ export async function midin(input) { otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' }`, ); + } + // ensure refs for this input are initialized + if (!refs[input]) { refs[input] = {}; } const cc = (cc) => ref(() => refs[input][cc] || 0); From 3292f888108cd4633ac869ba625809ef07e6d761 Mon Sep 17 00:00:00 2001 From: robase <11038379+robase@users.noreply.github.com> Date: Sun, 27 Jul 2025 18:50:37 +0100 Subject: [PATCH 02/20] fix: repl autocomplete not rendering correctly --- packages/codemirror/autocomplete.mjs | 123 ++++++++++++++----------- website/src/repl/Repl.css | 131 +++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 53 deletions(-) diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs index 203ab8556..d3091d874 100644 --- a/packages/codemirror/autocomplete.mjs +++ b/packages/codemirror/autocomplete.mjs @@ -1,68 +1,91 @@ import jsdoc from '../../doc.json'; -// import { javascriptLanguage } from '@codemirror/lang-javascript'; import { autocompletion } from '@codemirror/autocomplete'; import { h } from './html'; -function plaintext(str) { +const escapeHtml = (str) => { const div = document.createElement('div'); div.innerText = str; return div.innerHTML; -} +}; -const getDocLabel = (doc) => doc.name || doc.longname; -const getInnerText = (html) => { - var div = document.createElement('div'); +const stripHtml = (html) => { + const div = document.createElement('div'); div.innerHTML = html; return div.textContent || div.innerText || ''; }; -export function Autocomplete({ doc, label }) { - return h`
-

${label || getDocLabel(doc)}

-${doc.description} - -
- ${doc.examples?.map((example) => `
${plaintext(example)}
`)} -
-
`[0]; - /* -
 {
-  console.log('ola!');
-  navigator.clipboard.writeText(example);
-  e.stopPropagation();
-}}
->
-{example}
-
-*/ -} +const getDocLabel = (doc) => doc.name || doc.longname; + +const buildParamsList = (params) => + params?.length + ? ` +
+

Parameters

+ +
+ ` + : ''; + +const buildExamples = (examples) => + examples?.length + ? ` +
+

Examples

+ ${examples + .map( + (example) => ` +
${escapeHtml(example)}
+ `, + ) + .join('')} +
+ ` + : ''; + +export const Autocomplete = ({ doc, label }) => + h` +
+

${label || getDocLabel(doc)}

+ ${doc.description ? `

${doc.description}

` : ''} + ${buildParamsList(doc.params)} + ${buildExamples(doc.examples)} +
+`[0]; + +const isValidDoc = (doc) => { + const label = getDocLabel(doc); + return label && !label.startsWith('_') && !['package'].includes(doc.kind); +}; + +const hasExcludedTags = (doc) => + ['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)); const jsdocCompletions = jsdoc.docs - .filter( - (doc) => - getDocLabel(doc) && - !getDocLabel(doc).startsWith('_') && - !['package'].includes(doc.kind) && - !['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)), - ) - // https://codemirror.net/docs/ref/#autocomplete.Completion - .map((doc) /*: Completion */ => ({ + .filter((doc) => isValidDoc(doc) && !hasExcludedTags(doc)) + // https://codemirror.net/docs/ref/#autocomplete.Completion + .map((doc) => ({ label: getDocLabel(doc), // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. info: () => Autocomplete({ doc }), type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type })); -export const strudelAutocomplete = (context /* : CompletionContext */) => { - let word = context.matchBefore(/\w*/); - if (word.from == word.to && !context.explicit) return null; +export const strudelAutocomplete = (context) => { + const word = context.matchBefore(/\w*/); + if (word.from === word.to && !context.explicit) return null; + return { from: word.from, options: jsdocCompletions, @@ -74,11 +97,5 @@ export const strudelAutocomplete = (context /* : CompletionContext */) => { }; }; -export function isAutoCompletionEnabled(on) { - return on - ? [ - autocompletion({ override: [strudelAutocomplete] }), - //javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }), - ] - : []; // autocompletion({ override: [] }) -} +export const isAutoCompletionEnabled = (enabled) => + enabled ? [autocompletion({ override: [strudelAutocomplete] })] : []; diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index 3e13ff5a2..5f1d427e6 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -69,3 +69,134 @@ text-decoration: underline 0.18rem; text-underline-offset: 0.22rem; } + +/* Override default styles from the codemirror inline css for autocomplete info tooltip*/ +.cm-tooltip.cm-completionInfo { + padding: 0 !important; + background: #1e1e1e !important; + border: 1px solid #3a3a3a !important; + border-radius: 4px !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important; + max-width: 500px !important; + min-width: 300px !important; + max-height: 400px !important; + white-space: normal !important; +} + +/* Main tooltip container */ +.autocomplete-info-tooltip { + padding: 12px; + color: #d4d4d4; + font-family: 'SF Mono', 'Monaco', monospace; + font-size: 13px; + line-height: 1.4; + overflow-y: auto; + max-width: 600px; + max-height: 400px; +} + +/* Function name */ +.autocomplete-info-function-name { + font-size: 15px; + font-weight: 600; + color: #ffffff; + margin: 0 0 8px 0; +} + +/* Function description */ +.autocomplete-info-function-description { + margin: 0 0 12px 0; + color: #b4b4b4; + line-height: 1.5; +} + +/* Section titles */ +.autocomplete-info-section-title { + font-size: 12px; + font-weight: 600; + color: #ffffff; + margin: 16px 0 6px 0; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.autocomplete-info-section-title:first-child { + margin-top: 0; +} + +/* Parameters */ +.autocomplete-info-params-section { + margin-top: 12px; +} + +.autocomplete-info-params-list { + list-style: none; + margin: 0; + padding: 0; +} + +.autocomplete-info-param-item { + margin-bottom: 8px; + padding: 8px; + background: #2a2a2a; + border-radius: 3px; + border-left: 2px solid #555; +} + +.autocomplete-info-param-item:last-child { + margin-bottom: 0; +} + +.autocomplete-info-param-name { + font-weight: 600; + color: #ffffff; + margin-right: 8px; +} + +.autocomplete-info-param-type { + color: #888; + font-size: 12px; + background: #333; + padding: 1px 4px; + border-radius: 2px; +} + +.autocomplete-info-param-desc { + color: #b4b4b4; + font-size: 10px; + margin-top: 4px; + line-height: 1.4; +} + +/* Examples */ +.autocomplete-info-examples-section { + margin-top: 12px; +} + +.autocomplete-info-example-code { + background: #2a2a2a; + color: #d4d4d4; + padding: 8px; + border-radius: 3px; + font-family: 'SF Mono', 'Monaco', monospace; + font-size: 12px; + line-height: 1.5; + margin: 4px 0; + overflow-x: auto; + white-space: pre; + border: 1px solid #3a3a3a; +} + +/* Scrollbar */ +.autocomplete-info-tooltip::-webkit-scrollbar { + width: 4px; +} + +.autocomplete-info-tooltip::-webkit-scrollbar-track { + background: #2a2a2a; +} + +.autocomplete-info-tooltip::-webkit-scrollbar-thumb { + background: #555; + border-radius: 2px; +} From e7839a09a1c12fe3d803b747fe41289280a8ebac Mon Sep 17 00:00:00 2001 From: robase <11038379+robase@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:02:06 +0100 Subject: [PATCH 03/20] fix: tooltip should respect themes --- website/src/repl/Repl.css | 57 ++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index 5f1d427e6..3a5a6eb23 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -71,50 +71,54 @@ } /* Override default styles from the codemirror inline css for autocomplete info tooltip*/ +/* Override default styles from the c odemirror inline css for autocomplete info tooltip*/ .cm-tooltip.cm-completionInfo { - padding: 0 !important; - background: #1e1e1e !important; - border: 1px solid #3a3a3a !important; + padding: 12px !important; + padding-bottom: 12px !important; + border: 1px solid var(--foreground) !important; border-radius: 4px !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important; max-width: 500px !important; min-width: 300px !important; max-height: 400px !important; white-space: normal !important; + overflow: auto !important; + background-color: var(--lineHighlight) !important; } /* Main tooltip container */ .autocomplete-info-tooltip { - padding: 12px; - color: #d4d4d4; - font-family: 'SF Mono', 'Monaco', monospace; - font-size: 13px; + border-radius: 4px !important; + color: var(--foreground); + font-family: var(--font-family, 'SF Mono', 'Monaco', monospace); + font-size: var(--font-size, 13px); line-height: 1.4; - overflow-y: auto; max-width: 600px; max-height: 400px; + min-width: 400px; } /* Function name */ .autocomplete-info-function-name { font-size: 15px; font-weight: 600; - color: #ffffff; + color: var(--foreground); margin: 0 0 8px 0; } /* Function description */ .autocomplete-info-function-description { margin: 0 0 12px 0; - color: #b4b4b4; + color: var(--foreground); line-height: 1.5; + opacity: 0.8; } /* Section titles */ .autocomplete-info-section-title { font-size: 12px; font-weight: 600; - color: #ffffff; + color: var(--foreground); margin: 16px 0 6px 0; text-transform: uppercase; letter-spacing: 0.5px; @@ -138,9 +142,9 @@ .autocomplete-info-param-item { margin-bottom: 8px; padding: 8px; - background: #2a2a2a; + background-color: var(--lineBackground); border-radius: 3px; - border-left: 2px solid #555; + border-left: 2px solid var(--foreground, #555); } .autocomplete-info-param-item:last-child { @@ -149,23 +153,24 @@ .autocomplete-info-param-name { font-weight: 600; - color: #ffffff; + color: var(--variable, var(--foreground)); margin-right: 8px; } .autocomplete-info-param-type { - color: #888; + color: var(--comment); font-size: 12px; - background: #333; + background-color: var(--gutterForeground); padding: 1px 4px; border-radius: 2px; } .autocomplete-info-param-desc { - color: #b4b4b4; + color: var(--foreground); font-size: 10px; margin-top: 4px; line-height: 1.4; + opacity: 0.7; } /* Examples */ @@ -174,29 +179,33 @@ } .autocomplete-info-example-code { - background: #2a2a2a; - color: #d4d4d4; + background: var(--lineBackground); + color: var(--foreground); padding: 8px; border-radius: 3px; - font-family: 'SF Mono', 'Monaco', monospace; + font-family: var(--font-family, 'SF Mono', 'Monaco', monospace); font-size: 12px; line-height: 1.5; margin: 4px 0; overflow-x: auto; white-space: pre; - border: 1px solid #3a3a3a; + border: 1px solid var(--foreground, #3a3a3a); } -/* Scrollbar */ +/* Scrollbar - using theme colors */ .autocomplete-info-tooltip::-webkit-scrollbar { width: 4px; } .autocomplete-info-tooltip::-webkit-scrollbar-track { - background: #2a2a2a; + /* background: var(--lineBackground, var(--background)); */ } .autocomplete-info-tooltip::-webkit-scrollbar-thumb { - background: #555; + /* background: var(--border, #555); */ border-radius: 2px; } + +.autocomplete-info-tooltip::-webkit-scrollbar-thumb:hover { + /* background: var(--selection, var(--border, #777)); */ +} From 612efad3eb47ce376281a7fd3043338538ab842f Mon Sep 17 00:00:00 2001 From: robase <11038379+robase@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:09:58 +0100 Subject: [PATCH 04/20] clean up comments --- website/src/repl/Repl.css | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index 3a5a6eb23..62e7dcf24 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -71,7 +71,6 @@ } /* Override default styles from the codemirror inline css for autocomplete info tooltip*/ -/* Override default styles from the c odemirror inline css for autocomplete info tooltip*/ .cm-tooltip.cm-completionInfo { padding: 12px !important; padding-bottom: 12px !important; @@ -98,7 +97,6 @@ min-width: 400px; } -/* Function name */ .autocomplete-info-function-name { font-size: 15px; font-weight: 600; @@ -106,7 +104,6 @@ margin: 0 0 8px 0; } -/* Function description */ .autocomplete-info-function-description { margin: 0 0 12px 0; color: var(--foreground); @@ -114,7 +111,6 @@ opacity: 0.8; } -/* Section titles */ .autocomplete-info-section-title { font-size: 12px; font-weight: 600; @@ -128,7 +124,6 @@ margin-top: 0; } -/* Parameters */ .autocomplete-info-params-section { margin-top: 12px; } @@ -173,7 +168,6 @@ opacity: 0.7; } -/* Examples */ .autocomplete-info-examples-section { margin-top: 12px; } @@ -192,20 +186,17 @@ border: 1px solid var(--foreground, #3a3a3a); } -/* Scrollbar - using theme colors */ .autocomplete-info-tooltip::-webkit-scrollbar { width: 4px; } .autocomplete-info-tooltip::-webkit-scrollbar-track { - /* background: var(--lineBackground, var(--background)); */ } .autocomplete-info-tooltip::-webkit-scrollbar-thumb { - /* background: var(--border, #555); */ border-radius: 2px; } .autocomplete-info-tooltip::-webkit-scrollbar-thumb:hover { - /* background: var(--selection, var(--border, #777)); */ + } From 7d24e4569279d567b10789102391a49bf858dffc Mon Sep 17 00:00:00 2001 From: fyynn Date: Mon, 18 Aug 2025 23:36:39 +0200 Subject: [PATCH 05/20] Added scrub() to Learn Docs Signed-off-by: fyynn --- website/src/pages/learn/samples.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index bbe650cf5..dbcb8acb2 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -361,6 +361,10 @@ Sampler effects are functions that can be used to change the behaviour of sample +### scrub + + + ### speed From f7f1bd63e868126090adff8861ed8ea9c1e189f0 Mon Sep 17 00:00:00 2001 From: fyynn Date: Mon, 18 Aug 2025 23:49:43 +0200 Subject: [PATCH 06/20] Adding the Duck/Sidechain effect to Learn Docs (learn/effects.mdx) Added the duck Effect to the section global effects. Signed-off-by: fyynn --- website/src/pages/learn/effects.mdx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index 8f6a3b8aa..89f36fe96 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -339,4 +339,18 @@ global effects use the same chain for all events of the same orbit: +## Duck + +### duckorbit + + + +### duckattack + + + +### duckdepth + + + Next, we'll look at input / output via [MIDI, OSC and other methods](/learn/input-output). From d009b9592ec7b8975294182399d9a0891855e863 Mon Sep 17 00:00:00 2001 From: fyynn Date: Tue, 19 Aug 2025 00:09:41 +0200 Subject: [PATCH 07/20] Added Refrence to arp() arpWidth() and hush to Docs Signed-off-by: fyynn --- website/src/pages/learn/conditional-modifiers.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/pages/learn/conditional-modifiers.mdx b/website/src/pages/learn/conditional-modifiers.mdx index c2e22595b..b408883a9 100644 --- a/website/src/pages/learn/conditional-modifiers.mdx +++ b/website/src/pages/learn/conditional-modifiers.mdx @@ -34,11 +34,11 @@ import { JsDoc } from '../../docs/JsDoc'; ## arp - + ## arpWith 🧪 - + ## struct @@ -58,7 +58,7 @@ import { JsDoc } from '../../docs/JsDoc'; ## hush - + ## invert From 189daa3942aa8518f6da85c3f263015bb1859a88 Mon Sep 17 00:00:00 2001 From: fyynn Date: Tue, 19 Aug 2025 00:21:32 +0200 Subject: [PATCH 08/20] Added Refrence to arp() arpWidth() to Docs Signed-off-by: fyynn --- website/src/pages/learn/conditional-modifiers.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/pages/learn/conditional-modifiers.mdx b/website/src/pages/learn/conditional-modifiers.mdx index b408883a9..ea4b92f06 100644 --- a/website/src/pages/learn/conditional-modifiers.mdx +++ b/website/src/pages/learn/conditional-modifiers.mdx @@ -34,11 +34,11 @@ import { JsDoc } from '../../docs/JsDoc'; ## arp - + ## arpWith 🧪 - + ## struct From 36441e7b73a2b1d8dad141bde27da5807714961e Mon Sep 17 00:00:00 2001 From: fyynn Date: Tue, 19 Aug 2025 00:25:31 +0200 Subject: [PATCH 09/20] Fixing hush refrence to code documentation Signed-off-by: fyynn --- website/src/pages/learn/conditional-modifiers.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/learn/conditional-modifiers.mdx b/website/src/pages/learn/conditional-modifiers.mdx index ea4b92f06..d31ab0cf0 100644 --- a/website/src/pages/learn/conditional-modifiers.mdx +++ b/website/src/pages/learn/conditional-modifiers.mdx @@ -58,7 +58,7 @@ import { JsDoc } from '../../docs/JsDoc'; ## hush - + ## invert From d6ee10e05c99b08e5e8e2c0645c2183b0301b307 Mon Sep 17 00:00:00 2001 From: fyynn Date: Tue, 19 Aug 2025 00:44:46 +0200 Subject: [PATCH 10/20] fixed new scrub refrence --- website/src/pages/learn/samples.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index dbcb8acb2..475319748 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -363,7 +363,7 @@ Sampler effects are functions that can be used to change the behaviour of sample ### scrub - + ### speed From 215ab878092605c71867096b4dcab619dd9edc29 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 21 Aug 2025 09:41:21 +0200 Subject: [PATCH 11/20] fix: prettier --- packages/codemirror/autocomplete.mjs | 2 +- website/src/pages/learn/samples.mdx | 2 +- website/src/repl/Repl.css | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs index d3091d874..c0845895e 100644 --- a/packages/codemirror/autocomplete.mjs +++ b/packages/codemirror/autocomplete.mjs @@ -74,7 +74,7 @@ const hasExcludedTags = (doc) => const jsdocCompletions = jsdoc.docs .filter((doc) => isValidDoc(doc) && !hasExcludedTags(doc)) - // https://codemirror.net/docs/ref/#autocomplete.Completion + // https://codemirror.net/docs/ref/#autocomplete.Completion .map((doc) => ({ label: getDocLabel(doc), // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index 475319748..0deb60704 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -363,7 +363,7 @@ Sampler effects are functions that can be used to change the behaviour of sample ### scrub - +{' '} ### speed diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index 62e7dcf24..b8443081f 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -198,5 +198,4 @@ } .autocomplete-info-tooltip::-webkit-scrollbar-thumb:hover { - } From 8465517c76aef62b081190cb33419b755f72fa9b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 21 Aug 2025 10:00:41 +0200 Subject: [PATCH 12/20] remove hs2js postinstall --- website/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/package.json b/website/package.json index 499758147..87f4025e4 100644 --- a/website/package.json +++ b/website/package.json @@ -8,8 +8,7 @@ "start": "astro dev", "build": "astro build", "preview": "astro preview --port 3009 --host 0.0.0.0", - "astro": "astro", - "postinstall": "cp node_modules/hs2js/dist/tree-sitter.wasm public && cp node_modules/hs2js/dist/tree-sitter-haskell.wasm public" + "astro": "astro" }, "dependencies": { "@algolia/client-search": "^5.20.0", From 66aa3ac1da2d1151d5bf50222152ec21966376ae Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 21 Aug 2025 20:30:02 +0200 Subject: [PATCH 13/20] add --json flag to strudel sampler --- packages/sampler/README.md | 10 ++++++++++ packages/sampler/sample-server.mjs | 26 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/sampler/README.md b/packages/sampler/README.md index c495c2add..c7b44610a 100644 --- a/packages/sampler/README.md +++ b/packages/sampler/README.md @@ -20,3 +20,13 @@ samples('http://localhost:5432') LOG=1 npx @strudel/sampler # adds logging PORT=5555 npx @strudel/sampler # changes port ``` + +## static json + +when running with `--json`, you will simply get the json logged back: + +```sh +npx @strudel/sampler --json > strudel.json +``` + +this is useful if you want to create a sample pack from the current folder. \ No newline at end of file diff --git a/packages/sampler/sample-server.mjs b/packages/sampler/sample-server.mjs index d1e56108f..08456add9 100644 --- a/packages/sampler/sample-server.mjs +++ b/packages/sampler/sample-server.mjs @@ -10,14 +10,6 @@ import os from 'os'; // eslint-disable-next-line const LOG = !!process.env.LOG || false; -console.log( - cowsay.say({ - text: 'welcome to @strudel/sampler', - e: 'oO', - T: 'U ', - }), -); - async function getFilesInDirectory(directory) { let files = []; const dirents = await readdir(directory, { withFileTypes: true }); @@ -60,8 +52,26 @@ async function getBanks(directory) { return { banks, files }; } +const args = process.argv.slice(2); + // eslint-disable-next-line const directory = process.cwd(); + +if (args.includes('--json')) { + const { banks, files } = await getBanks(directory); + const json = JSON.stringify(banks); + console.log(json); + process.exit(0); +} + +console.log( + cowsay.say({ + text: 'welcome to @strudel/sampler', + e: 'oO', + T: 'U ', + }), +); + const server = http.createServer(async (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); const { banks, files } = await getBanks(directory); From b12707316ac2c0493d9330a9cec9a3a349b7a326 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 21 Aug 2025 20:30:32 +0200 Subject: [PATCH 14/20] bump @strudel/sampler to 0.2.1 --- packages/sampler/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sampler/package.json b/packages/sampler/package.json index b0f27f86a..765f2962d 100644 --- a/packages/sampler/package.json +++ b/packages/sampler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/sampler", - "version": "0.2.0", + "version": "0.2.1", "description": "", "keywords": [ "tidalcycles", From da283eb55a94d4ee9ba9bd68a12c850be6eba439 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 21 Aug 2025 20:36:17 +0200 Subject: [PATCH 15/20] doc: generate strudel.json --- website/src/pages/learn/samples.mdx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index 0deb60704..a87b8f7dd 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -178,6 +178,16 @@ the version number). It is also possible, of course, to just remove it from cache (deleting cache in browser Privacy settings, or from the dev console if you're technically minded, or by using a cache deleting extension). +## Generating strudel.json + +You can use [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) to generate a strudel.json file for you, by running: + +```sh +npx --yes @strudel/sampler --json > strudel.json +``` + +See other uses of strudel/sampler further below, under "From Disk via @strudel/sampler". + ## Github Shortcut Because loading samples from github is common, there is a shortcut: From 42a903ecc080b1615f6b097e8f478aa21ae8837e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 21 Aug 2025 20:36:42 +0200 Subject: [PATCH 16/20] add --yes flag to readme, making sure first run works --- packages/sampler/README.md | 2 +- packages/sampler/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sampler/README.md b/packages/sampler/README.md index c7b44610a..1142176b7 100644 --- a/packages/sampler/README.md +++ b/packages/sampler/README.md @@ -26,7 +26,7 @@ PORT=5555 npx @strudel/sampler # changes port when running with `--json`, you will simply get the json logged back: ```sh -npx @strudel/sampler --json > strudel.json +npx --yes @strudel/sampler --json > strudel.json ``` this is useful if you want to create a sample pack from the current folder. \ No newline at end of file diff --git a/packages/sampler/package.json b/packages/sampler/package.json index 765f2962d..dd701bcfb 100644 --- a/packages/sampler/package.json +++ b/packages/sampler/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/sampler", - "version": "0.2.1", + "version": "0.2.2", "description": "", "keywords": [ "tidalcycles", From b8c98736c5024fc213ed4e84edb92c7a2e5c3c0b Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 23 Aug 2025 17:29:15 +0100 Subject: [PATCH 17/20] fix benchmarks --- packages/core/bench/pattern.bench.mjs | 12 ++++++------ packages/mini/bench/mini.bench.mjs | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/bench/pattern.bench.mjs b/packages/core/bench/pattern.bench.mjs index 1b5be0b9c..56a849801 100644 --- a/packages/core/bench/pattern.bench.mjs +++ b/packages/core/bench/pattern.bench.mjs @@ -1,11 +1,11 @@ import { describe, bench } from 'vitest'; -import { calculateTactus, sequence, stack } from '../index.mjs'; +import { calculateSteps, sequence, stack } from '../index.mjs'; const pat64 = sequence(...Array(64).keys()); describe('steps', () => { - calculateTactus(true); + calculateSteps(true); bench( '+tactus', () => { @@ -14,7 +14,7 @@ describe('steps', () => { { time: 1000 }, ); - calculateTactus(false); + calculateSteps(false); bench( '-tactus', () => { @@ -25,7 +25,7 @@ describe('steps', () => { }); describe('stack', () => { - calculateTactus(true); + calculateSteps(true); bench( '+tactus', () => { @@ -34,7 +34,7 @@ describe('stack', () => { { time: 1000 }, ); - calculateTactus(false); + calculateSteps(false); bench( '-tactus', () => { @@ -43,4 +43,4 @@ describe('stack', () => { { time: 1000 }, ); }); -calculateTactus(true); +calculateSteps(true); diff --git a/packages/mini/bench/mini.bench.mjs b/packages/mini/bench/mini.bench.mjs index 782ac86ba..e7471bf22 100644 --- a/packages/mini/bench/mini.bench.mjs +++ b/packages/mini/bench/mini.bench.mjs @@ -1,10 +1,10 @@ import { describe, bench } from 'vitest'; -import { calculateTactus } from '../../core/index.mjs'; +import { calculateSteps } from '../../core/index.mjs'; import { mini } from '../index.mjs'; describe('mini', () => { - calculateTactus(true); + calculateSteps(true); bench( '+tactus', () => { @@ -13,7 +13,7 @@ describe('mini', () => { { time: 1000 }, ); - calculateTactus(false); + calculateSteps(false); bench( '-tactus', () => { @@ -21,5 +21,5 @@ describe('mini', () => { }, { time: 1000 }, ); - calculateTactus(true); + calculateSteps(true); }); From 64f7bc444288a98410da299b6aa7b1e62b9a3110 Mon Sep 17 00:00:00 2001 From: Aria Date: Tue, 19 Aug 2025 09:12:29 -0500 Subject: [PATCH 18/20] Working version of duck on iOS safari --- packages/superdough/helpers.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index c09acf1a5..1217b5163 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -211,7 +211,14 @@ export function getVibratoOscillator(param, value, t) { export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) { const constantNode = new ConstantSourceNode(audioContext); + // Safari requires audio nodes to be connected in order for their onended events + // to fire, so we _mute it_ and then connect it to the destination + const zeroGain = gainNode(0); + zeroGain.connect(audioContext.destination); + constantNode.connect(zeroGain); constantNode.start(startTime); + + // Schedule the `onComplete` callback to occur at `stopTime` constantNode.stop(stopTime); constantNode.onended = () => { onComplete(); From 659071a4ee6b7a67f0e1a5c46a10c0f854bd3ffa Mon Sep 17 00:00:00 2001 From: Aria Date: Tue, 19 Aug 2025 16:18:48 -0500 Subject: [PATCH 19/20] Ensure zeroGain is cleaned up after use; move start/stop to after callback assigned for safety --- packages/superdough/helpers.mjs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 1217b5163..53922aa2c 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -211,18 +211,25 @@ export function getVibratoOscillator(param, value, t) { export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) { const constantNode = new ConstantSourceNode(audioContext); - // Safari requires audio nodes to be connected in order for their onended events + // Certain browsers requires audio nodes to be connected in order for their onended events // to fire, so we _mute it_ and then connect it to the destination const zeroGain = gainNode(0); zeroGain.connect(audioContext.destination); constantNode.connect(zeroGain); - constantNode.start(startTime); // Schedule the `onComplete` callback to occur at `stopTime` - constantNode.stop(stopTime); constantNode.onended = () => { + // Ensure garbage collection + try { + zeroGain.disconnect(); + } catch {} + try { + constantNode.disconnect(); + } catch {} onComplete(); }; + constantNode.start(startTime); + constantNode.stop(stopTime); return constantNode; } const mod = (freq, range = 1, type = 'sine') => { From d64a0ef0eb8d79d90ff8fb51dce0ef47631f6d1b Mon Sep 17 00:00:00 2001 From: Aria Date: Tue, 19 Aug 2025 16:21:00 -0500 Subject: [PATCH 20/20] Lint requires no empty blocks: pass comment --- packages/superdough/helpers.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 53922aa2c..81bec9399 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -222,10 +222,14 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) { // Ensure garbage collection try { zeroGain.disconnect(); - } catch {} + } catch { + // pass + } try { constantNode.disconnect(); - } catch {} + } catch { + // pass + } onComplete(); }; constantNode.start(startTime);