mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-23 13:42:56 -04:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 535a822d2a | |||
| 4c35f6a77b | |||
| e92f4938cd | |||
| 12204c9053 | |||
| 980bf17059 | |||
| f2009a33aa | |||
| a65a68b828 | |||
| c02def03b6 | |||
| 0d5dc1f8c1 | |||
| 319a2d7289 | |||
| 3a7d50924d | |||
| f33db5f07f | |||
| b7fa440bda | |||
| 3dc9b3524c | |||
| f48f8e46e5 | |||
| a79fc3db5b | |||
| 30a2e14959 | |||
| cfe17fcba5 | |||
| 46ba165c7e | |||
| 73a9834959 | |||
| be9368e8a2 | |||
| 39a73f2499 | |||
| 11dd8a318a | |||
| 3ef6c7c921 | |||
| 08cc9b03d7 | |||
| 46949d165d | |||
| 88b94b21b8 | |||
| 80d6a1903a | |||
| 35d98f4d40 | |||
| 2c7198a5f0 | |||
| 58c88c56ba | |||
| ecad4d0b11 | |||
| fddb66d74c |
@@ -10,7 +10,7 @@ function getExports(code) {
|
|||||||
let ast;
|
let ast;
|
||||||
try {
|
try {
|
||||||
ast = parse(code, {
|
ast = parse(code, {
|
||||||
ecmaVersion: 11,
|
ecmaVersion: 'latest',
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -49,9 +49,7 @@ function getExports(code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isDocumented(name, docs) {
|
function isDocumented(name, docs) {
|
||||||
return docs.find(
|
return docs.find((d) => d.name === name || d.synonyms?.includes(name));
|
||||||
(d) => d.name === name || d.tags?.find((t) => t.title === 'synonyms' && t.value.split(', ').includes(name)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUndocumented(path, docs) {
|
async function getUndocumented(path, docs) {
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
import { closeBrackets } from '@codemirror/autocomplete';
|
import { closeBrackets } from '@codemirror/autocomplete';
|
||||||
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
|
import { indentWithTab, toggleLineComment } from '@codemirror/commands';
|
||||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
|
||||||
import { indentWithTab } from '@codemirror/commands';
|
|
||||||
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
||||||
import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from '@codemirror/language';
|
import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||||
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
||||||
import {
|
import {
|
||||||
|
drawSelection,
|
||||||
EditorView,
|
EditorView,
|
||||||
highlightActiveLineGutter,
|
|
||||||
highlightActiveLine,
|
highlightActiveLine,
|
||||||
|
highlightActiveLineGutter,
|
||||||
keymap,
|
keymap,
|
||||||
lineNumbers,
|
lineNumbers,
|
||||||
drawSelection,
|
|
||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
import { repl, registerControl } from '@strudel/core';
|
import { persistentAtom } from '@nanostores/persistent';
|
||||||
import { Drawer, cleanupDraw } from '@strudel/draw';
|
import { logger, registerControl, repl } from '@strudel/core';
|
||||||
|
import { cleanupDraw, Drawer } from '@strudel/draw';
|
||||||
|
|
||||||
import { isAutoCompletionEnabled } from './autocomplete.mjs';
|
import { isAutoCompletionEnabled } from './autocomplete.mjs';
|
||||||
import { isTooltipEnabled } from './tooltip.mjs';
|
import { basicSetup } from './basicSetup.mjs';
|
||||||
import { flash, isFlashEnabled } from './flash.mjs';
|
import { flash, isFlashEnabled } from './flash.mjs';
|
||||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||||
import { keybindings } from './keybindings.mjs';
|
import { keybindings } from './keybindings.mjs';
|
||||||
import { initTheme, activateTheme, theme } from './themes.mjs';
|
|
||||||
import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
||||||
import { widgetPlugin, updateWidgets } from './widget.mjs';
|
import { activateTheme, initTheme, theme } from './themes.mjs';
|
||||||
import { persistentAtom } from '@nanostores/persistent';
|
import { isTooltipEnabled } from './tooltip.mjs';
|
||||||
import { basicSetup } from './basicSetup.mjs';
|
import { updateWidgets, widgetPlugin } from './widget.mjs';
|
||||||
|
|
||||||
|
export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands';
|
||||||
|
|
||||||
const extensions = {
|
const extensions = {
|
||||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||||
@@ -94,8 +95,8 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
}),
|
}),
|
||||||
sliderPlugin,
|
sliderPlugin,
|
||||||
widgetPlugin,
|
widgetPlugin,
|
||||||
// indentOnInput(), // works without. already brought with javascript extension?
|
// indentOnInput(), // works without. already brought with javascript
|
||||||
// bracketMatching(), // does not do anything
|
// extension? bracketMatching(), // does not do anything
|
||||||
syntaxHighlighting(defaultHighlightStyle),
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
EditorView.updateListener.of((v) => onChange(v)),
|
EditorView.updateListener.of((v) => onChange(v)),
|
||||||
drawSelection({ cursorBlinkRate: 0 }),
|
drawSelection({ cursorBlinkRate: 0 }),
|
||||||
@@ -119,13 +120,13 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
run: () => onStop?.(),
|
run: () => onStop?.(),
|
||||||
},
|
},
|
||||||
/* {
|
/* {
|
||||||
key: 'Ctrl-Shift-.',
|
key: 'Ctrl-Shift-.',
|
||||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Ctrl-Shift-Enter',
|
key: 'Ctrl-Shift-Enter',
|
||||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||||
}, */
|
}, */
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -206,7 +207,8 @@ export class StrudelMirror {
|
|||||||
updateWidgets(this.editor, widgets);
|
updateWidgets(this.editor, widgets);
|
||||||
updateMiniLocations(this.editor, this.miniLocations);
|
updateMiniLocations(this.editor, this.miniLocations);
|
||||||
replOptions?.afterEval?.(options);
|
replOptions?.afterEval?.(options);
|
||||||
// if no painters are set (.onPaint was not called), then we only need the present moment (for highlighting)
|
// if no painters are set (.onPaint was not called), then we only need
|
||||||
|
// the present moment (for highlighting)
|
||||||
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
|
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
|
||||||
this.drawer.setDrawTime(drawTime);
|
this.drawer.setDrawTime(drawTime);
|
||||||
// invalidate drawer after we've set the appropriate drawTime
|
// invalidate drawer after we've set the appropriate drawTime
|
||||||
@@ -245,6 +247,33 @@ export class StrudelMirror {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener('start-repl', this.onStartRepl);
|
document.addEventListener('start-repl', this.onStartRepl);
|
||||||
|
|
||||||
|
// Handle global evaluation requests (e.g., from Vim :w)
|
||||||
|
this.onEvaluateRequest = (e) => {
|
||||||
|
try {
|
||||||
|
// Evaluate current editor on repl-evaluate
|
||||||
|
logger('[repl] evaluate via event');
|
||||||
|
this.evaluate();
|
||||||
|
e?.cancelable && e.preventDefault?.();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error handling repl-evaluate event', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('repl-evaluate', this.onEvaluateRequest);
|
||||||
|
document.addEventListener('repl-stop', this.onStopRequest);
|
||||||
|
|
||||||
|
// Toggle comments requested from Vim (gc)
|
||||||
|
this.onToggleComment = (e) => {
|
||||||
|
try {
|
||||||
|
// Honor selections; toggleLineComment handles both selections and
|
||||||
|
// single line
|
||||||
|
toggleLineComment(this.editor);
|
||||||
|
e?.cancelable && e.preventDefault?.();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error handling repl-toggle-comment event', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('repl-toggle-comment', this.onToggleComment);
|
||||||
}
|
}
|
||||||
draw(haps, time, painters) {
|
draw(haps, time, painters) {
|
||||||
painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
|
painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
|
||||||
@@ -271,6 +300,16 @@ export class StrudelMirror {
|
|||||||
async stop() {
|
async stop() {
|
||||||
this.repl.scheduler.stop();
|
this.repl.scheduler.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for global stop requests (e.g., from Vim :q)
|
||||||
|
onStopRequest = (e) => {
|
||||||
|
try {
|
||||||
|
this.stop();
|
||||||
|
e?.cancelable && e.preventDefault?.();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error handling repl-stop event', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
async toggle() {
|
async toggle() {
|
||||||
if (this.repl.scheduler.started) {
|
if (this.repl.scheduler.started) {
|
||||||
this.repl.stop();
|
this.repl.stop();
|
||||||
@@ -346,11 +385,18 @@ export class StrudelMirror {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setCode(code) {
|
setCode(code) {
|
||||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
const changes = {
|
||||||
|
from: 0,
|
||||||
|
to: this.editor.state.doc.length,
|
||||||
|
insert: code,
|
||||||
|
};
|
||||||
this.editor.dispatch({ changes });
|
this.editor.dispatch({ changes });
|
||||||
}
|
}
|
||||||
clear() {
|
clear() {
|
||||||
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
||||||
|
this.onEvaluateRequest && document.removeEventListener('repl-evaluate', this.onEvaluateRequest);
|
||||||
|
this.onStopRequest && document.removeEventListener('repl-stop', this.onStopRequest);
|
||||||
|
this.onToggleComment && document.removeEventListener('repl-toggle-comment', this.onToggleComment);
|
||||||
}
|
}
|
||||||
getCursorLocation() {
|
getCursorLocation() {
|
||||||
return this.editor.state.selection.main.head;
|
return this.editor.state.selection.main.head;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { defaultKeymap } from '@codemirror/commands';
|
||||||
import { Prec } from '@codemirror/state';
|
import { Prec } from '@codemirror/state';
|
||||||
import { keymap, ViewPlugin } from '@codemirror/view';
|
import { keymap, ViewPlugin } from '@codemirror/view';
|
||||||
// import { searchKeymap } from '@codemirror/search';
|
// import { searchKeymap } from '@codemirror/search';
|
||||||
import { emacs } from '@replit/codemirror-emacs';
|
import { emacs } from '@replit/codemirror-emacs';
|
||||||
import { vim } from '@replit/codemirror-vim';
|
import { vim, Vim } from '@replit/codemirror-vim';
|
||||||
// import { vim } from './vim_test.mjs';
|
// import { vim } from './vim_test.mjs';
|
||||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||||
import { defaultKeymap } from '@codemirror/commands';
|
import { logger } from '@strudel/core';
|
||||||
|
|
||||||
const vscodePlugin = ViewPlugin.fromClass(
|
const vscodePlugin = ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
@@ -19,6 +20,110 @@ const vscodePlugin = ViewPlugin.fromClass(
|
|||||||
);
|
);
|
||||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// appears in the Console panel.
|
||||||
|
try {
|
||||||
|
if (Vim && typeof Vim.defineEx === 'function') {
|
||||||
|
// Map gc to toggle line comments by dispatching a custom event that our
|
||||||
|
// CodeMirror integration listens to. This avoids depending on Vim's
|
||||||
|
// internal actions and works with current selections/visual mode.
|
||||||
|
try {
|
||||||
|
Vim.defineAction('strudelToggleComment', (cm) => {
|
||||||
|
const view = cm?.view || cm;
|
||||||
|
try {
|
||||||
|
const ev = new CustomEvent('repl-toggle-comment', { detail: { source: 'vim', view }, cancelable: true });
|
||||||
|
document.dispatchEvent(ev);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('strudelToggleComment dispatch failed', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Vim.mapCommand('gc', 'action', 'strudelToggleComment', {}, { context: 'normal' });
|
||||||
|
Vim.mapCommand('gc', 'action', 'strudelToggleComment', {}, { context: 'visual' });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Vim gc mapping failed', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// :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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// :w to evaluate
|
||||||
|
Vim.defineEx('write', 'w', (cm) => {
|
||||||
|
const view = cm?.view || cm; // CM6 Vim passes either an object with view or the view itself
|
||||||
|
try {
|
||||||
|
view?.focus?.();
|
||||||
|
// Let the app know this came from Vim :w
|
||||||
|
try {
|
||||||
|
logger('[vim] :w — evaluating code');
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error dispatching :w evaluation event', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e);
|
||||||
|
}
|
||||||
|
|
||||||
const keymaps = {
|
const keymaps = {
|
||||||
vim,
|
vim,
|
||||||
emacs,
|
emacs,
|
||||||
|
|||||||
@@ -35,18 +35,18 @@ const right = function (n, x) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _bjork = function (n, x) {
|
const _bjorklund = function (n, x) {
|
||||||
const [ons, offs] = n;
|
const [ons, offs] = n;
|
||||||
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)));
|
return Math.min(ons, offs) <= 1 ? [n, x] : _bjorklund(...(ons > offs ? left(n, x) : right(n, x)));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bjork = function (ons, steps) {
|
export const bjorklund = function (ons, steps) {
|
||||||
const inverted = ons < 0;
|
const inverted = ons < 0;
|
||||||
const absOns = Math.abs(ons);
|
const absOns = Math.abs(ons);
|
||||||
const offs = steps - absOns;
|
const offs = steps - absOns;
|
||||||
const ones = Array(absOns).fill([1]);
|
const ones = Array(absOns).fill([1]);
|
||||||
const zeros = Array(offs).fill([0]);
|
const zeros = Array(offs).fill([0]);
|
||||||
const result = _bjork([absOns, offs], [ones, zeros]);
|
const result = _bjorklund([absOns, offs], [ones, zeros]);
|
||||||
const pattern = flatten(result[1][0]).concat(flatten(result[1][1]));
|
const pattern = flatten(result[1][0]).concat(flatten(result[1][1]));
|
||||||
return inverted ? pattern.map((x) => 1 - x) : pattern;
|
return inverted ? pattern.map((x) => 1 - x) : pattern;
|
||||||
};
|
};
|
||||||
@@ -128,7 +128,7 @@ export const bjork = function (ons, steps) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const _euclidRot = function (pulses, steps, rotation) {
|
const _euclidRot = function (pulses, steps, rotation) {
|
||||||
const b = bjork(pulses, steps);
|
const b = bjorklund(pulses, steps);
|
||||||
if (rotation) {
|
if (rotation) {
|
||||||
return rotate(b, -rotation);
|
return rotate(b, -rotation);
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ export const euclid = register('euclid', function (pulses, steps, pat) {
|
|||||||
return pat.struct(_euclidRot(pulses, steps, 0));
|
return pat.struct(_euclidRot(pulses, steps, 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
export const e = register('e', function (euc, pat) {
|
export const bjork = register('bjork', function (euc, pat) {
|
||||||
if (!Array.isArray(euc)) {
|
if (!Array.isArray(euc)) {
|
||||||
euc = [euc];
|
euc = [euc];
|
||||||
}
|
}
|
||||||
@@ -216,6 +216,6 @@ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, s
|
|||||||
* .pan(sine.slow(8))
|
* .pan(sine.slow(8))
|
||||||
*/
|
*/
|
||||||
export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) {
|
export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) {
|
||||||
const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc);
|
const morphed = _morph(bjorklund(pulses, steps), new Array(pulses).fill(1), perc);
|
||||||
return pat.struct(morphed).setSteps(steps);
|
return pat.struct(morphed).setSteps(steps);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2574,8 +2574,8 @@ export const { chunkBack, chunkback } = register(
|
|||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
* "<0 8> 1 2 3 4 5 6 7"
|
* "<0 8> 1 2 3 4 5 6 7"
|
||||||
* .fastChunk(4, x => x.color('red')).slow(2)
|
|
||||||
* .scale("C2:major").note()
|
* .scale("C2:major").note()
|
||||||
|
* .fastChunk(4, x => x.color('red')).slow(2)
|
||||||
*/
|
*/
|
||||||
export const { fastchunk, fastChunk } = register(
|
export const { fastchunk, fastChunk } = register(
|
||||||
['fastchunk', 'fastChunk'],
|
['fastchunk', 'fastChunk'],
|
||||||
@@ -3289,6 +3289,7 @@ export const striate = register('striate', function (n, pat) {
|
|||||||
/**
|
/**
|
||||||
* Makes the sample fit the given number of cycles by changing the speed.
|
* Makes the sample fit the given number of cycles by changing the speed.
|
||||||
* @name loopAt
|
* @name loopAt
|
||||||
|
* @synonyms loopat
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
@@ -3421,6 +3422,7 @@ export const fit = register('fit', (pat) =>
|
|||||||
* given by a global clock and this function will be
|
* given by a global clock and this function will be
|
||||||
* deprecated/removed.
|
* deprecated/removed.
|
||||||
* @name loopAtCps
|
* @name loopAtCps
|
||||||
|
* @synonyms loopatcps
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
|
|||||||
@@ -10,11 +10,27 @@ import Fraction from './fraction.mjs';
|
|||||||
|
|
||||||
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
|
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `signal` consisting of a constant value. Similar to `pure`, except that function
|
||||||
|
* creates a pattern with one event per cycle, whereas this pattern doesn't have an intrinsic
|
||||||
|
* structure.
|
||||||
|
*
|
||||||
|
* @param {*} value The constant value of the resulting pattern
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
export function steady(value) {
|
export function steady(value) {
|
||||||
// A continuous value
|
// A continuous value
|
||||||
return new Pattern((state) => [new Hap(undefined, state.span, value)]);
|
return new Pattern((state) => [new Hap(undefined, state.span, value)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a "signal", an unstructured pattern consisting of a single value that changes
|
||||||
|
* over time.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} func
|
||||||
|
* @returns Pattern
|
||||||
|
*/
|
||||||
export const signal = (func) => {
|
export const signal = (func) => {
|
||||||
const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))];
|
const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))];
|
||||||
return new Pattern(query);
|
return new Pattern(query);
|
||||||
@@ -23,7 +39,7 @@ export const signal = (func) => {
|
|||||||
/**
|
/**
|
||||||
* A sawtooth signal between 0 and 1.
|
* A sawtooth signal between 0 and 1.
|
||||||
*
|
*
|
||||||
* @return {Pattern}
|
* @type {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
|
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
|
||||||
* .clip(saw.slow(2))
|
* .clip(saw.slow(2))
|
||||||
@@ -157,6 +173,7 @@ export const time = signal(id);
|
|||||||
/**
|
/**
|
||||||
* The mouse's x position value ranges from 0 to 1.
|
* The mouse's x position value ranges from 0 to 1.
|
||||||
* @name mousex
|
* @name mousex
|
||||||
|
* @synonyms mouseX
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* n(mousex.segment(4).range(0,7)).scale("C:minor")
|
* n(mousex.segment(4).range(0,7)).scale("C:minor")
|
||||||
@@ -166,6 +183,7 @@ export const time = signal(id);
|
|||||||
/**
|
/**
|
||||||
* The mouse's y position value ranges from 0 to 1.
|
* The mouse's y position value ranges from 0 to 1.
|
||||||
* @name mousey
|
* @name mousey
|
||||||
|
* @synonyms mouseY
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* n(mousey.segment(4).range(0,7)).scale("C:minor")
|
* n(mousey.segment(4).range(0,7)).scale("C:minor")
|
||||||
@@ -215,10 +233,6 @@ const timeToRandsPrime = (seed, n) => {
|
|||||||
|
|
||||||
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A discrete pattern of numbers from 0 to n-1
|
* A discrete pattern of numbers from 0 to n-1
|
||||||
* @example
|
* @example
|
||||||
@@ -389,17 +403,23 @@ export const chooseInWith = (pat, xs) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Chooses randomly from the given list of elements.
|
* Chooses randomly from the given list of elements.
|
||||||
|
* @synonyms chooseOut
|
||||||
* @param {...any} xs values / patterns to choose from.
|
* @param {...any} xs values / patterns to choose from.
|
||||||
* @returns {Pattern} - a continuous pattern.
|
* @returns {Pattern} - a continuous pattern.
|
||||||
* @example
|
* @example
|
||||||
* note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6"))
|
* note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6"))
|
||||||
*/
|
*/
|
||||||
export const choose = (...xs) => chooseWith(rand, xs);
|
export const choose = (...xs) => chooseWith(rand, xs);
|
||||||
|
|
||||||
// todo: doc
|
|
||||||
export const chooseIn = (...xs) => chooseInWith(rand, xs);
|
|
||||||
export const chooseOut = choose;
|
export const chooseOut = choose;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As with {choose}, but the structure comes from the chosen values, rather
|
||||||
|
* than the pattern you're using to choose with.
|
||||||
|
* @param {...any} xs
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const chooseIn = (...xs) => chooseInWith(rand, xs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chooses from the given list of values (or patterns of values), according
|
* Chooses from the given list of values (or patterns of values), according
|
||||||
* to the pattern that the method is called on. The pattern should be in
|
* to the pattern that the method is called on. The pattern should be in
|
||||||
@@ -496,6 +516,7 @@ function _perlin(t) {
|
|||||||
const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb));
|
const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb));
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const perlinWith = (tpat) => {
|
export const perlinWith = (tpat) => {
|
||||||
return tpat.fmap(_perlin);
|
return tpat.fmap(_perlin);
|
||||||
};
|
};
|
||||||
@@ -541,6 +562,15 @@ export const perlin = perlinWith(time.fmap((v) => Number(v)));
|
|||||||
*/
|
*/
|
||||||
export const berlin = berlinWith(time.fmap((v) => Number(v)));
|
export const berlin = berlinWith(time.fmap((v) => Number(v)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes events from a pattern given a signal of numbers and a cutoff
|
||||||
|
* value for interpreting that pattern as true or false.
|
||||||
|
* @param {number} withPat - A numeric pattern for comparing the main pattern
|
||||||
|
* against. Values higher than the cutoff will let events through and lower
|
||||||
|
* values will remove events from the main pattern
|
||||||
|
* @param {number} cutoff - The threshold value to use when interpreting the
|
||||||
|
* `withPat`
|
||||||
|
*/
|
||||||
export const degradeByWith = register(
|
export const degradeByWith = register(
|
||||||
'degradeByWith',
|
'degradeByWith',
|
||||||
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
|
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { bjork } from '../euclid.mjs';
|
import { bjorklund } from '../euclid.mjs';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { fastcat } from '../pattern.mjs';
|
import { fastcat } from '../pattern.mjs';
|
||||||
|
|
||||||
describe('bjork', () => {
|
describe('bjorklund', () => {
|
||||||
it('should apply bjorklund to ons and steps', () => {
|
it('should apply bjorklundlund to ons and steps', () => {
|
||||||
expect(bjork(3, 8)).toStrictEqual([1, 0, 0, 1, 0, 0, 1, 0]);
|
expect(bjorklund(3, 8)).toStrictEqual([1, 0, 0, 1, 0, 0, 1, 0]);
|
||||||
expect(bjork(-3, 8)).toStrictEqual([0, 1, 1, 0, 1, 1, 0, 1]);
|
expect(bjorklund(-3, 8)).toStrictEqual([0, 1, 1, 0, 1, 1, 0, 1]);
|
||||||
expect(bjork(8, 8)).toStrictEqual([1, 1, 1, 1, 1, 1, 1, 1]);
|
expect(bjorklund(8, 8)).toStrictEqual([1, 1, 1, 1, 1, 1, 1, 1]);
|
||||||
expect(bjork(-8, 8)).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0]);
|
expect(bjorklund(-8, 8)).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
expect(bjork(5, 8)).toStrictEqual([1, 0, 1, 1, 0, 1, 1, 0]);
|
expect(bjorklund(5, 8)).toStrictEqual([1, 0, 1, 1, 0, 1, 1, 0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
// returns true if the given string is a note
|
// returns true if the given string is a note
|
||||||
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
|
export const isNoteWithOctave = (name) => /^[a-gA-G][#bsf]*[0-9]*$/.test(name);
|
||||||
export const isNote = (name) => /^[a-gA-G][#bsf]*-?[0-9]?$/.test(name);
|
export const isNote = (name) => /^[a-gA-G][#bsf]*-?[0-9]*$/.test(name);
|
||||||
export const tokenizeNote = (note) => {
|
export const tokenizeNote = (note) => {
|
||||||
if (typeof note !== 'string') {
|
if (typeof note !== 'string') {
|
||||||
return [];
|
return [];
|
||||||
@@ -23,6 +23,10 @@ export const tokenizeNote = (note) => {
|
|||||||
const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
|
const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
|
||||||
const accs = { '#': 1, b: -1, s: 1, f: -1 };
|
const accs = { '#': 1, b: -1, s: 1, f: -1 };
|
||||||
|
|
||||||
|
export const getAccidentalsOffset = (accidentals) => {
|
||||||
|
return accidentals?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
// turns the given note into its midi number representation
|
// turns the given note into its midi number representation
|
||||||
export const noteToMidi = (note, defaultOctave = 3) => {
|
export const noteToMidi = (note, defaultOctave = 3) => {
|
||||||
const [pc, acc, oct = defaultOctave] = tokenizeNote(note);
|
const [pc, acc, oct = defaultOctave] = tokenizeNote(note);
|
||||||
@@ -30,7 +34,7 @@ export const noteToMidi = (note, defaultOctave = 3) => {
|
|||||||
throw new Error('not a note: "' + note + '"');
|
throw new Error('not a note: "' + note + '"');
|
||||||
}
|
}
|
||||||
const chroma = chromas[pc.toLowerCase()];
|
const chroma = chromas[pc.toLowerCase()];
|
||||||
const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
const offset = getAccidentalsOffset(acc);
|
||||||
return (Number(oct) + 1) * 12 + chroma + offset;
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
};
|
};
|
||||||
export const midiToFreq = (n) => {
|
export const midiToFreq = (n) => {
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ export class MondoParser {
|
|||||||
close_curly: /^\}/,
|
close_curly: /^\}/,
|
||||||
number: /^-?[0-9]*\.?[0-9]+/, // before pipe!
|
number: /^-?[0-9]*\.?[0-9]+/, // before pipe!
|
||||||
// TODO: better error handling when "-" is used as rest, e.g "s [- bd]"
|
// TODO: better error handling when "-" is used as rest, e.g "s [- bd]"
|
||||||
op: /^[*/:!@%?+-]|^\.{2}/, // * / : ! @ % ? ..
|
op: /^[*/:!@%?+\-&]|^\.{2}/, // * / : ! @ % ? ..
|
||||||
// dollar: /^\$/,
|
// dollar: /^\$/,
|
||||||
pipe: /^#/,
|
pipe: /^#/,
|
||||||
stack: /^[,$]/,
|
stack: /^[,$]/,
|
||||||
or: /^[|]/,
|
or: /^[|]/,
|
||||||
plain: /^[a-zA-Z0-9-~_^#]+/,
|
plain: /^[a-zA-Z0-9-~_^#]+/,
|
||||||
};
|
};
|
||||||
|
op_precedence = [['*', '/', ':', '!', '@', '%', '?', '+', '-', '..'], ['&']];
|
||||||
// matches next token
|
// matches next token
|
||||||
next_token(code, offset = 0) {
|
next_token(code, offset = 0) {
|
||||||
for (let type in this.token_types) {
|
for (let type in this.token_types) {
|
||||||
@@ -150,9 +151,9 @@ export class MondoParser {
|
|||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
desugar_ops(children) {
|
desugar_ops(children, types) {
|
||||||
while (true) {
|
while (true) {
|
||||||
let opIndex = children.findIndex((child) => child.type === 'op');
|
let opIndex = children.findIndex((child) => child.type === 'op' && types.includes(child.value));
|
||||||
if (opIndex === -1) break;
|
if (opIndex === -1) break;
|
||||||
const op = { type: 'plain', value: children[opIndex].value };
|
const op = { type: 'plain', value: children[opIndex].value };
|
||||||
if (opIndex === children.length - 1) {
|
if (opIndex === children.length - 1) {
|
||||||
@@ -263,8 +264,10 @@ export class MondoParser {
|
|||||||
// the type we've removed before splitting needs to be added back
|
// the type we've removed before splitting needs to be added back
|
||||||
children = [{ type: 'plain', value: type }, ...children];
|
children = [{ type: 'plain', value: type }, ...children];
|
||||||
}
|
}
|
||||||
children = this.desugar_ops(children);
|
// for each precendence group, call desugar_ops once
|
||||||
// children = this.desugar_pipes(children, (children) => this.desugar_dollars(children));
|
this.op_precedence.forEach((ops) => {
|
||||||
|
children = this.desugar_ops(children, ops);
|
||||||
|
});
|
||||||
children = this.desugar_pipes(children);
|
children = this.desugar_pipes(children);
|
||||||
return children;
|
return children;
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ describe('mondo sugar', () => {
|
|||||||
it('should desugar x:y', () => expect(desguar('x:y')).toEqual('(: y x)'));
|
it('should desugar x:y', () => expect(desguar('x:y')).toEqual('(: y x)'));
|
||||||
it('should desugar x:y:z', () => expect(desguar('x:y:z')).toEqual('(: z (: y x))'));
|
it('should desugar x:y:z', () => expect(desguar('x:y:z')).toEqual('(: z (: y x))'));
|
||||||
it('should desugar x:y*x', () => expect(desguar('bd:0*2')).toEqual('(* 2 (: 0 bd))'));
|
it('should desugar x:y*x', () => expect(desguar('bd:0*2')).toEqual('(* 2 (: 0 bd))'));
|
||||||
|
it('should desugar x&y:z', () => expect(desguar('bd&3:8')).toEqual('(& (: 8 3) bd)'));
|
||||||
it('should desugar a..b', () => expect(desguar('0..2')).toEqual('(.. 2 0)'));
|
it('should desugar a..b', () => expect(desguar('0..2')).toEqual('(.. 2 0)'));
|
||||||
/* it('should desugar x $ y', () => expect(desguar('x $ y')).toEqual('(x y)'));
|
/* it('should desugar x $ y', () => expect(desguar('x $ y')).toEqual('(x y)'));
|
||||||
it('should desugar x $ y z', () => expect(desguar('x $ y z')).toEqual('(x (y z))'));
|
it('should desugar x $ y z', () => expect(desguar('x $ y z')).toEqual('(x (y z))'));
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
chooseIn,
|
chooseIn,
|
||||||
degradeBy,
|
degradeBy,
|
||||||
silence,
|
silence,
|
||||||
|
bjork,
|
||||||
} from '@strudel/core';
|
} from '@strudel/core';
|
||||||
import { registerLanguage } from '@strudel/transpiler';
|
import { registerLanguage } from '@strudel/transpiler';
|
||||||
import { MondoRunner } from 'mondolang';
|
import { MondoRunner } from 'mondolang';
|
||||||
@@ -40,6 +41,7 @@ lib['!'] = replicate;
|
|||||||
lib['@'] = expand;
|
lib['@'] = expand;
|
||||||
lib['%'] = pace;
|
lib['%'] = pace;
|
||||||
lib['?'] = degradeBy; // todo: default 0.5 not working..
|
lib['?'] = degradeBy; // todo: default 0.5 not working..
|
||||||
|
lib['&'] = bjork;
|
||||||
lib[':'] = tail;
|
lib[':'] = tail;
|
||||||
lib['..'] = range;
|
lib['..'] = range;
|
||||||
lib['def'] = () => silence;
|
lib['def'] = () => silence;
|
||||||
|
|||||||
@@ -16,13 +16,17 @@ export const tokenizeNote = (note) => {
|
|||||||
const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
|
const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
|
||||||
const accs = { '#': 1, b: -1, s: 1, f: -1 };
|
const accs = { '#': 1, b: -1, s: 1, f: -1 };
|
||||||
|
|
||||||
|
export const getAccidentalsOffset = (accidentals) => {
|
||||||
|
return accidentals?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
export const noteToMidi = (note, defaultOctave = 3) => {
|
export const noteToMidi = (note, defaultOctave = 3) => {
|
||||||
const [pc, acc, oct = defaultOctave] = tokenizeNote(note);
|
const [pc, acc, oct = defaultOctave] = tokenizeNote(note);
|
||||||
if (!pc) {
|
if (!pc) {
|
||||||
throw new Error('not a note: "' + note + '"');
|
throw new Error('not a note: "' + note + '"');
|
||||||
}
|
}
|
||||||
const chroma = chromas[pc.toLowerCase()];
|
const chroma = chromas[pc.toLowerCase()];
|
||||||
const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
const offset = getAccidentalsOffset(acc);
|
||||||
return (Number(oct) + 1) * 12 + chroma + offset;
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
};
|
};
|
||||||
export const midiToFreq = (n) => {
|
export const midiToFreq = (n) => {
|
||||||
|
|||||||
@@ -61,13 +61,6 @@ describe('tonal', () => {
|
|||||||
.firstCycleValues.map((h) => h.note),
|
.firstCycleValues.map((h) => h.note),
|
||||||
).toEqual(['B2', 'Eb3', 'A2', 'G3', 'F3']);
|
).toEqual(['B2', 'Eb3', 'A2', 'G3', 'F3']);
|
||||||
});
|
});
|
||||||
it('produces silence for mixed sharps and flats', () => {
|
|
||||||
expect(
|
|
||||||
n(seq('0b#', '1#b', '2#b#'))
|
|
||||||
.scale('C major')
|
|
||||||
.firstCycleValues.map((h) => h.note),
|
|
||||||
).toEqual([]);
|
|
||||||
});
|
|
||||||
it('snaps notes (upwards) to scale', () => {
|
it('snaps notes (upwards) to scale', () => {
|
||||||
const inputNotes = ['Cb', 'Eb', 'G', 'A#', 'Bb'];
|
const inputNotes = ['Cb', 'Eb', 'G', 'A#', 'Bb'];
|
||||||
const expectedNotes = ['B2', 'E3', 'G3', 'B3', 'B3'];
|
const expectedNotes = ['B2', 'E3', 'G3', 'B3', 'B3'];
|
||||||
|
|||||||
+45
-46
@@ -5,9 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
||||||
import { register, _mod, silence, logger, pure, isNote } from '@strudel/core';
|
import { register, _mod, logger, isNote, noteToMidi, removeUndefineds, getAccidentalsOffset } from '@strudel/core';
|
||||||
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
|
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
|
||||||
import { noteToMidi } from '../core/util.mjs';
|
|
||||||
|
|
||||||
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
|
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
|
||||||
|
|
||||||
@@ -185,17 +184,15 @@ function _convertStepToNumberAndOffset(step) {
|
|||||||
step = String(step);
|
step = String(step);
|
||||||
// Check to see if the step matches the expected format:
|
// Check to see if the step matches the expected format:
|
||||||
// - A number (possibly negative)
|
// - A number (possibly negative)
|
||||||
// - Some number of sharps or flats (but not both)
|
// - Some number of sharps or flats
|
||||||
const match = /^(-?\d+)(#+|b+)?$/.exec(step);
|
const match = /^(-?\d+)([#bsf]*)$/.exec(step);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new Error(`invalid scale step "${step}", expected number or integer with optional # b suffixes`);
|
throw new Error(`invalid scale step "${step}", expected number or integer with optional # b suffixes`);
|
||||||
}
|
}
|
||||||
asNumber = Number(match[1]);
|
asNumber = Number(match[1]);
|
||||||
// These decorations will determine the semitone offset based on the number of
|
const accidentals = match[2] || '';
|
||||||
// sharps or flats
|
offset = getAccidentalsOffset(accidentals);
|
||||||
const decorations = match[2] || '';
|
|
||||||
offset = decorations[0] === '#' ? decorations.length : -decorations.length;
|
|
||||||
}
|
}
|
||||||
return [asNumber, offset];
|
return [asNumber, offset];
|
||||||
}
|
}
|
||||||
@@ -226,7 +223,7 @@ function _getNearestScaleNote(scaleName, note, preferHigher = true) {
|
|||||||
* Turns numbers into notes in the scale (zero indexed) or quantizes notes to a scale.
|
* Turns numbers into notes in the scale (zero indexed) or quantizes notes to a scale.
|
||||||
*
|
*
|
||||||
* When describing notes via numbers, note that negative numbers can be used to wrap backwards
|
* When describing notes via numbers, note that negative numbers can be used to wrap backwards
|
||||||
* in the scale as well as sharps or flats (but not both) to produce notes outside of the scale.
|
* in the scale as well as sharps or flats to produce notes outside of the scale.
|
||||||
*
|
*
|
||||||
* Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
|
* Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
|
||||||
*
|
*
|
||||||
@@ -254,7 +251,6 @@ function _getNearestScaleNote(scaleName, note, preferHigher = true) {
|
|||||||
* @example
|
* @example
|
||||||
* note("C1*16").transpose(irand(36)).scale('Cb2 major').scaleTranspose(3)
|
* note("C1*16").transpose(irand(36)).scale('Cb2 major').scaleTranspose(3)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const scale = register(
|
export const scale = register(
|
||||||
'scale',
|
'scale',
|
||||||
function (scale, pat) {
|
function (scale, pat) {
|
||||||
@@ -262,44 +258,47 @@ export const scale = register(
|
|||||||
if (Array.isArray(scale)) {
|
if (Array.isArray(scale)) {
|
||||||
scale = scale.flat().join(' ');
|
scale = scale.flat().join(' ');
|
||||||
}
|
}
|
||||||
return (
|
return pat.withHaps((haps) => {
|
||||||
pat
|
haps = haps.map((hap) => {
|
||||||
.fmap((value) => {
|
let hVal = hap.value;
|
||||||
const isObject = typeof value === 'object';
|
const isObject = typeof hVal === 'object';
|
||||||
// The case where the note has been defined via `n` or `pure`
|
// If hVal is a pure value, place it on `n` so that we interpret it as a scale degree
|
||||||
if (!isObject || (isObject && ('n' in value || 'value' in value))) {
|
hVal = isObject ? hVal : { n: hVal };
|
||||||
const step = isObject ? (value.n ?? value.value) : value;
|
const { note, n, value, ...otherValues } = hVal;
|
||||||
delete value.n; // remove n so it won't cause trouble
|
const noteOrStep = note ?? n ?? value;
|
||||||
if (isNote(step)) {
|
if (noteOrStep === undefined) {
|
||||||
// legacy..
|
logger(
|
||||||
return pure(step);
|
`[tonal] Invalid value format for 'scale'. Value must contain n, note, or value but received keys [${Object.keys(hVal).join(', ')}]`,
|
||||||
|
'error',
|
||||||
|
);
|
||||||
|
return hap; // pass the value through unchanged
|
||||||
|
}
|
||||||
|
let scaleNote;
|
||||||
|
if (isNote(noteOrStep)) {
|
||||||
|
// Note case (quantize to scale)
|
||||||
|
scaleNote = _getNearestScaleNote(scale, noteOrStep);
|
||||||
|
hap.value = { ...otherValues, note: scaleNote };
|
||||||
|
} else {
|
||||||
|
// Step case (convert to note in scale)
|
||||||
|
try {
|
||||||
|
const [number, offset] = _convertStepToNumberAndOffset(noteOrStep);
|
||||||
|
if (otherValues.anchor) {
|
||||||
|
scaleNote = stepInNamedScale(number, scale, otherValues.anchor);
|
||||||
|
} else {
|
||||||
|
scaleNote = scaleStep(number, scale);
|
||||||
}
|
}
|
||||||
try {
|
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
|
||||||
const [number, offset] = _convertStepToNumberAndOffset(step);
|
} catch (err) {
|
||||||
let note;
|
logger(`[tonal] ${err.message}`, 'error');
|
||||||
if (isObject && value.anchor) {
|
return; // will be removed
|
||||||
note = stepInNamedScale(number, scale, value.anchor);
|
|
||||||
} else {
|
|
||||||
note = scaleStep(number, scale);
|
|
||||||
}
|
|
||||||
if (offset != 0) note = Note.transpose(note, Interval.fromSemitones(offset));
|
|
||||||
value = pure(isObject ? { ...value, note } : note);
|
|
||||||
} catch (err) {
|
|
||||||
logger(`[tonal] ${err.message}`, 'error');
|
|
||||||
return silence;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
// The case where the note has been defined via `note`
|
}
|
||||||
else {
|
hap.value = isObject ? { ...otherValues, note: scaleNote } : scaleNote;
|
||||||
const note = _getNearestScaleNote(scale, value.note);
|
// Tag with scale for downsteam scale-aware operations
|
||||||
return pure(isObject ? { ...value, note } : note);
|
return hap.setContext({ ...hap.context, scale });
|
||||||
}
|
});
|
||||||
})
|
return removeUndefineds(haps);
|
||||||
.outerJoin()
|
});
|
||||||
// legacy:
|
|
||||||
.withHap((hap) => hap.setContext({ ...hap.context, scale }))
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
true, // preserve step count
|
true, // preserve step count
|
||||||
|
|||||||
@@ -3838,8 +3838,8 @@ exports[`runs examples > example "fast" example index 0 1`] = `
|
|||||||
|
|
||||||
exports[`runs examples > example "fastChunk" example index 0 1`] = `
|
exports[`runs examples > example "fastChunk" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | color:red note:0 ]",
|
"[ 0/1 → 1/4 | note:C2 color:red ]",
|
||||||
"[ 1/4 → 1/2 | color:red note:1 ]",
|
"[ 1/4 → 1/2 | note:D2 color:red ]",
|
||||||
"[ 1/2 → 3/4 | note:E2 ]",
|
"[ 1/2 → 3/4 | note:E2 ]",
|
||||||
"[ 3/4 → 1/1 | note:F2 ]",
|
"[ 3/4 → 1/1 | note:F2 ]",
|
||||||
"[ 1/1 → 5/4 | note:G2 ]",
|
"[ 1/1 → 5/4 | note:G2 ]",
|
||||||
@@ -3848,8 +3848,8 @@ exports[`runs examples > example "fastChunk" example index 0 1`] = `
|
|||||||
"[ 7/4 → 2/1 | note:C3 ]",
|
"[ 7/4 → 2/1 | note:C3 ]",
|
||||||
"[ 2/1 → 9/4 | note:D3 ]",
|
"[ 2/1 → 9/4 | note:D3 ]",
|
||||||
"[ 9/4 → 5/2 | note:D2 ]",
|
"[ 9/4 → 5/2 | note:D2 ]",
|
||||||
"[ 5/2 → 11/4 | color:red note:2 ]",
|
"[ 5/2 → 11/4 | note:E2 color:red ]",
|
||||||
"[ 11/4 → 3/1 | color:red note:3 ]",
|
"[ 11/4 → 3/1 | note:F2 color:red ]",
|
||||||
"[ 3/1 → 13/4 | note:G2 ]",
|
"[ 3/1 → 13/4 | note:G2 ]",
|
||||||
"[ 13/4 → 7/2 | note:A2 ]",
|
"[ 13/4 → 7/2 | note:A2 ]",
|
||||||
"[ 7/2 → 15/4 | note:B2 ]",
|
"[ 7/2 → 15/4 | note:B2 ]",
|
||||||
|
|||||||
+250
-171
@@ -32,7 +32,12 @@
|
|||||||
"/packages/codemirror/keybindings.mjs": [
|
"/packages/codemirror/keybindings.mjs": [
|
||||||
"keybindings"
|
"keybindings"
|
||||||
],
|
],
|
||||||
"/packages/codemirror/themes/strudel-theme.mjs": [],
|
"/packages/codemirror/themes/theme-helper.mjs": [
|
||||||
|
"createTheme"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/strudel-theme.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
"/packages/codemirror/themes/bluescreen.mjs": [
|
"/packages/codemirror/themes/bluescreen.mjs": [
|
||||||
"settings"
|
"settings"
|
||||||
],
|
],
|
||||||
@@ -48,7 +53,103 @@
|
|||||||
"/packages/codemirror/themes/algoboy.mjs": [
|
"/packages/codemirror/themes/algoboy.mjs": [
|
||||||
"settings"
|
"settings"
|
||||||
],
|
],
|
||||||
"/packages/codemirror/themes/terminal.mjs": [
|
"/packages/codemirror/themes/CutiePi.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/sonic-pink.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/red-text.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/green-text.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/archBtw.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/fruitDaw.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/bluescreenlight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/androidstudio.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/atomone.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/aura.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/darcula.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/dracula.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/duotoneDark.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/eclipse.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/githubDark.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/githubLight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/gruvboxDark.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/gruvboxLight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/materialDark.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/materialLight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/nord.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/monokai.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/solarizedDark.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/solarizedLight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/sublime.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/tokyoNight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/tokioNightStorm.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/tokyoNightDay.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/vscodeDark.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/vscodeLight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/xcodeLight.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/bbedit.mjs": [
|
||||||
|
"settings"
|
||||||
|
],
|
||||||
|
"/packages/codemirror/themes/noctisLilac.mjs": [
|
||||||
"settings"
|
"settings"
|
||||||
],
|
],
|
||||||
"/packages/codemirror/themes.mjs": [
|
"/packages/codemirror/themes.mjs": [
|
||||||
@@ -61,7 +162,12 @@
|
|||||||
"activateTheme"
|
"activateTheme"
|
||||||
],
|
],
|
||||||
"/packages/codemirror/slider.mjs": [
|
"/packages/codemirror/slider.mjs": [
|
||||||
"acorn parse error: SyntaxError: undefined"
|
"sliderValues",
|
||||||
|
"SliderWidget",
|
||||||
|
"setSliderWidgets",
|
||||||
|
"updateSliderWidgets",
|
||||||
|
"sliderPlugin",
|
||||||
|
"sliderWithID"
|
||||||
],
|
],
|
||||||
"/packages/codemirror/widget.mjs": [
|
"/packages/codemirror/widget.mjs": [
|
||||||
"addWidget",
|
"addWidget",
|
||||||
@@ -78,6 +184,59 @@
|
|||||||
"StrudelMirror"
|
"StrudelMirror"
|
||||||
],
|
],
|
||||||
"/packages/codemirror/index.mjs": [],
|
"/packages/codemirror/index.mjs": [],
|
||||||
|
"/packages/core/logger.mjs": [
|
||||||
|
"logKey",
|
||||||
|
"errorLogger",
|
||||||
|
"logger"
|
||||||
|
],
|
||||||
|
"/packages/core/util.mjs": [
|
||||||
|
"isNoteWithOctave",
|
||||||
|
"isNote",
|
||||||
|
"tokenizeNote",
|
||||||
|
"noteToMidi",
|
||||||
|
"midiToFreq",
|
||||||
|
"freqToMidi",
|
||||||
|
"valueToMidi",
|
||||||
|
"getEventOffsetMs",
|
||||||
|
"_mod",
|
||||||
|
"averageArray",
|
||||||
|
"nanFallback",
|
||||||
|
"getSoundIndex",
|
||||||
|
"getPlayableNoteValue",
|
||||||
|
"getFrequency",
|
||||||
|
"rotate",
|
||||||
|
"pipe",
|
||||||
|
"compose",
|
||||||
|
"flatten",
|
||||||
|
"id",
|
||||||
|
"constant",
|
||||||
|
"listRange",
|
||||||
|
"curry",
|
||||||
|
"parseNumeral",
|
||||||
|
"mapArgs",
|
||||||
|
"numeralArgs",
|
||||||
|
"parseFractional",
|
||||||
|
"fractionalArgs",
|
||||||
|
"splitAt",
|
||||||
|
"zipWith",
|
||||||
|
"pairs",
|
||||||
|
"clamp",
|
||||||
|
"sol2note",
|
||||||
|
"uniq",
|
||||||
|
"uniqsort",
|
||||||
|
"uniqsortr",
|
||||||
|
"unicodeToBase64",
|
||||||
|
"base64ToUnicode",
|
||||||
|
"code2hash",
|
||||||
|
"hash2code",
|
||||||
|
"objectMap",
|
||||||
|
"cycleToSeconds",
|
||||||
|
"ClockCollator",
|
||||||
|
"getPerformanceTimeSeconds",
|
||||||
|
"keyAlias",
|
||||||
|
"getCurrentKeyboardState",
|
||||||
|
"stringifyValues"
|
||||||
|
],
|
||||||
"/packages/core/fraction.mjs": [
|
"/packages/core/fraction.mjs": [
|
||||||
"gcd",
|
"gcd",
|
||||||
"lcm"
|
"lcm"
|
||||||
@@ -91,45 +250,6 @@
|
|||||||
"/packages/core/state.mjs": [
|
"/packages/core/state.mjs": [
|
||||||
"State"
|
"State"
|
||||||
],
|
],
|
||||||
"/packages/core/logger.mjs": [
|
|
||||||
"logKey",
|
|
||||||
"logger"
|
|
||||||
],
|
|
||||||
"/packages/core/util.mjs": [
|
|
||||||
"isNoteWithOctave",
|
|
||||||
"isNote",
|
|
||||||
"tokenizeNote",
|
|
||||||
"noteToMidi",
|
|
||||||
"midiToFreq",
|
|
||||||
"freqToMidi",
|
|
||||||
"valueToMidi",
|
|
||||||
"_mod",
|
|
||||||
"nanFallback",
|
|
||||||
"getSoundIndex",
|
|
||||||
"getPlayableNoteValue",
|
|
||||||
"getFrequency",
|
|
||||||
"rotate",
|
|
||||||
"pipe",
|
|
||||||
"compose",
|
|
||||||
"flatten",
|
|
||||||
"constant",
|
|
||||||
"listRange",
|
|
||||||
"curry",
|
|
||||||
"parseNumeral",
|
|
||||||
"mapArgs",
|
|
||||||
"numeralArgs",
|
|
||||||
"parseFractional",
|
|
||||||
"fractionalArgs",
|
|
||||||
"splitAt",
|
|
||||||
"zipWith",
|
|
||||||
"clamp",
|
|
||||||
"sol2note",
|
|
||||||
"unicodeToBase64",
|
|
||||||
"base64ToUnicode",
|
|
||||||
"code2hash",
|
|
||||||
"hash2code",
|
|
||||||
"objectMap"
|
|
||||||
],
|
|
||||||
"/packages/core/value.mjs": [
|
"/packages/core/value.mjs": [
|
||||||
"unionWithObj",
|
"unionWithObj",
|
||||||
"valued",
|
"valued",
|
||||||
@@ -138,10 +258,8 @@
|
|||||||
],
|
],
|
||||||
"/packages/core/drawLine.mjs": [],
|
"/packages/core/drawLine.mjs": [],
|
||||||
"/packages/core/pattern.mjs": [
|
"/packages/core/pattern.mjs": [
|
||||||
|
"calculateSteps",
|
||||||
"setStringParser",
|
"setStringParser",
|
||||||
"polyrhythm",
|
|
||||||
"pr",
|
|
||||||
"pm",
|
|
||||||
"nothing",
|
"nothing",
|
||||||
"isPattern",
|
"isPattern",
|
||||||
"reify",
|
"reify",
|
||||||
@@ -149,8 +267,12 @@
|
|||||||
"stackRight",
|
"stackRight",
|
||||||
"stackCentre",
|
"stackCentre",
|
||||||
"stackBy",
|
"stackBy",
|
||||||
"fastcat",
|
"bind",
|
||||||
"_polymeterListSteps",
|
"innerBind",
|
||||||
|
"outerBind",
|
||||||
|
"squeezeBind",
|
||||||
|
"stepBind",
|
||||||
|
"polyBind",
|
||||||
"set",
|
"set",
|
||||||
"keep",
|
"keep",
|
||||||
"keepif",
|
"keepif",
|
||||||
@@ -174,96 +296,51 @@
|
|||||||
"func",
|
"func",
|
||||||
"compressSpan",
|
"compressSpan",
|
||||||
"compressspan",
|
"compressspan",
|
||||||
"fastgap",
|
|
||||||
"focusSpan",
|
"focusSpan",
|
||||||
"focusspan",
|
"focusspan",
|
||||||
"density",
|
|
||||||
"sparsity",
|
|
||||||
"zoomArc",
|
"zoomArc",
|
||||||
"zoomarc",
|
"zoomarc",
|
||||||
"inv",
|
|
||||||
"juxby",
|
|
||||||
"echowith",
|
|
||||||
"stutWith",
|
|
||||||
"stutwith",
|
|
||||||
"iterback",
|
|
||||||
"slowchunk",
|
|
||||||
"slowChunk",
|
|
||||||
"chunkback",
|
|
||||||
"fastchunk",
|
|
||||||
"bypass",
|
"bypass",
|
||||||
"hsla",
|
"hsla",
|
||||||
"hsl",
|
"hsl",
|
||||||
"loopat",
|
"_retime",
|
||||||
"loopatcps"
|
"_slices",
|
||||||
|
"_fitslice",
|
||||||
|
"_match",
|
||||||
|
"_polymeterListSteps",
|
||||||
|
"shrinklist",
|
||||||
|
"s_cat",
|
||||||
|
"s_alt",
|
||||||
|
"s_polymeter",
|
||||||
|
"s_taper",
|
||||||
|
"s_taperlist",
|
||||||
|
"s_add",
|
||||||
|
"s_sub",
|
||||||
|
"s_expand",
|
||||||
|
"s_extend",
|
||||||
|
"s_contract",
|
||||||
|
"s_tour",
|
||||||
|
"s_zip",
|
||||||
|
"steps",
|
||||||
|
"_morph"
|
||||||
],
|
],
|
||||||
"/packages/core/controls.mjs": [
|
"/packages/core/controls.mjs": [
|
||||||
"createParam",
|
"createParam",
|
||||||
"sound",
|
"isControlName",
|
||||||
"src",
|
"registerControl",
|
||||||
"att",
|
|
||||||
"fmi",
|
|
||||||
"fmrelease",
|
"fmrelease",
|
||||||
"fmvelocity",
|
"fmvelocity",
|
||||||
"analyze",
|
"analyze",
|
||||||
"fft",
|
"fft",
|
||||||
"dec",
|
|
||||||
"sus",
|
|
||||||
"rel",
|
|
||||||
"hold",
|
"hold",
|
||||||
"bandf",
|
"duck",
|
||||||
"bp",
|
|
||||||
"bandq",
|
|
||||||
"loopb",
|
|
||||||
"loope",
|
|
||||||
"ch",
|
|
||||||
"phaserrate",
|
"phaserrate",
|
||||||
"phasr",
|
|
||||||
"ph",
|
|
||||||
"phs",
|
|
||||||
"phc",
|
|
||||||
"phd",
|
|
||||||
"phasdp",
|
"phasdp",
|
||||||
"cutoff",
|
|
||||||
"ctf",
|
|
||||||
"lp",
|
|
||||||
"lpe",
|
|
||||||
"hpe",
|
|
||||||
"bpe",
|
|
||||||
"lpa",
|
|
||||||
"hpa",
|
|
||||||
"bpa",
|
|
||||||
"lpd",
|
|
||||||
"hpd",
|
|
||||||
"bpd",
|
|
||||||
"lps",
|
|
||||||
"hps",
|
|
||||||
"bps",
|
|
||||||
"lpr",
|
|
||||||
"hpr",
|
|
||||||
"bpr",
|
|
||||||
"fanchor",
|
|
||||||
"vibrato",
|
|
||||||
"v",
|
|
||||||
"vmod",
|
|
||||||
"hcutoff",
|
|
||||||
"hp",
|
|
||||||
"hresonance",
|
|
||||||
"resonance",
|
|
||||||
"delayfb",
|
|
||||||
"dfb",
|
|
||||||
"delayt",
|
|
||||||
"dt",
|
|
||||||
"lock",
|
|
||||||
"det",
|
|
||||||
"fadeTime",
|
"fadeTime",
|
||||||
"fadeOutTime",
|
"fadeOutTime",
|
||||||
"fadeInTime",
|
"fadeInTime",
|
||||||
"patt",
|
|
||||||
"pdec",
|
|
||||||
"psustain",
|
"psustain",
|
||||||
"psus",
|
"psus",
|
||||||
"prel",
|
|
||||||
"gate",
|
"gate",
|
||||||
"gat",
|
"gat",
|
||||||
"activeLabel",
|
"activeLabel",
|
||||||
@@ -291,26 +368,13 @@
|
|||||||
"offset",
|
"offset",
|
||||||
"octaves",
|
"octaves",
|
||||||
"mode",
|
"mode",
|
||||||
"rlp",
|
|
||||||
"rdim",
|
|
||||||
"rfade",
|
|
||||||
"ir",
|
|
||||||
"size",
|
|
||||||
"sz",
|
|
||||||
"rsize",
|
|
||||||
"dist",
|
|
||||||
"compressorKnee",
|
"compressorKnee",
|
||||||
"compressorRatio",
|
"compressorRatio",
|
||||||
"compressorAttack",
|
"compressorAttack",
|
||||||
"compressorRelease",
|
"compressorRelease",
|
||||||
"waveloss",
|
"waveloss",
|
||||||
"density",
|
|
||||||
"expression",
|
"expression",
|
||||||
"sustainpedal",
|
"sustainpedal",
|
||||||
"tremolodepth",
|
|
||||||
"tremdp",
|
|
||||||
"tremolorate",
|
|
||||||
"tremr",
|
|
||||||
"fshift",
|
"fshift",
|
||||||
"fshiftnote",
|
"fshiftnote",
|
||||||
"fshiftphase",
|
"fshiftphase",
|
||||||
@@ -336,27 +400,15 @@
|
|||||||
"binshift",
|
"binshift",
|
||||||
"hbrick",
|
"hbrick",
|
||||||
"lbrick",
|
"lbrick",
|
||||||
"midichan",
|
|
||||||
"control",
|
|
||||||
"ccn",
|
|
||||||
"ccv",
|
|
||||||
"polyTouch",
|
|
||||||
"midibend",
|
|
||||||
"miditouch",
|
|
||||||
"ctlNum",
|
|
||||||
"frameRate",
|
"frameRate",
|
||||||
"frames",
|
"frames",
|
||||||
"hours",
|
"hours",
|
||||||
"midicmd",
|
|
||||||
"minutes",
|
"minutes",
|
||||||
"progNum",
|
|
||||||
"seconds",
|
"seconds",
|
||||||
"songPtr",
|
"songPtr",
|
||||||
"uid",
|
"uid",
|
||||||
"val",
|
"val",
|
||||||
"cps",
|
"cps",
|
||||||
"legato",
|
|
||||||
"dur",
|
|
||||||
"zrand",
|
"zrand",
|
||||||
"curve",
|
"curve",
|
||||||
"deltaSlide",
|
"deltaSlide",
|
||||||
@@ -368,44 +420,40 @@
|
|||||||
"zmod",
|
"zmod",
|
||||||
"zcrush",
|
"zcrush",
|
||||||
"zdelay",
|
"zdelay",
|
||||||
"tremolo",
|
|
||||||
"zzfx",
|
"zzfx",
|
||||||
"colour",
|
|
||||||
"createParams",
|
"createParams",
|
||||||
"ad",
|
"ad",
|
||||||
"ds",
|
"ds",
|
||||||
"ar"
|
"ar",
|
||||||
|
"midimap",
|
||||||
|
"ctlNum",
|
||||||
|
"polyTouch",
|
||||||
|
"getControlName"
|
||||||
],
|
],
|
||||||
"/packages/core/euclid.mjs": [
|
"/packages/core/euclid.mjs": [
|
||||||
"bjork",
|
"bjork",
|
||||||
|
"e",
|
||||||
"euclidrot"
|
"euclidrot"
|
||||||
],
|
],
|
||||||
"/packages/core/zyklus.mjs": [],
|
"/packages/core/zyklus.mjs": [],
|
||||||
"/packages/core/signal.mjs": [
|
"/packages/core/signal.mjs": [
|
||||||
"steady",
|
"randrun",
|
||||||
"signal",
|
|
||||||
"isaw",
|
|
||||||
"isaw2",
|
|
||||||
"saw2",
|
|
||||||
"sine2",
|
|
||||||
"cosine2",
|
|
||||||
"square2",
|
|
||||||
"tri2",
|
|
||||||
"time",
|
|
||||||
"_brandBy",
|
"_brandBy",
|
||||||
"_irand",
|
"_irand",
|
||||||
"pickSqueeze",
|
|
||||||
"pickmodSqueeze",
|
|
||||||
"__chooseWith",
|
"__chooseWith",
|
||||||
"randcat",
|
"chooseIn",
|
||||||
"wrandcat",
|
"chooseOut",
|
||||||
"perlinWith",
|
"perlinWith",
|
||||||
"degradeByWith"
|
"berlinWith",
|
||||||
|
"degradeByWith",
|
||||||
|
"_keyDown"
|
||||||
],
|
],
|
||||||
|
"/packages/core/pick.mjs": [],
|
||||||
"/packages/core/speak.mjs": [
|
"/packages/core/speak.mjs": [
|
||||||
"speak"
|
"speak"
|
||||||
],
|
],
|
||||||
"/packages/core/evaluate.mjs": [
|
"/packages/core/evaluate.mjs": [
|
||||||
|
"strudelScope",
|
||||||
"evalScope",
|
"evalScope",
|
||||||
"evaluate"
|
"evaluate"
|
||||||
],
|
],
|
||||||
@@ -440,13 +488,18 @@
|
|||||||
"isTauri"
|
"isTauri"
|
||||||
],
|
],
|
||||||
"/packages/desktopbridge/midibridge.mjs": [],
|
"/packages/desktopbridge/midibridge.mjs": [],
|
||||||
"/packages/desktopbridge/oscbridge.mjs": [],
|
"/packages/desktopbridge/oscbridge.mjs": [
|
||||||
|
"oscTriggerTauri"
|
||||||
|
],
|
||||||
"/packages/desktopbridge/index.mjs": [],
|
"/packages/desktopbridge/index.mjs": [],
|
||||||
"/packages/draw/draw.mjs": [
|
"/packages/draw/draw.mjs": [
|
||||||
"getDrawContext",
|
"getDrawContext",
|
||||||
"cleanupDraw",
|
"cleanupDraw",
|
||||||
"Framer",
|
"Framer",
|
||||||
"Drawer"
|
"Drawer",
|
||||||
|
"getComputedPropertyValue",
|
||||||
|
"getTheme",
|
||||||
|
"setTheme"
|
||||||
],
|
],
|
||||||
"/packages/draw/animate.mjs": [
|
"/packages/draw/animate.mjs": [
|
||||||
"x",
|
"x",
|
||||||
@@ -467,16 +520,19 @@
|
|||||||
"convertHexToNumber"
|
"convertHexToNumber"
|
||||||
],
|
],
|
||||||
"/packages/draw/pianoroll.mjs": [
|
"/packages/draw/pianoroll.mjs": [
|
||||||
|
"__pianoroll",
|
||||||
"getDrawOptions",
|
"getDrawOptions",
|
||||||
"getPunchcardPainter",
|
"getPunchcardPainter",
|
||||||
"drawPianoroll"
|
"drawPianoroll"
|
||||||
],
|
],
|
||||||
"/packages/draw/spiral.mjs": [],
|
"/packages/draw/spiral.mjs": [],
|
||||||
|
"/packages/draw/pitchwheel.mjs": [],
|
||||||
"/packages/draw/index.mjs": [],
|
"/packages/draw/index.mjs": [],
|
||||||
"/packages/midi/midi.mjs": [
|
"/packages/midi/midi.mjs": [
|
||||||
"WebMidi",
|
"WebMidi",
|
||||||
"enableWebMidi",
|
"enableWebMidi",
|
||||||
"midin"
|
"midicontrolMap",
|
||||||
|
"midisoundMap"
|
||||||
],
|
],
|
||||||
"/packages/midi/index.mjs": [],
|
"/packages/midi/index.mjs": [],
|
||||||
"/packages/mini/krill-parser.js": [],
|
"/packages/mini/krill-parser.js": [],
|
||||||
@@ -496,12 +552,11 @@
|
|||||||
"/packages/repl/prebake.mjs": [
|
"/packages/repl/prebake.mjs": [
|
||||||
"prebake"
|
"prebake"
|
||||||
],
|
],
|
||||||
"/packages/repl/repl-component.mjs": [
|
"/packages/repl/repl-component.mjs": [],
|
||||||
"acorn parse error: SyntaxError: undefined"
|
|
||||||
],
|
|
||||||
"/packages/repl/index.mjs": [],
|
"/packages/repl/index.mjs": [],
|
||||||
"/packages/soundfonts/gm.mjs": [],
|
"/packages/soundfonts/gm.mjs": [],
|
||||||
"/packages/soundfonts/fontloader.mjs": [
|
"/packages/soundfonts/fontloader.mjs": [
|
||||||
|
"setSoundfontUrl",
|
||||||
"getFontBufferSource",
|
"getFontBufferSource",
|
||||||
"getFontPitch",
|
"getFontPitch",
|
||||||
"registerSoundfonts"
|
"registerSoundfonts"
|
||||||
@@ -522,6 +577,7 @@
|
|||||||
"vowelFormant"
|
"vowelFormant"
|
||||||
],
|
],
|
||||||
"/packages/superdough/logger.mjs": [
|
"/packages/superdough/logger.mjs": [
|
||||||
|
"errorLogger",
|
||||||
"logger",
|
"logger",
|
||||||
"setLogger"
|
"setLogger"
|
||||||
],
|
],
|
||||||
@@ -534,10 +590,19 @@
|
|||||||
"valueToMidi",
|
"valueToMidi",
|
||||||
"nanFallback",
|
"nanFallback",
|
||||||
"_mod",
|
"_mod",
|
||||||
"getSoundIndex"
|
"getSoundIndex",
|
||||||
|
"cycleToSeconds",
|
||||||
|
"secondsToCycle"
|
||||||
|
],
|
||||||
|
"/packages/superdough/noise.mjs": [
|
||||||
|
"getNoiseBuffer",
|
||||||
|
"getNoiseOscillator",
|
||||||
|
"getNoiseMix"
|
||||||
],
|
],
|
||||||
"/packages/superdough/helpers.mjs": [
|
"/packages/superdough/helpers.mjs": [
|
||||||
|
"noises",
|
||||||
"gainNode",
|
"gainNode",
|
||||||
|
"getWorklet",
|
||||||
"getParamADSR",
|
"getParamADSR",
|
||||||
"getCompressor",
|
"getCompressor",
|
||||||
"getADSRValues",
|
"getADSRValues",
|
||||||
@@ -550,6 +615,8 @@
|
|||||||
],
|
],
|
||||||
"/packages/superdough/sampler.mjs": [
|
"/packages/superdough/sampler.mjs": [
|
||||||
"getCachedBuffer",
|
"getCachedBuffer",
|
||||||
|
"getSampleInfo",
|
||||||
|
"getSampleBuffer",
|
||||||
"getSampleBufferSource",
|
"getSampleBufferSource",
|
||||||
"loadBuffer",
|
"loadBuffer",
|
||||||
"reverseBuffer",
|
"reverseBuffer",
|
||||||
@@ -559,18 +626,32 @@
|
|||||||
"onTriggerSample"
|
"onTriggerSample"
|
||||||
],
|
],
|
||||||
"/packages/superdough/superdough.mjs": [
|
"/packages/superdough/superdough.mjs": [
|
||||||
|
"DEFAULT_MAX_POLYPHONY",
|
||||||
|
"setMaxPolyphony",
|
||||||
|
"setMultiChannelOrbits",
|
||||||
"soundMap",
|
"soundMap",
|
||||||
"registerSound",
|
"registerSound",
|
||||||
|
"applyGainCurve",
|
||||||
|
"setGainCurve",
|
||||||
"getSound",
|
"getSound",
|
||||||
|
"getAudioDevices",
|
||||||
|
"setDefault",
|
||||||
|
"resetDefaults",
|
||||||
|
"setDefaultValue",
|
||||||
|
"getDefaultValue",
|
||||||
|
"setDefaultValues",
|
||||||
|
"resetDefaultValues",
|
||||||
|
"setVersionDefaults",
|
||||||
"resetLoadedSounds",
|
"resetLoadedSounds",
|
||||||
"setDefaultAudioContext",
|
"setDefaultAudioContext",
|
||||||
"getAudioContext",
|
"getAudioContext",
|
||||||
"getWorklet",
|
"getAudioContextCurrentTime",
|
||||||
"initAudio",
|
"initAudio",
|
||||||
"initAudioOnFirstClick",
|
"initAudioOnFirstClick",
|
||||||
"initializeAudioOutput",
|
"initializeAudioOutput",
|
||||||
"connectToDestination",
|
"connectToDestination",
|
||||||
"panic",
|
"panic",
|
||||||
|
"getLfo",
|
||||||
"analysers",
|
"analysers",
|
||||||
"analysersData",
|
"analysersData",
|
||||||
"getAnalyserById",
|
"getAnalyserById",
|
||||||
@@ -579,17 +660,13 @@
|
|||||||
"superdough",
|
"superdough",
|
||||||
"superdoughTrigger"
|
"superdoughTrigger"
|
||||||
],
|
],
|
||||||
"/packages/superdough/noise.mjs": [
|
|
||||||
"getNoiseOscillator",
|
|
||||||
"getNoiseMix"
|
|
||||||
],
|
|
||||||
"/packages/superdough/synth.mjs": [
|
"/packages/superdough/synth.mjs": [
|
||||||
"registerSynthSounds",
|
"registerSynthSounds",
|
||||||
"waveformN",
|
"waveformN",
|
||||||
"getOscillator"
|
"getOscillator"
|
||||||
],
|
],
|
||||||
"/packages/superdough/zzfx_fork.mjs": [
|
"/packages/superdough/zzfx_fork.mjs": [
|
||||||
"acorn parse error: SyntaxError: undefined"
|
"buildSamples"
|
||||||
],
|
],
|
||||||
"/packages/superdough/zzfx.mjs": [
|
"/packages/superdough/zzfx.mjs": [
|
||||||
"getZZFX",
|
"getZZFX",
|
||||||
@@ -640,6 +717,7 @@
|
|||||||
],
|
],
|
||||||
"/packages/transpiler/transpiler.mjs": [
|
"/packages/transpiler/transpiler.mjs": [
|
||||||
"registerWidgetType",
|
"registerWidgetType",
|
||||||
|
"registerLanguage",
|
||||||
"transpiler",
|
"transpiler",
|
||||||
"getWidgetID"
|
"getWidgetID"
|
||||||
],
|
],
|
||||||
@@ -654,6 +732,7 @@
|
|||||||
"drawTimeScope",
|
"drawTimeScope",
|
||||||
"drawFrequencyScope"
|
"drawFrequencyScope"
|
||||||
],
|
],
|
||||||
|
"/packages/webaudio/spectrum.mjs": [],
|
||||||
"/packages/webaudio/index.mjs": [],
|
"/packages/webaudio/index.mjs": [],
|
||||||
"/packages/xen/xen.mjs": [
|
"/packages/xen/xen.mjs": [
|
||||||
"edo",
|
"edo",
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ Here's an example AST for `c3 [e3 g3]`
|
|||||||
|
|
||||||
which translates to `seq(c3, seq(e3, g3))`
|
which translates to `seq(c3, seq(e3, g3))`
|
||||||
|
|
||||||
|
## Vim Keybindings
|
||||||
|
|
||||||
|
See the separate page on Vim shortcuts for a quick reference: [/technical-manual/vim](/technical-manual/vim)
|
||||||
|
|
||||||
## Scheduling Events
|
## Scheduling Events
|
||||||
|
|
||||||
After an instance of `Pattern` is obtained from the user code,
|
After an instance of `Pattern` is obtained from the user code,
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: Vim Shortcuts
|
||||||
|
layout: ../../layouts/MainLayout.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vim Shortcuts in the REPL
|
||||||
|
|
||||||
|
When the REPL editor (CodeMirror) is configured to use Vim keybindings, the following commands are available:
|
||||||
|
|
||||||
|
- :w — Evaluate the current code
|
||||||
|
|
||||||
|
- Triggers the same evaluation as Ctrl+Enter / Alt+Enter
|
||||||
|
- You'll see messages in the Console panel such as:
|
||||||
|
- [vim] :w — evaluating code
|
||||||
|
- [repl] evaluate via event
|
||||||
|
- [eval] code updated
|
||||||
|
|
||||||
|
- :q — Stop/pause playback
|
||||||
|
|
||||||
|
- Triggers the same stop action as Alt+.
|
||||||
|
- Useful to quickly stop scheduling without leaving Vim mode
|
||||||
|
|
||||||
|
- gc — Toggle line comments for the current selection(s)
|
||||||
|
|
||||||
|
- Works in normal and visual mode
|
||||||
|
- If there's a selection, all selected lines are toggled
|
||||||
|
|
||||||
|
Notes
|
||||||
|
|
||||||
|
- Behavior respects the current language mode in the editor for comment syntax.
|
||||||
|
- If multiple REPL editors are open, commands target the active editor. The implementation dispatches custom events handled by the editor.
|
||||||
|
- If you don't see the Console panel, open the right panel in the REPL UI.
|
||||||
|
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user