Compare commits

..

8 Commits

Author SHA1 Message Date
Felix Roos 1f2202e6a1 fix clock timing problems after system sleep deprivation 2026-10-12 23:02:04 +02:00
Felix Roos 63e2810375 throw out setup-node to maybe speed up ci + remove failed cache: pnpm 2026-10-12 21:08:13 +02:00
Felix Roos 262a6d85db fix fast 2026-10-12 19:39:23 +02:00
Felix Roos e66723230b make sure pnpm version is the same locally and in ci 2026-10-12 19:39:05 +02:00
Felix Roos cb06042d3b doc fixes 2026-10-12 19:38:22 +02:00
Felix Roos 88fdb424c4 make linter less violent 2026-10-12 18:30:06 +02:00
Felix Roos 0d4816804e dont cache csound 2026-10-12 18:29:51 +02:00
Felix Roos ce4ef629bf fix some annoying build warnings 2026-10-12 18:29:42 +02:00
28 changed files with 345 additions and 202 deletions
-5
View File
@@ -19,11 +19,6 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: pnpm/action-setup@v4
with:
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
run: pnpm install
+1 -6
View File
@@ -19,12 +19,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: 20
# cache: "pnpm"
- name: Install Dependencies
run: pnpm install
+1 -6
View File
@@ -16,12 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: 20
# cache: "pnpm"
- name: Install Dependencies
run: pnpm install
-11
View File
@@ -6,21 +6,10 @@ jobs:
build:
runs-on: docker
container: node:22-bookworm
strategy:
matrix:
node-version: [20]
steps:
- uses: actions/checkout@v4
- name: apt install ztd
run: apt update && apt install -y zstd
- uses: pnpm/action-setup@v4
with:
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- run: pnpm install
- run: pnpm run format-check
- run: pnpm run lint
+1
View File
@@ -3,6 +3,7 @@
"version": "0.5.0",
"private": true,
"description": "Port of tidalcycles to javascript",
"packageManager": "pnpm@10.15.0",
"scripts": {
"setup": "pnpm i",
"pretest": "npm run jsdoc-json",
+2 -12
View File
@@ -2,23 +2,13 @@ import {
keymap,
highlightSpecialChars,
drawSelection,
highlightActiveLine,
dropCursor,
rectangularSelection,
crosshairCursor,
lineNumbers,
highlightActiveLineGutter,
} from '@codemirror/view';
import {
defaultHighlightStyle,
syntaxHighlighting,
bracketMatching,
foldGutter,
foldKeymap,
} from '@codemirror/language';
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
import { completionKeymap, closeBracketsKeymap } from '@codemirror/autocomplete';
import { closeBracketsKeymap } from '@codemirror/autocomplete';
// Taken + slightly modified from https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
+107 -55
View File
@@ -6,6 +6,7 @@ import { emacs } from '@replit/codemirror-emacs';
import { vim, Vim } from '@replit/codemirror-vim';
// import { vim } from './vim_test.mjs';
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
import { helix, commands } from 'codemirror-helix';
import { logger } from '@strudel/core';
const vscodePlugin = ViewPlugin.fromClass(
@@ -20,6 +21,70 @@ const vscodePlugin = ViewPlugin.fromClass(
);
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
function replEval(view) {
try {
// Dispatch a dedicated evaluate event first
let handled = false;
try {
const ev = new CustomEvent('repl-evaluate', { detail: { source: 'vim', view }, cancelable: true });
handled = document.dispatchEvent(ev) === false; // false means preventDefault was called
} catch (e) {
console.error('Error dispatching repl-evaluate event', e);
}
if (handled) {
return;
}
// Try Ctrl+Enter first if not handled by custom event
const ctrlEnter = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
ctrlKey: true,
bubbles: true,
cancelable: true,
});
view?.dom?.dispatchEvent?.(ctrlEnter);
// If not handled (no handler called preventDefault), try Alt+Enter as
// fallback
if (!ctrlEnter.defaultPrevented) {
const altEnter = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
altKey: true,
bubbles: true,
cancelable: true,
});
view?.dom?.dispatchEvent?.(altEnter);
}
} catch (e) {
console.error('Error dispatching repl evaluation event', e);
}
}
function replStop(view) {
try {
// First try dispatching our custom stop event, then fallback to Alt+.
let handled = false;
try {
const ev = new CustomEvent('repl-stop', { detail: { source: 'vim', view }, cancelable: true });
handled = document.dispatchEvent(ev) === false;
} catch (e) {
console.error('Error dispatching repl-stop event', e);
}
if (!handled) {
const altDot = new KeyboardEvent('keydown', {
key: '.',
code: 'Period',
altKey: true,
bubbles: true,
cancelable: true,
});
view?.dom?.dispatchEvent?.(altDot);
}
} catch (e) {
console.error('Error dispatching repl stop event', e);
}
}
// Map Vim :w to trigger the same action as evaluation. We dispatch a custom
// event 'repl-evaluate' that the editor listens for, and also simulate
// Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it
@@ -47,29 +112,9 @@ try {
// :q to pause/stop
Vim.defineEx('quit', 'q', (cm) => {
try {
const view = cm?.view || cm;
// First try dispatching our custom stop event, then fallback to Alt+.
let handled = false;
try {
const ev = new CustomEvent('repl-stop', { detail: { source: 'vim', view }, cancelable: true });
handled = document.dispatchEvent(ev) === false;
} catch (e) {
console.error('Error dispatching repl-stop event', e);
}
if (!handled) {
const altDot = new KeyboardEvent('keydown', {
key: '.',
code: 'Period',
altKey: true,
bubbles: true,
cancelable: true,
});
view?.dom?.dispatchEvent?.(altDot);
}
} catch (e) {
console.error('Error dispatching :q stop event', e);
}
const view = cm.cm6;
logger('[vim] :q — stopping repl');
replStop(view);
});
// :w to evaluate
@@ -83,38 +128,7 @@ try {
} catch (e) {
console.error('Error logging Vim :w evaluation', e);
}
// Dispatch a dedicated evaluate event first
let handled = false;
try {
const ev = new CustomEvent('repl-evaluate', { detail: { source: 'vim', view }, cancelable: true });
handled = document.dispatchEvent(ev) === false; // false means preventDefault was called
} catch (e) {
console.error('Error dispatching repl-evaluate event', e);
}
if (handled) {
return;
}
// Try Ctrl+Enter first if not handled by custom event
const ctrlEnter = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
ctrlKey: true,
bubbles: true,
cancelable: true,
});
view?.dom?.dispatchEvent?.(ctrlEnter);
// If not handled (no handler called preventDefault), try Alt+Enter as
// fallback
if (!ctrlEnter.defaultPrevented) {
const altEnter = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
altKey: true,
bubbles: true,
cancelable: true,
});
view?.dom?.dispatchEvent?.(altEnter);
}
replEval(view);
} catch (e) {
console.error('Error dispatching :w evaluation event', e);
}
@@ -124,11 +138,49 @@ try {
console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e);
}
// Map Helix :w to trigger the same action as evaluation. We dispatch a custom
// event 'repl-evaluate' that the editor listens for, and also simulate
// Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it
// appears in the Console panel.
const helixCommands = commands.of([
{
// :w to evaluate
name: 'write',
aliases: ['w'],
help: 'Repl-eval',
handler(view, args) {
try {
view?.focus?.(); // Let the app know this came from Helix :w
logger('[helix] :w — evaluating code');
replEval(view);
} catch (e) {
console.error('Error dispatching helix :w evaluation event', e);
}
},
},
{
// :q to pause/stop
name: 'quit',
aliases: ['q'],
help: 'Repl-stop',
handler(view, args) {
try {
view?.focus?.(); // Let the app know this came from Helix :q
logger('[helix] :q — stopping repl');
replStop(view);
} catch (e) {
console.error('Error dispatching helix :q stop event', e);
}
},
},
]);
const keymaps = {
vim,
emacs,
codemirror: () => keymap.of(defaultKeymap),
vscode: vscodeExtension,
helix: () => [helix(), helixCommands],
};
export { Vim } from '@replit/codemirror-vim';
+1
View File
@@ -44,6 +44,7 @@
"@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.3.0",
"@replit/codemirror-vscode-keymap": "^6.0.2",
"codemirror-helix": "^0.5.0",
"@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*",
"@strudel/tonal": "workspace:*",
+2 -2
View File
@@ -519,10 +519,10 @@ export const { fmh, fmh1, fmh2, fmh3, fmh4, fmh5, fmh6, fmh7, fmh8 } = registerM
* each other with matrix commands like `fm13`, which would send `fm1` back into
* `fm3`
*
* @name fmi
* @name fm
* @tags fm, superdough, supradough
* @param {number | Pattern} brightness modulation index
* @synonyms fm
* @synonyms fmi
* @example
* note("c e g b g e")
* .fm("<0 1 2 8 32>")
+2 -2
View File
@@ -2354,8 +2354,8 @@ export const ply = register('ply', function (factor, pat) {
* @example
* s("bd hh sd hh").fast(2) // s("[bd hh sd hh]*2")
*/
export const { fast, density } = register(
['fast', 'density'],
export const fast = register(
'fast',
function (factor, pat) {
if (factor === 0) {
return silence;
+10 -1
View File
@@ -55,6 +55,7 @@ export function parseControlsFromHap(hap, cps) {
return controls;
}
let timeOrigin = performance.timeOrigin;
export async function oscOutput(hap, targetTime, cps = 1) {
const ws = await connect();
@@ -63,7 +64,15 @@ export async function oscOutput(hap, targetTime, cps = 1) {
logger('[osc] clockbridge not ready');
return;
}
const ts = performance.timeOrigin + timeMs;
// when the system sleeps while the browser is open, performance.now() doesnt tick
// .. which means performance.timeOrigin + performance.now() doesnt land in the present
const deprivation = timeOrigin + performance.now() - Date.now();
if (Math.abs(deprivation) > 20) {
console.log(`[osc] snap with ${(deprivation / 1000).toFixed(2)}s deprivation`);
// shift time after microsleep
timeOrigin -= deprivation;
}
const ts = timeOrigin + timeMs;
const controls = parseControlsFromHap(hap, cps);
const keyvals = Object.entries(controls).flat();
+1 -1
View File
@@ -28,7 +28,7 @@ async function loadFont(name) {
const url = `${soundfontUrl}/${name}.js`;
const preset = await fetch(url).then((res) => res.text());
let [_, data] = preset.split('={');
return eval('{' + data);
return new Function('return {' + data)().zones;
};
loadCache[name] = load();
return loadCache[name];
+10 -2
View File
@@ -25,8 +25,16 @@ export class ClockBridge {
}
const offset = performanceTime - contextTime * 1000; // clock offset in ms
const dt = performanceTime - (this.lastTime ?? performanceTime); // delta time since last hap
const decay = 1 / 10000; // how fast offset changes have an effect
this.p = expDecay(this.p ?? offset, offset, decay, dt); // smooth clock offset
const deprivation = offset - this.p;
// when the system sleeps, the audio and system clock get jumbled
// so we dont smooth if the jump is too high, to snap the offset
if (Math.abs(deprivation) > 20) {
console.log(`[ClockBridge] snap with ${(deprivation / 1000).toFixed(2)}s deprivation`);
this.p = offset;
} else {
const decay = 1 / 10000; // how fast offset changes have an effect
this.p = expDecay(this.p ?? offset, offset, decay, dt); // smooth clock offset
}
this.lastTime = performanceTime;
return this.p;
}
+2 -2
View File
@@ -61,7 +61,7 @@ function githubPath(base, subpath = '') {
}
return `https://raw.githubusercontent.com/${path}/${subpath}`;
}
export async function fetchSampleMap(url) {
export async function _fetchSampleMap(url) {
if (url.startsWith('github:')) {
url = githubPath(url, 'strudel.json');
}
@@ -116,7 +116,7 @@ async function fetchSample(url) {
export async function supradoughsamples(sampleMap, baseUrl) {
if (typeof sampleMap === 'string') {
const [json, base] = await fetchSampleMap(sampleMap);
const [json, base] = await _fetchSampleMap(sampleMap);
// console.log('json', json, 'base', base);
return supradoughsamples(json, base);
}
+1 -1
View File
@@ -106,7 +106,7 @@ export const xen = register(
let k = parray(input);
return new Pattern((state) => {
let res = k.withValue((x) => x.map((y) => eval(y.join('/')))).query(state);
let res = k.withValue((x) => x.map((y) => y.reduce((acc, v) => acc / v))).query(state);
return res;
});
};
+20
View File
@@ -251,6 +251,9 @@ importers:
'@tonaljs/tonal':
specifier: ^4.10.0
version: 4.10.0
codemirror-helix:
specifier: ^0.5.0
version: 0.5.0(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)
nanostores:
specifier: ^0.11.3
version: 0.11.3
@@ -3657,6 +3660,15 @@ packages:
resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
codemirror-helix@0.5.0:
resolution: {integrity: sha512-hI56hf9VGz53H1YvL6H1GC7HtP6te8vX+MsIHaE9J7Q3PQ6KFapKtIRg6lqSH898ikHWpMCPu42r6HJN0IfVLA==}
peerDependencies:
'@codemirror/commands': ^6.0.0
'@codemirror/language': ^6.0.0
'@codemirror/search': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
collapse-white-space@2.1.0:
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
@@ -11399,6 +11411,14 @@ snapshots:
cmd-shim@6.0.3: {}
codemirror-helix@0.5.0(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2):
dependencies:
'@codemirror/commands': 6.8.0
'@codemirror/language': 6.10.8
'@codemirror/search': 6.5.8
'@codemirror/state': 6.5.1
'@codemirror/view': 6.36.2
collapse-white-space@2.1.0: {}
color-convert@2.0.1:
+13 -12
View File
@@ -4,18 +4,19 @@ packages:
- tools/dbpatch
- website/
onlyBuiltDependencies:
catalog:
'@codemirror/autocomplete': ^6.18.4
'@codemirror/commands': ^6.8.0
'@codemirror/lang-javascript': ^6.2.2
'@codemirror/language': ^6.10.8
'@codemirror/search': ^6.5.8
'@codemirror/state': ^6.5.1
'@codemirror/view': ^6.36.2
ignoredBuiltDependencies:
- '@serialport/bindings-cpp'
- tree-sitter-haskell
- tree-sitter
- esbuild
- nx
- sharp
- tree-sitter
- tree-sitter-haskell
catalog:
"@codemirror/commands": "^6.8.0"
"@codemirror/language": "^6.10.8"
"@codemirror/lang-javascript": "^6.2.2"
"@codemirror/state": "^6.5.1"
"@codemirror/view": "^6.36.2"
"@codemirror/autocomplete": "^6.18.4"
"@codemirror/search": "^6.5.8"
+66 -66
View File
@@ -4777,6 +4777,72 @@ exports[`runs examples > example "floor" example index 0 1`] = `
]
`;
exports[`runs examples > example "fm" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:0 ]",
"[ 1/6 → 1/3 | note:e fmi:0 ]",
"[ 1/3 → 1/2 | note:g fmi:0 ]",
"[ 1/2 → 2/3 | note:b fmi:0 ]",
"[ 2/3 → 5/6 | note:g fmi:0 ]",
"[ 5/6 → 1/1 | note:e fmi:0 ]",
"[ 1/1 → 7/6 | note:c fmi:1 ]",
"[ 7/6 → 4/3 | note:e fmi:1 ]",
"[ 4/3 → 3/2 | note:g fmi:1 ]",
"[ 3/2 → 5/3 | note:b fmi:1 ]",
"[ 5/3 → 11/6 | note:g fmi:1 ]",
"[ 11/6 → 2/1 | note:e fmi:1 ]",
"[ 2/1 → 13/6 | note:c fmi:2 ]",
"[ 13/6 → 7/3 | note:e fmi:2 ]",
"[ 7/3 → 5/2 | note:g fmi:2 ]",
"[ 5/2 → 8/3 | note:b fmi:2 ]",
"[ 8/3 → 17/6 | note:g fmi:2 ]",
"[ 17/6 → 3/1 | note:e fmi:2 ]",
"[ 3/1 → 19/6 | note:c fmi:8 ]",
"[ 19/6 → 10/3 | note:e fmi:8 ]",
"[ 10/3 → 7/2 | note:g fmi:8 ]",
"[ 7/2 → 11/3 | note:b fmi:8 ]",
"[ 11/3 → 23/6 | note:g fmi:8 ]",
"[ 23/6 → 4/1 | note:e fmi:8 ]",
]
`;
exports[`runs examples > example "fm" example index 1 1`] = `
[
"[ 0/1 → 1/8 | s:sine note:F1 fmi:4 fmi2:0 fmi3:0 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/8 → 1/4 | s:sine note:F1 fmi:4 fmi2:2.7408622801303864 fmi3:0.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/4 → 3/8 | s:sine note:F1 fmi:4 fmi2:1.479038767516613 fmi3:0.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/8 → 1/2 | s:sine note:F1 fmi:4 fmi2:1.605570062994957 fmi3:0.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/2 → 5/8 | s:sine note:F1 fmi:4 fmi2:1.041922464966774 fmi3:0.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 5/8 → 3/4 | s:sine note:F1 fmi:4 fmi2:0.5425434708595276 fmi3:0.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/4 → 7/8 | s:sine note:F1 fmi:4 fmi2:0.7833059206604958 fmi3:0.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 7/8 → 1/1 | s:sine note:F1 fmi:4 fmi2:1.590524323284626 fmi3:0.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/1 → 9/8 | s:sine note:F1 fmi:4 fmi2:2.078168660402298 fmi3:1 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 9/8 → 5/4 | s:sine note:F1 fmi:4 fmi2:2.689958058297634 fmi3:1.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 5/4 → 11/8 | s:sine note:F1 fmi:4 fmi2:2.914912812411785 fmi3:1.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 11/8 → 3/2 | s:sine note:F1 fmi:4 fmi2:0.7312150076031685 fmi3:1.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/2 → 13/8 | s:sine note:F1 fmi:4 fmi2:2.4336322993040085 fmi3:1.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 13/8 → 7/4 | s:sine note:F1 fmi:4 fmi2:1.987018845975399 fmi3:1.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 7/4 → 15/8 | s:sine note:F1 fmi:4 fmi2:0.8189515843987465 fmi3:1.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 15/8 → 2/1 | s:sine note:F1 fmi:4 fmi2:2.3782287165522575 fmi3:1.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 2/1 → 17/8 | s:sine note:F1 fmi:4 fmi2:3.838108479976654 fmi3:2 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 17/8 → 9/4 | s:sine note:F1 fmi:4 fmi2:3.613561175763607 fmi3:2.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 9/4 → 19/8 | s:sine note:F1 fmi:4 fmi2:1.3819300457835197 fmi3:2.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 19/8 → 5/2 | s:sine note:F1 fmi:4 fmi2:3.346026562154293 fmi3:2.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 5/2 → 21/8 | s:sine note:F1 fmi:4 fmi2:1.8344642966985703 fmi3:2.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 21/8 → 11/4 | s:sine note:F1 fmi:4 fmi2:0.6407739371061325 fmi3:2.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 11/4 → 23/8 | s:sine note:F1 fmi:4 fmi2:2.5329310819506645 fmi3:2.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 23/8 → 3/1 | s:sine note:F1 fmi:4 fmi2:0.06501668691635132 fmi3:2.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/1 → 25/8 | s:sine note:F1 fmi:4 fmi2:0.8691564425826073 fmi3:3 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 25/8 → 13/4 | s:sine note:F1 fmi:4 fmi2:1.3729755133390427 fmi3:3.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 13/4 → 27/8 | s:sine note:F1 fmi:4 fmi2:3.9694602862000465 fmi3:3.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 27/8 → 7/2 | s:sine note:F1 fmi:4 fmi2:1.6347672045230865 fmi3:3.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 7/2 → 29/8 | s:sine note:F1 fmi:4 fmi2:1.639795258641243 fmi3:3.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 29/8 → 15/4 | s:sine note:F1 fmi:4 fmi2:3.7565943151712418 fmi3:3.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 15/4 → 31/8 | s:sine note:F1 fmi:4 fmi2:3.2486692890524864 fmi3:3.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 31/8 → 4/1 | s:sine note:F1 fmi:4 fmi2:2.9446661546826363 fmi3:3.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
]
`;
exports[`runs examples > example "fmattack" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:4 fmattack:0 ]",
@@ -4893,72 +4959,6 @@ exports[`runs examples > example "fmh" example index 0 1`] = `
]
`;
exports[`runs examples > example "fmi" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:0 ]",
"[ 1/6 → 1/3 | note:e fmi:0 ]",
"[ 1/3 → 1/2 | note:g fmi:0 ]",
"[ 1/2 → 2/3 | note:b fmi:0 ]",
"[ 2/3 → 5/6 | note:g fmi:0 ]",
"[ 5/6 → 1/1 | note:e fmi:0 ]",
"[ 1/1 → 7/6 | note:c fmi:1 ]",
"[ 7/6 → 4/3 | note:e fmi:1 ]",
"[ 4/3 → 3/2 | note:g fmi:1 ]",
"[ 3/2 → 5/3 | note:b fmi:1 ]",
"[ 5/3 → 11/6 | note:g fmi:1 ]",
"[ 11/6 → 2/1 | note:e fmi:1 ]",
"[ 2/1 → 13/6 | note:c fmi:2 ]",
"[ 13/6 → 7/3 | note:e fmi:2 ]",
"[ 7/3 → 5/2 | note:g fmi:2 ]",
"[ 5/2 → 8/3 | note:b fmi:2 ]",
"[ 8/3 → 17/6 | note:g fmi:2 ]",
"[ 17/6 → 3/1 | note:e fmi:2 ]",
"[ 3/1 → 19/6 | note:c fmi:8 ]",
"[ 19/6 → 10/3 | note:e fmi:8 ]",
"[ 10/3 → 7/2 | note:g fmi:8 ]",
"[ 7/2 → 11/3 | note:b fmi:8 ]",
"[ 11/3 → 23/6 | note:g fmi:8 ]",
"[ 23/6 → 4/1 | note:e fmi:8 ]",
]
`;
exports[`runs examples > example "fmi" example index 1 1`] = `
[
"[ 0/1 → 1/8 | s:sine note:F1 fmi:4 fmi2:0 fmi3:0 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/8 → 1/4 | s:sine note:F1 fmi:4 fmi2:2.7408622801303864 fmi3:0.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/4 → 3/8 | s:sine note:F1 fmi:4 fmi2:1.479038767516613 fmi3:0.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/8 → 1/2 | s:sine note:F1 fmi:4 fmi2:1.605570062994957 fmi3:0.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/2 → 5/8 | s:sine note:F1 fmi:4 fmi2:1.041922464966774 fmi3:0.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 5/8 → 3/4 | s:sine note:F1 fmi:4 fmi2:0.5425434708595276 fmi3:0.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/4 → 7/8 | s:sine note:F1 fmi:4 fmi2:0.7833059206604958 fmi3:0.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 7/8 → 1/1 | s:sine note:F1 fmi:4 fmi2:1.590524323284626 fmi3:0.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 1/1 → 9/8 | s:sine note:F1 fmi:4 fmi2:2.078168660402298 fmi3:1 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 9/8 → 5/4 | s:sine note:F1 fmi:4 fmi2:2.689958058297634 fmi3:1.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 5/4 → 11/8 | s:sine note:F1 fmi:4 fmi2:2.914912812411785 fmi3:1.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 11/8 → 3/2 | s:sine note:F1 fmi:4 fmi2:0.7312150076031685 fmi3:1.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/2 → 13/8 | s:sine note:F1 fmi:4 fmi2:2.4336322993040085 fmi3:1.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 13/8 → 7/4 | s:sine note:F1 fmi:4 fmi2:1.987018845975399 fmi3:1.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 7/4 → 15/8 | s:sine note:F1 fmi:4 fmi2:0.8189515843987465 fmi3:1.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 15/8 → 2/1 | s:sine note:F1 fmi:4 fmi2:2.3782287165522575 fmi3:1.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 2/1 → 17/8 | s:sine note:F1 fmi:4 fmi2:3.838108479976654 fmi3:2 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 17/8 → 9/4 | s:sine note:F1 fmi:4 fmi2:3.613561175763607 fmi3:2.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 9/4 → 19/8 | s:sine note:F1 fmi:4 fmi2:1.3819300457835197 fmi3:2.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 19/8 → 5/2 | s:sine note:F1 fmi:4 fmi2:3.346026562154293 fmi3:2.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 5/2 → 21/8 | s:sine note:F1 fmi:4 fmi2:1.8344642966985703 fmi3:2.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 21/8 → 11/4 | s:sine note:F1 fmi:4 fmi2:0.6407739371061325 fmi3:2.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 11/4 → 23/8 | s:sine note:F1 fmi:4 fmi2:2.5329310819506645 fmi3:2.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 23/8 → 3/1 | s:sine note:F1 fmi:4 fmi2:0.06501668691635132 fmi3:2.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 3/1 → 25/8 | s:sine note:F1 fmi:4 fmi2:0.8691564425826073 fmi3:3 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 25/8 → 13/4 | s:sine note:F1 fmi:4 fmi2:1.3729755133390427 fmi3:3.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 13/4 → 27/8 | s:sine note:F1 fmi:4 fmi2:3.9694602862000465 fmi3:3.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 27/8 → 7/2 | s:sine note:F1 fmi:4 fmi2:1.6347672045230865 fmi3:3.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 7/2 → 29/8 | s:sine note:F1 fmi:4 fmi2:1.639795258641243 fmi3:3.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 29/8 → 15/4 | s:sine note:F1 fmi:4 fmi2:3.7565943151712418 fmi3:3.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 15/4 → 31/8 | s:sine note:F1 fmi:4 fmi2:3.2486692890524864 fmi3:3.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
"[ 31/8 → 4/1 | s:sine note:F1 fmi:4 fmi2:2.9446661546826363 fmi3:3.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
]
`;
exports[`runs examples > example "fmsustain" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]",
+2 -1
View File
@@ -73,6 +73,7 @@ export default defineConfig({
workbox: {
maximumFileSizeToCacheInBytes: 4194304, // 4MB
globPatterns: ['**/*.{js,css,html,ico,png,svg,json,wav,mp3,ogg,ttf,woff2,TTF,otf}'],
globIgnores: ['img/**', '_astro/csound*.js'],
runtimeCaching: [
{
urlPattern: ({ url }) =>
@@ -88,7 +89,7 @@ export default defineConfig({
cacheName: 'external-samples',
expiration: {
maxEntries: 5000,
maxAgeSeconds: 60 * 60 * 24 * 30, // <== 14 days
maxAgeSeconds: 60 * 60 * 24 * 30, // <== 30 days
},
cacheableResponse: {
statuses: [0, 200],
+3
View File
@@ -2,3 +2,6 @@
/// <reference types="astro/client" />
declare module 'date-fns';
declare module '@strudel/*';
declare module 'dough-synth';
declare module 'paho-mqtt';
+1 -1
View File
@@ -1,7 +1,7 @@
import { getMetadata } from './metadata_parser';
export function getMyPatterns() {
const my = import.meta.glob('../../my-patterns/**', { as: 'raw', eager: true });
const my = import.meta.glob('../../my-patterns/**', { query: '?raw', eager: true });
return Object.fromEntries(
Object.entries(my)
.filter(([name]) => name.endsWith('.txt'))
+2 -2
View File
@@ -184,9 +184,9 @@ Each filter has 2 parameters:
Amplitude modulation changes the amplitude (gain) periodically over time.
## am
## tremolo
<JsDoc client:idle name="am" h={0} />
<JsDoc client:idle name="tremolo" h={0} />
## tremolosync
-5
View File
@@ -18,7 +18,6 @@ These are the equivalents used by the Mini Notation:
| `stack(x, y)` | `"x,y"` |
| `stepcat([3,x],[2,y])` | `"x@3 y@2"` |
| `polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` |
| `polymeterSteps(2, x, y, z)` | `"{x y z}%2"` |
| `silence` | `"~"` |
## cat
@@ -45,10 +44,6 @@ These are the equivalents used by the Mini Notation:
<JsDoc client:idle name="polymeter" h={0} />
## polymeterSteps
<JsDoc client:idle name="polymeterSteps" h={0} />
## silence
<JsDoc client:idle name="silence" h={0} />
+1 -1
View File
@@ -329,7 +329,7 @@ Sampler effects are functions that can be used to change the behaviour of sample
### begin
<JsDoc client:idle name="Pattern.begin" h={0} />
<JsDoc client:idle name="begin" h={0} />
### end
+1 -1
View File
@@ -22,7 +22,7 @@ These functions allow the use of scales other than your typical chromatic 12 bas
{/* TODO (maybe): combine jsdoc things in tune.mjs with here */}
<JsDoc client:idle name="tune" h={0} />
{/* <JsDoc client:idle name="tune" h={0} /> */}
Here's an example of how to configure a basic hexany scale:
@@ -0,0 +1,85 @@
---
title: Helix Keybindings
layout: ../../layouts/MainLayout.astro
---
# Helix Keybindings in the REPL
The Strudel REPL supports [Helix editor](https://helix-editor.com/) keybindings through the `codemirror-helix` extension. Helix is a post-modern modal text editor with a focus on selection-first editing and multiple cursors.
## Enabling Helix Mode
1. Open the Settings panel in the REPL (click the settings icon or use the keyboard shortcut)
2. Find the "Keybindings" section
3. Select "Helix" from the available options
## Key Differences from Vim
Helix uses a selection-first approach, which means:
- **Selection → Action** (Helix) vs **Action → Motion** (Vim)
- For example, to delete a word in Helix: `w` (select word) → `d` (delete)
- In Vim, it would be: `dw` (delete word)
## Common Helix Commands
### Normal Mode
- `i` — Enter insert mode before selection
- `a` — Enter insert mode after selection
- `v` — Enter visual/select mode
- `w` — Select next word
- `e` — Select to end of word
- `b` — Select previous word
- `x` — Select/extend line
- `d` — Delete selection
- `c` — Change selection (delete and enter insert mode)
- `y` — Yank (copy) selection
- `p` — Paste after selection
- `u` — Undo
- `U` — Redo
- `/` — Search
- `n` — Select next search match
- `N` — Select previous search match
### Movement
- `h, j, k, l` — Move left, down, up, right
- `gg` — Go to start of document
- `ge` — Go to end of document
- `{` / `}` — Move to previous/next paragraph
- `%` — Match bracket
### Multi-cursor
- `C` — Duplicate cursor to line below
- `Alt-C` — Duplicate cursor to line above
- `,` — Remove primary cursor
- `Alt-,` — Remove all secondary cursors
## Strudel-Specific Features
Currently, Helix mode in Strudel provides standard Helix keybindings. Unlike Vim mode, there are no custom Strudel-specific commands (like `:w` to evaluate) yet.
To evaluate code while in Helix mode, use the standard shortcuts:
- `Ctrl+Enter` or `Alt+Enter` — Evaluate code
- `Alt+.` — Stop playback
## Resources
- [Helix Documentation](https://docs.helix-editor.com/)
- [Helix Keymap](https://docs.helix-editor.com/keymap.html)
- [codemirror-helix on npm](https://www.npmjs.com/package/codemirror-helix)
- [codemirror-helix on GitLab](https://gitlab.com/_rvidal/codemirror-helix)
## Notes
- The `codemirror-helix` extension is described as an "initial version" and may not implement all Helix features
- Behavior may differ slightly from the native Helix editor
- If you encounter issues, you can switch back to other keybinding modes in Settings
- The keybinding preference is saved in your browser's local storage
## Contributing
If you'd like to add Strudel-specific Helix commands (similar to Vim's `:w` for evaluate), contributions are welcome! See the implementation in `/packages/codemirror/keybindings.mjs` for reference.
@@ -235,7 +235,7 @@ function MainSettingsContent({ started }) {
<ButtonGroup
value={keybindings}
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs', vscode: 'VSCode' }}
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs', helix: 'Helix', vscode: 'VSCode' }}
></ButtonGroup>
</FormItem>
<FormItem label="Panel Position">
+9 -6
View File
@@ -1,12 +1,15 @@
import { code2hash, errorLogger, evalScope, hash2code, logger } from '@strudel/core';
import * as strudelCore from '@strudel/core';
import * as strudelWebaudio from '@strudel/webaudio';
import { settingPatterns } from '../settings.mjs';
import { setVersionDefaults } from '@strudel/webaudio';
import { getMetadata } from '../metadata_parser';
import { getMetadata } from '../metadata_parser.js';
import { isTauri } from '../tauri.mjs';
import './Repl.css';
import { createClient } from '@supabase/supabase-js';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs';
import { $featuredPatterns } from '@src/user_pattern_utils.mjs';
const { setVersionDefaults } = strudelWebaudio;
const { code2hash, errorLogger, evalScope, hash2code, logger } = strudelCore;
// Create a single supabase client for interacting with your database
export const supabase = createClient(
@@ -68,13 +71,13 @@ export async function getRandomTune() {
export function loadModules() {
let modules = [
import('@strudel/core'),
strudelCore,
import('@strudel/draw'),
import('@strudel/edo'),
import('@strudel/tonal'),
import('@strudel/mini'),
import('@strudel/xen'),
import('@strudel/webaudio'),
strudelWebaudio,
import('@strudel/codemirror'),
import('@strudel/hydra'),
import('@strudel/serial'),