mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-02 05:41:50 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a109f634e4 | |||
| 11ad67640b |
@@ -1,44 +0,0 @@
|
|||||||
name: Build and Deploy hot PRs
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [labeled]
|
|
||||||
|
|
||||||
# Allow one concurrent deployment
|
|
||||||
concurrency:
|
|
||||||
group: "warm-pages"
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: ${{ github.event.label.name == 'serve-hot' }}
|
|
||||||
runs-on: docker
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
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
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
env:
|
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
||||||
run: |
|
|
||||||
eval $(ssh-agent -s)
|
|
||||||
echo "$SSH_PRIVATE_KEY" | ssh-add -
|
|
||||||
apt update && apt install -y rsync
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts
|
|
||||||
rsync -atv --delete --delete-after --progress \
|
|
||||||
./website/dist/ \
|
|
||||||
strudel@matrix.toplap.org:/home/strudel/deploy/pr-${{ github.event.pull_request.number }}.hot.strudel.cc
|
|
||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
keymap,
|
keymap,
|
||||||
lineNumbers,
|
lineNumbers,
|
||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
import {repeatCharKeymap} from './repeatcharacter.mjs';
|
|
||||||
import { persistentAtom } from '@nanostores/persistent';
|
import { persistentAtom } from '@nanostores/persistent';
|
||||||
import { logger, registerControl, repl } from '@strudel/core';
|
import { logger, registerControl, repl } from '@strudel/core';
|
||||||
import { cleanupDraw, cleanupDrawContext, Drawer } from '@strudel/draw';
|
import { cleanupDraw, cleanupDrawContext, Drawer } from '@strudel/draw';
|
||||||
@@ -21,16 +20,11 @@ import { evalBlock } from './block_utilities.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 { jumpToCharacter } from './labelJump.mjs';
|
||||||
import { getSliderWidgets, sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
import { getSliderWidgets, sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
||||||
import { activateTheme, initTheme, theme } from './themes.mjs';
|
import { activateTheme, initTheme, theme } from './themes.mjs';
|
||||||
import { isTooltipEnabled } from './tooltip.mjs';
|
import { isTooltipEnabled } from './tooltip.mjs';
|
||||||
import { getActiveWidgets, updateWidgets, widgetPlugin } from './widget.mjs';
|
import { getActiveWidgets, updateWidgets, widgetPlugin } from './widget.mjs';
|
||||||
import {
|
|
||||||
deleteAllInlineBeforeCharacter,
|
|
||||||
InsertCharBeforeChar,
|
|
||||||
jumpToCharacter,
|
|
||||||
jumpToNextCharacter,
|
|
||||||
} from './labelJump.mjs';
|
|
||||||
|
|
||||||
export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands';
|
export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands';
|
||||||
|
|
||||||
@@ -50,9 +44,9 @@ export const extensions = {
|
|||||||
isMultiCursorEnabled: (on) =>
|
isMultiCursorEnabled: (on) =>
|
||||||
on
|
on
|
||||||
? [
|
? [
|
||||||
EditorState.allowMultipleSelections.of(true),
|
EditorState.allowMultipleSelections.of(true),
|
||||||
EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
|
EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
export const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
export const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||||
@@ -81,10 +75,6 @@ export const codemirrorSettings = persistentAtom('codemirror-settings', defaultS
|
|||||||
decode: JSON.parse,
|
decode: JSON.parse,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ANON_LABEL = '$';
|
|
||||||
const SOLO_LABEL = 'S';
|
|
||||||
const MUTE_LABEL = '_';
|
|
||||||
|
|
||||||
// https://codemirror.net/docs/guide/
|
// https://codemirror.net/docs/guide/
|
||||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo, strudelMirror }) {
|
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo, strudelMirror }) {
|
||||||
const settings = codemirrorSettings.get();
|
const settings = codemirrorSettings.get();
|
||||||
@@ -112,7 +102,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
syntaxHighlighting(defaultHighlightStyle),
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
EditorView.updateListener.of((v) => onChange(v)),
|
EditorView.updateListener.of((v) => onChange(v)),
|
||||||
drawSelection({ cursorBlinkRate: 0 }),
|
drawSelection({ cursorBlinkRate: 0 }),
|
||||||
repeatCharKeymap,
|
|
||||||
Prec.highest(
|
Prec.highest(
|
||||||
keymap.of([
|
keymap.of([
|
||||||
{
|
{
|
||||||
@@ -149,74 +138,12 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Alt-w',
|
key: 'Alt-w',
|
||||||
run: (view) => jumpToNextCharacter(view, ANON_LABEL, 1),
|
run: (view) => jumpToCharacter(view, '$', 1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Alt-q',
|
key: 'Alt-q',
|
||||||
run: (view) => {
|
run: (view) => jumpToCharacter(view, '$', -1),
|
||||||
return jumpToNextCharacter(view, ANON_LABEL, -1);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// clear all muted
|
|
||||||
{
|
|
||||||
key: `Alt-Ctrl-0`,
|
|
||||||
run: (view) => {
|
|
||||||
return deleteAllInlineBeforeCharacter(view, MUTE_LABEL + ANON_LABEL);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// clear all solod
|
|
||||||
{
|
|
||||||
key: `Alt-Shift-0`,
|
|
||||||
run: (view) => {
|
|
||||||
return deleteAllInlineBeforeCharacter(view, SOLO_LABEL + ANON_LABEL);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// clear all solo and mute
|
|
||||||
{
|
|
||||||
key: `Ctrl-Shift-0`,
|
|
||||||
run: (view) => {
|
|
||||||
return deleteAllInlineBeforeCharacter(view, ANON_LABEL);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...Array.from({ length: 9 }).map((_, i) => {
|
|
||||||
let num = i + 1;
|
|
||||||
return {
|
|
||||||
key: `Alt-${num}`,
|
|
||||||
run: (view) => {
|
|
||||||
return jumpToCharacter(view, ANON_LABEL, i);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
// handle solo toggles 1-9
|
|
||||||
...Array.from({ length: 9 }).map((_, i) => {
|
|
||||||
let num = i + 1;
|
|
||||||
return {
|
|
||||||
key: `Alt-Shift-${num}`,
|
|
||||||
run: (view) => {
|
|
||||||
return InsertCharBeforeChar(view, ANON_LABEL, SOLO_LABEL, i);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
// handle mute toggles 1-9
|
|
||||||
...Array.from({ length: 9 }).map((_, i) => {
|
|
||||||
let num = i + 1;
|
|
||||||
return {
|
|
||||||
key: `Alt-Ctrl-${num}`,
|
|
||||||
run: (view) => {
|
|
||||||
return InsertCharBeforeChar(view, ANON_LABEL, MUTE_LABEL, i);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
// Handle clearing mutes and solos 1-9
|
|
||||||
...Array.from({ length: 9 }).map((_, i) => {
|
|
||||||
let num = i + 1;
|
|
||||||
return {
|
|
||||||
key: `Ctrl-Shift-${num}`,
|
|
||||||
run: (view) => {
|
|
||||||
return InsertCharBeforeChar(view, ANON_LABEL, '', i);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
/* {
|
/* {
|
||||||
key: 'Ctrl-Shift-.',
|
key: 'Ctrl-Shift-.',
|
||||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { emacs } from '@replit/codemirror-emacs';
|
|||||||
import { vim, 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 { helix, commands } from 'codemirror-helix';
|
|
||||||
import { logger } from '@strudel/core';
|
import { logger } from '@strudel/core';
|
||||||
|
|
||||||
const vscodePlugin = ViewPlugin.fromClass(
|
const vscodePlugin = ViewPlugin.fromClass(
|
||||||
@@ -21,70 +20,6 @@ const vscodePlugin = ViewPlugin.fromClass(
|
|||||||
);
|
);
|
||||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
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
|
// 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
|
// 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
|
// Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it
|
||||||
@@ -112,8 +47,29 @@ try {
|
|||||||
|
|
||||||
// :q to pause/stop
|
// :q to pause/stop
|
||||||
Vim.defineEx('quit', 'q', (cm) => {
|
Vim.defineEx('quit', 'q', (cm) => {
|
||||||
const view = cm?.view || cm;
|
try {
|
||||||
replStop(view);
|
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
|
// :w to evaluate
|
||||||
@@ -127,7 +83,38 @@ try {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error logging Vim :w evaluation', e);
|
console.error('Error logging Vim :w evaluation', e);
|
||||||
}
|
}
|
||||||
replEval(view);
|
// 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) {
|
} catch (e) {
|
||||||
console.error('Error dispatching :w evaluation event', e);
|
console.error('Error dispatching :w evaluation event', e);
|
||||||
}
|
}
|
||||||
@@ -137,49 +124,11 @@ try {
|
|||||||
console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e);
|
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 = {
|
const keymaps = {
|
||||||
vim,
|
vim,
|
||||||
emacs,
|
emacs,
|
||||||
codemirror: () => keymap.of(defaultKeymap),
|
codemirror: () => keymap.of(defaultKeymap),
|
||||||
vscode: vscodeExtension,
|
vscode: vscodeExtension,
|
||||||
helix: () => [helix(), helixCommands],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Vim } from '@replit/codemirror-vim';
|
export { Vim } from '@replit/codemirror-vim';
|
||||||
|
|||||||
@@ -1,54 +1,18 @@
|
|||||||
import { EditorSelection } from '@codemirror/state';
|
import { EditorSelection } from '@codemirror/state';
|
||||||
import { SearchCursor } from '@codemirror/search';
|
import { SearchCursor } from '@codemirror/search';
|
||||||
import { EditorView } from '@codemirror/view';
|
|
||||||
import { syntaxTree } from '@codemirror/language';
|
|
||||||
|
|
||||||
/**
|
export function jumpToCharacter(view, character, direction = 1) {
|
||||||
* gets all of the positions of a character in a document, excluding commented out lines
|
|
||||||
* @param { EditorState} state
|
|
||||||
* @param {String} character
|
|
||||||
* @returns {number[]}
|
|
||||||
*/
|
|
||||||
function getCharacterPositions(state, character) {
|
|
||||||
const cursor = new SearchCursor(state.doc, character);
|
|
||||||
|
|
||||||
const characterPositions = [];
|
|
||||||
while (!cursor.next().done) {
|
|
||||||
|
|
||||||
const linestartpos = state.doc.lineAt(cursor.value.to).from
|
|
||||||
if (!isLineCommentedOut(state, linestartpos)) {
|
|
||||||
|
|
||||||
characterPositions.push(cursor.value.to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return characterPositions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLineCommentedOut(state, pos) {
|
|
||||||
|
|
||||||
const line = state.doc.lineAt(pos);
|
|
||||||
// remove white space
|
|
||||||
pos = line.from + line.text.search(/\S/)
|
|
||||||
|
|
||||||
const tree = syntaxTree(state);
|
|
||||||
const node = tree.resolveInner(pos, 1)
|
|
||||||
return node.name.includes("Comment")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* jump to the next character in a document
|
|
||||||
* @param {EditorView} view
|
|
||||||
* @param {String} character
|
|
||||||
* @param {number} direction 0 or 1
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export function jumpToNextCharacter(view, character, direction = 1) {
|
|
||||||
const { state, dispatch } = view;
|
const { state, dispatch } = view;
|
||||||
const pos = state.selection.main.head;
|
const pos = state.selection.main.head;
|
||||||
|
const cursor = new SearchCursor(state.doc, character);
|
||||||
|
|
||||||
|
let characterPositions = [];
|
||||||
let jumpPos;
|
let jumpPos;
|
||||||
const characterPositions = getCharacterPositions(state, character);
|
while (!cursor.next().done) {
|
||||||
|
characterPositions.push(cursor.value.to);
|
||||||
|
}
|
||||||
if (!characterPositions.length) {
|
if (!characterPositions.length) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
if (direction > 0) {
|
if (direction > 0) {
|
||||||
jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience
|
jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience
|
||||||
@@ -57,97 +21,11 @@ export function jumpToNextCharacter(view, character, direction = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (jumpPos == null) {
|
if (jumpPos == null) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
const selection = EditorSelection.cursor(jumpPos - 1);
|
|
||||||
dispatch({
|
dispatch({
|
||||||
selection,
|
selection: EditorSelection.cursor(jumpPos - 1),
|
||||||
effects: EditorView.scrollIntoView(
|
scrollIntoView: true,
|
||||||
selection.head,
|
|
||||||
{ y: "start" }
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {EditorView} view
|
|
||||||
* @param {String} character
|
|
||||||
* @param {number} index the instance of the character
|
|
||||||
* @returns {true}
|
|
||||||
*/
|
|
||||||
export function jumpToCharacter(view, character, index) {
|
|
||||||
const { state, dispatch } = view;
|
|
||||||
const characterPositions = getCharacterPositions(state, character);
|
|
||||||
const pos = characterPositions.at(index) ?? characterPositions.at(-1);
|
|
||||||
if (pos == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selection = EditorSelection.cursor(pos - 1);
|
|
||||||
dispatch({
|
|
||||||
selection,
|
|
||||||
effects: EditorView.scrollIntoView(
|
|
||||||
selection.head,
|
|
||||||
{ y: "start" }
|
|
||||||
)
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {EditorView} view
|
|
||||||
* @param {String} character
|
|
||||||
* @returns {true}
|
|
||||||
*/
|
|
||||||
export function deleteAllInlineBeforeCharacter(view, character) {
|
|
||||||
const { state, dispatch } = view;
|
|
||||||
const characterPositions = getCharacterPositions(state, character);
|
|
||||||
|
|
||||||
const changes = [];
|
|
||||||
characterPositions.forEach((pos) => {
|
|
||||||
const line = state.doc.lineAt(pos);
|
|
||||||
if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
changes.push({
|
|
||||||
from: line.from,
|
|
||||||
to: pos - 1,
|
|
||||||
insert: '',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
dispatch({ changes });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {EditorView} view
|
|
||||||
* @param {String} character
|
|
||||||
* @param {String} character2
|
|
||||||
* @param {number} index
|
|
||||||
* @returns {true}
|
|
||||||
*/
|
|
||||||
export function InsertCharBeforeChar(view, character, character2, index) {
|
|
||||||
const { state, dispatch } = view;
|
|
||||||
|
|
||||||
const changes = [];
|
|
||||||
const characterPositions = getCharacterPositions(state, character);
|
|
||||||
const labelpos = characterPositions.at(index) ?? characterPositions.at(-1);
|
|
||||||
const line = state.doc.lineAt(labelpos);
|
|
||||||
|
|
||||||
//delete preceeding characters
|
|
||||||
changes.push({
|
|
||||||
from: line.from,
|
|
||||||
to: labelpos - 1,
|
|
||||||
insert: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
changes.push({
|
|
||||||
insert: character2,
|
|
||||||
from: line.from,
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch({ changes });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
"@replit/codemirror-emacs": "^6.1.0",
|
"@replit/codemirror-emacs": "^6.1.0",
|
||||||
"@replit/codemirror-vim": "^6.3.0",
|
"@replit/codemirror-vim": "^6.3.0",
|
||||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||||
"codemirror-helix": "^0.5.0",
|
|
||||||
"@strudel/core": "workspace:*",
|
"@strudel/core": "workspace:*",
|
||||||
"@strudel/draw": "workspace:*",
|
"@strudel/draw": "workspace:*",
|
||||||
"@strudel/tonal": "workspace:*",
|
"@strudel/tonal": "workspace:*",
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import { Compartment } from "@codemirror/state";
|
|
||||||
import { keymap } from "@codemirror/view";
|
|
||||||
|
|
||||||
const repeatMode = new Compartment();
|
|
||||||
|
|
||||||
function repeatPreviousChar(times) {
|
|
||||||
return ({ state, dispatch }) => {
|
|
||||||
const { from, empty } = state.selection.main;
|
|
||||||
if (!empty || from === 0) return false;
|
|
||||||
|
|
||||||
const prevChar = state.doc.sliceString(from - 1, from);
|
|
||||||
const text = Array(times).fill(prevChar).join(" ");
|
|
||||||
|
|
||||||
dispatch(state.update({
|
|
||||||
changes: {
|
|
||||||
from,
|
|
||||||
insert: " " + text
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function exitRepeatMode(view) {
|
|
||||||
view.dispatch({
|
|
||||||
effects: repeatMode.reconfigure([])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const digitBindings = Array.from({ length: 9 }, (_, i) => ({
|
|
||||||
key: String(i + 1),
|
|
||||||
run(view) {
|
|
||||||
repeatPreviousChar(i + 1)(view);
|
|
||||||
exitRepeatMode(view);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const repeatCharKeymap = [
|
|
||||||
|
|
||||||
repeatMode.of([]),
|
|
||||||
|
|
||||||
keymap.of([
|
|
||||||
{
|
|
||||||
key: "Alt-r",
|
|
||||||
run(view) {
|
|
||||||
view.dispatch({
|
|
||||||
effects: repeatMode.reconfigure(keymap.of(digitBindings))
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
];
|
|
||||||
+86
-96
@@ -98,10 +98,10 @@ export function registerMultiControl(names, maxControls, ...aliases) {
|
|||||||
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
||||||
* separated by ':'.
|
* separated by ':'.
|
||||||
*
|
*
|
||||||
* @name s
|
* @name sound
|
||||||
* @tags superdough, samples
|
* @tags samples, superdough, dough
|
||||||
* @param {string | Pattern} sound The sound / pattern of sounds to pick
|
* @param {string | Pattern} sound The sound / pattern of sounds to pick
|
||||||
* @synonyms sound
|
* @synonyms s
|
||||||
* @example
|
* @example
|
||||||
* s("bd hh")
|
* s("bd hh")
|
||||||
* @example
|
* @example
|
||||||
@@ -387,25 +387,13 @@ export const { source, src } = registerControl('source', 'src');
|
|||||||
* - for voicings, it picks the voice index
|
* - for voicings, it picks the voice index
|
||||||
*
|
*
|
||||||
* @name n
|
* @name n
|
||||||
* @tags superdough, samples, tonal
|
* @tags samples, tonal, superdough, dough
|
||||||
* @param {number | Pattern} value sample index starting from 0
|
* @param {number | Pattern} value sample index starting from 0
|
||||||
* @example
|
* @example
|
||||||
* s("bd sd [~ bd] sd,hh*6").n("<0 1>")
|
* s("bd sd [~ bd] sd,hh*6").n("<0 1>")
|
||||||
*/
|
*/
|
||||||
// also see https://codeberg.org/uzu/strudel/pulls/63
|
// also see https://codeberg.org/uzu/strudel/pulls/63
|
||||||
export const { n } = registerControl('n');
|
export const { n } = registerControl('n');
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects the given degree. Currently used in `xen` and `tune`:
|
|
||||||
*
|
|
||||||
* @name i
|
|
||||||
* @tags tonal
|
|
||||||
* @param {number | Pattern} value
|
|
||||||
* @example
|
|
||||||
* i("0 1 2 3 4 5 6 7").xen("<5edo 10edo 15edo hexany15>")
|
|
||||||
*/
|
|
||||||
export const { i } = registerControl('i');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the given note name or midi number. A note name consists of
|
* Plays the given note name or midi number. A note name consists of
|
||||||
*
|
*
|
||||||
@@ -418,7 +406,7 @@ export const { i } = registerControl('i');
|
|||||||
* You can also use midi numbers instead of note names, where 69 is mapped to A4 440Hz in 12EDO.
|
* You can also use midi numbers instead of note names, where 69 is mapped to A4 440Hz in 12EDO.
|
||||||
*
|
*
|
||||||
* @name note
|
* @name note
|
||||||
* @tags tonal
|
* @tags tonal, superdough, dough
|
||||||
* @example
|
* @example
|
||||||
* note("c a f e")
|
* note("c a f e")
|
||||||
* @example
|
* @example
|
||||||
@@ -446,7 +434,7 @@ export const { accelerate } = registerControl('accelerate');
|
|||||||
* Sets the velocity from 0 to 1. Is multiplied together with gain.
|
* Sets the velocity from 0 to 1. Is multiplied together with gain.
|
||||||
*
|
*
|
||||||
* @name velocity
|
* @name velocity
|
||||||
* @tags amplitude, superdough, supradough
|
* @tags amplitude, superdough, supradough, dough
|
||||||
* @synonyms vel
|
* @synonyms vel
|
||||||
* @example
|
* @example
|
||||||
* s("hh*8")
|
* s("hh*8")
|
||||||
@@ -458,7 +446,7 @@ export const { velocity, vel } = registerControl('velocity', 'vel');
|
|||||||
* Controls the gain by an exponential amount.
|
* Controls the gain by an exponential amount.
|
||||||
*
|
*
|
||||||
* @name gain
|
* @name gain
|
||||||
* @tags amplitude, superdough, supradough
|
* @tags amplitude, superdough, supradough, dough
|
||||||
* @param {number | Pattern} amount gain.
|
* @param {number | Pattern} amount gain.
|
||||||
* @example
|
* @example
|
||||||
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2)
|
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2)
|
||||||
@@ -469,7 +457,7 @@ export const { gain } = registerControl('gain');
|
|||||||
* Gain applied after all effects have been processed.
|
* Gain applied after all effects have been processed.
|
||||||
*
|
*
|
||||||
* @name postgain
|
* @name postgain
|
||||||
* @tags amplitude, superdough, supradough
|
* @tags amplitude, superdough, supradough, dough
|
||||||
* @example
|
* @example
|
||||||
* s("bd sd [~ bd] sd,hh*8")
|
* s("bd sd [~ bd] sd,hh*8")
|
||||||
* .compressor("-20:20:10:.002:.02").postgain(1.5)
|
* .compressor("-20:20:10:.002:.02").postgain(1.5)
|
||||||
@@ -499,7 +487,7 @@ export const { amp } = registerControl('amp');
|
|||||||
* any of the 8 individual FMs (e.g. `fmh2`)
|
* any of the 8 individual FMs (e.g. `fmh2`)
|
||||||
*
|
*
|
||||||
* @name fmh
|
* @name fmh
|
||||||
* @tags fm, superdough, supradough
|
* @tags fm, superdough, supradough, dough
|
||||||
* @param {number | Pattern} harmonicity
|
* @param {number | Pattern} harmonicity
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
@@ -519,8 +507,8 @@ 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
|
* each other with matrix commands like `fm13`, which would send `fm1` back into
|
||||||
* `fm3`
|
* `fm3`
|
||||||
*
|
*
|
||||||
* @name fmi
|
* @name fm
|
||||||
* @tags fm, superdough, supradough
|
* @tags fmi, superdough, supradough, dough
|
||||||
* @param {number | Pattern} brightness modulation index
|
* @param {number | Pattern} brightness modulation index
|
||||||
* @synonyms fm
|
* @synonyms fm
|
||||||
* @example
|
* @example
|
||||||
@@ -544,7 +532,7 @@ export const { fmi, fmi1, fmi2, fmi3, fmi4, fmi5, fmi6, fmi7, fmi8, fm, fm1, fm2
|
|||||||
* any of the 8 individual FMs (e.g. `fmenv4`)
|
* any of the 8 individual FMs (e.g. `fmenv4`)
|
||||||
*
|
*
|
||||||
* @name fmenv
|
* @name fmenv
|
||||||
* @tags fm, envelope, superdough, supradough
|
* @tags fm, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} type lin | exp
|
* @param {number | Pattern} type lin | exp
|
||||||
* @synonyms fme
|
* @synonyms fme
|
||||||
* @example
|
* @example
|
||||||
@@ -569,10 +557,9 @@ export const { fmenv, fmenv1, fmenv2, fmenv3, fmenv4, fmenv5, fmenv6, fmenv7, fm
|
|||||||
* any of the 8 individual FMs (e.g. `fmatt5`)
|
* any of the 8 individual FMs (e.g. `fmatt5`)
|
||||||
*
|
*
|
||||||
* @name fmattack
|
* @name fmattack
|
||||||
* @tags fm, envelope, superdough, supradough
|
* @tags fm, envelope, superdough, supradough, dough
|
||||||
* @synonyms fmatt
|
* @synonyms fmatt
|
||||||
* @param {number | Pattern} time attack time
|
* @param {number | Pattern} time attack time
|
||||||
* @synonyms fmatt
|
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
* .fm(4)
|
* .fm(4)
|
||||||
@@ -628,7 +615,7 @@ export const { fmwave, fmwave1, fmwave2, fmwave3, fmwave4, fmwave5, fmwave6, fmw
|
|||||||
* any of the 8 individual FMs (e.g. `fmdec6`)
|
* any of the 8 individual FMs (e.g. `fmdec6`)
|
||||||
*
|
*
|
||||||
* @name fmdecay
|
* @name fmdecay
|
||||||
* @tags fm, envelope, superdough, supradough
|
* @tags fm, envelope, superdough, supradough, dough
|
||||||
* @synonyms fmdec
|
* @synonyms fmdec
|
||||||
* @param {number | Pattern} time decay time
|
* @param {number | Pattern} time decay time
|
||||||
* @synonyms fmdec
|
* @synonyms fmdec
|
||||||
@@ -668,7 +655,7 @@ export const {
|
|||||||
* any of the 8 individual FMs (e.g. `fmsus7`)
|
* any of the 8 individual FMs (e.g. `fmsus7`)
|
||||||
*
|
*
|
||||||
* @name fmsustain
|
* @name fmsustain
|
||||||
* @tags fm, envelope, superdough, supradough
|
* @tags fm, envelope, superdough, supradough, dough
|
||||||
* @synonyms fmsus
|
* @synonyms fmsus
|
||||||
* @param {number | Pattern} level sustain level
|
* @param {number | Pattern} level sustain level
|
||||||
* @synonyms fmsus
|
* @synonyms fmsus
|
||||||
@@ -708,7 +695,7 @@ export const {
|
|||||||
* any of the 8 individual FMs (e.g. `fmrel8`)
|
* any of the 8 individual FMs (e.g. `fmrel8`)
|
||||||
*
|
*
|
||||||
* @name fmrelease
|
* @name fmrelease
|
||||||
* @tags fm, envelope, superdough, supradough
|
* @tags fm, envelope, superdough, supradough, dough
|
||||||
* @synonyms fmrel
|
* @synonyms fmrel
|
||||||
* @param {number | Pattern} time release time
|
* @param {number | Pattern} time release time
|
||||||
*
|
*
|
||||||
@@ -761,7 +748,7 @@ export const { bank } = registerControl('bank');
|
|||||||
* mix control for the chorus effect
|
* mix control for the chorus effect
|
||||||
*
|
*
|
||||||
* @name chorus
|
* @name chorus
|
||||||
* @tags pitch
|
* @tags pitch, supradough, superdirt
|
||||||
* @param {string | Pattern} chorus mix amount between 0 and 1
|
* @param {string | Pattern} chorus mix amount between 0 and 1
|
||||||
* @example
|
* @example
|
||||||
* note("d d a# a").s("sawtooth").chorus(.5)
|
* note("d d a# a").s("sawtooth").chorus(.5)
|
||||||
@@ -778,7 +765,7 @@ export const { fft } = registerControl('fft');
|
|||||||
* Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset.
|
* Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset.
|
||||||
*
|
*
|
||||||
* @name attack
|
* @name attack
|
||||||
* @tags amplitude, envelope, superdough, supradough
|
* @tags amplitude, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} attack time in seconds.
|
* @param {number | Pattern} attack time in seconds.
|
||||||
* @synonyms att
|
* @synonyms att
|
||||||
* @example
|
* @example
|
||||||
@@ -792,7 +779,7 @@ export const { attack, att } = registerControl('attack', 'att');
|
|||||||
* Note that the decay is only audible if the sustain value is lower than 1.
|
* Note that the decay is only audible if the sustain value is lower than 1.
|
||||||
*
|
*
|
||||||
* @name decay
|
* @name decay
|
||||||
* @tags amplitude, envelope, superdough, supradough
|
* @tags amplitude, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} time decay time in seconds
|
* @param {number | Pattern} time decay time in seconds
|
||||||
* @synonyms dec
|
* @synonyms dec
|
||||||
* @example
|
* @example
|
||||||
@@ -804,7 +791,7 @@ export const { decay, dec } = registerControl('decay', 'dec');
|
|||||||
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
|
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
|
||||||
*
|
*
|
||||||
* @name sustain
|
* @name sustain
|
||||||
* @tags amplitude, envelope, superdough, supradough
|
* @tags amplitude, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} gain sustain level between 0 and 1
|
* @param {number | Pattern} gain sustain level between 0 and 1
|
||||||
* @synonyms sus
|
* @synonyms sus
|
||||||
* @example
|
* @example
|
||||||
@@ -816,7 +803,7 @@ export const { sustain, sus } = registerControl('sustain', 'sus');
|
|||||||
* Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero.
|
* Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero.
|
||||||
*
|
*
|
||||||
* @name release
|
* @name release
|
||||||
* @tags amplitude, envelope, superdough, supradough
|
* @tags amplitude, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} time release time in seconds
|
* @param {number | Pattern} time release time in seconds
|
||||||
* @synonyms rel
|
* @synonyms rel
|
||||||
* @example
|
* @example
|
||||||
@@ -858,7 +845,7 @@ export const { bandq, bpq } = registerControl('bandq', 'bpq');
|
|||||||
* A pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
|
* A pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
|
||||||
*
|
*
|
||||||
* @name begin
|
* @name begin
|
||||||
* @tags samples
|
* @tags samples, superdough, dough
|
||||||
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
|
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
|
||||||
* @example
|
* @example
|
||||||
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples')
|
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples')
|
||||||
@@ -871,7 +858,7 @@ export const { begin } = registerControl('begin');
|
|||||||
*
|
*
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @name end
|
* @name end
|
||||||
* @tags samples
|
* @tags samples, superdough, dough
|
||||||
* @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
|
* @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
|
||||||
* @example
|
* @example
|
||||||
* s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2)
|
* s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2)
|
||||||
@@ -884,7 +871,7 @@ export const { end } = registerControl('end');
|
|||||||
* To change the loop region, use loopBegin / loopEnd.
|
* To change the loop region, use loopBegin / loopEnd.
|
||||||
*
|
*
|
||||||
* @name loop
|
* @name loop
|
||||||
* @tags samples
|
* @tags samples, superdough
|
||||||
* @param {number | Pattern} on If 1, the sample is looped
|
* @param {number | Pattern} on If 1, the sample is looped
|
||||||
* @example
|
* @example
|
||||||
* s("casio").loop(1)
|
* s("casio").loop(1)
|
||||||
@@ -897,7 +884,7 @@ export const { loop } = registerControl('loop');
|
|||||||
* Note: Samples starting with wt_ will automatically loop! (wt = wavetable)
|
* Note: Samples starting with wt_ will automatically loop! (wt = wavetable)
|
||||||
*
|
*
|
||||||
* @name loopBegin
|
* @name loopBegin
|
||||||
* @tags samples
|
* @tags samples, superdough
|
||||||
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
|
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
|
||||||
* @synonyms loopb
|
* @synonyms loopb
|
||||||
* @example
|
* @example
|
||||||
@@ -911,7 +898,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb');
|
|||||||
* Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`!
|
* Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`!
|
||||||
*
|
*
|
||||||
* @name loopEnd
|
* @name loopEnd
|
||||||
* @tags samples
|
* @tags samples, superdough
|
||||||
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
|
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
|
||||||
* @synonyms loope
|
* @synonyms loope
|
||||||
* @example
|
* @example
|
||||||
@@ -923,7 +910,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope');
|
|||||||
* Bit crusher effect.
|
* Bit crusher effect.
|
||||||
*
|
*
|
||||||
* @name crush
|
* @name crush
|
||||||
* @tags superdough, supradough
|
* @tags superdough, supradough, dough
|
||||||
* @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).
|
* @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).
|
||||||
* @example
|
* @example
|
||||||
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
|
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
|
||||||
@@ -935,7 +922,7 @@ export const { crush } = registerControl('crush');
|
|||||||
* Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
|
* Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
|
||||||
*
|
*
|
||||||
* @name coarse
|
* @name coarse
|
||||||
* @tags superdough, supradough
|
* @tags superdough, supradough, dough
|
||||||
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
|
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
|
||||||
* @example
|
* @example
|
||||||
* s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>")
|
* s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>")
|
||||||
@@ -1170,7 +1157,7 @@ export const { channels, ch } = registerControl('channels', 'ch');
|
|||||||
* Controls the pulsewidth of the pulse oscillator
|
* Controls the pulsewidth of the pulse oscillator
|
||||||
*
|
*
|
||||||
* @name pw
|
* @name pw
|
||||||
* @tags superdough
|
* @tags superdough, dough
|
||||||
* @param {number | Pattern} pulsewidth
|
* @param {number | Pattern} pulsewidth
|
||||||
* @example
|
* @example
|
||||||
* note("{f a c e}%16").s("pulse").pw(".8:1:.2")
|
* note("{f a c e}%16").s("pulse").pw(".8:1:.2")
|
||||||
@@ -1210,7 +1197,7 @@ export const { pwsweep } = registerControl('pwsweep', 'pws');
|
|||||||
* Phaser audio effect that approximates popular guitar pedals.
|
* Phaser audio effect that approximates popular guitar pedals.
|
||||||
*
|
*
|
||||||
* @name phaser
|
* @name phaser
|
||||||
* @tags superdough
|
* @tags superdough, dough
|
||||||
* @synonyms ph
|
* @synonyms ph
|
||||||
* @param {number | Pattern} speed speed of modulation
|
* @param {number | Pattern} speed speed of modulation
|
||||||
* @example
|
* @example
|
||||||
@@ -1228,7 +1215,7 @@ export const { phaserrate, ph, phaser } = registerControl(
|
|||||||
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
|
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
|
||||||
*
|
*
|
||||||
* @name phasersweep
|
* @name phasersweep
|
||||||
* @tags superdough, lfo
|
* @tags lfo, superdough, dough
|
||||||
* @synonyms phs
|
* @synonyms phs
|
||||||
* @param {number | Pattern} phasersweep most useful values are between 0 and 4000
|
* @param {number | Pattern} phasersweep most useful values are between 0 and 4000
|
||||||
* @example
|
* @example
|
||||||
@@ -1242,7 +1229,7 @@ export const { phasersweep, phs } = registerControl('phasersweep', 'phs');
|
|||||||
* The center frequency of the phaser in HZ. Defaults to 1000
|
* The center frequency of the phaser in HZ. Defaults to 1000
|
||||||
*
|
*
|
||||||
* @name phasercenter
|
* @name phasercenter
|
||||||
* @tags superdough
|
* @tags superdough, dough
|
||||||
* @synonyms phc
|
* @synonyms phc
|
||||||
* @param {number | Pattern} centerfrequency in HZ
|
* @param {number | Pattern} centerfrequency in HZ
|
||||||
* @example
|
* @example
|
||||||
@@ -1257,7 +1244,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc');
|
|||||||
* The amount the signal is affected by the phaser effect. Defaults to 0.75
|
* The amount the signal is affected by the phaser effect. Defaults to 0.75
|
||||||
*
|
*
|
||||||
* @name phaserdepth
|
* @name phaserdepth
|
||||||
* @tags superdough, superdirt
|
* @tags superdough, superdirt, dough
|
||||||
* @synonyms phd, phasdp
|
* @synonyms phd, phasdp
|
||||||
* @param {number | Pattern} depth number between 0 and 1
|
* @param {number | Pattern} depth number between 0 and 1
|
||||||
* @example
|
* @example
|
||||||
@@ -1294,7 +1281,7 @@ export const { cut } = registerControl('cut');
|
|||||||
* When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'.
|
* When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'.
|
||||||
*
|
*
|
||||||
* @name lpf
|
* @name lpf
|
||||||
* @tags filter, superdough, supradough
|
* @tags filter, superdough, supradough, dough
|
||||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||||
* @synonyms cutoff, ctf, lp
|
* @synonyms cutoff, ctf, lp
|
||||||
* @example
|
* @example
|
||||||
@@ -1308,7 +1295,7 @@ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance',
|
|||||||
/**
|
/**
|
||||||
* Sets the lowpass filter envelope modulation depth.
|
* Sets the lowpass filter envelope modulation depth.
|
||||||
* @name lpenv
|
* @name lpenv
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_
|
* @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_
|
||||||
* @synonyms lpe
|
* @synonyms lpe
|
||||||
* @example
|
* @example
|
||||||
@@ -1322,7 +1309,7 @@ export const { lpenv, lpe } = registerControl('lpenv', 'lpe');
|
|||||||
/**
|
/**
|
||||||
* Sets the highpass filter envelope modulation depth.
|
* Sets the highpass filter envelope modulation depth.
|
||||||
* @name hpenv
|
* @name hpenv
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_
|
* @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_
|
||||||
* @synonyms hpe
|
* @synonyms hpe
|
||||||
* @example
|
* @example
|
||||||
@@ -1336,7 +1323,7 @@ export const { hpenv, hpe } = registerControl('hpenv', 'hpe');
|
|||||||
/**
|
/**
|
||||||
* Sets the bandpass filter envelope modulation depth.
|
* Sets the bandpass filter envelope modulation depth.
|
||||||
* @name bpenv
|
* @name bpenv
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_
|
* @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_
|
||||||
* @synonyms bpe
|
* @synonyms bpe
|
||||||
* @example
|
* @example
|
||||||
@@ -1350,7 +1337,7 @@ export const { bpenv, bpe } = registerControl('bpenv', 'bpe');
|
|||||||
/**
|
/**
|
||||||
* Sets the attack duration for the lowpass filter envelope.
|
* Sets the attack duration for the lowpass filter envelope.
|
||||||
* @name lpattack
|
* @name lpattack
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} attack time of the filter envelope
|
* @param {number | Pattern} attack time of the filter envelope
|
||||||
* @synonyms lpa
|
* @synonyms lpa
|
||||||
* @example
|
* @example
|
||||||
@@ -1364,7 +1351,7 @@ export const { lpattack, lpa } = registerControl('lpattack', 'lpa');
|
|||||||
/**
|
/**
|
||||||
* Sets the attack duration for the highpass filter envelope.
|
* Sets the attack duration for the highpass filter envelope.
|
||||||
* @name hpattack
|
* @name hpattack
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} attack time of the highpass filter envelope
|
* @param {number | Pattern} attack time of the highpass filter envelope
|
||||||
* @synonyms hpa
|
* @synonyms hpa
|
||||||
* @example
|
* @example
|
||||||
@@ -1378,7 +1365,7 @@ export const { hpattack, hpa } = registerControl('hpattack', 'hpa');
|
|||||||
/**
|
/**
|
||||||
* Sets the attack duration for the bandpass filter envelope.
|
* Sets the attack duration for the bandpass filter envelope.
|
||||||
* @name bpattack
|
* @name bpattack
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} attack time of the bandpass filter envelope
|
* @param {number | Pattern} attack time of the bandpass filter envelope
|
||||||
* @synonyms bpa
|
* @synonyms bpa
|
||||||
* @example
|
* @example
|
||||||
@@ -1392,7 +1379,7 @@ export const { bpattack, bpa } = registerControl('bpattack', 'bpa');
|
|||||||
/**
|
/**
|
||||||
* Sets the decay duration for the lowpass filter envelope.
|
* Sets the decay duration for the lowpass filter envelope.
|
||||||
* @name lpdecay
|
* @name lpdecay
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} decay time of the filter envelope
|
* @param {number | Pattern} decay time of the filter envelope
|
||||||
* @synonyms lpd
|
* @synonyms lpd
|
||||||
* @example
|
* @example
|
||||||
@@ -1406,7 +1393,7 @@ export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd');
|
|||||||
/**
|
/**
|
||||||
* Sets the decay duration for the highpass filter envelope.
|
* Sets the decay duration for the highpass filter envelope.
|
||||||
* @name hpdecay
|
* @name hpdecay
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} decay time of the highpass filter envelope
|
* @param {number | Pattern} decay time of the highpass filter envelope
|
||||||
* @synonyms hpd
|
* @synonyms hpd
|
||||||
* @example
|
* @example
|
||||||
@@ -1421,7 +1408,7 @@ export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd');
|
|||||||
/**
|
/**
|
||||||
* Sets the decay duration for the bandpass filter envelope.
|
* Sets the decay duration for the bandpass filter envelope.
|
||||||
* @name bpdecay
|
* @name bpdecay
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} decay time of the bandpass filter envelope
|
* @param {number | Pattern} decay time of the bandpass filter envelope
|
||||||
* @synonyms bpd
|
* @synonyms bpd
|
||||||
* @example
|
* @example
|
||||||
@@ -1436,7 +1423,7 @@ export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd');
|
|||||||
/**
|
/**
|
||||||
* Sets the sustain amplitude for the lowpass filter envelope.
|
* Sets the sustain amplitude for the lowpass filter envelope.
|
||||||
* @name lpsustain
|
* @name lpsustain
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} sustain amplitude of the lowpass filter envelope
|
* @param {number | Pattern} sustain amplitude of the lowpass filter envelope
|
||||||
* @synonyms lps
|
* @synonyms lps
|
||||||
* @example
|
* @example
|
||||||
@@ -1451,7 +1438,7 @@ export const { lpsustain, lps } = registerControl('lpsustain', 'lps');
|
|||||||
/**
|
/**
|
||||||
* Sets the sustain amplitude for the highpass filter envelope.
|
* Sets the sustain amplitude for the highpass filter envelope.
|
||||||
* @name hpsustain
|
* @name hpsustain
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} sustain amplitude of the highpass filter envelope
|
* @param {number | Pattern} sustain amplitude of the highpass filter envelope
|
||||||
* @synonyms hps
|
* @synonyms hps
|
||||||
* @example
|
* @example
|
||||||
@@ -1466,7 +1453,7 @@ export const { hpsustain, hps } = registerControl('hpsustain', 'hps');
|
|||||||
/**
|
/**
|
||||||
* Sets the sustain amplitude for the bandpass filter envelope.
|
* Sets the sustain amplitude for the bandpass filter envelope.
|
||||||
* @name bpsustain
|
* @name bpsustain
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} sustain amplitude of the bandpass filter envelope
|
* @param {number | Pattern} sustain amplitude of the bandpass filter envelope
|
||||||
* @synonyms bps
|
* @synonyms bps
|
||||||
* @example
|
* @example
|
||||||
@@ -1481,7 +1468,7 @@ export const { bpsustain, bps } = registerControl('bpsustain', 'bps');
|
|||||||
/**
|
/**
|
||||||
* Sets the release time for the lowpass filter envelope.
|
* Sets the release time for the lowpass filter envelope.
|
||||||
* @name lprelease
|
* @name lprelease
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} release time of the filter envelope
|
* @param {number | Pattern} release time of the filter envelope
|
||||||
* @synonyms lpr
|
* @synonyms lpr
|
||||||
* @example
|
* @example
|
||||||
@@ -1497,7 +1484,7 @@ export const { lprelease, lpr } = registerControl('lprelease', 'lpr');
|
|||||||
/**
|
/**
|
||||||
* Sets the release time for the highpass filter envelope.
|
* Sets the release time for the highpass filter envelope.
|
||||||
* @name hprelease
|
* @name hprelease
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} release time of the highpass filter envelope
|
* @param {number | Pattern} release time of the highpass filter envelope
|
||||||
* @synonyms hpr
|
* @synonyms hpr
|
||||||
* @example
|
* @example
|
||||||
@@ -1513,7 +1500,7 @@ export const { hprelease, hpr } = registerControl('hprelease', 'hpr');
|
|||||||
/**
|
/**
|
||||||
* Sets the release time for the bandpass filter envelope.
|
* Sets the release time for the bandpass filter envelope.
|
||||||
* @name bprelease
|
* @name bprelease
|
||||||
* @tags filter, envelope, superdough, supradough
|
* @tags filter, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} release time of the bandpass filter envelope
|
* @param {number | Pattern} release time of the bandpass filter envelope
|
||||||
* @synonyms bpr
|
* @synonyms bpr
|
||||||
* @example
|
* @example
|
||||||
@@ -1529,7 +1516,7 @@ export const { bprelease, bpr } = registerControl('bprelease', 'bpr');
|
|||||||
/**
|
/**
|
||||||
* Sets the filter type. The ladder filter is more aggressive. More types might be added in the future.
|
* Sets the filter type. The ladder filter is more aggressive. More types might be added in the future.
|
||||||
* @name ftype
|
* @name ftype
|
||||||
* @tags filter, superdough
|
* @tags filter, superdough, dough
|
||||||
* @param {number | Pattern} type 12db (0), ladder (1), or 24db (2)
|
* @param {number | Pattern} type 12db (0), ladder (1), or 24db (2)
|
||||||
* @example
|
* @example
|
||||||
* note("{f g g c d a a#}%8").s("sawtooth").lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1)
|
* note("{f g g c d a a#}%8").s("sawtooth").lpenv(4).lpf(500).ftype("<0 1 2>").lpq(1)
|
||||||
@@ -1558,7 +1545,7 @@ export const { fanchor } = registerControl('fanchor');
|
|||||||
* When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'.
|
* When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'.
|
||||||
*
|
*
|
||||||
* @name hpf
|
* @name hpf
|
||||||
* @tags filter, superdough, supradough
|
* @tags filter, superdough, supradough, dough
|
||||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||||
* @synonyms hp, hcutoff
|
* @synonyms hp, hcutoff
|
||||||
* @example
|
* @example
|
||||||
@@ -1781,7 +1768,7 @@ export const { hpskew } = registerControl('hpskew');
|
|||||||
* Applies a vibrato to the frequency of the oscillator.
|
* Applies a vibrato to the frequency of the oscillator.
|
||||||
*
|
*
|
||||||
* @name vib
|
* @name vib
|
||||||
* @tags pitch, lfo, superdough, supradough
|
* @tags pitch, lfo, superdough, supradough, dough
|
||||||
* @synonyms vibrato, v
|
* @synonyms vibrato, v
|
||||||
* @param {number | Pattern} frequency of the vibrato in hertz
|
* @param {number | Pattern} frequency of the vibrato in hertz
|
||||||
* @example
|
* @example
|
||||||
@@ -1809,7 +1796,7 @@ export const { noise } = registerControl('noise');
|
|||||||
* Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set
|
* Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set
|
||||||
*
|
*
|
||||||
* @name vibmod
|
* @name vibmod
|
||||||
* @tags pitch, lfo, superdough, supradough
|
* @tags pitch, lfo, superdough, supradough, dough
|
||||||
* @synonyms vmod
|
* @synonyms vmod
|
||||||
* @param {number | Pattern} depth of vibrato (in semitones)
|
* @param {number | Pattern} depth of vibrato (in semitones)
|
||||||
* @example
|
* @example
|
||||||
@@ -1870,7 +1857,7 @@ export const { djf } = registerControl('djf');
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @name delay
|
* @name delay
|
||||||
* @tags orbit, superdough, supradough
|
* @tags orbit, superdough, supradough, dough
|
||||||
* @param {number | Pattern} level between 0 and 1
|
* @param {number | Pattern} level between 0 and 1
|
||||||
* @example
|
* @example
|
||||||
* s("bd bd").delay("<0 .25 .5 1>")
|
* s("bd bd").delay("<0 .25 .5 1>")
|
||||||
@@ -1884,7 +1871,7 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback']
|
|||||||
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
|
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
|
||||||
*
|
*
|
||||||
* @name delayfeedback
|
* @name delayfeedback
|
||||||
* @tags orbit, superdough, supradough
|
* @tags orbit, superdough, supradough, dough
|
||||||
* @param {number | Pattern} feedback between 0 and 1
|
* @param {number | Pattern} feedback between 0 and 1
|
||||||
* @synonyms delayfb, dfb
|
* @synonyms delayfb, dfb
|
||||||
* @example
|
* @example
|
||||||
@@ -1894,26 +1881,29 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback']
|
|||||||
export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', 'delayfb', 'dfb');
|
export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', 'delayfb', 'dfb');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the time of the delay effect.
|
* Sets the level of the signal that is fed back into the delay.
|
||||||
|
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
|
||||||
*
|
*
|
||||||
* @name delayspeed
|
* @name delayspeed
|
||||||
* @tags supradough
|
* @tags orbit, supradough
|
||||||
|
* @param {number | Pattern} feedback between 0 and 1
|
||||||
|
* @synonyms delayfb, dfb
|
||||||
|
* @example
|
||||||
|
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const { delayspeed } = registerControl('delayspeed');
|
||||||
|
/**
|
||||||
|
* Sets the time of the delay effect.
|
||||||
|
*
|
||||||
|
* @name delaytime
|
||||||
|
* @tags orbit, superdough, supradough, dough
|
||||||
* @param {number | Pattern} delayspeed controls the pitch of the delay feedback
|
* @param {number | Pattern} delayspeed controls the pitch of the delay feedback
|
||||||
* @synonyms delayt, dt
|
* @synonyms delayt, dt
|
||||||
* @example
|
* @example
|
||||||
* note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>")
|
* note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>")
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { delayspeed } = registerControl('delayspeed');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @name delaytime
|
|
||||||
* @tags orbit, superdough, supradough
|
|
||||||
* @param {number | Pattern} delaytime sets the time of the delay effect.
|
|
||||||
* @synonyms delayt, dt
|
|
||||||
* @example
|
|
||||||
* note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>")
|
|
||||||
*/
|
|
||||||
export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt');
|
export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2007,7 +1997,7 @@ export const { fadeInTime } = registerControl('fadeInTime');
|
|||||||
* Set frequency of sound.
|
* Set frequency of sound.
|
||||||
*
|
*
|
||||||
* @name freq
|
* @name freq
|
||||||
* @tags pitch, superdough
|
* @tags pitch, superdough, dough
|
||||||
* @param {number | Pattern} frequency in Hz. the audible range is between 20 and 20000 Hz
|
* @param {number | Pattern} frequency in Hz. the audible range is between 20 and 20000 Hz
|
||||||
* @example
|
* @example
|
||||||
* freq("220 110 440 110").s("superzow").osc()
|
* freq("220 110 440 110").s("superzow").osc()
|
||||||
@@ -2021,7 +2011,7 @@ export const { freq } = registerControl('freq');
|
|||||||
* Attack time of pitch envelope.
|
* Attack time of pitch envelope.
|
||||||
*
|
*
|
||||||
* @name pattack
|
* @name pattack
|
||||||
* @tags pitch, envelope, superdough, supradough
|
* @tags pitch, envelope, superdough, supradough, dough
|
||||||
* @synonyms patt
|
* @synonyms patt
|
||||||
* @param {number | Pattern} time time in seconds
|
* @param {number | Pattern} time time in seconds
|
||||||
* @example
|
* @example
|
||||||
@@ -2033,7 +2023,7 @@ export const { pattack, patt } = registerControl('pattack', 'patt');
|
|||||||
* Decay time of pitch envelope.
|
* Decay time of pitch envelope.
|
||||||
*
|
*
|
||||||
* @name pdecay
|
* @name pdecay
|
||||||
* @tags pitch, envelope, superdough, supradough
|
* @tags pitch, envelope, superdough, supradough, dough
|
||||||
* @synonyms pdec
|
* @synonyms pdec
|
||||||
* @param {number | Pattern} time time in seconds
|
* @param {number | Pattern} time time in seconds
|
||||||
* @example
|
* @example
|
||||||
@@ -2047,7 +2037,7 @@ export const { psustain, psus } = registerControl('psustain', 'psus');
|
|||||||
* Release time of pitch envelope
|
* Release time of pitch envelope
|
||||||
*
|
*
|
||||||
* @name prelease
|
* @name prelease
|
||||||
* @tags pitch, envelope, superdough, supradough
|
* @tags pitch, envelope, superdough, supradough, dough
|
||||||
* @synonyms prel
|
* @synonyms prel
|
||||||
* @param {number | Pattern} time time in seconds
|
* @param {number | Pattern} time time in seconds
|
||||||
* @example
|
* @example
|
||||||
@@ -2062,7 +2052,7 @@ export const { prelease, prel } = registerControl('prelease', 'prel');
|
|||||||
* If you don't set other pitch envelope controls, `pattack:.2` will be the default.
|
* If you don't set other pitch envelope controls, `pattack:.2` will be the default.
|
||||||
*
|
*
|
||||||
* @name penv
|
* @name penv
|
||||||
* @tags pitch, envelope, superdough, supradough
|
* @tags pitch, envelope, superdough, supradough, dough
|
||||||
* @param {number | Pattern} semitones change in semitones
|
* @param {number | Pattern} semitones change in semitones
|
||||||
* @example
|
* @example
|
||||||
* note("c")
|
* note("c")
|
||||||
@@ -2201,7 +2191,7 @@ export const { octave, oct } = registerControl('octave', 'oct');
|
|||||||
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
||||||
*
|
*
|
||||||
* @name orbit
|
* @name orbit
|
||||||
* @tags superdough
|
* @tags superdough, dough
|
||||||
* @synonyms o
|
* @synonyms o
|
||||||
* @param {number | Pattern} number
|
* @param {number | Pattern} number
|
||||||
* @example
|
* @example
|
||||||
@@ -2243,7 +2233,7 @@ export const { overshape } = registerControl('overshape');
|
|||||||
* Sets position in stereo.
|
* Sets position in stereo.
|
||||||
*
|
*
|
||||||
* @name pan
|
* @name pan
|
||||||
* @tags superdough, supradough
|
* @tags superdough, supradough, dough
|
||||||
* @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
|
* @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
|
||||||
* @example
|
* @example
|
||||||
* s("[bd hh]*2").pan("<.5 1 .5 0>")
|
* s("[bd hh]*2").pan("<.5 1 .5 0>")
|
||||||
@@ -2363,7 +2353,7 @@ export const { mode } = registerControl(['mode', 'anchor']);
|
|||||||
* When using mininotation, you can also optionally add the 'size' parameter, separated by ':'.
|
* When using mininotation, you can also optionally add the 'size' parameter, separated by ':'.
|
||||||
*
|
*
|
||||||
* @name room
|
* @name room
|
||||||
* @tags orbit, superdough
|
* @tags orbit, superdough, dough
|
||||||
* @param {number | Pattern} level between 0 and 1
|
* @param {number | Pattern} level between 0 and 1
|
||||||
* @example
|
* @example
|
||||||
* s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>")
|
* s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>")
|
||||||
@@ -2494,7 +2484,7 @@ export const { shape } = registerControl(['shape', 'shapevol']);
|
|||||||
* Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;)
|
* Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;)
|
||||||
*
|
*
|
||||||
* @name distort
|
* @name distort
|
||||||
* @tags distortion, superdough, supradough
|
* @tags distortion, superdough, supradough, dough
|
||||||
* @synonyms dist
|
* @synonyms dist
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
@@ -2513,7 +2503,7 @@ export const { distort, dist } = registerControl(['distort', 'distortvol', 'dist
|
|||||||
* Postgain for waveshaping distortion.
|
* Postgain for waveshaping distortion.
|
||||||
*
|
*
|
||||||
* @name distortvol
|
* @name distortvol
|
||||||
* @synonyms distortion, distvol
|
* @synonyms distortion, distvol, superdough, dough
|
||||||
* @tags superdough, supradough
|
* @tags superdough, supradough
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
* @example
|
* @example
|
||||||
@@ -2565,7 +2555,7 @@ export const { compressorRelease } = registerControl('compressorRelease');
|
|||||||
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
||||||
*
|
*
|
||||||
* @name speed
|
* @name speed
|
||||||
* @tags pitch, samples
|
* @tags pitch, samples, superdough, dough
|
||||||
* @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards.
|
* @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards.
|
||||||
* @example
|
* @example
|
||||||
* s("bd*6").speed("1 2 4 1 -2 -4")
|
* s("bd*6").speed("1 2 4 1 -2 -4")
|
||||||
@@ -2579,7 +2569,7 @@ export const { speed } = registerControl('speed');
|
|||||||
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
||||||
*
|
*
|
||||||
* @name stretch
|
* @name stretch
|
||||||
* @tags pitch, samples
|
* @tags pitch, samples, superdough
|
||||||
* @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards.
|
* @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards.
|
||||||
* @example
|
* @example
|
||||||
* s("gm_flute").stretch("1 2 .5")
|
* s("gm_flute").stretch("1 2 .5")
|
||||||
@@ -2630,7 +2620,7 @@ export const { squiz } = registerControl('squiz');
|
|||||||
* Formant filter to make things sound like vowels.
|
* Formant filter to make things sound like vowels.
|
||||||
*
|
*
|
||||||
* @name vowel
|
* @name vowel
|
||||||
* @tags superdough
|
* @tags superdough, superdirt
|
||||||
* @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ.
|
* @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ.
|
||||||
* @example
|
* @example
|
||||||
* note("[c2 <eb2 <g2 g1>>]*2").s('sawtooth')
|
* note("[c2 <eb2 <g2 g1>>]*2").s('sawtooth')
|
||||||
@@ -2718,7 +2708,7 @@ export const { clip, legato } = registerControl('clip', 'legato');
|
|||||||
* Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration.
|
* Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration.
|
||||||
*
|
*
|
||||||
* @name duration
|
* @name duration
|
||||||
* @tags superdough
|
* @tags superdough, dough
|
||||||
* @synonyms dur
|
* @synonyms dur
|
||||||
* @param {number | Pattern} seconds >= 0
|
* @param {number | Pattern} seconds >= 0
|
||||||
* @example
|
* @example
|
||||||
@@ -2761,7 +2751,7 @@ export let createParams = (...names) =>
|
|||||||
* ADSR envelope: Combination of Attack, Decay, Sustain, and Release.
|
* ADSR envelope: Combination of Attack, Decay, Sustain, and Release.
|
||||||
*
|
*
|
||||||
* @name adsr
|
* @name adsr
|
||||||
* @tags envelope, amplitude
|
* @tags envelope, amplitude, superdough, dough
|
||||||
* @param {number | Pattern} time attack time in seconds
|
* @param {number | Pattern} time attack time in seconds
|
||||||
* @param {number | Pattern} time decay time in seconds
|
* @param {number | Pattern} time decay time in seconds
|
||||||
* @param {number | Pattern} gain sustain level (0 to 1)
|
* @param {number | Pattern} gain sustain level (0 to 1)
|
||||||
@@ -3005,7 +2995,7 @@ export const as = register('as', (mapping, pat) => {
|
|||||||
* Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file
|
* Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file
|
||||||
* in the optional array syntax ex: "0.5:2", the second value controls the speed of playback
|
* in the optional array syntax ex: "0.5:2", the second value controls the speed of playback
|
||||||
* @name scrub
|
* @name scrub
|
||||||
* @tags samples
|
* @tags samples, superdough, dough
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
|
|||||||
@@ -339,13 +339,8 @@ export function uniqsortr(a) {
|
|||||||
|
|
||||||
export function unicodeToBase64(text) {
|
export function unicodeToBase64(text) {
|
||||||
const utf8Bytes = new TextEncoder().encode(text);
|
const utf8Bytes = new TextEncoder().encode(text);
|
||||||
let binaryString = '';
|
const base64String = btoa(String.fromCharCode(...utf8Bytes));
|
||||||
const chunkSize = 0x8000;
|
return base64String;
|
||||||
for (let i = 0; i < utf8Bytes.length; i += chunkSize) {
|
|
||||||
const chunk = utf8Bytes.subarray(i, i + chunkSize);
|
|
||||||
binaryString += String.fromCharCode.apply(null, chunk);
|
|
||||||
}
|
|
||||||
return btoa(binaryString);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function base64ToUnicode(base64String) {
|
export function base64ToUnicode(base64String) {
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
/*
|
|
||||||
input.mjs - MIDI input wrapper
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/midi/midi.mjs>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { WebMidi } from 'webmidi';
|
|
||||||
import { logger, ref } from '@strudel/core';
|
|
||||||
import { getDevice } from './util.mjs';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MIDI input device wrapper that manages connection and reconnection, tracks
|
|
||||||
* persisted CC states, etc. These instances are long-lived and are maintained as singletons
|
|
||||||
* (keyed globally by input string/number).
|
|
||||||
*/
|
|
||||||
export class MidiInput {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string | number} input MIDI device name or index defaulting to 0
|
|
||||||
*/
|
|
||||||
constructor(input) {
|
|
||||||
this.input = input;
|
|
||||||
this.stateKey = typeof input === 'string' ? input : undefined; // Saved state is not tracked for numeric index inputs
|
|
||||||
|
|
||||||
this._refs = {};
|
|
||||||
this._refsByChan = {};
|
|
||||||
|
|
||||||
this._loadAllStates();
|
|
||||||
|
|
||||||
this.initialDevice = this._startDeviceListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation for the cc() factory function tied to this specific input.
|
|
||||||
* @param {number} cc MIDI CC number
|
|
||||||
* @param {number | undefined} chan MIDI channel (1-16) or undefined for all channels
|
|
||||||
*/
|
|
||||||
createCC(cc, chan) {
|
|
||||||
const lookupMap = chan === undefined ? this._refs : this._refsByChan[chan];
|
|
||||||
if (!(cc in lookupMap)) {
|
|
||||||
const initialState = this._loadState(chan);
|
|
||||||
lookupMap[cc] = initialState[cc] || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ref(() => lookupMap[cc]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_startDeviceListener() {
|
|
||||||
const initialDevice = getDevice(this.input, WebMidi.inputs);
|
|
||||||
|
|
||||||
// Background connection loop
|
|
||||||
(async () => {
|
|
||||||
const midiListener = this._onMidiMessage.bind(this);
|
|
||||||
let device = initialDevice;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (!device) {
|
|
||||||
device = await this._waitForDevice();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a bit for device to be ready to receive last state
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Still continue if sending did not work
|
|
||||||
this._sendAllStates(device);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('midiin: failed to send last state on connect:', device.name, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for incoming MIDI messages and for disconnection
|
|
||||||
device.addListener('midimessage', midiListener);
|
|
||||||
|
|
||||||
await this._waitForDeviceDisconnect(device);
|
|
||||||
|
|
||||||
device.removeListener('midimessage', midiListener);
|
|
||||||
device = null; // Clear var to trigger wait for connection
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return initialDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a promise that resolves when the specified device is connected
|
|
||||||
_waitForDevice() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const connListener = () => {
|
|
||||||
const device = getDevice(this.input, WebMidi.inputs);
|
|
||||||
if (device) {
|
|
||||||
logger(`[midi] device reconnected: ${device.name}`);
|
|
||||||
|
|
||||||
WebMidi.removeListener('connected', connListener);
|
|
||||||
resolve(device);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WebMidi.addListener('connected', connListener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a promise that resolves when the specified device is disconnected
|
|
||||||
_waitForDeviceDisconnect(device) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const disconnListener = (e) => {
|
|
||||||
if (e.port.name === device.name) {
|
|
||||||
logger(`[midi] device disconnected: ${device.name}`);
|
|
||||||
|
|
||||||
WebMidi.removeListener('disconnected', disconnListener);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WebMidi.addListener('disconnected', disconnListener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMidiMessage(e) {
|
|
||||||
const ccNum = e.dataBytes[0];
|
|
||||||
const v = e.dataBytes[1];
|
|
||||||
const chan = e.message.channel;
|
|
||||||
const scaled = v / 127;
|
|
||||||
|
|
||||||
this._refs[ccNum] = scaled;
|
|
||||||
this._refsByChan[chan] ??= {};
|
|
||||||
this._refsByChan[chan][ccNum] = scaled;
|
|
||||||
|
|
||||||
this._saveState(undefined, ccNum, scaled);
|
|
||||||
this._saveState(chan, ccNum, scaled);
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadAllStates() {
|
|
||||||
Object.assign(this._refs, this._loadState(undefined));
|
|
||||||
|
|
||||||
for (let chan = 1; chan <= 16; chan++) {
|
|
||||||
this._refsByChan[chan] ??= {};
|
|
||||||
Object.assign(this._refsByChan[chan], this._loadState(chan));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadState(chan) {
|
|
||||||
if (!this.stateKey) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialDataRaw = localStorage.getItem(
|
|
||||||
`strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`,
|
|
||||||
);
|
|
||||||
if (!initialDataRaw) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(initialDataRaw);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(
|
|
||||||
`Failed to parse MIDI state from localStorage for input "${this.stateKey}" and channel "${chan}"`,
|
|
||||||
initialDataRaw,
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_saveState(chan, cc, value) {
|
|
||||||
if (!this.stateKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = this._loadState(chan);
|
|
||||||
state[cc] = value;
|
|
||||||
localStorage.setItem(
|
|
||||||
`strudel-midin-${this.stateKey}-chan${chan !== undefined ? chan : 'all'}`,
|
|
||||||
JSON.stringify(state),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send CC values back to device to restore encoders and motorized sliders
|
|
||||||
_sendAllStates(device) {
|
|
||||||
const output = WebMidi.outputs.find((o) => o.name === device.name);
|
|
||||||
if (!output) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [chan, refs] of Object.entries(this._refsByChan)) {
|
|
||||||
const channel = Number(chan);
|
|
||||||
for (const [cc, value] of Object.entries(refs)) {
|
|
||||||
const ccn = Number(cc);
|
|
||||||
const scaled = Math.round(value * 127);
|
|
||||||
output.sendControlChange(ccn, scaled, channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+66
-36
@@ -23,8 +23,6 @@ import { noteToMidi, getControlName } from '@strudel/core';
|
|||||||
import { Note } from 'webmidi';
|
import { Note } from 'webmidi';
|
||||||
import { getAudioContext } from '@strudel/webaudio';
|
import { getAudioContext } from '@strudel/webaudio';
|
||||||
import { scheduleAtTime } from '../superdough/helpers.mjs';
|
import { scheduleAtTime } from '../superdough/helpers.mjs';
|
||||||
import { getMidiDeviceNamesString, getDevice } from './util.mjs';
|
|
||||||
import { MidiInput } from './input.mjs';
|
|
||||||
|
|
||||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||||
export const { WebMidi } = _WebMidi;
|
export const { WebMidi } = _WebMidi;
|
||||||
@@ -33,6 +31,10 @@ function supportsMidi() {
|
|||||||
return typeof navigator.requestMIDIAccess === 'function';
|
return typeof navigator.requestMIDIAccess === 'function';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMidiDeviceNamesString(devices) {
|
||||||
|
return devices.map((o) => `'${o.name}'`).join(' | ');
|
||||||
|
}
|
||||||
|
|
||||||
export function enableWebMidi(options = {}) {
|
export function enableWebMidi(options = {}) {
|
||||||
const { onReady, onConnected, onDisconnected, onEnabled } = options;
|
const { onReady, onConnected, onDisconnected, onEnabled } = options;
|
||||||
if (WebMidi.enabled) {
|
if (WebMidi.enabled) {
|
||||||
@@ -70,6 +72,29 @@ export function enableWebMidi(options = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDevice(indexOrName, devices) {
|
||||||
|
if (!devices.length) {
|
||||||
|
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
||||||
|
}
|
||||||
|
if (typeof indexOrName === 'number') {
|
||||||
|
return devices[indexOrName];
|
||||||
|
}
|
||||||
|
const byName = (name) => devices.find((output) => output.name.includes(name));
|
||||||
|
if (typeof indexOrName === 'string') {
|
||||||
|
return byName(indexOrName);
|
||||||
|
}
|
||||||
|
// attempt to default to first IAC device if none is specified
|
||||||
|
const IACOutput = byName('IAC');
|
||||||
|
const device = IACOutput ?? devices[0];
|
||||||
|
if (!device) {
|
||||||
|
throw new Error(
|
||||||
|
`🔌 MIDI device '${device ? device : ''}' not found. Use one of ${getMidiDeviceNamesString(devices)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IACOutput ?? devices[0];
|
||||||
|
}
|
||||||
|
|
||||||
// send start/stop messages to outputs when repl starts/stops
|
// send start/stop messages to outputs when repl starts/stops
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.addEventListener('message', (e) => {
|
window.addEventListener('message', (e) => {
|
||||||
@@ -470,9 +495,9 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a midi input device
|
* Initialize a midi device
|
||||||
*/
|
*/
|
||||||
async function _initializeInput(input) {
|
async function _initialize(input) {
|
||||||
if (isPattern(input)) {
|
if (isPattern(input)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[midi] Midi input cannot be a pattern. Make sure to pass device name with single quotes. Example: midin('${
|
`[midi] Midi input cannot be a pattern. Make sure to pass device name with single quotes. Example: midin('${
|
||||||
@@ -480,31 +505,24 @@ async function _initializeInput(input) {
|
|||||||
}')`,
|
}')`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const initial = await enableWebMidi(); // only returns on first init
|
const initial = await enableWebMidi(); // only returns on first init
|
||||||
|
const device = getDevice(input, WebMidi.inputs);
|
||||||
const instance = midiInputs[input] || new MidiInput(input);
|
if (!device) {
|
||||||
midiInputs[input] = instance;
|
throw new Error(
|
||||||
|
`[midi] Midi device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
|
||||||
if (initial) {
|
|
||||||
const device = instance.initialDevice;
|
|
||||||
|
|
||||||
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
|
|
||||||
logger(
|
|
||||||
device
|
|
||||||
? `[midi] Midi enabled! Using "${device.name}". ${
|
|
||||||
otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : ''
|
|
||||||
}`
|
|
||||||
: `[midi] Midi enabled! Waiting for device "${input}"... Currently connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (initial) {
|
||||||
return instance;
|
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
|
||||||
|
logger(
|
||||||
|
`[midi] Midi enabled! Using "${device.name}". ${
|
||||||
|
otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MIDI input wrappers, by specified input string/index
|
|
||||||
const midiInputs = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
|
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
|
||||||
*
|
*
|
||||||
@@ -525,10 +543,31 @@ const midiInputs = {};
|
|||||||
* note("c a f e").s("saw")
|
* note("c a f e").s("saw")
|
||||||
* .when(cc(0).gt(0), x => x.postgain(0))
|
* .when(cc(0).gt(0), x => x.postgain(0))
|
||||||
*/
|
*/
|
||||||
|
let listeners = {};
|
||||||
|
const refs = {};
|
||||||
|
const refsByChan = {};
|
||||||
export async function midin(input) {
|
export async function midin(input) {
|
||||||
const instance = await _initializeInput(input);
|
const device = await _initialize(input);
|
||||||
|
refs[input] ??= {};
|
||||||
|
refsByChan[input] ??= {};
|
||||||
|
const cc = (cc, chan) => {
|
||||||
|
if (chan !== undefined) {
|
||||||
|
return ref(() => refsByChan[input][cc]?.[chan] || 0);
|
||||||
|
}
|
||||||
|
return ref(() => refs[input][cc] || 0);
|
||||||
|
};
|
||||||
|
|
||||||
return instance.createCC.bind(instance);
|
listeners[input] && device.removeListener('midimessage', listeners[input]);
|
||||||
|
listeners[input] = (e) => {
|
||||||
|
const [ccNum, v] = e.dataBytes;
|
||||||
|
const chan = e.message.channel;
|
||||||
|
const scaled = v / 127;
|
||||||
|
refsByChan[input][ccNum] ??= {};
|
||||||
|
refsByChan[input][ccNum][chan] = scaled;
|
||||||
|
refs[input][ccNum] = scaled;
|
||||||
|
};
|
||||||
|
device.addListener('midimessage', listeners[input]);
|
||||||
|
return cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -583,16 +622,7 @@ function _triggerKeyboard(input, cps, now, latencyCycles) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
export async function midikeys(input) {
|
export async function midikeys(input) {
|
||||||
const instance = await _initializeInput(input);
|
const device = await _initialize(input);
|
||||||
|
|
||||||
// TODO: support unpluggable device usage
|
|
||||||
const device = instance.initialDevice;
|
|
||||||
if (!device) {
|
|
||||||
throw new Error(
|
|
||||||
`[midi] Midi device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kHaps[input]) {
|
if (!kHaps[input]) {
|
||||||
kHaps[input] = [];
|
kHaps[input] = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
util.mjs - MIDI utility functions
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/midi/midi.mjs>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Input, Output } from 'webmidi';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a string listing device names for error messages.
|
|
||||||
* @param {Input[] | Output[]} devices
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function getMidiDeviceNamesString(devices) {
|
|
||||||
return devices.map((o) => `'${o.name}'`).join(' | ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up a device by index or name. Otherwise return a default device, or fail if none are connected.
|
|
||||||
*
|
|
||||||
* @param {string | number} indexOrName
|
|
||||||
* @param {Input[] | Output[]} devices
|
|
||||||
* @returns {Input | Output | undefined}
|
|
||||||
*/
|
|
||||||
export function getDevice(indexOrName, devices) {
|
|
||||||
if (typeof indexOrName === 'number') {
|
|
||||||
return devices[indexOrName];
|
|
||||||
}
|
|
||||||
const byName = (name) => devices.find((output) => output.name.includes(name));
|
|
||||||
if (typeof indexOrName === 'string') {
|
|
||||||
return byName(indexOrName);
|
|
||||||
}
|
|
||||||
// attempt to default to first IAC device if none is specified
|
|
||||||
const IACOutput = byName('IAC');
|
|
||||||
const device = IACOutput ?? devices[0];
|
|
||||||
if (!device) {
|
|
||||||
if (!devices.length) {
|
|
||||||
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
|
||||||
}
|
|
||||||
throw new Error(`🔌 Default MIDI device not found. Use one of ${getMidiDeviceNamesString(devices)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BASE_MIDI_NOTE, getBaseURL, getCommonSampleInfo, noteToFreq, noteToMidi } from './util.mjs';
|
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||||
import { registerSound, registerWaveTable } from './index.mjs';
|
import { registerSound, registerWaveTable } from './index.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import {
|
import {
|
||||||
@@ -14,13 +14,6 @@ import { logger } from './logger.mjs';
|
|||||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||||
const loadCache = {}; // string: Promise<ArrayBuffer>
|
const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @typedef {Object} SampleMetaData
|
|
||||||
* @property {string} url
|
|
||||||
* @property {midi} number
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const getCachedBuffer = (url) => bufferCache[url];
|
export const getCachedBuffer = (url) => bufferCache[url];
|
||||||
|
|
||||||
function humanFileSize(bytes, si) {
|
function humanFileSize(bytes, si) {
|
||||||
@@ -37,11 +30,10 @@ function humanFileSize(bytes, si) {
|
|||||||
return bytes.toFixed(1) + ' ' + units[u];
|
return bytes.toFixed(1) + ' ' + units[u];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSampleInfo(hapValue, bank) {
|
export function getSampleInfo(hapValue, bank) {
|
||||||
const { speed = 1.0 } = hapValue;
|
const { speed = 1.0 } = hapValue;
|
||||||
const { transpose, url, index, midi, label, baseFrequency } = getCommonSampleInfo(hapValue, bank);
|
const { transpose, url, index, midi, label } = getCommonSampleInfo(hapValue, bank);
|
||||||
|
let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12);
|
||||||
const playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12);
|
|
||||||
return { transpose, url, index, midi, label, playbackRate };
|
return { transpose, url, index, midi, label, playbackRate };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,12 +156,7 @@ export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '')
|
|||||||
if (baseUrl.startsWith('github:')) {
|
if (baseUrl.startsWith('github:')) {
|
||||||
baseUrl = githubPath(baseUrl, '');
|
baseUrl = githubPath(baseUrl, '');
|
||||||
}
|
}
|
||||||
/**
|
const fullUrl = (v) => baseUrl + v;
|
||||||
*
|
|
||||||
* @param {string} v
|
|
||||||
* @returns {SampleMetaData}
|
|
||||||
*/
|
|
||||||
const fullUrl = (v) => ({ url: baseUrl + v, midi: extractMidiNoteFromString(v) });
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
//return [key, value.map(replaceUrl)];
|
//return [key, value.map(replaceUrl)];
|
||||||
value = value.map(fullUrl);
|
value = value.map(fullUrl);
|
||||||
@@ -380,16 +367,3 @@ export function registerSampleSource(key, bank, params) {
|
|||||||
registerSample(key, bank, params);
|
registerSample(key, bank, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractMidiNoteFromString(str) {
|
|
||||||
const regex = /_([a-gA-G])([#b])?([1-9])?\b/;
|
|
||||||
const match = str.match(regex);
|
|
||||||
if (match == null) {
|
|
||||||
return BASE_MIDI_NOTE;
|
|
||||||
}
|
|
||||||
const base = match[1].toUpperCase();
|
|
||||||
const accidental = match[2] ?? '';
|
|
||||||
const octave_str = match[3] ?? '';
|
|
||||||
const parsedVal = base + accidental + octave_str;
|
|
||||||
return noteToMidi(parsedVal);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,12 +19,7 @@ const accs = { '#': 1, b: -1, s: 1, f: -1 };
|
|||||||
export const getAccidentalsOffset = (accidentals) => {
|
export const getAccidentalsOffset = (accidentals) => {
|
||||||
return accidentals?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
return accidentals?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} note
|
|
||||||
* @param {number} defaultOctave
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
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) {
|
||||||
@@ -34,11 +29,6 @@ export const noteToMidi = (note, defaultOctave = 3) => {
|
|||||||
const offset = getAccidentalsOffset(acc);
|
const offset = getAccidentalsOffset(acc);
|
||||||
return (Number(oct) + 1) * 12 + chroma + offset;
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {number} n
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
export const midiToFreq = (n) => {
|
export const midiToFreq = (n) => {
|
||||||
return Math.pow(2, (n - 69) / 12) * 440;
|
return Math.pow(2, (n - 69) / 12) * 440;
|
||||||
};
|
};
|
||||||
@@ -48,17 +38,7 @@ export const freqToMidi = (freq) => {
|
|||||||
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
|
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const valueToMidi = (value, fallbackValue) => {
|
||||||
*
|
|
||||||
* @param {string} note
|
|
||||||
* @param {number} defaultOctave
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const noteToFreq = (note, defaultOctave = 3) => {
|
|
||||||
return midiToFreq(noteToMidi(note, defaultOctave));
|
|
||||||
};
|
|
||||||
function __valueToMidi(value) {
|
|
||||||
if (typeof value !== 'object') {
|
if (typeof value !== 'object') {
|
||||||
throw new Error('valueToMidi: expected object value');
|
throw new Error('valueToMidi: expected object value');
|
||||||
}
|
}
|
||||||
@@ -72,15 +52,10 @@ function __valueToMidi(value) {
|
|||||||
if (typeof note === 'number') {
|
if (typeof note === 'number') {
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
return;
|
if (!fallbackValue) {
|
||||||
}
|
|
||||||
|
|
||||||
export const valueToMidi = (value, fallbackValue) => {
|
|
||||||
const parsedValue = __valueToMidi(value) ?? fallbackValue;
|
|
||||||
if (parsedValue == null) {
|
|
||||||
throw new Error('valueToMidi: expected freq or note to be set');
|
throw new Error('valueToMidi: expected freq or note to be set');
|
||||||
}
|
}
|
||||||
return parsedValue;
|
return fallbackValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function nanFallback(value, fallback = 0, silent) {
|
export function nanFallback(value, fallback = 0, silent) {
|
||||||
@@ -109,18 +84,15 @@ export function secondsToCycle(t, cps) {
|
|||||||
// deduces relevant info for sample loading from hap.value and sample definition
|
// deduces relevant info for sample loading from hap.value and sample definition
|
||||||
// it encapsulates the core sampler logic into a pure and synchronous function
|
// it encapsulates the core sampler logic into a pure and synchronous function
|
||||||
// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format)
|
// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format)
|
||||||
export const BASE_MIDI_NOTE = 36;
|
|
||||||
|
|
||||||
export function getCommonSampleInfo(hapValue, bank) {
|
export function getCommonSampleInfo(hapValue, bank) {
|
||||||
const { s, n = 0 } = hapValue;
|
const { s, n = 0 } = hapValue;
|
||||||
const maybeMidiNote = __valueToMidi(hapValue);
|
let midi = valueToMidi(hapValue, 36);
|
||||||
const midi = maybeMidiNote ?? BASE_MIDI_NOTE;
|
let transpose = midi - 36; // C3 is middle C;
|
||||||
let transpose = midi - BASE_MIDI_NOTE; // C3 is middle C;
|
let url;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let samplemeta;
|
|
||||||
if (Array.isArray(bank)) {
|
if (Array.isArray(bank)) {
|
||||||
index = getSoundIndex(n, bank.length);
|
index = getSoundIndex(n, bank.length);
|
||||||
samplemeta = bank[index];
|
url = bank[index];
|
||||||
} else {
|
} else {
|
||||||
const midiDiff = (noteA) => noteToMidi(noteA) - midi;
|
const midiDiff = (noteA) => noteToMidi(noteA) - midi;
|
||||||
// object format will expect keys as notes
|
// object format will expect keys as notes
|
||||||
@@ -132,14 +104,10 @@ export function getCommonSampleInfo(hapValue, bank) {
|
|||||||
);
|
);
|
||||||
transpose = -midiDiff(closest); // semitones to repitch
|
transpose = -midiDiff(closest); // semitones to repitch
|
||||||
index = getSoundIndex(n, bank[closest].length);
|
index = getSoundIndex(n, bank[closest].length);
|
||||||
samplemeta = bank[closest][index];
|
url = bank[closest][index];
|
||||||
}
|
}
|
||||||
const label = `${s}:${index}`;
|
const label = `${s}:${index}`;
|
||||||
if (maybeMidiNote != null) {
|
return { transpose, url, index, midi, label };
|
||||||
transpose = transpose + (BASE_MIDI_NOTE - samplemeta.midi);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { transpose, index, midi, label, url: samplemeta.url };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Selects entries from `source` and renames them via `map`
|
/** Selects entries from `source` and renames them via `map`
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class CoarseProcessor extends AudioWorkletProcessor {
|
|||||||
coarse = Math.max(1, coarse);
|
coarse = Math.max(1, coarse);
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
for (let i = 0; i < input.length; i++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
output[i][n] = n % coarse < 1 ? input[i][n] : output[i][n - 1];
|
output[i][n] = n % coarse === 0 ? input[i][n] : output[i][n - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+1
-38
@@ -7,51 +7,14 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
import Tune from './tunejs.js';
|
import Tune from './tunejs.js';
|
||||||
import { register } from '@strudel/core';
|
import { register } from '@strudel/core';
|
||||||
|
|
||||||
/**
|
|
||||||
* Assumes pattern contains numerical scale degrees on the `i` control (see examples below). Accepts a scale name or list of frequencies (see all available names at the link on the reference). Returns a new pattern with all values mapped to a frequency ratio. Similar to `xen`.
|
|
||||||
* @name tune
|
|
||||||
* @returns Pattern
|
|
||||||
* @memberof Pattern
|
|
||||||
* @param {(string | number[] )} scale
|
|
||||||
* @example
|
|
||||||
* i("0 1 2 3 4 5").tune("hexany15").mul("220").freq()
|
|
||||||
* @example
|
|
||||||
* // You can set your root to be a
|
|
||||||
* // particular note with getFreq:
|
|
||||||
* i("4 8 9 10 - - 5 7 9 11 - -").tune("tranh3")
|
|
||||||
* .mul(getFreq('c3'))
|
|
||||||
* .freq().clip(.5).room(1)
|
|
||||||
* @example
|
|
||||||
* // You can also give tune a list of
|
|
||||||
* // frequencies to use as the scale:
|
|
||||||
* i("0 1 2 3 4").tune([
|
|
||||||
* 261.6255653006,
|
|
||||||
* 302.72962012827,
|
|
||||||
* 350.29154279212,
|
|
||||||
* 405.32593044476,
|
|
||||||
* 469.00678383895,
|
|
||||||
* 523.2511306012
|
|
||||||
* ]).mul(220).freq();
|
|
||||||
*
|
|
||||||
* @tags tonal
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Tune.scale seems to be in ratio format
|
|
||||||
export const tune = register('tune', (scale, pat) => {
|
export const tune = register('tune', (scale, pat) => {
|
||||||
const tune = new Tune();
|
const tune = new Tune();
|
||||||
if (!tune.isValidScale(scale)) {
|
if (!tune.isValidScale(scale)) {
|
||||||
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
|
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
|
||||||
}
|
}
|
||||||
tune.loadScale(scale);
|
tune.loadScale(scale);
|
||||||
// if the tonic is a frequency, why are we putting in "1"
|
|
||||||
tune.tonicize(1);
|
tune.tonicize(1);
|
||||||
return pat.withHap((hap) => {
|
return pat.withHap((hap) => {
|
||||||
if (typeof hap.value !== 'object') {
|
return hap.withValue(() => tune.note(hap.value));
|
||||||
throw new Error(`Expected hap to have control 'i' set, but received ${hap.value.i}, try wrapping input in i()`);
|
|
||||||
}
|
|
||||||
// const { i, ...otherValues } = hap.value;
|
|
||||||
// hap.value = { ...otherValues, freq: tune.note(i)}
|
|
||||||
// return hap
|
|
||||||
return hap.withValue(() => tune.note(hap.value.i));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-14
@@ -78,21 +78,14 @@ Tune.prototype.frequency = function(stepIn, octaveIn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// which scale degree (0 - scale length) is our input
|
// which scale degree (0 - scale length) is our input
|
||||||
// 60 % 12 = 0
|
|
||||||
var scaleDegree = stepIn % this.scale.length
|
var scaleDegree = stepIn % this.scale.length
|
||||||
|
|
||||||
// what's this doing
|
|
||||||
// 0 scaleDegree = 12
|
|
||||||
// seems to simply be for a negative result from above, maybe another way to do it, but ok for now
|
|
||||||
while (scaleDegree < 0) {
|
while (scaleDegree < 0) {
|
||||||
scaleDegree += this.scale.length
|
scaleDegree += this.scale.length
|
||||||
}
|
}
|
||||||
|
|
||||||
// tonic is currently always 1
|
|
||||||
// so this is 1*scale[scaledegree]
|
|
||||||
var freq = this.tonic*this.scale[scaleDegree]
|
var freq = this.tonic*this.scale[scaleDegree]
|
||||||
|
|
||||||
// map it to octave
|
|
||||||
freq = freq*(Math.pow(2,octave))
|
freq = freq*(Math.pow(2,octave))
|
||||||
|
|
||||||
// truncate irrational numbers
|
// truncate irrational numbers
|
||||||
@@ -144,13 +137,10 @@ Tune.prototype.MIDI = function(stepIn,octaveIn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Load a new scale */
|
/* Load a new scale */
|
||||||
// sets .scale to ratios
|
|
||||||
|
|
||||||
Tune.prototype.loadScale = function(scale){
|
Tune.prototype.loadScale = function(scale){
|
||||||
|
|
||||||
/* load the scale */
|
/* load the scale */
|
||||||
let name
|
|
||||||
if (typeof scale === 'string') name = scale;
|
|
||||||
var freqs = isArrayOfNumbers(scale) ? scale : TuningList[scale].frequencies
|
var freqs = isArrayOfNumbers(scale) ? scale : TuningList[scale].frequencies
|
||||||
this.scale = []
|
this.scale = []
|
||||||
for (var i=0;i<freqs.length-1;i++) {
|
for (var i=0;i<freqs.length-1;i++) {
|
||||||
@@ -158,10 +148,10 @@ Tune.prototype.loadScale = function(scale){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* visualize in console */
|
/* visualize in console */
|
||||||
// console.log(" ");
|
/* console.log(" ");
|
||||||
// console.log("LOADED "+name);
|
console.log("LOADED "+name);
|
||||||
// console.log(TuningList[name].description);
|
console.log(TuningList[name].description);
|
||||||
// console.log(this.scale);
|
console.log(this.scale); */
|
||||||
var vis = [];
|
var vis = [];
|
||||||
for (var i=0;i<100;i++) {
|
for (var i=0;i<100;i++) {
|
||||||
vis[i] = " ";
|
vis[i] = " ";
|
||||||
|
|||||||
+9
-176
@@ -4,10 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { register, _mod, parseNumeral, removeUndefineds } from '@strudel/core';
|
import { register, _mod, parseNumeral } from '@strudel/core';
|
||||||
import Tune from './tunejs.js';
|
|
||||||
|
|
||||||
// returns a list of frequency ratios for given edo scale
|
|
||||||
export function edo(name) {
|
export function edo(name) {
|
||||||
if (!/^[1-9]+[0-9]*edo$/.test(name)) {
|
if (!/^[1-9]+[0-9]*edo$/.test(name)) {
|
||||||
throw new Error('not an edo scale: "' + name + '"');
|
throw new Error('not an edo scale: "' + name + '"');
|
||||||
@@ -20,33 +18,23 @@ const presets = {
|
|||||||
'12ji': [1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4, 4 / 3, 45 / 32, 3 / 2, 8 / 5, 5 / 3, 16 / 9, 15 / 8],
|
'12ji': [1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4, 4 / 3, 45 / 32, 3 / 2, 8 / 5, 5 / 3, 16 / 9, 15 / 8],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Given a base frequency such as 220 and an edo scale, returns
|
function withBase(freq, scale) {
|
||||||
// an array of frequencies representing the given edo scale in that base
|
|
||||||
function _withBase(freq, scale) {
|
|
||||||
return scale.map((r) => r * freq);
|
return scale.map((r) => r * freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBase = 220;
|
const defaultBase = 220;
|
||||||
|
|
||||||
const isEdo = (scale) => /^[1-9]+[0-9]*edo$/.test(scale);
|
|
||||||
|
|
||||||
// Assumes a base of 220. Returns a filtered scale based on 'indices'
|
|
||||||
// NOTE: indices functionality is unused
|
|
||||||
function getXenScale(scale, indices) {
|
function getXenScale(scale, indices) {
|
||||||
let tune = new Tune();
|
|
||||||
if (typeof scale === 'string') {
|
if (typeof scale === 'string') {
|
||||||
if (isEdo(scale)) {
|
if (/^[1-9]+[0-9]*edo$/.test(scale)) {
|
||||||
scale = edo(scale);
|
scale = edo(scale);
|
||||||
} else if (presets[scale]) {
|
} else if (presets[scale]) {
|
||||||
scale = presets[scale];
|
scale = presets[scale];
|
||||||
} else if (tune.isValidScale(scale)) {
|
|
||||||
tune.loadScale(scale);
|
|
||||||
scale = tune.scale;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unknown scale name: "' + scale + '"');
|
throw new Error('unknown scale name: "' + scale + '"');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scale = _withBase(defaultBase, scale);
|
scale = withBase(defaultBase, scale);
|
||||||
if (!indices) {
|
if (!indices) {
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
@@ -59,171 +47,16 @@ function xenOffset(xenScale, offset, index = 0) {
|
|||||||
return xenScale[i] * Math.pow(2, oct);
|
return xenScale[i] * Math.pow(2, oct);
|
||||||
}
|
}
|
||||||
|
|
||||||
const trimFreq = (freq) => parseFloat(freq.toPrecision(10));
|
|
||||||
|
|
||||||
// accepts a scale name such as 31edo, and a pattern
|
|
||||||
// pattern expected to follow format such that a value can be mapped
|
|
||||||
// to an edostep within the scale. Returns the pattern with
|
|
||||||
// values mapped to the frequencies associated with the given edosteps
|
|
||||||
// scaleNameOrRatios: string || number[], steps?: number
|
// scaleNameOrRatios: string || number[], steps?: number
|
||||||
|
|
||||||
/**
|
|
||||||
* Assumes a numerical pattern of scale steps, and a scale. Scales accepted are all preset scale names of `tune`, arbitrary edos such as 31edo, or an array of frequency ratios. Assumes scales repeat at octave (2/1). Returns a new pattern with all values mapped to their associated frequency, assuming a base frequency of 220hz.
|
|
||||||
*
|
|
||||||
* @name xen
|
|
||||||
* @returns Pattern
|
|
||||||
* @memberof Pattern
|
|
||||||
* @param {(string | number[] )} scaleNameOrRatios
|
|
||||||
* @tags tonal
|
|
||||||
* @example
|
|
||||||
* // A minor triad in 31edo:
|
|
||||||
* i("0 8 18").xen("31edo").piano()
|
|
||||||
* @example
|
|
||||||
* // You can also use xen with frequency ratios.
|
|
||||||
* // This is equivalent to the above:
|
|
||||||
* i("0 1 2").xen([
|
|
||||||
* Math.pow(2, 0/31),
|
|
||||||
* Math.pow(2, 8/31),
|
|
||||||
* Math.pow(2, 18/31),
|
|
||||||
* ]).piano()
|
|
||||||
* @example
|
|
||||||
* // xen also supports all scale names that
|
|
||||||
* // tune does:
|
|
||||||
* i("0 1 2 3 4 5").xen("hexany15")
|
|
||||||
* // equiv to:
|
|
||||||
* // "0 1 2 3 4 5".tune("hexany15").mul("220").freq()
|
|
||||||
* @example
|
|
||||||
* i("0 1 2 3 4 5 6 7").xen("<5edo 10edo 15edo hexany15>")
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const xen = register('xen', function (scaleNameOrRatios, pat) {
|
export const xen = register('xen', function (scaleNameOrRatios, pat) {
|
||||||
return pat.withHaps((haps) => {
|
return pat.withHap((hap) => {
|
||||||
haps = haps.map((hap) => {
|
const scale = getXenScale(scaleNameOrRatios);
|
||||||
let hVal = hap.value;
|
const frequency = xenOffset(scale, parseNumeral(hap.value));
|
||||||
const isObject = typeof hVal === 'object';
|
return hap.withValue(() => frequency);
|
||||||
if (!isObject) {
|
|
||||||
throw new Error(`Expected hap to have control 'i' set, but received ${hap.value.i}, try wrapping input in i()`);
|
|
||||||
}
|
|
||||||
const { i, ...otherValues } = hVal;
|
|
||||||
const scale = getXenScale(scaleNameOrRatios);
|
|
||||||
let freq = xenOffset(scale, parseNumeral(hVal.i));
|
|
||||||
// 10 is somewhat arbitrary
|
|
||||||
freq = trimFreq(freq);
|
|
||||||
hap.value = { ...otherValues, freq };
|
|
||||||
return isEdo(scaleNameOrRatios)
|
|
||||||
? hap.setContext({ ...hap.context, edoSize: scaleNameOrRatios.match(/^([1-9]+[0-9]*)edo$/)[1] })
|
|
||||||
: hap;
|
|
||||||
});
|
|
||||||
return removeUndefineds(haps);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
export const tuning = register('tuning', function (ratios, pat) {
|
||||||
* Assumes pattern of frequencies tuned to some `base` frequency, such as the output of `xen`
|
|
||||||
* Because `xen` defaults to `220Hz`, so will `withBase`.
|
|
||||||
* but you can specify a different original base with the standard optional array syntax '`:`'
|
|
||||||
* @name withBase
|
|
||||||
* @param {number} base
|
|
||||||
* @param {number} (optional) originalBase
|
|
||||||
* @tags tonal
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* i("[0 1 2 3] [3 4] [4 3 2 1]").xen("hexany23").withBase("<220 [300 200]>")
|
|
||||||
* @example
|
|
||||||
* mini([1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4].join(' ')).withBase("220:1")
|
|
||||||
* // mini([1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4].join(' ')).mul(220).freq()
|
|
||||||
*
|
|
||||||
* @returns Pattern
|
|
||||||
*/
|
|
||||||
export const withBase = register('withBase', (b, pat) => {
|
|
||||||
let base;
|
|
||||||
let originalBase = 220;
|
|
||||||
if (Array.isArray(b)) {
|
|
||||||
base = b[0];
|
|
||||||
originalBase = b[1];
|
|
||||||
} else {
|
|
||||||
base = b;
|
|
||||||
}
|
|
||||||
return pat.withHaps((haps) => {
|
|
||||||
haps = haps.map((hap) => {
|
|
||||||
let hVal = hap.value;
|
|
||||||
const isObject = typeof hVal === 'object';
|
|
||||||
let freq = isObject ? hVal.freq : hVal;
|
|
||||||
freq = (freq * base) / originalBase;
|
|
||||||
hap.value = isObject ? { ...hap.value, freq } : { freq };
|
|
||||||
return hap;
|
|
||||||
});
|
|
||||||
return removeUndefineds(haps);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frequency transpose. Assumes pattern either has `freq` set, or has values that can be interpreted as frequencies
|
|
||||||
* amt has optional `edoSize` param, defaults to 12.
|
|
||||||
* If haps have edoSize param set, such as from the output of `xen("31edo")`,
|
|
||||||
* `ftrans` will fallback to that instead of 12 as the default.
|
|
||||||
*
|
|
||||||
* Transposes the frequency by `amt` edoSteps
|
|
||||||
* @name ftranspose
|
|
||||||
* @synonyms ftrans, fTrans, ftranspose, fTranspose
|
|
||||||
* @tags tonal
|
|
||||||
* @param {number} amt
|
|
||||||
* @param {number} edoSize (optional)
|
|
||||||
* @returns {Pattern}
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* i("0 1 2").xen("12edo").ftrans("7")
|
|
||||||
* // n("0 1 2").scale("A:chromatic").trans("7")
|
|
||||||
* @example
|
|
||||||
* i("0 8 18").xen("31edo").ftrans("<8 -8>")
|
|
||||||
* @example
|
|
||||||
* // to transpose by steps of an edo, use "step:edo" :
|
|
||||||
* i("0 7 8 18").xen("31edo").ftrans("<0 1:31 1:12>")
|
|
||||||
* @example
|
|
||||||
* // it can also work with frequency values directly
|
|
||||||
* freq("200 300 400").ftrans("<0 7:31 7>")
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* f = frequency (Hz)
|
|
||||||
n = edo (steps per octave)
|
|
||||||
x = number of steps
|
|
||||||
if 0\n = f, then x\n = f * 2^(x/n)
|
|
||||||
example: 5edo, 0\5 = 220 Hz, then 2\5 = 220*2^(2/5) = 290.29 Hz */
|
|
||||||
|
|
||||||
export const { ftrans, fTrans, ftranspose, fTranspose } = register(
|
|
||||||
['ftrans', 'fTrans', 'ftranspose', 'fTranspose'],
|
|
||||||
(amt, pat) => {
|
|
||||||
let edoSize;
|
|
||||||
let numSteps;
|
|
||||||
if (Array.isArray(amt)) {
|
|
||||||
edoSize = amt[1];
|
|
||||||
numSteps = amt[0];
|
|
||||||
} else {
|
|
||||||
numSteps = amt;
|
|
||||||
}
|
|
||||||
return pat.withHaps((haps) => {
|
|
||||||
haps = haps.map((hap) => {
|
|
||||||
let hVal = hap.value;
|
|
||||||
const isObject = typeof hVal === 'object';
|
|
||||||
hVal = isObject ? hVal : { freq: hVal };
|
|
||||||
let { freq, ...otherValues } = hVal;
|
|
||||||
if (edoSize == undefined && hap.context.edoSize != undefined) {
|
|
||||||
edoSize = hap.context.edoSize;
|
|
||||||
} else if (edoSize == undefined) {
|
|
||||||
edoSize = 12;
|
|
||||||
}
|
|
||||||
freq = freq * Math.pow(2, numSteps / edoSize);
|
|
||||||
freq = trimFreq(freq);
|
|
||||||
hap.value = isObject ? { ...otherValues, freq } : freq;
|
|
||||||
return hap.setContext({ ...hap.context, edoSize });
|
|
||||||
});
|
|
||||||
return removeUndefineds(haps);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// not sure there's a point to having this and the above, seems like a proto version of the above.
|
|
||||||
const tuning = register('tuning', function (ratios, pat) {
|
|
||||||
return pat.withHap((hap) => {
|
return pat.withHap((hap) => {
|
||||||
const frequency = xenOffset(ratios, parseNumeral(hap.value));
|
const frequency = xenOffset(ratios, parseNumeral(hap.value));
|
||||||
return hap.withValue(() => frequency);
|
return hap.withValue(() => frequency);
|
||||||
|
|||||||
Generated
-20
@@ -221,9 +221,6 @@ importers:
|
|||||||
'@tonaljs/tonal':
|
'@tonaljs/tonal':
|
||||||
specifier: ^4.10.0
|
specifier: ^4.10.0
|
||||||
version: 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:
|
nanostores:
|
||||||
specifier: ^0.11.3
|
specifier: ^0.11.3
|
||||||
version: 0.11.3
|
version: 0.11.3
|
||||||
@@ -3554,15 +3551,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==}
|
resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==}
|
||||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
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:
|
collapse-white-space@2.1.0:
|
||||||
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
|
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
|
||||||
|
|
||||||
@@ -11292,14 +11280,6 @@ snapshots:
|
|||||||
|
|
||||||
cmd-shim@6.0.3: {}
|
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: {}
|
collapse-white-space@2.1.0: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
|
|||||||
@@ -4950,78 +4950,6 @@ exports[`runs examples > example "fscope" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "ftranspose" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/3 | freq:329.6275569 ]",
|
|
||||||
"[ 1/3 → 2/3 | freq:349.2282315 ]",
|
|
||||||
"[ 2/3 → 1/1 | freq:369.9944227 ]",
|
|
||||||
"[ 1/1 → 4/3 | freq:329.6275569 ]",
|
|
||||||
"[ 4/3 → 5/3 | freq:349.2282315 ]",
|
|
||||||
"[ 5/3 → 2/1 | freq:369.9944227 ]",
|
|
||||||
"[ 2/1 → 7/3 | freq:329.6275569 ]",
|
|
||||||
"[ 7/3 → 8/3 | freq:349.2282315 ]",
|
|
||||||
"[ 8/3 → 3/1 | freq:369.9944227 ]",
|
|
||||||
"[ 3/1 → 10/3 | freq:329.6275569 ]",
|
|
||||||
"[ 10/3 → 11/3 | freq:349.2282315 ]",
|
|
||||||
"[ 11/3 → 4/1 | freq:369.9944227 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "ftranspose" example index 1 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/3 | freq:263.0921203 ]",
|
|
||||||
"[ 1/3 → 2/3 | freq:314.6248353 ]",
|
|
||||||
"[ 2/3 → 1/1 | freq:393.4589706 ]",
|
|
||||||
"[ 1/1 → 4/3 | freq:183.965981 ]",
|
|
||||||
"[ 4/3 → 5/3 | freq:220 ]",
|
|
||||||
"[ 5/3 → 2/1 | freq:275.1244143 ]",
|
|
||||||
"[ 2/1 → 7/3 | freq:263.0921203 ]",
|
|
||||||
"[ 7/3 → 8/3 | freq:314.6248353 ]",
|
|
||||||
"[ 8/3 → 3/1 | freq:393.4589706 ]",
|
|
||||||
"[ 3/1 → 10/3 | freq:183.965981 ]",
|
|
||||||
"[ 10/3 → 11/3 | freq:220 ]",
|
|
||||||
"[ 11/3 → 4/1 | freq:275.1244143 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "ftranspose" example index 2 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/4 | freq:220 ]",
|
|
||||||
"[ 1/4 → 1/2 | freq:257.2747684 ]",
|
|
||||||
"[ 1/2 → 3/4 | freq:263.0921203 ]",
|
|
||||||
"[ 3/4 → 1/1 | freq:329.0139341 ]",
|
|
||||||
"[ 1/1 → 5/4 | freq:224.9745158 ]",
|
|
||||||
"[ 5/4 → 3/2 | freq:263.0921203 ]",
|
|
||||||
"[ 3/2 → 7/4 | freq:269.0410108 ]",
|
|
||||||
"[ 7/4 → 2/1 | freq:336.4534115 ]",
|
|
||||||
"[ 2/1 → 9/4 | freq:233.0818808 ]",
|
|
||||||
"[ 9/4 → 5/2 | freq:272.5731222 ]",
|
|
||||||
"[ 5/2 → 11/4 | freq:278.7363919 ]",
|
|
||||||
"[ 11/4 → 3/1 | freq:348.5781207 ]",
|
|
||||||
"[ 3/1 → 13/4 | freq:220 ]",
|
|
||||||
"[ 13/4 → 7/2 | freq:257.2747684 ]",
|
|
||||||
"[ 7/2 → 15/4 | freq:263.0921203 ]",
|
|
||||||
"[ 15/4 → 4/1 | freq:329.0139341 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "ftranspose" example index 3 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/3 | freq:200 ]",
|
|
||||||
"[ 1/3 → 2/3 | freq:300 ]",
|
|
||||||
"[ 2/3 → 1/1 | freq:400 ]",
|
|
||||||
"[ 1/1 → 4/3 | freq:233.8861531 ]",
|
|
||||||
"[ 4/3 → 5/3 | freq:350.8292297 ]",
|
|
||||||
"[ 5/3 → 2/1 | freq:467.7723062 ]",
|
|
||||||
"[ 2/1 → 7/3 | freq:299.6614154 ]",
|
|
||||||
"[ 7/3 → 8/3 | freq:449.4921231 ]",
|
|
||||||
"[ 8/3 → 3/1 | freq:599.3228308 ]",
|
|
||||||
"[ 3/1 → 10/3 | freq:200 ]",
|
|
||||||
"[ 10/3 → 11/3 | freq:300 ]",
|
|
||||||
"[ 11/3 → 4/1 | freq:400 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "ftype" example index 0 1`] = `
|
exports[`runs examples > example "ftype" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | note:f s:sawtooth lpenv:4 cutoff:500 ftype:0 resonance:1 ]",
|
"[ 0/1 → 1/8 | note:f s:sawtooth lpenv:4 cutoff:500 ftype:0 resonance:1 ]",
|
||||||
@@ -5706,43 +5634,6 @@ exports[`runs examples > example "hush" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "i" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/8 | freq:220 ]",
|
|
||||||
"[ 1/8 → 1/4 | freq:252.7136381 ]",
|
|
||||||
"[ 1/4 → 3/8 | freq:290.2917404 ]",
|
|
||||||
"[ 3/8 → 1/2 | freq:333.4576446 ]",
|
|
||||||
"[ 1/2 → 5/8 | freq:383.0422479 ]",
|
|
||||||
"[ 5/8 → 3/4 | freq:440 ]",
|
|
||||||
"[ 3/4 → 7/8 | freq:505.4272762 ]",
|
|
||||||
"[ 7/8 → 1/1 | freq:580.5834807 ]",
|
|
||||||
"[ 1/1 → 9/8 | freq:220 ]",
|
|
||||||
"[ 9/8 → 5/4 | freq:235.7901618 ]",
|
|
||||||
"[ 5/4 → 11/8 | freq:252.7136381 ]",
|
|
||||||
"[ 11/8 → 3/2 | freq:270.8517709 ]",
|
|
||||||
"[ 3/2 → 13/8 | freq:290.2917404 ]",
|
|
||||||
"[ 13/8 → 7/4 | freq:311.1269837 ]",
|
|
||||||
"[ 7/4 → 15/8 | freq:333.4576446 ]",
|
|
||||||
"[ 15/8 → 2/1 | freq:357.3910544 ]",
|
|
||||||
"[ 2/1 → 17/8 | freq:220 ]",
|
|
||||||
"[ 17/8 → 9/4 | freq:230.404707 ]",
|
|
||||||
"[ 9/4 → 19/8 | freq:241.3014955 ]",
|
|
||||||
"[ 19/8 → 5/2 | freq:252.7136381 ]",
|
|
||||||
"[ 5/2 → 21/8 | freq:264.6655079 ]",
|
|
||||||
"[ 21/8 → 11/4 | freq:277.182631 ]",
|
|
||||||
"[ 11/4 → 23/8 | freq:290.2917404 ]",
|
|
||||||
"[ 23/8 → 3/1 | freq:304.0208336 ]",
|
|
||||||
"[ 3/1 → 25/8 | freq:220 ]",
|
|
||||||
"[ 25/8 → 13/4 | freq:275 ]",
|
|
||||||
"[ 13/4 → 27/8 | freq:293.3333333 ]",
|
|
||||||
"[ 27/8 → 7/2 | freq:330 ]",
|
|
||||||
"[ 7/2 → 29/8 | freq:352 ]",
|
|
||||||
"[ 29/8 → 15/4 | freq:440 ]",
|
|
||||||
"[ 15/4 → 31/8 | freq:550 ]",
|
|
||||||
"[ 31/8 → 4/1 | freq:586.6666667 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "inhabit" example index 0 1`] = `
|
exports[`runs examples > example "inhabit" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | s:bd ]",
|
"[ 0/1 → 1/8 | s:bd ]",
|
||||||
@@ -13324,97 +13215,6 @@ exports[`runs examples > example "tri" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "tune" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/6 | freq:{i:0} ]",
|
|
||||||
"[ 1/6 → 1/3 | freq:{i:1} ]",
|
|
||||||
"[ 1/3 → 1/2 | freq:{i:2} ]",
|
|
||||||
"[ 1/2 → 2/3 | freq:{i:3} ]",
|
|
||||||
"[ 2/3 → 5/6 | freq:{i:4} ]",
|
|
||||||
"[ 5/6 → 1/1 | freq:{i:5} ]",
|
|
||||||
"[ 1/1 → 7/6 | freq:{i:0} ]",
|
|
||||||
"[ 7/6 → 4/3 | freq:{i:1} ]",
|
|
||||||
"[ 4/3 → 3/2 | freq:{i:2} ]",
|
|
||||||
"[ 3/2 → 5/3 | freq:{i:3} ]",
|
|
||||||
"[ 5/3 → 11/6 | freq:{i:4} ]",
|
|
||||||
"[ 11/6 → 2/1 | freq:{i:5} ]",
|
|
||||||
"[ 2/1 → 13/6 | freq:{i:0} ]",
|
|
||||||
"[ 13/6 → 7/3 | freq:{i:1} ]",
|
|
||||||
"[ 7/3 → 5/2 | freq:{i:2} ]",
|
|
||||||
"[ 5/2 → 8/3 | freq:{i:3} ]",
|
|
||||||
"[ 8/3 → 17/6 | freq:{i:4} ]",
|
|
||||||
"[ 17/6 → 3/1 | freq:{i:5} ]",
|
|
||||||
"[ 3/1 → 19/6 | freq:{i:0} ]",
|
|
||||||
"[ 19/6 → 10/3 | freq:{i:1} ]",
|
|
||||||
"[ 10/3 → 7/2 | freq:{i:2} ]",
|
|
||||||
"[ 7/2 → 11/3 | freq:{i:3} ]",
|
|
||||||
"[ 11/3 → 23/6 | freq:{i:4} ]",
|
|
||||||
"[ 23/6 → 4/1 | freq:{i:5} ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "tune" example index 1 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/12 | freq:{i:4} clip:0.5 room:1 ]",
|
|
||||||
"[ 1/12 → 1/6 | freq:{i:8} clip:0.5 room:1 ]",
|
|
||||||
"[ 1/6 → 1/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 1/4 → 1/3 | freq:{i:10} clip:0.5 room:1 ]",
|
|
||||||
"[ 1/2 → 7/12 | freq:{i:5} clip:0.5 room:1 ]",
|
|
||||||
"[ 7/12 → 2/3 | freq:{i:7} clip:0.5 room:1 ]",
|
|
||||||
"[ 2/3 → 3/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 3/4 → 5/6 | freq:{i:11} clip:0.5 room:1 ]",
|
|
||||||
"[ 1/1 → 13/12 | freq:{i:4} clip:0.5 room:1 ]",
|
|
||||||
"[ 13/12 → 7/6 | freq:{i:8} clip:0.5 room:1 ]",
|
|
||||||
"[ 7/6 → 5/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 5/4 → 4/3 | freq:{i:10} clip:0.5 room:1 ]",
|
|
||||||
"[ 3/2 → 19/12 | freq:{i:5} clip:0.5 room:1 ]",
|
|
||||||
"[ 19/12 → 5/3 | freq:{i:7} clip:0.5 room:1 ]",
|
|
||||||
"[ 5/3 → 7/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 7/4 → 11/6 | freq:{i:11} clip:0.5 room:1 ]",
|
|
||||||
"[ 2/1 → 25/12 | freq:{i:4} clip:0.5 room:1 ]",
|
|
||||||
"[ 25/12 → 13/6 | freq:{i:8} clip:0.5 room:1 ]",
|
|
||||||
"[ 13/6 → 9/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 9/4 → 7/3 | freq:{i:10} clip:0.5 room:1 ]",
|
|
||||||
"[ 5/2 → 31/12 | freq:{i:5} clip:0.5 room:1 ]",
|
|
||||||
"[ 31/12 → 8/3 | freq:{i:7} clip:0.5 room:1 ]",
|
|
||||||
"[ 8/3 → 11/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 11/4 → 17/6 | freq:{i:11} clip:0.5 room:1 ]",
|
|
||||||
"[ 3/1 → 37/12 | freq:{i:4} clip:0.5 room:1 ]",
|
|
||||||
"[ 37/12 → 19/6 | freq:{i:8} clip:0.5 room:1 ]",
|
|
||||||
"[ 19/6 → 13/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 13/4 → 10/3 | freq:{i:10} clip:0.5 room:1 ]",
|
|
||||||
"[ 7/2 → 43/12 | freq:{i:5} clip:0.5 room:1 ]",
|
|
||||||
"[ 43/12 → 11/3 | freq:{i:7} clip:0.5 room:1 ]",
|
|
||||||
"[ 11/3 → 15/4 | freq:{i:9} clip:0.5 room:1 ]",
|
|
||||||
"[ 15/4 → 23/6 | freq:{i:11} clip:0.5 room:1 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "tune" example index 2 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/5 | freq:{i:0} ]",
|
|
||||||
"[ 1/5 → 2/5 | freq:{i:1} ]",
|
|
||||||
"[ 2/5 → 3/5 | freq:{i:2} ]",
|
|
||||||
"[ 3/5 → 4/5 | freq:{i:3} ]",
|
|
||||||
"[ 4/5 → 1/1 | freq:{i:4} ]",
|
|
||||||
"[ 1/1 → 6/5 | freq:{i:0} ]",
|
|
||||||
"[ 6/5 → 7/5 | freq:{i:1} ]",
|
|
||||||
"[ 7/5 → 8/5 | freq:{i:2} ]",
|
|
||||||
"[ 8/5 → 9/5 | freq:{i:3} ]",
|
|
||||||
"[ 9/5 → 2/1 | freq:{i:4} ]",
|
|
||||||
"[ 2/1 → 11/5 | freq:{i:0} ]",
|
|
||||||
"[ 11/5 → 12/5 | freq:{i:1} ]",
|
|
||||||
"[ 12/5 → 13/5 | freq:{i:2} ]",
|
|
||||||
"[ 13/5 → 14/5 | freq:{i:3} ]",
|
|
||||||
"[ 14/5 → 3/1 | freq:{i:4} ]",
|
|
||||||
"[ 3/1 → 16/5 | freq:{i:0} ]",
|
|
||||||
"[ 16/5 → 17/5 | freq:{i:1} ]",
|
|
||||||
"[ 17/5 → 18/5 | freq:{i:2} ]",
|
|
||||||
"[ 18/5 → 19/5 | freq:{i:3} ]",
|
|
||||||
"[ 19/5 → 4/1 | freq:{i:4} ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "undegrade" example index 0 1`] = `
|
exports[`runs examples > example "undegrade" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | s:hh ]",
|
"[ 0/1 → 1/8 | s:hh ]",
|
||||||
@@ -14188,76 +13988,6 @@ exports[`runs examples > example "when" example index 0 1`] = `
|
|||||||
|
|
||||||
exports[`runs examples > example "whenKey" example index 0 1`] = `[]`;
|
exports[`runs examples > example "whenKey" example index 0 1`] = `[]`;
|
||||||
|
|
||||||
exports[`runs examples > example "withBase" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/12 | freq:220 ]",
|
|
||||||
"[ 1/12 → 1/6 | freq:293.3333333 ]",
|
|
||||||
"[ 1/6 → 1/4 | freq:302.5 ]",
|
|
||||||
"[ 1/4 → 1/3 | freq:320 ]",
|
|
||||||
"[ 1/3 → 1/2 | freq:320 ]",
|
|
||||||
"[ 1/2 → 2/3 | freq:330 ]",
|
|
||||||
"[ 2/3 → 3/4 | freq:330 ]",
|
|
||||||
"[ 3/4 → 5/6 | freq:320 ]",
|
|
||||||
"[ 5/6 → 11/12 | freq:302.5 ]",
|
|
||||||
"[ 11/12 → 1/1 | freq:293.3333333 ]",
|
|
||||||
"[ 1/1 → 13/12 | freq:300 ]",
|
|
||||||
"[ 13/12 → 7/6 | freq:399.99999995454544 ]",
|
|
||||||
"[ 7/6 → 5/4 | freq:412.5 ]",
|
|
||||||
"[ 5/4 → 4/3 | freq:436.3636363636364 ]",
|
|
||||||
"[ 4/3 → 3/2 | freq:436.3636363636364 ]",
|
|
||||||
"[ 3/2 → 5/3 | freq:300 ]",
|
|
||||||
"[ 5/3 → 7/4 | freq:300 ]",
|
|
||||||
"[ 7/4 → 11/6 | freq:290.90909090909093 ]",
|
|
||||||
"[ 11/6 → 23/12 | freq:275 ]",
|
|
||||||
"[ 23/12 → 2/1 | freq:266.66666663636363 ]",
|
|
||||||
"[ 2/1 → 25/12 | freq:220 ]",
|
|
||||||
"[ 25/12 → 13/6 | freq:293.3333333 ]",
|
|
||||||
"[ 13/6 → 9/4 | freq:302.5 ]",
|
|
||||||
"[ 9/4 → 7/3 | freq:320 ]",
|
|
||||||
"[ 7/3 → 5/2 | freq:320 ]",
|
|
||||||
"[ 5/2 → 8/3 | freq:330 ]",
|
|
||||||
"[ 8/3 → 11/4 | freq:330 ]",
|
|
||||||
"[ 11/4 → 17/6 | freq:320 ]",
|
|
||||||
"[ 17/6 → 35/12 | freq:302.5 ]",
|
|
||||||
"[ 35/12 → 3/1 | freq:293.3333333 ]",
|
|
||||||
"[ 3/1 → 37/12 | freq:300 ]",
|
|
||||||
"[ 37/12 → 19/6 | freq:399.99999995454544 ]",
|
|
||||||
"[ 19/6 → 13/4 | freq:412.5 ]",
|
|
||||||
"[ 13/4 → 10/3 | freq:436.3636363636364 ]",
|
|
||||||
"[ 10/3 → 7/2 | freq:436.3636363636364 ]",
|
|
||||||
"[ 7/2 → 11/3 | freq:300 ]",
|
|
||||||
"[ 11/3 → 15/4 | freq:300 ]",
|
|
||||||
"[ 15/4 → 23/6 | freq:290.90909090909093 ]",
|
|
||||||
"[ 23/6 → 47/12 | freq:275 ]",
|
|
||||||
"[ 47/12 → 4/1 | freq:266.66666663636363 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "withBase" example index 1 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/5 | freq:220 ]",
|
|
||||||
"[ 1/5 → 2/5 | freq:234.66666666666666 ]",
|
|
||||||
"[ 2/5 → 3/5 | freq:247.5 ]",
|
|
||||||
"[ 3/5 → 4/5 | freq:264 ]",
|
|
||||||
"[ 4/5 → 1/1 | freq:275 ]",
|
|
||||||
"[ 1/1 → 6/5 | freq:220 ]",
|
|
||||||
"[ 6/5 → 7/5 | freq:234.66666666666666 ]",
|
|
||||||
"[ 7/5 → 8/5 | freq:247.5 ]",
|
|
||||||
"[ 8/5 → 9/5 | freq:264 ]",
|
|
||||||
"[ 9/5 → 2/1 | freq:275 ]",
|
|
||||||
"[ 2/1 → 11/5 | freq:220 ]",
|
|
||||||
"[ 11/5 → 12/5 | freq:234.66666666666666 ]",
|
|
||||||
"[ 12/5 → 13/5 | freq:247.5 ]",
|
|
||||||
"[ 13/5 → 14/5 | freq:264 ]",
|
|
||||||
"[ 14/5 → 3/1 | freq:275 ]",
|
|
||||||
"[ 3/1 → 16/5 | freq:220 ]",
|
|
||||||
"[ 16/5 → 17/5 | freq:234.66666666666666 ]",
|
|
||||||
"[ 17/5 → 18/5 | freq:247.5 ]",
|
|
||||||
"[ 18/5 → 19/5 | freq:264 ]",
|
|
||||||
"[ 19/5 → 4/1 | freq:275 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "withValue" example index 0 1`] = `
|
exports[`runs examples > example "withValue" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/3 | 10 ]",
|
"[ 0/1 → 1/3 | 10 ]",
|
||||||
@@ -14397,106 +14127,6 @@ exports[`runs examples > example "wtphaserand" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "xen" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 1/3 → 2/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 2/3 → 1/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
"[ 1/1 → 4/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 4/3 → 5/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 5/3 → 2/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
"[ 2/1 → 7/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 7/3 → 8/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 8/3 → 3/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
"[ 3/1 → 10/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 10/3 → 11/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 11/3 → 4/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "xen" example index 1 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 1/3 → 2/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 2/3 → 1/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
"[ 1/1 → 4/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 4/3 → 5/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 5/3 → 2/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
"[ 2/1 → 7/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 7/3 → 8/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 8/3 → 3/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
"[ 3/1 → 10/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]",
|
|
||||||
"[ 10/3 → 11/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]",
|
|
||||||
"[ 11/3 → 4/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "xen" example index 2 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/6 | freq:220 ]",
|
|
||||||
"[ 1/6 → 1/3 | freq:275 ]",
|
|
||||||
"[ 1/3 → 1/2 | freq:293.3333333 ]",
|
|
||||||
"[ 1/2 → 2/3 | freq:330 ]",
|
|
||||||
"[ 2/3 → 5/6 | freq:352 ]",
|
|
||||||
"[ 5/6 → 1/1 | freq:440 ]",
|
|
||||||
"[ 1/1 → 7/6 | freq:220 ]",
|
|
||||||
"[ 7/6 → 4/3 | freq:275 ]",
|
|
||||||
"[ 4/3 → 3/2 | freq:293.3333333 ]",
|
|
||||||
"[ 3/2 → 5/3 | freq:330 ]",
|
|
||||||
"[ 5/3 → 11/6 | freq:352 ]",
|
|
||||||
"[ 11/6 → 2/1 | freq:440 ]",
|
|
||||||
"[ 2/1 → 13/6 | freq:220 ]",
|
|
||||||
"[ 13/6 → 7/3 | freq:275 ]",
|
|
||||||
"[ 7/3 → 5/2 | freq:293.3333333 ]",
|
|
||||||
"[ 5/2 → 8/3 | freq:330 ]",
|
|
||||||
"[ 8/3 → 17/6 | freq:352 ]",
|
|
||||||
"[ 17/6 → 3/1 | freq:440 ]",
|
|
||||||
"[ 3/1 → 19/6 | freq:220 ]",
|
|
||||||
"[ 19/6 → 10/3 | freq:275 ]",
|
|
||||||
"[ 10/3 → 7/2 | freq:293.3333333 ]",
|
|
||||||
"[ 7/2 → 11/3 | freq:330 ]",
|
|
||||||
"[ 11/3 → 23/6 | freq:352 ]",
|
|
||||||
"[ 23/6 → 4/1 | freq:440 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "xen" example index 3 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/8 | freq:220 ]",
|
|
||||||
"[ 1/8 → 1/4 | freq:252.7136381 ]",
|
|
||||||
"[ 1/4 → 3/8 | freq:290.2917404 ]",
|
|
||||||
"[ 3/8 → 1/2 | freq:333.4576446 ]",
|
|
||||||
"[ 1/2 → 5/8 | freq:383.0422479 ]",
|
|
||||||
"[ 5/8 → 3/4 | freq:440 ]",
|
|
||||||
"[ 3/4 → 7/8 | freq:505.4272762 ]",
|
|
||||||
"[ 7/8 → 1/1 | freq:580.5834807 ]",
|
|
||||||
"[ 1/1 → 9/8 | freq:220 ]",
|
|
||||||
"[ 9/8 → 5/4 | freq:235.7901618 ]",
|
|
||||||
"[ 5/4 → 11/8 | freq:252.7136381 ]",
|
|
||||||
"[ 11/8 → 3/2 | freq:270.8517709 ]",
|
|
||||||
"[ 3/2 → 13/8 | freq:290.2917404 ]",
|
|
||||||
"[ 13/8 → 7/4 | freq:311.1269837 ]",
|
|
||||||
"[ 7/4 → 15/8 | freq:333.4576446 ]",
|
|
||||||
"[ 15/8 → 2/1 | freq:357.3910544 ]",
|
|
||||||
"[ 2/1 → 17/8 | freq:220 ]",
|
|
||||||
"[ 17/8 → 9/4 | freq:230.404707 ]",
|
|
||||||
"[ 9/4 → 19/8 | freq:241.3014955 ]",
|
|
||||||
"[ 19/8 → 5/2 | freq:252.7136381 ]",
|
|
||||||
"[ 5/2 → 21/8 | freq:264.6655079 ]",
|
|
||||||
"[ 21/8 → 11/4 | freq:277.182631 ]",
|
|
||||||
"[ 11/4 → 23/8 | freq:290.2917404 ]",
|
|
||||||
"[ 23/8 → 3/1 | freq:304.0208336 ]",
|
|
||||||
"[ 3/1 → 25/8 | freq:220 ]",
|
|
||||||
"[ 25/8 → 13/4 | freq:275 ]",
|
|
||||||
"[ 13/4 → 27/8 | freq:293.3333333 ]",
|
|
||||||
"[ 27/8 → 7/2 | freq:330 ]",
|
|
||||||
"[ 7/2 → 29/8 | freq:352 ]",
|
|
||||||
"[ 29/8 → 15/4 | freq:440 ]",
|
|
||||||
"[ 15/4 → 31/8 | freq:550 ]",
|
|
||||||
"[ 31/8 → 4/1 | freq:586.6666667 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "xfade" example index 0 1`] = `
|
exports[`runs examples > example "xfade" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | s:hh gain:0 ]",
|
"[ 0/1 → 1/8 | s:hh gain:0 ]",
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export const SIDEBAR: Sidebar = {
|
|||||||
Understand: [
|
Understand: [
|
||||||
{ text: 'Coding syntax', link: 'learn/code' },
|
{ text: 'Coding syntax', link: 'learn/code' },
|
||||||
{ text: 'Pitch', link: 'understand/pitch' },
|
{ text: 'Pitch', link: 'understand/pitch' },
|
||||||
{ text: 'Xenharmonic Functions', link: 'learn/xen' },
|
{ text: 'Xen Harmonic Functions', link: 'learn/xen' },
|
||||||
{ text: 'Cycles', link: 'understand/cycles' },
|
{ text: 'Cycles', link: 'understand/cycles' },
|
||||||
{ text: 'Voicings', link: 'understand/voicings' },
|
{ text: 'Voicings', link: 'understand/voicings' },
|
||||||
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
|
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Xenharmonic Functions
|
title: Xen Harmonic Functions
|
||||||
layout: ../../layouts/MainLayout.astro
|
layout: ../../layouts/MainLayout.astro
|
||||||
---
|
---
|
||||||
|
|
||||||
import { MiniRepl } from '../../docs/MiniRepl';
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
import { JsDoc } from '../../docs/JsDoc';
|
import { JsDoc } from '../../docs/JsDoc';
|
||||||
|
|
||||||
# Xenharmonic Functions (experimental)
|
# Xen Harmonic Functions
|
||||||
|
|
||||||
{/* TODO expand explanation of xenharmony */}
|
|
||||||
|
|
||||||
These functions allow the use of scales other than your typical chromatic 12 based ones.
|
These functions allow the use of scales other than your typical chromatic 12 based ones.
|
||||||
|
|
||||||
### tune(scale)
|
### tune(scale)
|
||||||
|
|
||||||
{/* 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:
|
Here's an example of how to configure a basic hexany scale:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`i("0 1 2 3 4 5").tune("hexany15").mul("220").freq()`} />
|
<MiniRepl client:idle tune={`"0 1 2 3 4 5".tune("hexany15").mul("220").freq()`} />
|
||||||
|
|
||||||
Try other scales like `hexany1`, `iraq`, `gumbeng`, `gunkali`, or `tranh3`
|
Try other scales like `hexany1`, `iraq`, `gumbeng`, `gunkali`, or `tranh3`
|
||||||
|
|
||||||
@@ -30,7 +26,7 @@ You can set your root to be a particular note with `getFreq`
|
|||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`i("4 8 9 10 - - 5 7 9 11 - -").tune("tranh3")
|
tune={`"4 8 9 10 - - 5 7 9 11 - -".tune("tranh3")
|
||||||
.mul(getFreq('c3'))
|
.mul(getFreq('c3'))
|
||||||
.freq().clip(.5).room(1)`}
|
.freq().clip(.5).room(1)`}
|
||||||
/>
|
/>
|
||||||
@@ -39,7 +35,7 @@ Some tunings become more pronounced with a longer reverb decay:
|
|||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`i("<[5 6 8 10] - [5 7 9 12] -> -").tune("gumbeng")
|
tune={`"<[5 6 8 10] - [5 7 9 12] -> -".tune("gumbeng")
|
||||||
.mul(getFreq('c3'))
|
.mul(getFreq('c3'))
|
||||||
.freq().clip(.8).room("3:10").rdim(10000).rfade(5)`}
|
.freq().clip(.8).room("3:10").rdim(10000).rfade(5)`}
|
||||||
/>
|
/>
|
||||||
@@ -48,7 +44,7 @@ Additionally, you can combo this with `fmap` so that the base note changes:
|
|||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`i("9 11 12 10 - - -").tune("gunkali")
|
tune={`"9 11 12 10 - - -".tune("gunkali")
|
||||||
.mul("<c3 c3 a3 d#3>".fmap(getFreq))
|
.mul("<c3 c3 a3 d#3>".fmap(getFreq))
|
||||||
.freq().legato("2 .7").room("1:15").rdim(8500).rlp(14000).rfade(8)`}
|
.freq().legato("2 .7").room("1:15").rdim(8500).rlp(14000).rfade(8)`}
|
||||||
/>
|
/>
|
||||||
@@ -57,7 +53,8 @@ Combining this with various polyrhythm tricks can become very evocative:
|
|||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`i("<[0 3 1 -] [-1 4 2 8]> ~ ~,<-4 -5>".add(4))
|
tune={`"<[0 3 1 -] [-1 4 2 8]> ~ ~,<-4 -5>"
|
||||||
|
.transpose(4)
|
||||||
.tune("iraq")
|
.tune("iraq")
|
||||||
.mul("<c3 d3 c#3>".fmap(getFreq))
|
.mul("<c3 d3 c#3>".fmap(getFreq))
|
||||||
.freq().clip(.5).room(1).rfade(9)`}
|
.freq().clip(.5).room(1).rfade(9)`}
|
||||||
@@ -70,7 +67,7 @@ Take the `sanza` tuning:
|
|||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`i("4 5 6 7 8 9").tune("sanza")
|
tune={`"4 5 6 7 8 9".tune("sanza")
|
||||||
.mul(getFreq('c3'))
|
.mul(getFreq('c3'))
|
||||||
.freq()`}
|
.freq()`}
|
||||||
/>
|
/>
|
||||||
@@ -78,15 +75,15 @@ Take the `sanza` tuning:
|
|||||||
Notes 7 and 9 will clash quite a bit if you arp them normally. Many tunings will have this sort of sound, and it can feel distracting on its own.
|
Notes 7 and 9 will clash quite a bit if you arp them normally. Many tunings will have this sort of sound, and it can feel distracting on its own.
|
||||||
See how close they are on the pitch wheel?
|
See how close they are on the pitch wheel?
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`i("[7 9]!3").tune("sanza").mul(getFreq('c3')).freq()._pitchwheel()`} />
|
<MiniRepl client:idle tune={`"[7 9]!3".tune("sanza").mul(getFreq('c3')).freq()._pitchwheel()`} />
|
||||||
|
|
||||||
This quality is often due to how the tunings were formed with instruments that were played differently than a piano.
|
This quality is often due to how the tunings were formed with instruments that were played differently than a piano.
|
||||||
As such, some tunings are much better strummed, with the subtle clash of the detuned notes actually making the sound much more magical:
|
As such, some tunings are much better strummed, with the subtle clash of the detuned notes actually making the sound much more magical:
|
||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`i("[0 1 2 3 4 5 6]@0.3 -"
|
tune={`"[0 1 2 3 4 5 6]@0.3 -"
|
||||||
.add("<2 5 8 1>"))
|
.transpose("<2 5 8 1>")
|
||||||
.tune("sanza")
|
.tune("sanza")
|
||||||
.mul(getFreq('c3')).freq()
|
.mul(getFreq('c3')).freq()
|
||||||
.legato("3").room(1).rfade(5)`}
|
.legato("3").room(1).rfade(5)`}
|
||||||
@@ -96,23 +93,3 @@ Note the legato and reverb effects make sure the sound of the strumming gets to
|
|||||||
tones sound even more alive, too.
|
tones sound even more alive, too.
|
||||||
|
|
||||||
The `tranh3` tuning has a similar set of notes, with two clashing. You might trying plugging that in above and see if you find a favorite strumming pattern.
|
The `tranh3` tuning has a similar set of notes, with two clashing. You might trying plugging that in above and see if you find a favorite strumming pattern.
|
||||||
|
|
||||||
You can also give tune a list of frequencies to use as the scale:
|
|
||||||
|
|
||||||
<MiniRepl
|
|
||||||
client:idle
|
|
||||||
tune={`i("0 1 2 3 4").tune([
|
|
||||||
261.6255653006,
|
|
||||||
302.72962012827,
|
|
||||||
350.29154279212,
|
|
||||||
405.32593044476,
|
|
||||||
469.00678383895,
|
|
||||||
523.2511306012
|
|
||||||
]).mul(220).freq();`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
### xen(scaleOrRatios)
|
|
||||||
|
|
||||||
{/* TODO add explanation of EDO to documentation */}
|
|
||||||
|
|
||||||
<JsDoc client:idle name="Pattern.xen" h={0} />
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
userPattern,
|
userPattern,
|
||||||
} from '../../../user_pattern_utils.mjs';
|
} from '../../../user_pattern_utils.mjs';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
|
import { getMetadata } from '../../../metadata_parser.js';
|
||||||
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
||||||
|
import { parseJSON, isUdels } from '../../util.mjs';
|
||||||
import { useSettings } from '../../../settings.mjs';
|
import { useSettings } from '../../../settings.mjs';
|
||||||
import { ActionButton } from '../button/action-button.jsx';
|
import { ActionButton } from '../button/action-button.jsx';
|
||||||
import { Pagination } from '../pagination/Pagination.jsx';
|
import { Pagination } from '../pagination/Pagination.jsx';
|
||||||
@@ -18,13 +20,8 @@ import { useDebounce } from '../usedebounce.jsx';
|
|||||||
import cx from '@src/cx.mjs';
|
import cx from '@src/cx.mjs';
|
||||||
import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx';
|
import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx';
|
||||||
|
|
||||||
const PATTERN_SORT = {
|
|
||||||
"NEWEST": "most recent",
|
|
||||||
"A-Z": "A-Z",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
||||||
const meta = pattern.meta
|
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
||||||
|
|
||||||
let title = meta.title;
|
let title = meta.title;
|
||||||
if (title == null) {
|
if (title == null) {
|
||||||
@@ -61,10 +58,6 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
|
|||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
{Object.values(patterns)
|
{Object.values(patterns)
|
||||||
.sort((a, b) => {
|
|
||||||
return (b.meta.title ?? "").localeCompare(a.meta.title ?? "")
|
|
||||||
|
|
||||||
})
|
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((pattern) => {
|
.map((pattern) => {
|
||||||
const id = pattern.id;
|
const id = pattern.id;
|
||||||
@@ -100,7 +93,7 @@ export function PatternsTab({ context }) {
|
|||||||
}
|
}
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(userPatterns).filter(([_key, pattern]) => {
|
Object.entries(userPatterns).filter(([_key, pattern]) => {
|
||||||
const meta = pattern.meta;
|
const meta = getMetadata(pattern.code);
|
||||||
|
|
||||||
// Search for specific meta keys
|
// Search for specific meta keys
|
||||||
const searchLowercaseTrimmed = search.trim().toLowerCase();
|
const searchLowercaseTrimmed = search.trim().toLowerCase();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const availableFunctions = (() => {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
const tagCounts = {};
|
const tagCounts = {};
|
||||||
const ignoredTags = ['supradough', 'superdirt'];
|
const ignoredTags = ['supradough', 'superdirt', 'dough'];
|
||||||
// const tagOptions = { all: `all (${availableFunctions.length})` };
|
// const tagOptions = { all: `all (${availableFunctions.length})` };
|
||||||
const tagOptions = { all: `all` };
|
const tagOptions = { all: `all` };
|
||||||
for (const doc of availableFunctions) {
|
for (const doc of availableFunctions) {
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ export function SettingsTab({ started }) {
|
|||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
value={keybindings}
|
value={keybindings}
|
||||||
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
||||||
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs', helix: 'Helix', vscode: 'VSCode' }}
|
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs', vscode: 'VSCode' }}
|
||||||
></ButtonGroup>
|
></ButtonGroup>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="Panel Position">
|
<FormItem label="Panel Position">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { extractMidiNoteFromString, registerSampleSource } from '@strudel/webaudio';
|
import { registerSampleSource } from '@strudel/webaudio';
|
||||||
import { isAudioFile } from './files.mjs';
|
import { isAudioFile } from './files.mjs';
|
||||||
import { logger } from '@strudel/core';
|
import { logger } from '@strudel/core';
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
|
|||||||
Promise.all(
|
Promise.all(
|
||||||
[...soundFiles]
|
[...soundFiles]
|
||||||
.sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' }))
|
.sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' }))
|
||||||
.map((soundFile) => {
|
.map((soundFile, i) => {
|
||||||
const title = soundFile.title;
|
const title = soundFile.title;
|
||||||
if (!isAudioFile(title)) {
|
if (!isAudioFile(title)) {
|
||||||
return;
|
return;
|
||||||
@@ -58,26 +58,25 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
|
|||||||
splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user';
|
splitRelativePath[splitRelativePath.length - 2] ?? soundFile.id.split(/\W+/)[0] ?? 'user';
|
||||||
const blob = soundFile.blob;
|
const blob = soundFile.blob;
|
||||||
|
|
||||||
return blobToDataUrl(blob).then((path) => {
|
return blobToDataUrl(blob).then((soundPath) => {
|
||||||
const sampleInfoMap = sounds.get(parentDirectory) ?? new Map();
|
const titlePathMap = sounds.get(parentDirectory) ?? new Map();
|
||||||
const midi = extractMidiNoteFromString(title);
|
|
||||||
/** @type {import('@strudel/webaudio').SampleMetaData} */
|
|
||||||
const samplemetadata = { url: path, midi };
|
|
||||||
sampleInfoMap.set(title, samplemetadata);
|
|
||||||
|
|
||||||
sounds.set(parentDirectory, sampleInfoMap);
|
titlePathMap.set(title, soundPath);
|
||||||
|
|
||||||
|
sounds.set(parentDirectory, titlePathMap);
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
sounds.forEach((sampleInfoMap, key) => {
|
sounds.forEach((titlePathMap, key) => {
|
||||||
const bank = Array.from(sampleInfoMap.keys())
|
const value = Array.from(titlePathMap.keys())
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return a.localeCompare(b);
|
return a.localeCompare(b);
|
||||||
})
|
})
|
||||||
.map((title) => sampleInfoMap.get(title));
|
.map((title) => titlePathMap.get(title));
|
||||||
registerSampleSource(key, bank, { prebake: false });
|
|
||||||
|
registerSampleSource(key, value, { prebake: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
logger('imported sounds registered!', 'success');
|
logger('imported sounds registered!', 'success');
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export function useReplContext() {
|
|||||||
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
|
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
|
||||||
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
|
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
|
||||||
const init = useCallback(() => {
|
const init = useCallback(() => {
|
||||||
setActivePattern(getViewingPatternData().id);
|
|
||||||
const drawTime = [-2, 2];
|
const drawTime = [-2, 2];
|
||||||
const drawContext = getDrawContext();
|
const drawContext = getDrawContext();
|
||||||
const editor = new StrudelMirror({
|
const editor = new StrudelMirror({
|
||||||
@@ -110,12 +109,7 @@ export function useReplContext() {
|
|||||||
// Get the full buffer content from the editor instead of just the evaluated block
|
// Get the full buffer content from the editor instead of just the evaluated block
|
||||||
const fullBufferCode = editorRef.current?.code || code;
|
const fullBufferCode = editorRef.current?.code || code;
|
||||||
setLatestCode(fullBufferCode);
|
setLatestCode(fullBufferCode);
|
||||||
|
window.location.hash = '#' + code2hash(fullBufferCode);
|
||||||
try {
|
|
||||||
window.location.hash = '#' + code2hash(fullBufferCode);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('[useReplContext] Failed to update hash:', e.message);
|
|
||||||
}
|
|
||||||
setDocumentTitle(fullBufferCode);
|
setDocumentTitle(fullBufferCode);
|
||||||
const viewingPatternData = getViewingPatternData();
|
const viewingPatternData = getViewingPatternData();
|
||||||
setVersionDefaultsFrom(fullBufferCode);
|
setVersionDefaultsFrom(fullBufferCode);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { register } from '@strudel/core';
|
import { register } from '@strudel/core';
|
||||||
import { isUdels } from './repl/util.mjs';
|
import { isUdels } from './repl/util.mjs';
|
||||||
import { computed } from 'nanostores';
|
import { computed } from 'nanostores';
|
||||||
import { getMetadata } from './metadata_parser';
|
|
||||||
|
|
||||||
export const audioEngineTargets = {
|
export const audioEngineTargets = {
|
||||||
webaudio: 'webaudio',
|
webaudio: 'webaudio',
|
||||||
@@ -19,11 +18,6 @@ export const soundFilterType = {
|
|||||||
ALL: 'all',
|
ALL: 'all',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PATTERN_SORT = {
|
|
||||||
"NEWEST": "most recent",
|
|
||||||
"A-Z": "A-Z",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultSettings = {
|
export const defaultSettings = {
|
||||||
activeFooter: 'intro',
|
activeFooter: 'intro',
|
||||||
keybindings: 'codemirror',
|
keybindings: 'codemirror',
|
||||||
@@ -77,7 +71,6 @@ export const $settings = computed(settingsMap, (state) => {
|
|||||||
Object.keys(userPatterns).forEach((key) => {
|
Object.keys(userPatterns).forEach((key) => {
|
||||||
const data = userPatterns[key];
|
const data = userPatterns[key];
|
||||||
data.id = data.id ?? key;
|
data.id = data.id ?? key;
|
||||||
data.meta = getMetadata(data.code)
|
|
||||||
userPatterns[key] = data;
|
userPatterns[key] = data;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -207,10 +207,9 @@ export async function exportPatterns() {
|
|||||||
const userPatterns = userPattern.getAll();
|
const userPatterns = userPattern.getAll();
|
||||||
const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' });
|
const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' });
|
||||||
const downloadLink = document.createElement('a');
|
const downloadLink = document.createElement('a');
|
||||||
const prefix = window.location.hostname.split('.').join('_');
|
|
||||||
downloadLink.href = window.URL.createObjectURL(blob);
|
downloadLink.href = window.URL.createObjectURL(blob);
|
||||||
const date = new Date().toISOString().split('T')[0];
|
const date = new Date().toISOString().split('T')[0];
|
||||||
downloadLink.download = `${prefix}_patterns_${date}.json`;
|
downloadLink.download = `strudel_patterns_${date}.json`;
|
||||||
document.body.appendChild(downloadLink);
|
document.body.appendChild(downloadLink);
|
||||||
downloadLink.click();
|
downloadLink.click();
|
||||||
document.body.removeChild(downloadLink);
|
document.body.removeChild(downloadLink);
|
||||||
|
|||||||
Reference in New Issue
Block a user