mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-21 18:48:36 +00:00
Merge branch 'main' into audio_device_selection
This commit is contained in:
commit
355ac2ea29
88
packages/codemirror/Autocomplete.jsx
Normal file
88
packages/codemirror/Autocomplete.jsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import jsdoc from '../../doc.json';
|
||||||
|
// import { javascriptLanguage } from '@codemirror/lang-javascript';
|
||||||
|
import { autocompletion } from '@codemirror/autocomplete';
|
||||||
|
|
||||||
|
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||||
|
const getInnerText = (html) => {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = html;
|
||||||
|
return div.textContent || div.innerText || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Autocomplete({ doc }) {
|
||||||
|
return (
|
||||||
|
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
|
||||||
|
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
|
||||||
|
<ul>
|
||||||
|
{doc.params?.map(({ name, type, description }, i) => (
|
||||||
|
<li key={i}>
|
||||||
|
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div>
|
||||||
|
{doc.examples?.map((example, i) => (
|
||||||
|
<div key={i}>
|
||||||
|
<pre
|
||||||
|
className="cursor-pointer"
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
console.log('ola!');
|
||||||
|
navigator.clipboard.writeText(example);
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{example}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsdocCompletions = jsdoc.docs
|
||||||
|
.filter(
|
||||||
|
(doc) =>
|
||||||
|
getDocLabel(doc) &&
|
||||||
|
!getDocLabel(doc).startsWith('_') &&
|
||||||
|
!['package'].includes(doc.kind) &&
|
||||||
|
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
|
||||||
|
)
|
||||||
|
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||||
|
.map((doc) /*: Completion */ => ({
|
||||||
|
label: getDocLabel(doc),
|
||||||
|
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
||||||
|
info: () => {
|
||||||
|
const node = document.createElement('div');
|
||||||
|
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
||||||
|
// .. using renderToStaticMarkup
|
||||||
|
createRoot(node).render(<Autocomplete doc={doc} />);
|
||||||
|
return node;
|
||||||
|
},
|
||||||
|
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
||||||
|
let word = context.matchBefore(/\w*/);
|
||||||
|
if (word.from == word.to && !context.explicit) return null;
|
||||||
|
return {
|
||||||
|
from: word.from,
|
||||||
|
options: jsdocCompletions,
|
||||||
|
/* options: [
|
||||||
|
{ label: 'match', type: 'keyword' },
|
||||||
|
{ label: 'hello', type: 'variable', info: '(World)' },
|
||||||
|
{ label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
|
||||||
|
], */
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isAutoCompletionEnabled(on) {
|
||||||
|
return on
|
||||||
|
? [
|
||||||
|
autocompletion({ override: [strudelAutocomplete] }),
|
||||||
|
//javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
|
||||||
|
]
|
||||||
|
: []; // autocompletion({ override: [] })
|
||||||
|
}
|
||||||
@ -1,37 +1,80 @@
|
|||||||
import { defaultKeymap } from '@codemirror/commands';
|
import { closeBrackets } from '@codemirror/autocomplete';
|
||||||
|
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
||||||
|
import { history } from '@codemirror/commands';
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||||
import { EditorState } from '@codemirror/state';
|
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
||||||
import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view';
|
import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view';
|
||||||
import { Drawer, repl } from '@strudel.cycles/core';
|
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
|
||||||
import { flashField, flash } from './flash.mjs';
|
// import { isAutoCompletionEnabled } from './Autocomplete';
|
||||||
import { highlightExtension, highlightMiniLocations } from './highlight.mjs';
|
import { flash, isFlashEnabled } from './flash.mjs';
|
||||||
import { oneDark } from './themes/one-dark';
|
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||||
|
import { keybindings } from './keybindings.mjs';
|
||||||
|
import { theme } from './themes.mjs';
|
||||||
|
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
||||||
|
|
||||||
|
const extensions = {
|
||||||
|
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||||
|
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
||||||
|
theme,
|
||||||
|
// isAutoCompletionEnabled,
|
||||||
|
isPatternHighlightingEnabled,
|
||||||
|
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
|
||||||
|
isFlashEnabled,
|
||||||
|
keybindings,
|
||||||
|
};
|
||||||
|
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||||
|
|
||||||
// https://codemirror.net/docs/guide/
|
// https://codemirror.net/docs/guide/
|
||||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) {
|
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
|
||||||
|
const initialSettings = Object.keys(compartments).map((key) =>
|
||||||
|
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||||
|
);
|
||||||
let state = EditorState.create({
|
let state = EditorState.create({
|
||||||
doc: initialCode,
|
doc: initialCode,
|
||||||
extensions: [
|
extensions: [
|
||||||
theme,
|
/* search(),
|
||||||
|
highlightSelectionMatches(), */
|
||||||
|
...initialSettings,
|
||||||
javascript(),
|
javascript(),
|
||||||
lineNumbers(),
|
sliderPlugin,
|
||||||
highlightExtension,
|
// indentOnInput(), // works without. already brought with javascript extension?
|
||||||
highlightActiveLineGutter(),
|
// bracketMatching(), // does not do anything
|
||||||
|
closeBrackets(),
|
||||||
syntaxHighlighting(defaultHighlightStyle),
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
keymap.of(defaultKeymap),
|
history(),
|
||||||
flashField,
|
|
||||||
EditorView.updateListener.of((v) => onChange(v)),
|
EditorView.updateListener.of((v) => onChange(v)),
|
||||||
keymap.of([
|
Prec.highest(
|
||||||
{
|
keymap.of([
|
||||||
key: 'Ctrl-Enter',
|
{
|
||||||
run: () => onEvaluate(),
|
key: 'Ctrl-Enter',
|
||||||
|
run: () => onEvaluate?.(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Alt-Enter',
|
||||||
|
run: () => onEvaluate?.(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Ctrl-.',
|
||||||
|
run: () => onStop?.(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Alt-.',
|
||||||
|
run: (_, e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onStop?.();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
key: 'Ctrl-Shift-.',
|
||||||
|
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Ctrl-.',
|
key: 'Ctrl-Shift-Enter',
|
||||||
run: () => onStop(),
|
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||||
},
|
}, */
|
||||||
]),
|
]),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -43,71 +86,168 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the
|
|||||||
|
|
||||||
export class StrudelMirror {
|
export class StrudelMirror {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, ...replOptions } = options;
|
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
|
||||||
this.code = initialCode;
|
this.code = initialCode;
|
||||||
|
this.root = root;
|
||||||
|
this.miniLocations = [];
|
||||||
|
this.widgets = [];
|
||||||
|
this.painters = [];
|
||||||
|
this.onDraw = onDraw;
|
||||||
|
const self = this;
|
||||||
|
|
||||||
this.drawer = new Drawer((haps, time) => {
|
this.drawer = new Drawer((haps, time) => {
|
||||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
||||||
this.highlight(currentFrame, time);
|
this.highlight(currentFrame, time);
|
||||||
onDraw?.(haps, time, currentFrame);
|
this.onDraw?.(haps, time, currentFrame, this.painters);
|
||||||
}, drawTime);
|
}, drawTime);
|
||||||
|
|
||||||
const prebaked = prebake();
|
// this approach might not work with multiple repls on screen..
|
||||||
prebaked.then(async () => {
|
Pattern.prototype.onPaint = function (onPaint) {
|
||||||
if (!onDraw) {
|
self.painters.push(onPaint);
|
||||||
return;
|
return this;
|
||||||
}
|
};
|
||||||
const { scheduler, evaluate } = await this.repl;
|
|
||||||
// draw first frame instantly
|
this.prebaked = prebake();
|
||||||
prebaked.then(async () => {
|
// this.drawFirstFrame();
|
||||||
await evaluate(this.code, false);
|
|
||||||
this.drawer.invalidate(scheduler);
|
|
||||||
onDraw?.(this.drawer.visibleHaps, 0, []);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.repl = repl({
|
this.repl = repl({
|
||||||
...replOptions,
|
...replOptions,
|
||||||
onToggle: async (started) => {
|
onToggle: (started) => {
|
||||||
replOptions?.onToggle?.(started);
|
replOptions?.onToggle?.(started);
|
||||||
const { scheduler } = await this.repl;
|
|
||||||
if (started) {
|
if (started) {
|
||||||
this.drawer.start(scheduler);
|
this.drawer.start(this.repl.scheduler);
|
||||||
} else {
|
} else {
|
||||||
this.drawer.stop();
|
this.drawer.stop();
|
||||||
|
updateMiniLocations(this.editor, []);
|
||||||
|
cleanupDraw(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeEval: async () => {
|
beforeEval: async () => {
|
||||||
await prebaked;
|
cleanupDraw();
|
||||||
|
this.painters = [];
|
||||||
|
await this.prebaked;
|
||||||
|
await replOptions?.beforeEval?.();
|
||||||
},
|
},
|
||||||
afterEval: (options) => {
|
afterEval: (options) => {
|
||||||
|
// remember for when highlighting is toggled on
|
||||||
|
this.miniLocations = options.meta?.miniLocations;
|
||||||
|
this.widgets = options.meta?.widgets;
|
||||||
|
updateWidgets(this.editor, this.widgets);
|
||||||
|
updateMiniLocations(this.editor, this.miniLocations);
|
||||||
replOptions?.afterEval?.(options);
|
replOptions?.afterEval?.(options);
|
||||||
this.drawer.invalidate();
|
this.drawer.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.editor = initEditor({
|
this.editor = initEditor({
|
||||||
root,
|
root,
|
||||||
|
settings,
|
||||||
initialCode,
|
initialCode,
|
||||||
onChange: (v) => {
|
onChange: (v) => {
|
||||||
this.code = v.state.doc.toString();
|
if (v.docChanged) {
|
||||||
|
this.code = v.state.doc.toString();
|
||||||
|
// TODO: repl is still untouched to make sure the old Repl.jsx stays untouched..
|
||||||
|
// this.repl.setCode(this.code);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onEvaluate: () => this.evaluate(),
|
onEvaluate: () => this.evaluate(),
|
||||||
onStop: () => this.stop(),
|
onStop: () => this.stop(),
|
||||||
});
|
});
|
||||||
|
const cmEditor = this.root.querySelector('.cm-editor');
|
||||||
|
if (cmEditor) {
|
||||||
|
this.root.style.backgroundColor = 'var(--background)';
|
||||||
|
cmEditor.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async drawFirstFrame() {
|
||||||
|
if (!this.onDraw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// draw first frame instantly
|
||||||
|
await this.prebaked;
|
||||||
|
try {
|
||||||
|
await this.repl.evaluate(this.code, false);
|
||||||
|
this.drawer.invalidate(this.repl.scheduler);
|
||||||
|
this.onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('first frame could not be painted');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async evaluate() {
|
async evaluate() {
|
||||||
const { evaluate } = await this.repl;
|
|
||||||
this.flash();
|
this.flash();
|
||||||
await evaluate(this.code);
|
await this.repl.evaluate(this.code);
|
||||||
}
|
}
|
||||||
async stop() {
|
async stop() {
|
||||||
const { scheduler } = await this.repl;
|
this.repl.scheduler.stop();
|
||||||
scheduler.stop();
|
}
|
||||||
|
async toggle() {
|
||||||
|
if (this.repl.scheduler.started) {
|
||||||
|
this.repl.scheduler.stop();
|
||||||
|
} else {
|
||||||
|
this.evaluate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
flash(ms) {
|
flash(ms) {
|
||||||
flash(this.editor, ms);
|
flash(this.editor, ms);
|
||||||
}
|
}
|
||||||
highlight(haps, time) {
|
highlight(haps, time) {
|
||||||
highlightMiniLocations(this.editor.view, time, haps);
|
highlightMiniLocations(this.editor, time, haps);
|
||||||
|
}
|
||||||
|
setFontSize(size) {
|
||||||
|
this.root.style.fontSize = size + 'px';
|
||||||
|
}
|
||||||
|
setFontFamily(family) {
|
||||||
|
this.root.style.fontFamily = family;
|
||||||
|
const scroller = this.root.querySelector('.cm-scroller');
|
||||||
|
if (scroller) {
|
||||||
|
scroller.style.fontFamily = family;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reconfigureExtension(key, value) {
|
||||||
|
if (!extensions[key]) {
|
||||||
|
console.warn(`extension ${key} is not known`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = parseBooleans(value);
|
||||||
|
const newValue = extensions[key](value, this);
|
||||||
|
this.editor.dispatch({
|
||||||
|
effects: compartments[key].reconfigure(newValue),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setLineWrappingEnabled(enabled) {
|
||||||
|
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||||
|
}
|
||||||
|
setLineNumbersDisplayed(enabled) {
|
||||||
|
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
|
||||||
|
}
|
||||||
|
setTheme(theme) {
|
||||||
|
this.reconfigureExtension('theme', theme);
|
||||||
|
}
|
||||||
|
setAutocompletionEnabled(enabled) {
|
||||||
|
this.reconfigureExtension('isAutoCompletionEnabled', enabled);
|
||||||
|
}
|
||||||
|
updateSettings(settings) {
|
||||||
|
this.setFontSize(settings.fontSize);
|
||||||
|
this.setFontFamily(settings.fontFamily);
|
||||||
|
for (let key in extensions) {
|
||||||
|
this.reconfigureExtension(key, settings[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeSetting(key, value) {
|
||||||
|
if (extensions[key]) {
|
||||||
|
this.reconfigureExtension(key, value);
|
||||||
|
return;
|
||||||
|
} else if (key === 'fontFamily') {
|
||||||
|
this.setFontFamily(value);
|
||||||
|
} else if (key === 'fontSize') {
|
||||||
|
this.setFontSize(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCode(code) {
|
||||||
|
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
||||||
|
this.editor.dispatch({ changes });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseBooleans(value) {
|
||||||
|
return { true: true, false: false }[value] ?? value;
|
||||||
|
}
|
||||||
|
|||||||
24
packages/codemirror/examples/strudelmirror/.gitignore
vendored
Normal file
24
packages/codemirror/examples/strudelmirror/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
87
packages/codemirror/examples/strudelmirror/index.html
Normal file
87
packages/codemirror/examples/strudelmirror/index.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>StrudelMirror Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="settings">
|
||||||
|
<form name="settings">
|
||||||
|
<label
|
||||||
|
>theme
|
||||||
|
<select name="theme">
|
||||||
|
<option>strudelTheme</option>
|
||||||
|
<option>bluescreen</option>
|
||||||
|
<option>blackscreen</option>
|
||||||
|
<option>whitescreen</option>
|
||||||
|
<option>teletext</option>
|
||||||
|
<option>algoboy</option>
|
||||||
|
<option>terminal</option>
|
||||||
|
<option>abcdef</option>
|
||||||
|
<option>androidstudio</option>
|
||||||
|
<option>atomone</option>
|
||||||
|
<option>aura</option>
|
||||||
|
<option>bespin</option>
|
||||||
|
<option>darcula</option>
|
||||||
|
<option>dracula</option>
|
||||||
|
<option>duotoneDark</option>
|
||||||
|
<option>eclipse</option>
|
||||||
|
<option>githubDark</option>
|
||||||
|
<option>gruvboxDark</option>
|
||||||
|
<option>materialDark</option>
|
||||||
|
<option>nord</option>
|
||||||
|
<option>okaidia</option>
|
||||||
|
<option>solarizedDark</option>
|
||||||
|
<option>sublime</option>
|
||||||
|
<option>tokyoNight</option>
|
||||||
|
<option>tokyoNightStorm</option>
|
||||||
|
<option>vscodeDark</option>
|
||||||
|
<option>xcodeDark</option>
|
||||||
|
<option>bbedit</option>
|
||||||
|
<option>duotoneLight</option>
|
||||||
|
<option>githubLight</option>
|
||||||
|
<option>gruvboxLight</option>
|
||||||
|
<option>materialLight</option>
|
||||||
|
<option>noctisLilac</option>
|
||||||
|
<option>solarizedLight</option>
|
||||||
|
<option>tokyoNightDay</option>
|
||||||
|
<option>xcodeLight</option>
|
||||||
|
</select> </label
|
||||||
|
><br />
|
||||||
|
<!-- <label
|
||||||
|
>keybindings
|
||||||
|
<select name="keybindings">
|
||||||
|
<option>codemirror</option>
|
||||||
|
<option>vim</option>
|
||||||
|
<option>emacs</option>
|
||||||
|
<option>vscode</option>
|
||||||
|
</select> </label
|
||||||
|
><br />
|
||||||
|
<label
|
||||||
|
>fontFamily
|
||||||
|
<select name="fontFamily">
|
||||||
|
<option>monospace</option>
|
||||||
|
<option>helvetica</option>
|
||||||
|
</select> </label
|
||||||
|
><br /> -->
|
||||||
|
<label>fontSize <input type="number" name="fontSize" /></label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isLineNumbersDisplayed" />isLineNumbersDisplayed</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isActiveLineHighlighted" />isActiveLineHighlighted</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isPatternHighlightingEnabled" />isPatternHighlightingEnabled</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isFlashEnabled" />isFlashEnabled</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isLineWrappingEnabled" />isLineWrappingEnabled</label>
|
||||||
|
<!-- <label><input type="checkbox" name="isAutoCompletionEnabled" />isAutoCompletionEnabled</label> -->
|
||||||
|
<!-- <label><input type="checkbox" name="isTooltipEnabled" />isTooltipEnabled</label> -->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="code"></div>
|
||||||
|
<script type="module" src="/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
199
packages/codemirror/examples/strudelmirror/main.js
Normal file
199
packages/codemirror/examples/strudelmirror/main.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { logger, getDrawContext, silence, controls, evalScope, hash2code, code2hash } from '@strudel.cycles/core';
|
||||||
|
import { StrudelMirror } from '@strudel/codemirror';
|
||||||
|
import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
|
import {
|
||||||
|
getAudioContext,
|
||||||
|
webaudioOutput,
|
||||||
|
registerSynthSounds,
|
||||||
|
registerZZFXSounds,
|
||||||
|
samples,
|
||||||
|
} from '@strudel.cycles/webaudio';
|
||||||
|
import './style.css';
|
||||||
|
|
||||||
|
let editor;
|
||||||
|
const initialSettings = {
|
||||||
|
keybindings: 'codemirror',
|
||||||
|
isLineNumbersDisplayed: true,
|
||||||
|
isActiveLineHighlighted: true,
|
||||||
|
isAutoCompletionEnabled: false,
|
||||||
|
isPatternHighlightingEnabled: true,
|
||||||
|
isFlashEnabled: true,
|
||||||
|
isTooltipEnabled: false,
|
||||||
|
isLineWrappingEnabled: false,
|
||||||
|
theme: 'teletext',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 18,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const container = document.getElementById('code');
|
||||||
|
if (!container) {
|
||||||
|
console.warn('could not init: no container found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawContext = getDrawContext();
|
||||||
|
const drawTime = [-2, 2];
|
||||||
|
editor = new StrudelMirror({
|
||||||
|
defaultOutput: webaudioOutput,
|
||||||
|
getTime: () => getAudioContext().currentTime,
|
||||||
|
transpiler,
|
||||||
|
root: container,
|
||||||
|
initialCode: '// LOADING',
|
||||||
|
pattern: silence,
|
||||||
|
settings: initialSettings,
|
||||||
|
drawTime,
|
||||||
|
onDraw: (haps, time, frame, painters) => {
|
||||||
|
painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2);
|
||||||
|
painters?.forEach((painter) => {
|
||||||
|
// ctx time haps drawTime paintOptions
|
||||||
|
painter(drawContext, time, haps, drawTime, { clear: false });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
prebake: async () => {
|
||||||
|
// populate scope / lazy load modules
|
||||||
|
const modulesLoading = evalScope(
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
import('@strudel.cycles/tonal'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
// import('@strudel.cycles/xen'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
import('@strudel/codemirror'),
|
||||||
|
/* import('@strudel/hydra'), */
|
||||||
|
// import('@strudel.cycles/serial'),
|
||||||
|
/* import('@strudel.cycles/soundfonts'), */
|
||||||
|
// import('@strudel.cycles/csound'),
|
||||||
|
/* import('@strudel.cycles/midi'), */
|
||||||
|
// import('@strudel.cycles/osc'),
|
||||||
|
controls, // sadly, this cannot be exported from core directly (yet)
|
||||||
|
);
|
||||||
|
// load samples
|
||||||
|
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
|
||||||
|
await Promise.all([
|
||||||
|
modulesLoading,
|
||||||
|
registerSynthSounds(),
|
||||||
|
registerZZFXSounds(),
|
||||||
|
samples(`${ds}/tidal-drum-machines.json`),
|
||||||
|
samples(`${ds}/piano.json`),
|
||||||
|
samples(`${ds}/Dirt-Samples.json`),
|
||||||
|
samples(`${ds}/EmuSP12.json`),
|
||||||
|
samples(`${ds}/vcsl.json`),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
afterEval: ({ code }) => {
|
||||||
|
window.location.hash = '#' + code2hash(code);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// init settings
|
||||||
|
editor.updateSettings(initialSettings);
|
||||||
|
|
||||||
|
logger(`Welcome to Strudel! Click into the editor and then hit ctrl+enter to run the code!`, 'highlight');
|
||||||
|
const codeParam = window.location.href.split('#')[1] || '';
|
||||||
|
|
||||||
|
const initialCode = codeParam
|
||||||
|
? hash2code(codeParam)
|
||||||
|
: `// @date 23-11-30
|
||||||
|
// "teigrührgerät" @by froos
|
||||||
|
|
||||||
|
stack(
|
||||||
|
stack(
|
||||||
|
s("bd(<3!3 5>,6)/2").bank('RolandTR707')
|
||||||
|
,
|
||||||
|
s("~ sd:<0 1>").bank('RolandTR707').room("<0 .5>")
|
||||||
|
.lastOf(8, x=>x.segment("12").end(.2).gain(isaw))
|
||||||
|
,
|
||||||
|
s("[tb ~ tb]").bank('RolandTR707')
|
||||||
|
.clip(0).release(.08).room(.2)
|
||||||
|
).off(-1/6, x=>x.speed(.7).gain(.2).degrade())
|
||||||
|
,
|
||||||
|
stack(
|
||||||
|
note("<g1(<3 4>,6) ~!2 [f1?]*2>")
|
||||||
|
.s("sawtooth").lpf(perlin.range(400,1000))
|
||||||
|
.lpa(.1).lpenv(-3).room(.2)
|
||||||
|
.lpq(8).noise(.2)
|
||||||
|
.add(note("0,.1"))
|
||||||
|
,
|
||||||
|
chord("<~ Gm9 ~!2>")
|
||||||
|
.dict('ireal').voicing()
|
||||||
|
.s("sawtooth").vib("2:.1")
|
||||||
|
.lpf(1000).lpa(.1).lpenv(-4)
|
||||||
|
.room(.5)
|
||||||
|
,
|
||||||
|
n(run(3)).chord("<Gm9 Gm11>/8")
|
||||||
|
.dict('ireal-ext')
|
||||||
|
.off(1/2, add(n(4)))
|
||||||
|
.voicing()
|
||||||
|
.clip(.1).release(.05)
|
||||||
|
.s("sine").jux(rev)
|
||||||
|
.sometimesBy(sine.slow(16), add(note(12)))
|
||||||
|
.room(.75)
|
||||||
|
.lpf(sine.range(200,2000).slow(16))
|
||||||
|
.gain(saw.slow(4).div(2))
|
||||||
|
).add(note(perlin.range(0,.5)))
|
||||||
|
)`;
|
||||||
|
|
||||||
|
editor.setCode(initialCode); // simpler alternative to above init
|
||||||
|
|
||||||
|
// settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key]));
|
||||||
|
onEvent('strudel-toggle-play', () => editor.toggle());
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
function onEvent(key, callback) {
|
||||||
|
const listener = (e) => {
|
||||||
|
if (e.data === key) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('message', listener);
|
||||||
|
return () => window.removeEventListener('message', listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// settings form
|
||||||
|
function getInput(form, name) {
|
||||||
|
return form.querySelector(`input[name=${name}]`) || form.querySelector(`select[name=${name}]`);
|
||||||
|
}
|
||||||
|
function getFormValues(form, initial) {
|
||||||
|
const entries = Object.entries(initial).map(([key, initialValue]) => {
|
||||||
|
const input = getInput(form, key);
|
||||||
|
if (!input) {
|
||||||
|
return [key, initialValue]; // fallback
|
||||||
|
}
|
||||||
|
if (input.type === 'checkbox') {
|
||||||
|
return [key, input.checked];
|
||||||
|
}
|
||||||
|
if (input.type === 'number') {
|
||||||
|
return [key, Number(input.value)];
|
||||||
|
}
|
||||||
|
if (input.tagName === 'SELECT') {
|
||||||
|
return [key, input.value];
|
||||||
|
}
|
||||||
|
return [key, input.value];
|
||||||
|
});
|
||||||
|
return Object.fromEntries(entries);
|
||||||
|
}
|
||||||
|
function setFormValues(form, values) {
|
||||||
|
Object.entries(values).forEach(([key, value]) => {
|
||||||
|
const input = getInput(form, key);
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (input.type === 'checkbox') {
|
||||||
|
input.checked = !!value;
|
||||||
|
} else if (input.type === 'number') {
|
||||||
|
input.value = value;
|
||||||
|
} else if (input.tagName) {
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = document.querySelector('form[name=settings]');
|
||||||
|
setFormValues(form, initialSettings);
|
||||||
|
form.addEventListener('change', () => {
|
||||||
|
const values = getFormValues(form, initialSettings);
|
||||||
|
// console.log('values', values);
|
||||||
|
editor.updateSettings(values);
|
||||||
|
});
|
||||||
29
packages/codemirror/examples/strudelmirror/package.json
Normal file
29
packages/codemirror/examples/strudelmirror/package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "strudelmirror",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^5.0.8"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@strudel/codemirror": "workspace:*",
|
||||||
|
"@strudel.cycles/core":"workspace:*",
|
||||||
|
"@strudel.cycles/transpiler":"workspace:*",
|
||||||
|
"@strudel.cycles/tonal":"workspace:*",
|
||||||
|
"@strudel.cycles/mini":"workspace:*",
|
||||||
|
"@strudel.cycles/xen":"workspace:*",
|
||||||
|
"@strudel.cycles/webaudio":"workspace:*",
|
||||||
|
"@strudel/hydra":"workspace:*",
|
||||||
|
"@strudel.cycles/serial":"workspace:*",
|
||||||
|
"@strudel.cycles/soundfonts":"workspace:*",
|
||||||
|
"@strudel.cycles/csound":"workspace:*",
|
||||||
|
"@strudel.cycles/midi":"workspace:*",
|
||||||
|
"@strudel.cycles/osc":"workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
packages/codemirror/examples/strudelmirror/style.css
Normal file
33
packages/codemirror/examples/strudelmirror/style.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
:root {
|
||||||
|
--foreground: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
input {
|
||||||
|
font-family: monospace;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#code,
|
||||||
|
.cm-editor,
|
||||||
|
.cm-scroller {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex-col;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings > form > * + * {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
@ -33,3 +33,5 @@ export const flash = (view, ms = 200) => {
|
|||||||
view.dispatch({ effects: setFlash.of(false) });
|
view.dispatch({ effects: setFlash.of(false) });
|
||||||
}, ms);
|
}, ms);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isFlashEnabled = (on) => (on ? flashField : []);
|
||||||
|
|||||||
@ -124,3 +124,12 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
||||||
|
|
||||||
|
export const isPatternHighlightingEnabled = (on, config) => {
|
||||||
|
on &&
|
||||||
|
config &&
|
||||||
|
setTimeout(() => {
|
||||||
|
updateMiniLocations(config.editor, config.miniLocations);
|
||||||
|
}, 100);
|
||||||
|
return on ? highlightExtension : [];
|
||||||
|
};
|
||||||
|
|||||||
@ -2,3 +2,4 @@ export * from './codemirror.mjs';
|
|||||||
export * from './highlight.mjs';
|
export * from './highlight.mjs';
|
||||||
export * from './flash.mjs';
|
export * from './flash.mjs';
|
||||||
export * from './slider.mjs';
|
export * from './slider.mjs';
|
||||||
|
export * from './themes.mjs';
|
||||||
|
|||||||
31
packages/codemirror/keybindings.mjs
Normal file
31
packages/codemirror/keybindings.mjs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Prec } from '@codemirror/state';
|
||||||
|
import { keymap, ViewPlugin } from '@codemirror/view';
|
||||||
|
// import { searchKeymap } from '@codemirror/search';
|
||||||
|
import { emacs } from '@replit/codemirror-emacs';
|
||||||
|
import { vim } from '@replit/codemirror-vim';
|
||||||
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||||
|
import { defaultKeymap, historyKeymap } from '@codemirror/commands';
|
||||||
|
|
||||||
|
const vscodePlugin = ViewPlugin.fromClass(
|
||||||
|
class {
|
||||||
|
constructor() {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: () => {
|
||||||
|
return Prec.highest(keymap.of([...vscodeKeymap]));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||||
|
|
||||||
|
const keymaps = {
|
||||||
|
vim,
|
||||||
|
emacs,
|
||||||
|
vscode: vscodeExtension,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function keybindings(name) {
|
||||||
|
const active = keymaps[name];
|
||||||
|
return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
|
||||||
|
// keymap.of(searchKeymap),
|
||||||
|
}
|
||||||
@ -33,13 +33,21 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.6.0",
|
||||||
"@codemirror/commands": "^6.2.4",
|
"@codemirror/commands": "^6.2.4",
|
||||||
"@codemirror/lang-javascript": "^6.1.7",
|
"@codemirror/lang-javascript": "^6.1.7",
|
||||||
"@codemirror/language": "^6.6.0",
|
"@codemirror/language": "^6.6.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
"@codemirror/state": "^6.2.0",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@codemirror/view": "^6.10.0",
|
"@codemirror/view": "^6.10.0",
|
||||||
"@lezer/highlight": "^1.1.4",
|
"@lezer/highlight": "^1.1.4",
|
||||||
"@strudel.cycles/core": "workspace:*"
|
"@replit/codemirror-emacs": "^6.0.1",
|
||||||
|
"@replit/codemirror-vim": "^6.0.14",
|
||||||
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||||
|
"@strudel.cycles/core": "workspace:*",
|
||||||
|
"@uiw/codemirror-themes": "^4.19.16",
|
||||||
|
"@uiw/codemirror-themes-all": "^4.19.16",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^4.3.3"
|
"vite": "^4.3.3"
|
||||||
|
|||||||
521
packages/codemirror/themes.mjs
vendored
Normal file
521
packages/codemirror/themes.mjs
vendored
Normal file
@ -0,0 +1,521 @@
|
|||||||
|
import {
|
||||||
|
abcdef,
|
||||||
|
androidstudio,
|
||||||
|
atomone,
|
||||||
|
aura,
|
||||||
|
bespin,
|
||||||
|
darcula,
|
||||||
|
dracula,
|
||||||
|
duotoneDark,
|
||||||
|
eclipse,
|
||||||
|
githubDark,
|
||||||
|
gruvboxDark,
|
||||||
|
materialDark,
|
||||||
|
nord,
|
||||||
|
okaidia,
|
||||||
|
solarizedDark,
|
||||||
|
sublime,
|
||||||
|
tokyoNight,
|
||||||
|
tokyoNightStorm,
|
||||||
|
vscodeDark,
|
||||||
|
xcodeDark,
|
||||||
|
bbedit,
|
||||||
|
duotoneLight,
|
||||||
|
githubLight,
|
||||||
|
gruvboxLight,
|
||||||
|
materialLight,
|
||||||
|
noctisLilac,
|
||||||
|
solarizedLight,
|
||||||
|
tokyoNightDay,
|
||||||
|
xcodeLight,
|
||||||
|
} from '@uiw/codemirror-themes-all';
|
||||||
|
|
||||||
|
import strudelTheme from './themes/strudel-theme';
|
||||||
|
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
|
||||||
|
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
|
||||||
|
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
|
||||||
|
import teletext, { settings as teletextSettings } from './themes/teletext';
|
||||||
|
import algoboy, { settings as algoboySettings } from './themes/algoboy';
|
||||||
|
import terminal, { settings as terminalSettings } from './themes/terminal';
|
||||||
|
|
||||||
|
export const themes = {
|
||||||
|
strudelTheme,
|
||||||
|
bluescreen,
|
||||||
|
blackscreen,
|
||||||
|
whitescreen,
|
||||||
|
teletext,
|
||||||
|
algoboy,
|
||||||
|
terminal,
|
||||||
|
abcdef,
|
||||||
|
androidstudio,
|
||||||
|
atomone,
|
||||||
|
aura,
|
||||||
|
bespin,
|
||||||
|
darcula,
|
||||||
|
dracula,
|
||||||
|
duotoneDark,
|
||||||
|
eclipse,
|
||||||
|
githubDark,
|
||||||
|
gruvboxDark,
|
||||||
|
materialDark,
|
||||||
|
nord,
|
||||||
|
okaidia,
|
||||||
|
solarizedDark,
|
||||||
|
sublime,
|
||||||
|
tokyoNight,
|
||||||
|
tokyoNightStorm,
|
||||||
|
vscodeDark,
|
||||||
|
xcodeDark,
|
||||||
|
bbedit,
|
||||||
|
duotoneLight,
|
||||||
|
githubLight,
|
||||||
|
gruvboxLight,
|
||||||
|
materialLight,
|
||||||
|
noctisLilac,
|
||||||
|
solarizedLight,
|
||||||
|
tokyoNightDay,
|
||||||
|
xcodeLight,
|
||||||
|
};
|
||||||
|
|
||||||
|
// lineBackground is background with 50% opacity, to make sure the selection below is visible
|
||||||
|
|
||||||
|
export const settings = {
|
||||||
|
strudelTheme: {
|
||||||
|
background: '#222',
|
||||||
|
lineBackground: '#22222299',
|
||||||
|
foreground: '#fff',
|
||||||
|
// foreground: '#75baff',
|
||||||
|
caret: '#ffcc00',
|
||||||
|
selection: 'rgba(128, 203, 196, 0.5)',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
// lineHighlight: '#8a91991a', // original
|
||||||
|
lineHighlight: '#00000050',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
// gutterForeground: '#8a919966',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
},
|
||||||
|
bluescreen: bluescreenSettings,
|
||||||
|
blackscreen: blackscreenSettings,
|
||||||
|
whitescreen: whitescreenSettings,
|
||||||
|
teletext: teletextSettings,
|
||||||
|
algoboy: algoboySettings,
|
||||||
|
terminal: terminalSettings,
|
||||||
|
abcdef: {
|
||||||
|
background: '#0f0f0f',
|
||||||
|
lineBackground: '#0f0f0f99',
|
||||||
|
foreground: '#defdef',
|
||||||
|
caret: '#00FF00',
|
||||||
|
selection: '#515151',
|
||||||
|
selectionMatch: '#515151',
|
||||||
|
gutterBackground: '#555',
|
||||||
|
gutterForeground: '#FFFFFF',
|
||||||
|
lineHighlight: '#314151',
|
||||||
|
},
|
||||||
|
androidstudio: {
|
||||||
|
background: '#282b2e',
|
||||||
|
lineBackground: '#282b2e99',
|
||||||
|
foreground: '#a9b7c6',
|
||||||
|
caret: '#00FF00',
|
||||||
|
selection: '#343739',
|
||||||
|
selectionMatch: '#343739',
|
||||||
|
lineHighlight: '#343739',
|
||||||
|
},
|
||||||
|
atomone: {
|
||||||
|
background: '#272C35',
|
||||||
|
lineBackground: '#272C3599',
|
||||||
|
foreground: '#9d9b97',
|
||||||
|
caret: '#797977',
|
||||||
|
selection: '#ffffff30',
|
||||||
|
selectionMatch: '#2B323D',
|
||||||
|
gutterBackground: '#272C35',
|
||||||
|
gutterForeground: '#465063',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#2B323D',
|
||||||
|
},
|
||||||
|
aura: {
|
||||||
|
background: '#21202e',
|
||||||
|
lineBackground: '#21202e99',
|
||||||
|
foreground: '#edecee',
|
||||||
|
caret: '#a277ff',
|
||||||
|
selection: '#3d375e7f',
|
||||||
|
selectionMatch: '#3d375e7f',
|
||||||
|
gutterBackground: '#21202e',
|
||||||
|
gutterForeground: '#edecee',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#a394f033',
|
||||||
|
},
|
||||||
|
bbedit: {
|
||||||
|
light: true,
|
||||||
|
background: '#FFFFFF',
|
||||||
|
lineBackground: '#FFFFFF99',
|
||||||
|
foreground: '#000000',
|
||||||
|
caret: '#FBAC52',
|
||||||
|
selection: '#FFD420',
|
||||||
|
selectionMatch: '#FFD420',
|
||||||
|
gutterBackground: '#f5f5f5',
|
||||||
|
gutterForeground: '#4D4D4C',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#00000012',
|
||||||
|
},
|
||||||
|
bespin: {
|
||||||
|
background: '#28211c',
|
||||||
|
lineBackground: '#28211c99',
|
||||||
|
foreground: '#9d9b97',
|
||||||
|
caret: '#797977',
|
||||||
|
selection: '#36312e',
|
||||||
|
selectionMatch: '#4f382b',
|
||||||
|
gutterBackground: '#28211c',
|
||||||
|
gutterForeground: '#666666',
|
||||||
|
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
},
|
||||||
|
darcula: {
|
||||||
|
background: '#2B2B2B',
|
||||||
|
lineBackground: '#2B2B2B99',
|
||||||
|
foreground: '#f8f8f2',
|
||||||
|
caret: '#FFFFFF',
|
||||||
|
selection: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
gutterBackground: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
gutterForeground: '#999',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
},
|
||||||
|
dracula: {
|
||||||
|
background: '#282a36',
|
||||||
|
lineBackground: '#282a3699',
|
||||||
|
foreground: '#f8f8f2',
|
||||||
|
caret: '#f8f8f0',
|
||||||
|
selection: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
gutterBackground: '#282a36',
|
||||||
|
gutterForeground: '#6D8A88',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
},
|
||||||
|
duotoneLight: {
|
||||||
|
light: true,
|
||||||
|
background: '#faf8f5',
|
||||||
|
lineBackground: '#faf8f599',
|
||||||
|
foreground: '#b29762',
|
||||||
|
caret: '#93abdc',
|
||||||
|
selection: '#e3dcce',
|
||||||
|
selectionMatch: '#e3dcce',
|
||||||
|
gutterBackground: '#faf8f5',
|
||||||
|
gutterForeground: '#cdc4b1',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#EFEFEF',
|
||||||
|
},
|
||||||
|
duotoneDark: {
|
||||||
|
background: '#2a2734',
|
||||||
|
lineBackground: '#2a273499',
|
||||||
|
foreground: '#6c6783',
|
||||||
|
caret: '#ffad5c',
|
||||||
|
selection: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
gutterBackground: '#2a2734',
|
||||||
|
gutterForeground: '#545167',
|
||||||
|
lineHighlight: '#36334280',
|
||||||
|
},
|
||||||
|
eclipse: {
|
||||||
|
light: true,
|
||||||
|
background: '#fff',
|
||||||
|
lineBackground: '#ffffff99',
|
||||||
|
foreground: '#000',
|
||||||
|
caret: '#FFFFFF',
|
||||||
|
selection: '#d7d4f0',
|
||||||
|
selectionMatch: '#d7d4f0',
|
||||||
|
gutterBackground: '#f7f7f7',
|
||||||
|
gutterForeground: '#999',
|
||||||
|
lineHighlight: '#e8f2ff',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
},
|
||||||
|
githubLight: {
|
||||||
|
light: true,
|
||||||
|
background: '#fff',
|
||||||
|
lineBackground: '#ffffff99',
|
||||||
|
foreground: '#24292e',
|
||||||
|
selection: '#BBDFFF',
|
||||||
|
selectionMatch: '#BBDFFF',
|
||||||
|
gutterBackground: '#fff',
|
||||||
|
gutterForeground: '#6e7781',
|
||||||
|
},
|
||||||
|
githubDark: {
|
||||||
|
background: '#0d1117',
|
||||||
|
lineBackground: '#0d111799',
|
||||||
|
foreground: '#c9d1d9',
|
||||||
|
caret: '#c9d1d9',
|
||||||
|
selection: '#003d73',
|
||||||
|
selectionMatch: '#003d73',
|
||||||
|
lineHighlight: '#36334280',
|
||||||
|
},
|
||||||
|
gruvboxDark: {
|
||||||
|
background: '#282828',
|
||||||
|
lineBackground: '#28282899',
|
||||||
|
foreground: '#ebdbb2',
|
||||||
|
caret: '#ebdbb2',
|
||||||
|
selection: '#bdae93',
|
||||||
|
selectionMatch: '#bdae93',
|
||||||
|
lineHighlight: '#3c3836',
|
||||||
|
gutterBackground: '#282828',
|
||||||
|
gutterForeground: '#7c6f64',
|
||||||
|
},
|
||||||
|
gruvboxLight: {
|
||||||
|
light: true,
|
||||||
|
background: '#fbf1c7',
|
||||||
|
lineBackground: '#fbf1c799',
|
||||||
|
foreground: '#3c3836',
|
||||||
|
caret: '#af3a03',
|
||||||
|
selection: '#ebdbb2',
|
||||||
|
selectionMatch: '#bdae93',
|
||||||
|
lineHighlight: '#ebdbb2',
|
||||||
|
gutterBackground: '#ebdbb2',
|
||||||
|
gutterForeground: '#665c54',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
},
|
||||||
|
materialDark: {
|
||||||
|
background: '#2e3235',
|
||||||
|
lineBackground: '#2e323599',
|
||||||
|
foreground: '#bdbdbd',
|
||||||
|
caret: '#a0a4ae',
|
||||||
|
selection: '#d7d4f0',
|
||||||
|
selectionMatch: '#d7d4f0',
|
||||||
|
gutterBackground: '#2e3235',
|
||||||
|
gutterForeground: '#999',
|
||||||
|
gutterActiveForeground: '#4f5b66',
|
||||||
|
lineHighlight: '#545b61',
|
||||||
|
},
|
||||||
|
materialLight: {
|
||||||
|
light: true,
|
||||||
|
background: '#FAFAFA',
|
||||||
|
lineBackground: '#FAFAFA99',
|
||||||
|
foreground: '#90A4AE',
|
||||||
|
caret: '#272727',
|
||||||
|
selection: '#80CBC440',
|
||||||
|
selectionMatch: '#FAFAFA',
|
||||||
|
gutterBackground: '#FAFAFA',
|
||||||
|
gutterForeground: '#90A4AE',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#CCD7DA50',
|
||||||
|
},
|
||||||
|
noctisLilac: {
|
||||||
|
light: true,
|
||||||
|
background: '#f2f1f8',
|
||||||
|
lineBackground: '#f2f1f899',
|
||||||
|
foreground: '#0c006b',
|
||||||
|
caret: '#5c49e9',
|
||||||
|
selection: '#d5d1f2',
|
||||||
|
selectionMatch: '#d5d1f2',
|
||||||
|
gutterBackground: '#f2f1f8',
|
||||||
|
gutterForeground: '#0c006b70',
|
||||||
|
lineHighlight: '#e1def3',
|
||||||
|
},
|
||||||
|
nord: {
|
||||||
|
background: '#2e3440',
|
||||||
|
lineBackground: '#2e344099',
|
||||||
|
foreground: '#FFFFFF',
|
||||||
|
caret: '#FFFFFF',
|
||||||
|
selection: '#3b4252',
|
||||||
|
selectionMatch: '#e5e9f0',
|
||||||
|
gutterBackground: '#2e3440',
|
||||||
|
gutterForeground: '#4c566a',
|
||||||
|
gutterActiveForeground: '#d8dee9',
|
||||||
|
lineHighlight: '#4c566a',
|
||||||
|
},
|
||||||
|
okaidia: {
|
||||||
|
background: '#272822',
|
||||||
|
lineBackground: '#27282299',
|
||||||
|
foreground: '#FFFFFF',
|
||||||
|
caret: '#FFFFFF',
|
||||||
|
selection: '#49483E',
|
||||||
|
selectionMatch: '#49483E',
|
||||||
|
gutterBackground: '#272822',
|
||||||
|
gutterForeground: '#FFFFFF70',
|
||||||
|
lineHighlight: '#00000059',
|
||||||
|
},
|
||||||
|
solarizedLight: {
|
||||||
|
light: true,
|
||||||
|
background: '#fdf6e3',
|
||||||
|
lineBackground: '#fdf6e399',
|
||||||
|
foreground: '#657b83',
|
||||||
|
caret: '#586e75',
|
||||||
|
selection: '#dfd9c8',
|
||||||
|
selectionMatch: '#dfd9c8',
|
||||||
|
gutterBackground: '#00000010',
|
||||||
|
gutterForeground: '#657b83',
|
||||||
|
lineHighlight: '#dfd9c8',
|
||||||
|
},
|
||||||
|
solarizedDark: {
|
||||||
|
background: '#002b36',
|
||||||
|
lineBackground: '#002b3699',
|
||||||
|
foreground: '#93a1a1',
|
||||||
|
caret: '#839496',
|
||||||
|
selection: '#173541',
|
||||||
|
selectionMatch: '#aafe661a',
|
||||||
|
gutterBackground: '#00252f',
|
||||||
|
gutterForeground: '#839496',
|
||||||
|
lineHighlight: '#173541',
|
||||||
|
},
|
||||||
|
sublime: {
|
||||||
|
background: '#303841',
|
||||||
|
lineBackground: '#30384199',
|
||||||
|
foreground: '#FFFFFF',
|
||||||
|
caret: '#FBAC52',
|
||||||
|
selection: '#4C5964',
|
||||||
|
selectionMatch: '#3A546E',
|
||||||
|
gutterBackground: '#303841',
|
||||||
|
gutterForeground: '#FFFFFF70',
|
||||||
|
lineHighlight: '#00000059',
|
||||||
|
},
|
||||||
|
tokyoNightDay: {
|
||||||
|
light: true,
|
||||||
|
background: '#e1e2e7',
|
||||||
|
lineBackground: '#e1e2e799',
|
||||||
|
foreground: '#3760bf',
|
||||||
|
caret: '#3760bf',
|
||||||
|
selection: '#99a7df',
|
||||||
|
selectionMatch: '#99a7df',
|
||||||
|
gutterBackground: '#e1e2e7',
|
||||||
|
gutterForeground: '#3760bf',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#5f5faf11',
|
||||||
|
},
|
||||||
|
tokyoNightStorm: {
|
||||||
|
background: '#24283b',
|
||||||
|
lineBackground: '#24283b99',
|
||||||
|
foreground: '#7982a9',
|
||||||
|
caret: '#c0caf5',
|
||||||
|
selection: '#6f7bb630',
|
||||||
|
selectionMatch: '#1f2335',
|
||||||
|
gutterBackground: '#24283b',
|
||||||
|
gutterForeground: '#7982a9',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#292e42',
|
||||||
|
},
|
||||||
|
tokyoNight: {
|
||||||
|
background: '#1a1b26',
|
||||||
|
lineBackground: '#1a1b2699',
|
||||||
|
foreground: '#787c99',
|
||||||
|
caret: '#c0caf5',
|
||||||
|
selection: '#515c7e40',
|
||||||
|
selectionMatch: '#16161e',
|
||||||
|
gutterBackground: '#1a1b26',
|
||||||
|
gutterForeground: '#787c99',
|
||||||
|
gutterBorder: 'transparent',
|
||||||
|
lineHighlight: '#1e202e',
|
||||||
|
},
|
||||||
|
vscodeDark: {
|
||||||
|
background: '#1e1e1e',
|
||||||
|
lineBackground: '#1e1e1e99',
|
||||||
|
foreground: '#9cdcfe',
|
||||||
|
caret: '#c6c6c6',
|
||||||
|
selection: '#6199ff2f',
|
||||||
|
selectionMatch: '#72a1ff59',
|
||||||
|
lineHighlight: '#ffffff0f',
|
||||||
|
gutterBackground: '#1e1e1e',
|
||||||
|
gutterForeground: '#838383',
|
||||||
|
gutterActiveForeground: '#fff',
|
||||||
|
},
|
||||||
|
xcodeLight: {
|
||||||
|
light: true,
|
||||||
|
background: '#fff',
|
||||||
|
lineBackground: '#ffffff99',
|
||||||
|
foreground: '#3D3D3D',
|
||||||
|
selection: '#BBDFFF',
|
||||||
|
selectionMatch: '#BBDFFF',
|
||||||
|
gutterBackground: '#fff',
|
||||||
|
gutterForeground: '#AFAFAF',
|
||||||
|
lineHighlight: '#EDF4FF',
|
||||||
|
},
|
||||||
|
xcodeDark: {
|
||||||
|
background: '#292A30',
|
||||||
|
lineBackground: '#292A3099',
|
||||||
|
foreground: '#CECFD0',
|
||||||
|
caret: '#fff',
|
||||||
|
selection: '#727377',
|
||||||
|
selectionMatch: '#727377',
|
||||||
|
lineHighlight: '#2F3239',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getColors(str) {
|
||||||
|
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
|
||||||
|
const colors = [];
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = colorRegex.exec(str)) !== null) {
|
||||||
|
const color = match[0];
|
||||||
|
if (!colors.includes(color)) {
|
||||||
|
colors.push(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove
|
||||||
|
export function themeColors(theme) {
|
||||||
|
return getColors(stringifySafe(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCircularReplacer() {
|
||||||
|
const seen = new WeakSet();
|
||||||
|
return (key, value) => {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if (seen.has(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seen.add(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifySafe(json) {
|
||||||
|
return JSON.stringify(json, getCircularReplacer());
|
||||||
|
}
|
||||||
|
|
||||||
|
export const theme = (theme) => themes[theme] || themes.strudelTheme;
|
||||||
|
|
||||||
|
// css style injection helpers
|
||||||
|
export function injectStyle(rule) {
|
||||||
|
const newStyle = document.createElement('style');
|
||||||
|
document.head.appendChild(newStyle);
|
||||||
|
const styleSheet = newStyle.sheet;
|
||||||
|
const ruleIndex = styleSheet.insertRule(rule, 0);
|
||||||
|
return () => styleSheet.deleteRule(ruleIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTheme, resetThemeStyle, themeStyle;
|
||||||
|
export function initTheme(theme) {
|
||||||
|
themeStyle = document.createElement('style');
|
||||||
|
themeStyle.id = 'strudel-theme';
|
||||||
|
document.head.append(themeStyle);
|
||||||
|
activateTheme(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activateTheme(name) {
|
||||||
|
if (currentTheme === name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!settings[name]) {
|
||||||
|
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
||||||
|
}
|
||||||
|
const themeSettings = settings[name] || settings.strudelTheme;
|
||||||
|
// set css variables
|
||||||
|
themeStyle.innerHTML = `:root {
|
||||||
|
${Object.entries(themeSettings)
|
||||||
|
// important to override fallback
|
||||||
|
.map(([key, value]) => `--${key}: ${value} !important;`)
|
||||||
|
.join('\n')}
|
||||||
|
}`;
|
||||||
|
// tailwind dark mode
|
||||||
|
if (themeSettings.light) {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
resetThemeStyle?.();
|
||||||
|
resetThemeStyle = undefined;
|
||||||
|
if (themeSettings.customStyle) {
|
||||||
|
resetThemeStyle = injectStyle(themeSettings.customStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
packages/codemirror/themes/algoboy.mjs
vendored
Normal file
41
packages/codemirror/themes/algoboy.mjs
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: '#9bbc0f',
|
||||||
|
foreground: '#0f380f', // whats that?
|
||||||
|
caret: '#0f380f',
|
||||||
|
selection: '#306230',
|
||||||
|
selectionMatch: '#ffffff26',
|
||||||
|
lineHighlight: '#8bac0f',
|
||||||
|
lineBackground: '#9bbc0f50',
|
||||||
|
//lineBackground: 'transparent',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: '#0f380f',
|
||||||
|
light: true,
|
||||||
|
customStyle: '.cm-line { line-height: 1 }',
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'light',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: '#0f380f' },
|
||||||
|
{ tag: t.operator, color: '#0f380f' },
|
||||||
|
{ tag: t.special(t.variableName), color: '#0f380f' },
|
||||||
|
{ tag: t.typeName, color: '#0f380f' },
|
||||||
|
{ tag: t.atom, color: '#0f380f' },
|
||||||
|
{ tag: t.number, color: '#0f380f' },
|
||||||
|
{ tag: t.definition(t.variableName), color: '#0f380f' },
|
||||||
|
{ tag: t.string, color: '#0f380f' },
|
||||||
|
{ tag: t.special(t.string), color: '#0f380f' },
|
||||||
|
{ tag: t.comment, color: '#0f380f' },
|
||||||
|
{ tag: t.variableName, color: '#0f380f' },
|
||||||
|
{ tag: t.tagName, color: '#0f380f' },
|
||||||
|
{ tag: t.bracket, color: '#0f380f' },
|
||||||
|
{ tag: t.meta, color: '#0f380f' },
|
||||||
|
{ tag: t.attributeName, color: '#0f380f' },
|
||||||
|
{ tag: t.propertyName, color: '#0f380f' },
|
||||||
|
{ tag: t.className, color: '#0f380f' },
|
||||||
|
{ tag: t.invalid, color: '#0f380f' },
|
||||||
|
{ tag: [t.unit, t.punctuation], color: '#0f380f' },
|
||||||
|
],
|
||||||
|
});
|
||||||
38
packages/codemirror/themes/blackscreen.mjs
vendored
Normal file
38
packages/codemirror/themes/blackscreen.mjs
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: 'black',
|
||||||
|
foreground: 'white', // whats that?
|
||||||
|
caret: 'white',
|
||||||
|
selection: '#ffffff20',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
lineHighlight: '#ffffff10',
|
||||||
|
lineBackground: '#00000050',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: 'white' },
|
||||||
|
{ tag: t.operator, color: 'white' },
|
||||||
|
{ tag: t.special(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.typeName, color: 'white' },
|
||||||
|
{ tag: t.atom, color: 'white' },
|
||||||
|
{ tag: t.number, color: 'white' },
|
||||||
|
{ tag: t.definition(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.string, color: 'white' },
|
||||||
|
{ tag: t.special(t.string), color: 'white' },
|
||||||
|
{ tag: t.comment, color: 'white' },
|
||||||
|
{ tag: t.variableName, color: 'white' },
|
||||||
|
{ tag: t.tagName, color: 'white' },
|
||||||
|
{ tag: t.bracket, color: 'white' },
|
||||||
|
{ tag: t.meta, color: 'white' },
|
||||||
|
{ tag: t.attributeName, color: 'white' },
|
||||||
|
{ tag: t.propertyName, color: 'white' },
|
||||||
|
{ tag: t.className, color: 'white' },
|
||||||
|
{ tag: t.invalid, color: 'white' },
|
||||||
|
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||||
|
],
|
||||||
|
});
|
||||||
41
packages/codemirror/themes/bluescreen.mjs
vendored
Normal file
41
packages/codemirror/themes/bluescreen.mjs
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: '#051DB5',
|
||||||
|
lineBackground: '#051DB550',
|
||||||
|
foreground: 'white', // whats that?
|
||||||
|
caret: 'white',
|
||||||
|
selection: 'rgba(128, 203, 196, 0.5)',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
// lineHighlight: '#8a91991a', // original
|
||||||
|
lineHighlight: '#00000050',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
// gutterForeground: '#8a919966',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: 'white' },
|
||||||
|
{ tag: t.operator, color: 'white' },
|
||||||
|
{ tag: t.special(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.typeName, color: 'white' },
|
||||||
|
{ tag: t.atom, color: 'white' },
|
||||||
|
{ tag: t.number, color: 'white' },
|
||||||
|
{ tag: t.definition(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.string, color: 'white' },
|
||||||
|
{ tag: t.special(t.string), color: 'white' },
|
||||||
|
{ tag: t.comment, color: 'white' },
|
||||||
|
{ tag: t.variableName, color: 'white' },
|
||||||
|
{ tag: t.tagName, color: 'white' },
|
||||||
|
{ tag: t.bracket, color: 'white' },
|
||||||
|
{ tag: t.meta, color: 'white' },
|
||||||
|
{ tag: t.attributeName, color: 'white' },
|
||||||
|
{ tag: t.propertyName, color: 'white' },
|
||||||
|
{ tag: t.className, color: 'white' },
|
||||||
|
{ tag: t.invalid, color: 'white' },
|
||||||
|
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||||
|
],
|
||||||
|
});
|
||||||
139
packages/codemirror/themes/one-dark.mjs
vendored
139
packages/codemirror/themes/one-dark.mjs
vendored
@ -1,139 +0,0 @@
|
|||||||
import { EditorView } from '@codemirror/view';
|
|
||||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
||||||
import { tags as t } from '@lezer/highlight';
|
|
||||||
|
|
||||||
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
|
||||||
|
|
||||||
const chalky = '#e5c07b',
|
|
||||||
coral = '#e06c75',
|
|
||||||
cyan = '#56b6c2',
|
|
||||||
invalid = '#ffffff',
|
|
||||||
ivory = '#abb2bf',
|
|
||||||
stone = '#7d8799', // Brightened compared to original to increase contrast
|
|
||||||
malibu = '#61afef',
|
|
||||||
sage = '#98c379',
|
|
||||||
whiskey = '#d19a66',
|
|
||||||
violet = '#c678dd',
|
|
||||||
darkBackground = '#21252b',
|
|
||||||
highlightBackground = '#2c313a',
|
|
||||||
background = '#282c34',
|
|
||||||
tooltipBackground = '#353a42',
|
|
||||||
selection = '#3E4451',
|
|
||||||
cursor = '#528bff';
|
|
||||||
|
|
||||||
/// The colors used in the theme, as CSS color strings.
|
|
||||||
export const color = {
|
|
||||||
chalky,
|
|
||||||
coral,
|
|
||||||
cyan,
|
|
||||||
invalid,
|
|
||||||
ivory,
|
|
||||||
stone,
|
|
||||||
malibu,
|
|
||||||
sage,
|
|
||||||
whiskey,
|
|
||||||
violet,
|
|
||||||
darkBackground,
|
|
||||||
highlightBackground,
|
|
||||||
background,
|
|
||||||
tooltipBackground,
|
|
||||||
selection,
|
|
||||||
cursor,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The editor theme styles for One Dark.
|
|
||||||
export const oneDarkTheme = EditorView.theme(
|
|
||||||
{
|
|
||||||
'&': {
|
|
||||||
color: ivory,
|
|
||||||
backgroundColor: background,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-content': {
|
|
||||||
caretColor: cursor,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
|
||||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
|
||||||
{ backgroundColor: selection },
|
|
||||||
|
|
||||||
'.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
|
||||||
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
|
||||||
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
|
||||||
|
|
||||||
'.cm-searchMatch': {
|
|
||||||
backgroundColor: '#72a1ff59',
|
|
||||||
outline: '1px solid #457dff',
|
|
||||||
},
|
|
||||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
|
||||||
backgroundColor: '#6199ff2f',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
|
||||||
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
|
||||||
|
|
||||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
|
||||||
backgroundColor: '#bad0f847',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-gutters': {
|
|
||||||
backgroundColor: background,
|
|
||||||
color: stone,
|
|
||||||
border: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-activeLineGutter': {
|
|
||||||
backgroundColor: highlightBackground,
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-foldPlaceholder': {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
color: '#ddd',
|
|
||||||
},
|
|
||||||
|
|
||||||
'.cm-tooltip': {
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: tooltipBackground,
|
|
||||||
},
|
|
||||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
|
||||||
borderTopColor: 'transparent',
|
|
||||||
borderBottomColor: 'transparent',
|
|
||||||
},
|
|
||||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
|
||||||
borderTopColor: tooltipBackground,
|
|
||||||
borderBottomColor: tooltipBackground,
|
|
||||||
},
|
|
||||||
'.cm-tooltip-autocomplete': {
|
|
||||||
'& > ul > li[aria-selected]': {
|
|
||||||
backgroundColor: highlightBackground,
|
|
||||||
color: ivory,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ dark: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
/// The highlighting style for code in the One Dark theme.
|
|
||||||
export const oneDarkHighlightStyle = HighlightStyle.define([
|
|
||||||
{ tag: t.keyword, color: violet },
|
|
||||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
|
|
||||||
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
|
|
||||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
|
|
||||||
{ tag: [t.definition(t.name), t.separator], color: ivory },
|
|
||||||
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
|
|
||||||
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
|
|
||||||
{ tag: [t.meta, t.comment], color: stone },
|
|
||||||
{ tag: t.strong, fontWeight: 'bold' },
|
|
||||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
|
||||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
|
||||||
{ tag: t.link, color: stone, textDecoration: 'underline' },
|
|
||||||
{ tag: t.heading, fontWeight: 'bold', color: coral },
|
|
||||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
|
|
||||||
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
|
|
||||||
{ tag: t.invalid, color: invalid },
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// Extension to enable the One Dark theme (both the editor theme and
|
|
||||||
/// the highlight style).
|
|
||||||
export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
|
|
||||||
45
packages/codemirror/themes/strudel-theme.mjs
vendored
Normal file
45
packages/codemirror/themes/strudel-theme.mjs
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings: {
|
||||||
|
background: '#222',
|
||||||
|
foreground: '#75baff', // whats that?
|
||||||
|
caret: '#ffcc00',
|
||||||
|
selection: 'rgba(128, 203, 196, 0.5)',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
// lineHighlight: '#8a91991a', // original
|
||||||
|
lineHighlight: '#00000050',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
// gutterForeground: '#8a919966',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
},
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: '#c792ea' },
|
||||||
|
{ tag: t.operator, color: '#89ddff' },
|
||||||
|
{ tag: t.special(t.variableName), color: '#eeffff' },
|
||||||
|
// { tag: t.typeName, color: '#f07178' }, // original
|
||||||
|
{ tag: t.typeName, color: '#c3e88d' },
|
||||||
|
{ tag: t.atom, color: '#f78c6c' },
|
||||||
|
// { tag: t.number, color: '#ff5370' }, // original
|
||||||
|
{ tag: t.number, color: '#c3e88d' },
|
||||||
|
{ tag: t.definition(t.variableName), color: '#82aaff' },
|
||||||
|
{ tag: t.string, color: '#c3e88d' },
|
||||||
|
// { tag: t.special(t.string), color: '#f07178' }, // original
|
||||||
|
{ tag: t.special(t.string), color: '#c3e88d' },
|
||||||
|
{ tag: t.comment, color: '#7d8799' },
|
||||||
|
// { tag: t.variableName, color: '#f07178' }, // original
|
||||||
|
{ tag: t.variableName, color: '#c792ea' },
|
||||||
|
// { tag: t.tagName, color: '#ff5370' }, // original
|
||||||
|
{ tag: t.tagName, color: '#c3e88d' },
|
||||||
|
{ tag: t.bracket, color: '#525154' },
|
||||||
|
// { tag: t.bracket, color: '#a2a1a4' }, // original
|
||||||
|
{ tag: t.meta, color: '#ffcb6b' },
|
||||||
|
{ tag: t.attributeName, color: '#c792ea' },
|
||||||
|
{ tag: t.propertyName, color: '#c792ea' },
|
||||||
|
|
||||||
|
{ tag: t.className, color: '#decb6b' },
|
||||||
|
{ tag: t.invalid, color: '#ffffff' },
|
||||||
|
{ tag: [t.unit, t.punctuation], color: '#82aaff' },
|
||||||
|
],
|
||||||
|
});
|
||||||
50
packages/codemirror/themes/teletext.mjs
vendored
Normal file
50
packages/codemirror/themes/teletext.mjs
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
|
||||||
|
let colorA = '#6edee4';
|
||||||
|
//let colorB = 'magenta';
|
||||||
|
let colorB = 'white';
|
||||||
|
let colorC = 'red';
|
||||||
|
let colorD = '#f8fc55';
|
||||||
|
|
||||||
|
export const settings = {
|
||||||
|
background: '#000000',
|
||||||
|
foreground: colorA, // whats that?
|
||||||
|
caret: colorC,
|
||||||
|
selection: colorD,
|
||||||
|
selectionMatch: colorA,
|
||||||
|
lineHighlight: '#6edee440', // panel bg
|
||||||
|
lineBackground: '#00000040',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
customStyle: '.cm-line { line-height: 1 }',
|
||||||
|
};
|
||||||
|
|
||||||
|
let punctuation = colorD;
|
||||||
|
let mini = colorB;
|
||||||
|
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: colorA },
|
||||||
|
{ tag: t.operator, color: mini },
|
||||||
|
{ tag: t.special(t.variableName), color: colorA },
|
||||||
|
{ tag: t.typeName, color: colorA },
|
||||||
|
{ tag: t.atom, color: colorA },
|
||||||
|
{ tag: t.number, color: mini },
|
||||||
|
{ tag: t.definition(t.variableName), color: colorA },
|
||||||
|
{ tag: t.string, color: mini },
|
||||||
|
{ tag: t.special(t.string), color: mini },
|
||||||
|
{ tag: t.comment, color: punctuation },
|
||||||
|
{ tag: t.variableName, color: colorA },
|
||||||
|
{ tag: t.tagName, color: colorA },
|
||||||
|
{ tag: t.bracket, color: punctuation },
|
||||||
|
{ tag: t.meta, color: colorA },
|
||||||
|
{ tag: t.attributeName, color: colorA },
|
||||||
|
{ tag: t.propertyName, color: colorA }, // methods
|
||||||
|
{ tag: t.className, color: colorA },
|
||||||
|
{ tag: t.invalid, color: colorC },
|
||||||
|
{ tag: [t.unit, t.punctuation], color: punctuation },
|
||||||
|
],
|
||||||
|
});
|
||||||
36
packages/codemirror/themes/terminal.mjs
vendored
Normal file
36
packages/codemirror/themes/terminal.mjs
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: 'black',
|
||||||
|
foreground: '#41FF00', // whats that?
|
||||||
|
caret: '#41FF00',
|
||||||
|
selection: '#ffffff20',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
lineHighlight: '#ffffff10',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: '#41FF00' },
|
||||||
|
{ tag: t.operator, color: '#41FF00' },
|
||||||
|
{ tag: t.special(t.variableName), color: '#41FF00' },
|
||||||
|
{ tag: t.typeName, color: '#41FF00' },
|
||||||
|
{ tag: t.atom, color: '#41FF00' },
|
||||||
|
{ tag: t.number, color: '#41FF00' },
|
||||||
|
{ tag: t.definition(t.variableName), color: '#41FF00' },
|
||||||
|
{ tag: t.string, color: '#41FF00' },
|
||||||
|
{ tag: t.special(t.string), color: '#41FF00' },
|
||||||
|
{ tag: t.comment, color: '#41FF00' },
|
||||||
|
{ tag: t.variableName, color: '#41FF00' },
|
||||||
|
{ tag: t.tagName, color: '#41FF00' },
|
||||||
|
{ tag: t.bracket, color: '#41FF00' },
|
||||||
|
{ tag: t.meta, color: '#41FF00' },
|
||||||
|
{ tag: t.attributeName, color: '#41FF00' },
|
||||||
|
{ tag: t.propertyName, color: '#41FF00' },
|
||||||
|
{ tag: t.className, color: '#41FF00' },
|
||||||
|
{ tag: t.invalid, color: '#41FF00' },
|
||||||
|
],
|
||||||
|
});
|
||||||
38
packages/codemirror/themes/whitescreen.mjs
vendored
Normal file
38
packages/codemirror/themes/whitescreen.mjs
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: 'white',
|
||||||
|
foreground: 'black', // whats that?
|
||||||
|
caret: 'black',
|
||||||
|
selection: 'rgba(128, 203, 196, 0.5)',
|
||||||
|
selectionMatch: '#ffffff26',
|
||||||
|
lineHighlight: '#cccccc50',
|
||||||
|
lineBackground: '#ffffff50',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: 'black',
|
||||||
|
light: true,
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'light',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: 'black' },
|
||||||
|
{ tag: t.operator, color: 'black' },
|
||||||
|
{ tag: t.special(t.variableName), color: 'black' },
|
||||||
|
{ tag: t.typeName, color: 'black' },
|
||||||
|
{ tag: t.atom, color: 'black' },
|
||||||
|
{ tag: t.number, color: 'black' },
|
||||||
|
{ tag: t.definition(t.variableName), color: 'black' },
|
||||||
|
{ tag: t.string, color: 'black' },
|
||||||
|
{ tag: t.special(t.string), color: 'black' },
|
||||||
|
{ tag: t.comment, color: 'black' },
|
||||||
|
{ tag: t.variableName, color: 'black' },
|
||||||
|
{ tag: t.tagName, color: 'black' },
|
||||||
|
{ tag: t.bracket, color: 'black' },
|
||||||
|
{ tag: t.meta, color: 'black' },
|
||||||
|
{ tag: t.attributeName, color: 'black' },
|
||||||
|
{ tag: t.propertyName, color: 'black' },
|
||||||
|
{ tag: t.className, color: 'black' },
|
||||||
|
{ tag: t.invalid, color: 'black' },
|
||||||
|
],
|
||||||
|
});
|
||||||
@ -274,3 +274,31 @@ export const sol2note = (n, notation = 'letters') => {
|
|||||||
const oct = Math.floor(n / 12) - 1;
|
const oct = Math.floor(n / 12) - 1;
|
||||||
return note + oct;
|
return note + oct;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// code hashing helpers
|
||||||
|
|
||||||
|
export function unicodeToBase64(text) {
|
||||||
|
const utf8Bytes = new TextEncoder().encode(text);
|
||||||
|
const base64String = btoa(String.fromCharCode(...utf8Bytes));
|
||||||
|
return base64String;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64ToUnicode(base64String) {
|
||||||
|
const utf8Bytes = new Uint8Array(
|
||||||
|
atob(base64String)
|
||||||
|
.split('')
|
||||||
|
.map((char) => char.charCodeAt(0)),
|
||||||
|
);
|
||||||
|
const decodedText = new TextDecoder().decode(utf8Bytes);
|
||||||
|
return decodedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function code2hash(code) {
|
||||||
|
return encodeURIComponent(unicodeToBase64(code));
|
||||||
|
//return '#' + encodeURIComponent(btoa(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hash2code(hash) {
|
||||||
|
return base64ToUnicode(decodeURIComponent(hash));
|
||||||
|
//return atob(decodeURIComponent(codeParam || ''));
|
||||||
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ function connect() {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
|
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
|
||||||
* For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output)
|
* For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output/)
|
||||||
*
|
*
|
||||||
* @name osc
|
* @name osc
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
|
|||||||
@ -139,8 +139,8 @@ export function registerSoundfonts() {
|
|||||||
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 0.3, time);
|
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 0.3, time);
|
||||||
bufferSource.connect(envelope);
|
bufferSource.connect(envelope);
|
||||||
const stop = (releaseTime) => {
|
const stop = (releaseTime) => {
|
||||||
bufferSource.stop(releaseTime + release);
|
const silentAt = releaseEnvelope(releaseTime);
|
||||||
releaseEnvelope(releaseTime);
|
bufferSource.stop(silentAt);
|
||||||
};
|
};
|
||||||
bufferSource.onended = () => {
|
bufferSource.onended = () => {
|
||||||
bufferSource.disconnect();
|
bufferSource.disconnect();
|
||||||
|
|||||||
@ -10,21 +10,24 @@ export function gainNode(value) {
|
|||||||
// alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future
|
// alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future
|
||||||
export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => {
|
export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => {
|
||||||
const gainNode = getAudioContext().createGain();
|
const gainNode = getAudioContext().createGain();
|
||||||
|
let phase = begin;
|
||||||
gainNode.gain.setValueAtTime(0, begin);
|
gainNode.gain.setValueAtTime(0, begin);
|
||||||
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
|
phase += attack;
|
||||||
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain start
|
gainNode.gain.linearRampToValueAtTime(velocity, phase); // attack
|
||||||
|
phase += decay;
|
||||||
|
let sustainLevel = sustain * velocity;
|
||||||
|
gainNode.gain.linearRampToValueAtTime(sustainLevel, phase); // decay / sustain
|
||||||
// sustain end
|
// sustain end
|
||||||
return {
|
return {
|
||||||
node: gainNode,
|
node: gainNode,
|
||||||
stop: (t) => {
|
stop: (t) => {
|
||||||
//if (typeof gainNode.gain.cancelAndHoldAtTime === 'function') {
|
// to make sure the release won't begin before sustain is reached
|
||||||
// gainNode.gain.cancelAndHoldAtTime(t); // this seems to release instantly....
|
phase = Math.max(t, phase);
|
||||||
// see https://discord.com/channels/779427371270275082/937365093082079272/1086053607360712735
|
// see https://github.com/tidalcycles/strudel/issues/522
|
||||||
//} else {
|
gainNode.gain.setValueAtTime(sustainLevel, phase);
|
||||||
// firefox: this will glitch when the sustain has not been reached yet at the time of release
|
phase += release;
|
||||||
gainNode.gain.setValueAtTime(sustain * velocity, t);
|
gainNode.gain.linearRampToValueAtTime(0, phase); // release
|
||||||
//}
|
return phase;
|
||||||
gainNode.gain.linearRampToValueAtTime(0, t + release);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "superdough",
|
"name": "superdough",
|
||||||
"version": "0.9.11",
|
"version": "0.9.12",
|
||||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -312,8 +312,8 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
|||||||
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
||||||
releaseTime = t + (end - begin) * bufferDuration;
|
releaseTime = t + (end - begin) * bufferDuration;
|
||||||
}
|
}
|
||||||
bufferSource.stop(releaseTime + release);
|
const silentAt = releaseEnvelope(releaseTime);
|
||||||
releaseEnvelope(releaseTime);
|
bufferSource.stop(silentAt);
|
||||||
};
|
};
|
||||||
const handle = { node: out, bufferSource, stop };
|
const handle = { node: out, bufferSource, stop };
|
||||||
|
|
||||||
|
|||||||
@ -56,10 +56,9 @@ export function registerSynthSounds() {
|
|||||||
return {
|
return {
|
||||||
node: o.connect(g).connect(envelope),
|
node: o.connect(g).connect(envelope),
|
||||||
stop: (releaseTime) => {
|
stop: (releaseTime) => {
|
||||||
releaseEnvelope(releaseTime);
|
const silentAt = releaseEnvelope(releaseTime);
|
||||||
triggerRelease?.(releaseTime);
|
triggerRelease?.(releaseTime);
|
||||||
let end = releaseTime + release;
|
stop(silentAt);
|
||||||
stop(end);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
280
pnpm-lock.yaml
generated
280
pnpm-lock.yaml
generated
@ -1,5 +1,9 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
@ -71,6 +75,9 @@ importers:
|
|||||||
|
|
||||||
packages/codemirror:
|
packages/codemirror:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@codemirror/autocomplete':
|
||||||
|
specifier: ^6.6.0
|
||||||
|
version: 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
||||||
'@codemirror/commands':
|
'@codemirror/commands':
|
||||||
specifier: ^6.2.4
|
specifier: ^6.2.4
|
||||||
version: 6.2.4
|
version: 6.2.4
|
||||||
@ -80,6 +87,9 @@ importers:
|
|||||||
'@codemirror/language':
|
'@codemirror/language':
|
||||||
specifier: ^6.6.0
|
specifier: ^6.6.0
|
||||||
version: 6.6.0
|
version: 6.6.0
|
||||||
|
'@codemirror/search':
|
||||||
|
specifier: ^6.0.0
|
||||||
|
version: 6.2.3
|
||||||
'@codemirror/state':
|
'@codemirror/state':
|
||||||
specifier: ^6.2.0
|
specifier: ^6.2.0
|
||||||
version: 6.2.0
|
version: 6.2.0
|
||||||
@ -89,14 +99,78 @@ importers:
|
|||||||
'@lezer/highlight':
|
'@lezer/highlight':
|
||||||
specifier: ^1.1.4
|
specifier: ^1.1.4
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
|
'@replit/codemirror-emacs':
|
||||||
|
specifier: ^6.0.1
|
||||||
|
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
|
'@replit/codemirror-vim':
|
||||||
|
specifier: ^6.0.14
|
||||||
|
version: 6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
|
'@replit/codemirror-vscode-keymap':
|
||||||
|
specifier: ^6.0.2
|
||||||
|
version: 6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
'@strudel.cycles/core':
|
'@strudel.cycles/core':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core
|
version: link:../core
|
||||||
|
'@uiw/codemirror-themes':
|
||||||
|
specifier: ^4.19.16
|
||||||
|
version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
|
'@uiw/codemirror-themes-all':
|
||||||
|
specifier: ^4.19.16
|
||||||
|
version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
|
react-dom:
|
||||||
|
specifier: ^18.2.0
|
||||||
|
version: 18.2.0(react@18.2.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3
|
version: 4.3.3
|
||||||
|
|
||||||
|
packages/codemirror/examples/strudelmirror:
|
||||||
|
dependencies:
|
||||||
|
'@strudel.cycles/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../core
|
||||||
|
'@strudel.cycles/csound':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../csound
|
||||||
|
'@strudel.cycles/midi':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../midi
|
||||||
|
'@strudel.cycles/mini':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../mini
|
||||||
|
'@strudel.cycles/osc':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../osc
|
||||||
|
'@strudel.cycles/serial':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../serial
|
||||||
|
'@strudel.cycles/soundfonts':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../soundfonts
|
||||||
|
'@strudel.cycles/tonal':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../tonal
|
||||||
|
'@strudel.cycles/transpiler':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../transpiler
|
||||||
|
'@strudel.cycles/webaudio':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../webaudio
|
||||||
|
'@strudel.cycles/xen':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../xen
|
||||||
|
'@strudel/codemirror':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../..
|
||||||
|
'@strudel/hydra':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../hydra
|
||||||
|
devDependencies:
|
||||||
|
vite:
|
||||||
|
specifier: ^5.0.8
|
||||||
|
version: 5.0.8
|
||||||
|
|
||||||
packages/core:
|
packages/core:
|
||||||
dependencies:
|
dependencies:
|
||||||
fraction.js:
|
fraction.js:
|
||||||
@ -4053,6 +4127,110 @@ packages:
|
|||||||
rollup: 2.79.1
|
rollup: 2.79.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@rollup/rollup-android-arm-eabi@4.9.0:
|
||||||
|
resolution: {integrity: sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-android-arm64@4.9.0:
|
||||||
|
resolution: {integrity: sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-darwin-arm64@4.9.0:
|
||||||
|
resolution: {integrity: sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-darwin-x64@4.9.0:
|
||||||
|
resolution: {integrity: sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-linux-arm-gnueabihf@4.9.0:
|
||||||
|
resolution: {integrity: sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-linux-arm64-gnu@4.9.0:
|
||||||
|
resolution: {integrity: sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-linux-arm64-musl@4.9.0:
|
||||||
|
resolution: {integrity: sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-linux-riscv64-gnu@4.9.0:
|
||||||
|
resolution: {integrity: sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-linux-x64-gnu@4.9.0:
|
||||||
|
resolution: {integrity: sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-linux-x64-musl@4.9.0:
|
||||||
|
resolution: {integrity: sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-win32-arm64-msvc@4.9.0:
|
||||||
|
resolution: {integrity: sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-win32-ia32-msvc@4.9.0:
|
||||||
|
resolution: {integrity: sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@rollup/rollup-win32-x64-msvc@4.9.0:
|
||||||
|
resolution: {integrity: sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: true
|
||||||
|
optional: true
|
||||||
|
|
||||||
/@sigstore/protobuf-specs@0.1.0:
|
/@sigstore/protobuf-specs@0.1.0:
|
||||||
resolution: {integrity: sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==}
|
resolution: {integrity: sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==}
|
||||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||||
@ -5572,6 +5750,7 @@ packages:
|
|||||||
|
|
||||||
/b4a@1.6.4:
|
/b4a@1.6.4:
|
||||||
resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
|
resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
|
||||||
|
requiresBuild: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/babel-plugin-add-module-exports@0.2.1:
|
/babel-plugin-add-module-exports@0.2.1:
|
||||||
@ -5968,7 +6147,7 @@ packages:
|
|||||||
normalize-path: 3.0.0
|
normalize-path: 3.0.0
|
||||||
readdirp: 3.6.0
|
readdirp: 3.6.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.3
|
||||||
|
|
||||||
/chord-voicings@0.0.1:
|
/chord-voicings@0.0.1:
|
||||||
resolution: {integrity: sha512-SutgB/4ynkkuiK6qdQ/k3QvCFcH0Vj8Ch4t6LbRyRQbVzP/TOztiCk3kvXd516UZ6fqk7ijDRELEFcKN+6V8sA==}
|
resolution: {integrity: sha512-SutgB/4ynkkuiK6qdQ/k3QvCFcH0Vj8Ch4t6LbRyRQbVzP/TOztiCk3kvXd516UZ6fqk7ijDRELEFcKN+6V8sA==}
|
||||||
@ -6154,6 +6333,7 @@ packages:
|
|||||||
|
|
||||||
/color-string@1.9.1:
|
/color-string@1.9.1:
|
||||||
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
simple-swizzle: 0.2.2
|
simple-swizzle: 0.2.2
|
||||||
@ -6166,6 +6346,7 @@ packages:
|
|||||||
/color@4.2.3:
|
/color@4.2.3:
|
||||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||||
engines: {node: '>=12.5.0'}
|
engines: {node: '>=12.5.0'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
color-string: 1.9.1
|
color-string: 1.9.1
|
||||||
@ -6629,6 +6810,7 @@ packages:
|
|||||||
/detect-libc@2.0.2:
|
/detect-libc@2.0.2:
|
||||||
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
|
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
requiresBuild: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/detective-amd@4.0.1:
|
/detective-amd@4.0.1:
|
||||||
@ -7472,6 +7654,7 @@ packages:
|
|||||||
|
|
||||||
/fast-fifo@1.3.2:
|
/fast-fifo@1.3.2:
|
||||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||||
|
requiresBuild: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/fast-glob@3.2.12:
|
/fast-glob@3.2.12:
|
||||||
@ -7736,8 +7919,8 @@ packages:
|
|||||||
/fs.realpath@1.0.0:
|
/fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
|
||||||
/fsevents@2.3.2:
|
/fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@ -10521,6 +10704,12 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
/nanoid@3.3.7:
|
||||||
|
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/nanoid@4.0.2:
|
/nanoid@4.0.2:
|
||||||
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
|
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
|
||||||
engines: {node: ^14 || ^16 || >=18}
|
engines: {node: ^14 || ^16 || >=18}
|
||||||
@ -10579,6 +10768,7 @@ packages:
|
|||||||
|
|
||||||
/node-addon-api@6.1.0:
|
/node-addon-api@6.1.0:
|
||||||
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
|
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
|
||||||
|
requiresBuild: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/node-domexception@1.0.0:
|
/node-domexception@1.0.0:
|
||||||
@ -11675,6 +11865,15 @@ packages:
|
|||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
|
|
||||||
|
/postcss@8.4.32:
|
||||||
|
resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==}
|
||||||
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.7
|
||||||
|
picocolors: 1.0.0
|
||||||
|
source-map-js: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/prebuild-install@7.1.1:
|
/prebuild-install@7.1.1:
|
||||||
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
|
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -11875,6 +12074,7 @@ packages:
|
|||||||
|
|
||||||
/queue-tick@1.0.1:
|
/queue-tick@1.0.1:
|
||||||
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
|
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
|
||||||
|
requiresBuild: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/quick-lru@4.0.1:
|
/quick-lru@4.0.1:
|
||||||
@ -12504,7 +12704,7 @@ packages:
|
|||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rollup@3.21.0:
|
/rollup@3.21.0:
|
||||||
@ -12512,7 +12712,7 @@ packages:
|
|||||||
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rollup@3.28.0:
|
/rollup@3.28.0:
|
||||||
@ -12520,7 +12720,28 @@ packages:
|
|||||||
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
/rollup@4.9.0:
|
||||||
|
resolution: {integrity: sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==}
|
||||||
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
optionalDependencies:
|
||||||
|
'@rollup/rollup-android-arm-eabi': 4.9.0
|
||||||
|
'@rollup/rollup-android-arm64': 4.9.0
|
||||||
|
'@rollup/rollup-darwin-arm64': 4.9.0
|
||||||
|
'@rollup/rollup-darwin-x64': 4.9.0
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf': 4.9.0
|
||||||
|
'@rollup/rollup-linux-arm64-gnu': 4.9.0
|
||||||
|
'@rollup/rollup-linux-arm64-musl': 4.9.0
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu': 4.9.0
|
||||||
|
'@rollup/rollup-linux-x64-gnu': 4.9.0
|
||||||
|
'@rollup/rollup-linux-x64-musl': 4.9.0
|
||||||
|
'@rollup/rollup-win32-arm64-msvc': 4.9.0
|
||||||
|
'@rollup/rollup-win32-ia32-msvc': 4.9.0
|
||||||
|
'@rollup/rollup-win32-x64-msvc': 4.9.0
|
||||||
|
fsevents: 2.3.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/run-async@2.4.1:
|
/run-async@2.4.1:
|
||||||
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
||||||
@ -12730,6 +12951,7 @@ packages:
|
|||||||
|
|
||||||
/simple-swizzle@0.2.2:
|
/simple-swizzle@0.2.2:
|
||||||
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish: 0.3.2
|
is-arrayish: 0.3.2
|
||||||
optional: true
|
optional: true
|
||||||
@ -12943,6 +13165,7 @@ packages:
|
|||||||
|
|
||||||
/streamx@2.15.2:
|
/streamx@2.15.2:
|
||||||
resolution: {integrity: sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg==}
|
resolution: {integrity: sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-fifo: 1.3.2
|
fast-fifo: 1.3.2
|
||||||
queue-tick: 1.0.1
|
queue-tick: 1.0.1
|
||||||
@ -13220,6 +13443,7 @@ packages:
|
|||||||
|
|
||||||
/tar-fs@3.0.4:
|
/tar-fs@3.0.4:
|
||||||
resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==}
|
resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
mkdirp-classic: 0.5.3
|
mkdirp-classic: 0.5.3
|
||||||
pump: 3.0.0
|
pump: 3.0.0
|
||||||
@ -13238,6 +13462,7 @@ packages:
|
|||||||
|
|
||||||
/tar-stream@3.1.6:
|
/tar-stream@3.1.6:
|
||||||
resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==}
|
resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
b4a: 1.6.4
|
b4a: 1.6.4
|
||||||
fast-fifo: 1.3.2
|
fast-fifo: 1.3.2
|
||||||
@ -14008,7 +14233,7 @@ packages:
|
|||||||
mlly: 1.4.0
|
mlly: 1.4.0
|
||||||
pathe: 1.1.1
|
pathe: 1.1.1
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
vite: 4.4.5(@types/node@18.16.3)
|
vite: 4.5.0(@types/node@18.16.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- less
|
- less
|
||||||
@ -14067,7 +14292,7 @@ packages:
|
|||||||
postcss: 8.4.23
|
postcss: 8.4.23
|
||||||
rollup: 3.21.0
|
rollup: 3.21.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite@4.4.5(@types/node@18.16.3):
|
/vite@4.4.5(@types/node@18.16.3):
|
||||||
@ -14103,7 +14328,7 @@ packages:
|
|||||||
postcss: 8.4.27
|
postcss: 8.4.27
|
||||||
rollup: 3.28.0
|
rollup: 3.28.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite@4.5.0(@types/node@18.16.3):
|
/vite@4.5.0(@types/node@18.16.3):
|
||||||
@ -14139,7 +14364,42 @@ packages:
|
|||||||
postcss: 8.4.31
|
postcss: 8.4.31
|
||||||
rollup: 3.28.0
|
rollup: 3.28.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
/vite@5.0.8:
|
||||||
|
resolution: {integrity: sha512-jYMALd8aeqR3yS9xlHd0OzQJndS9fH5ylVgWdB+pxTwxLKdO1pgC5Dlb398BUxpfaBxa4M9oT7j1g503Gaj5IQ==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': ^18.0.0 || >=20.0.0
|
||||||
|
less: '*'
|
||||||
|
lightningcss: ^1.21.0
|
||||||
|
sass: '*'
|
||||||
|
stylus: '*'
|
||||||
|
sugarss: '*'
|
||||||
|
terser: ^5.4.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
less:
|
||||||
|
optional: true
|
||||||
|
lightningcss:
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
stylus:
|
||||||
|
optional: true
|
||||||
|
sugarss:
|
||||||
|
optional: true
|
||||||
|
terser:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.19.5
|
||||||
|
postcss: 8.4.32
|
||||||
|
rollup: 4.9.0
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vitefu@0.2.4(vite@4.5.0):
|
/vitefu@0.2.4(vite@4.5.0):
|
||||||
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
||||||
|
|||||||
@ -7,3 +7,4 @@ packages:
|
|||||||
- "packages/react/examples/nano-repl"
|
- "packages/react/examples/nano-repl"
|
||||||
- "packages/web/examples/repl-example"
|
- "packages/web/examples/repl-example"
|
||||||
- "packages/superdough/example"
|
- "packages/superdough/example"
|
||||||
|
- "packages/codemirror/examples/strudelmirror"
|
||||||
|
|||||||
58
website/src/components/HeadCommonNew.astro
Normal file
58
website/src/components/HeadCommonNew.astro
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
import { pwaInfo } from 'virtual:pwa-info';
|
||||||
|
import '../styles/index.css';
|
||||||
|
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Global Metadata -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
|
<link rel="icon" type="image/svg+xml" href={`${baseNoTrailing}/favicon.ico`} />
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Strudel is a music live coding environment for the browser, porting the TidalCycles pattern language to JavaScript."
|
||||||
|
/>
|
||||||
|
<link rel="icon" href={`${baseNoTrailing}/favicon.ico`} />
|
||||||
|
<link rel="apple-touch-icon" href={`${baseNoTrailing}/icons/apple-icon-180.png`} sizes="180x180" />
|
||||||
|
<meta name="theme-color" content="#222222" />
|
||||||
|
|
||||||
|
<base href={BASE_URL} />
|
||||||
|
|
||||||
|
<!-- Scrollable a11y code helper -->
|
||||||
|
<script src{`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
|
||||||
|
|
||||||
|
<script src="/src/pwa.ts"></script>
|
||||||
|
<!-- this does not work for some reason: -->
|
||||||
|
<!-- <style is:global define:vars={strudelTheme}></style> -->
|
||||||
|
<!-- the following variables are just a fallback to make sure everything is readable without JS -->
|
||||||
|
<style is:global>
|
||||||
|
:root {
|
||||||
|
--background: #222;
|
||||||
|
--lineBackground: #22222299;
|
||||||
|
--foreground: #fff;
|
||||||
|
--caret: #ffcc00;
|
||||||
|
--selection: rgba(128, 203, 196, 0.5);
|
||||||
|
--selectionMatch: #036dd626;
|
||||||
|
--lineHighlight: #00000050;
|
||||||
|
--gutterBackground: transparent;
|
||||||
|
--gutterForeground: #8a919966;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// https://medium.com/quick-code/100vh-problem-with-ios-safari-92ab23c852a8
|
||||||
|
const appHeight = () => {
|
||||||
|
const doc = document.documentElement;
|
||||||
|
doc.style.setProperty('--app-height', `${window.innerHeight - 1}px`);
|
||||||
|
};
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('resize', appHeight);
|
||||||
|
appHeight();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -37,4 +37,4 @@ What about combining different notes with different sounds at the same time?
|
|||||||
|
|
||||||
Hmm, something interesting is going on there, related to there being five notes and three sounds.
|
Hmm, something interesting is going on there, related to there being five notes and three sounds.
|
||||||
|
|
||||||
Let's now take a step back and think about the Strudel [Code](/learn/code) we've been hearing so far.
|
Let's now take a step back and think about the Strudel [Code](/learn/code/) we've been hearing so far.
|
||||||
|
|||||||
@ -64,4 +64,4 @@ Together with layer, struct and voicings, this can be used to create a basic bac
|
|||||||
)`}
|
)`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
So far, we've stayed within the browser. [MIDI and OSC](/learn/input-output) are ways to break out of it.
|
So far, we've stayed within the browser. [MIDI and OSC](/learn/input-output/) are ways to break out of it.
|
||||||
|
|||||||
96
website/src/pages/vanilla/index.astro
Normal file
96
website/src/pages/vanilla/index.astro
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
import HeadCommonNew from '../../components/HeadCommonNew.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
<head>
|
||||||
|
<HeadCommonNew />
|
||||||
|
<title>Strudel Vanilla REPL</title>
|
||||||
|
</head>
|
||||||
|
<body class="h-app-height">
|
||||||
|
<div class="settings">
|
||||||
|
<form name="settings" class="flex flex-col space-y-1 bg-[#00000080]">
|
||||||
|
<label
|
||||||
|
>theme
|
||||||
|
<select name="theme">
|
||||||
|
<option>strudelTheme</option>
|
||||||
|
<option>bluescreen</option>
|
||||||
|
<option>blackscreen</option>
|
||||||
|
<option>whitescreen</option>
|
||||||
|
<option>teletext</option>
|
||||||
|
<option>algoboy</option>
|
||||||
|
<option>terminal</option>
|
||||||
|
<option>abcdef</option>
|
||||||
|
<option>androidstudio</option>
|
||||||
|
<option>atomone</option>
|
||||||
|
<option>aura</option>
|
||||||
|
<option>bespin</option>
|
||||||
|
<option>darcula</option>
|
||||||
|
<option>dracula</option>
|
||||||
|
<option>duotoneDark</option>
|
||||||
|
<option>eclipse</option>
|
||||||
|
<option>githubDark</option>
|
||||||
|
<option>gruvboxDark</option>
|
||||||
|
<option>materialDark</option>
|
||||||
|
<option>nord</option>
|
||||||
|
<option>okaidia</option>
|
||||||
|
<option>solarizedDark</option>
|
||||||
|
<option>sublime</option>
|
||||||
|
<option>tokyoNight</option>
|
||||||
|
<option>tokyoNightStorm</option>
|
||||||
|
<option>vscodeDark</option>
|
||||||
|
<option>xcodeDark</option>
|
||||||
|
<option>bbedit</option>
|
||||||
|
<option>duotoneLight</option>
|
||||||
|
<option>githubLight</option>
|
||||||
|
<option>gruvboxLight</option>
|
||||||
|
<option>materialLight</option>
|
||||||
|
<option>noctisLilac</option>
|
||||||
|
<option>solarizedLight</option>
|
||||||
|
<option>tokyoNightDay</option>
|
||||||
|
<option>xcodeLight</option>
|
||||||
|
</select> </label
|
||||||
|
><br />
|
||||||
|
<label
|
||||||
|
>keybindings
|
||||||
|
<select name="keybindings">
|
||||||
|
<option>codemirror</option>
|
||||||
|
<option>vim</option>
|
||||||
|
<option>emacs</option>
|
||||||
|
<option>vscode</option>
|
||||||
|
</select> </label
|
||||||
|
><br />
|
||||||
|
<label>fontFamily
|
||||||
|
<select name="fontFamily">
|
||||||
|
<option value="monospace">monospace</option>
|
||||||
|
<option value="BigBlueTerminal">BigBlueTerminal</option>
|
||||||
|
<option value="x3270">x3270</option>
|
||||||
|
<option value="PressStart">PressStart2P</option>
|
||||||
|
<option value="galactico">galactico</option>
|
||||||
|
<option value="we-come-in-peace">we-come-in-peace</option>
|
||||||
|
<option value="FiraCode">FiraCode</option>
|
||||||
|
<option value="FiraCode-SemiBold">FiraCode-SemiBold</option>
|
||||||
|
<option value="teletext">teletext</option>
|
||||||
|
<option value="mode7">mode7</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
<label>fontSize <input type="number" name="fontSize" /></label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isLineNumbersDisplayed" />isLineNumbersDisplayed</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isActiveLineHighlighted" />isActiveLineHighlighted</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isPatternHighlightingEnabled" />isPatternHighlightingEnabled</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isFlashEnabled" />isFlashEnabled</label>
|
||||||
|
<br />
|
||||||
|
<label><input type="checkbox" name="isLineWrappingEnabled" />isLineWrappingEnabled</label>
|
||||||
|
<!-- <label><input type="checkbox" name="isAutoCompletionEnabled" />isAutoCompletionEnabled</label> -->
|
||||||
|
<!-- <label><input type="checkbox" name="isTooltipEnabled" />isTooltipEnabled</label> -->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="code"></div>
|
||||||
|
<script src="../../repl/vanilla/vanilla.mjs"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -4,7 +4,16 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
|||||||
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 { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core';
|
import {
|
||||||
|
cleanupDraw,
|
||||||
|
cleanupUi,
|
||||||
|
controls,
|
||||||
|
evalScope,
|
||||||
|
getDrawContext,
|
||||||
|
logger,
|
||||||
|
code2hash,
|
||||||
|
hash2code,
|
||||||
|
} from '@strudel.cycles/core';
|
||||||
import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react';
|
import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react';
|
||||||
import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio';
|
import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
@ -17,14 +26,24 @@ import { prebake } from './prebake.mjs';
|
|||||||
import * as tunes from './tunes.mjs';
|
import * as tunes from './tunes.mjs';
|
||||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||||
import { themes } from './themes.mjs';
|
import { themes } from './themes.mjs';
|
||||||
import { settingsMap, useSettings, setLatestCode, updateUserCode, setActivePattern } from '../settings.mjs';
|
import {
|
||||||
|
settingsMap,
|
||||||
|
useSettings,
|
||||||
|
setLatestCode,
|
||||||
|
updateUserCode,
|
||||||
|
setActivePattern,
|
||||||
|
getActivePattern,
|
||||||
|
getUserPattern,
|
||||||
|
initUserCode,
|
||||||
|
} from '../settings.mjs';
|
||||||
import Loader from './Loader';
|
import Loader from './Loader';
|
||||||
import { settingPatterns } from '../settings.mjs';
|
import { settingPatterns } from '../settings.mjs';
|
||||||
import { code2hash, hash2code } from './helpers.mjs';
|
|
||||||
import { isTauri } from '../tauri.mjs';
|
import { isTauri } from '../tauri.mjs';
|
||||||
import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
|
import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
|
||||||
import { writeText } from '@tauri-apps/api/clipboard';
|
import { writeText } from '@tauri-apps/api/clipboard';
|
||||||
import { defaultAudioDeviceName, getAudioDevices, setAudioDevice } from './panel/AudioDeviceSelector';
|
import { defaultAudioDeviceName, getAudioDevices, setAudioDevice } from './panel/AudioDeviceSelector';
|
||||||
|
import { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs';
|
||||||
|
|
||||||
|
|
||||||
const { latestCode } = settingsMap.get();
|
const { latestCode } = settingsMap.get();
|
||||||
|
|
||||||
@ -132,6 +151,7 @@ export function Repl({ embedded = false }) {
|
|||||||
isLineWrappingEnabled,
|
isLineWrappingEnabled,
|
||||||
panelPosition,
|
panelPosition,
|
||||||
isZen,
|
isZen,
|
||||||
|
audio_device_selection,
|
||||||
activePattern,
|
activePattern,
|
||||||
audioDeviceName,
|
audioDeviceName,
|
||||||
} = useSettings();
|
} = useSettings();
|
||||||
@ -180,6 +200,7 @@ export function Repl({ embedded = false }) {
|
|||||||
let msg;
|
let msg;
|
||||||
if (decoded) {
|
if (decoded) {
|
||||||
setCode(decoded);
|
setCode(decoded);
|
||||||
|
initUserCode(decoded);
|
||||||
msg = `I have loaded the code from the URL.`;
|
msg = `I have loaded the code from the URL.`;
|
||||||
} else if (latestCode) {
|
} else if (latestCode) {
|
||||||
setCode(latestCode);
|
setCode(latestCode);
|
||||||
@ -188,6 +209,8 @@ export function Repl({ embedded = false }) {
|
|||||||
setCode(randomTune);
|
setCode(randomTune);
|
||||||
msg = `A random code snippet named "${name}" has been loaded!`;
|
msg = `A random code snippet named "${name}" has been loaded!`;
|
||||||
}
|
}
|
||||||
|
//registers samples that have been saved to the index DB
|
||||||
|
registerSamplesFromDB(userSamplesDBConfig);
|
||||||
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
|
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
|
||||||
setPending(false);
|
setPending(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
export function unicodeToBase64(text) {
|
|
||||||
const utf8Bytes = new TextEncoder().encode(text);
|
|
||||||
const base64String = btoa(String.fromCharCode(...utf8Bytes));
|
|
||||||
return base64String;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function base64ToUnicode(base64String) {
|
|
||||||
const utf8Bytes = new Uint8Array(
|
|
||||||
atob(base64String)
|
|
||||||
.split('')
|
|
||||||
.map((char) => char.charCodeAt(0)),
|
|
||||||
);
|
|
||||||
const decodedText = new TextDecoder().decode(utf8Bytes);
|
|
||||||
return decodedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function code2hash(code) {
|
|
||||||
return encodeURIComponent(unicodeToBase64(code));
|
|
||||||
//return '#' + encodeURIComponent(btoa(code));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hash2code(hash) {
|
|
||||||
return base64ToUnicode(decodeURIComponent(hash));
|
|
||||||
//return atob(decodeURIComponent(codeParam || ''));
|
|
||||||
}
|
|
||||||
148
website/src/repl/idbutils.mjs
Normal file
148
website/src/repl/idbutils.mjs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { registerSound, onTriggerSample } from '@strudel.cycles/webaudio';
|
||||||
|
import { isAudioFile } from './files.mjs';
|
||||||
|
import { logger } from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
//utilites for writing and reading to the indexdb
|
||||||
|
|
||||||
|
export const userSamplesDBConfig = {
|
||||||
|
dbName: 'samples',
|
||||||
|
table: 'usersamples',
|
||||||
|
columns: ['blob', 'title'],
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// deletes all of the databases, useful for debugging
|
||||||
|
const clearIDB = () => {
|
||||||
|
window.indexedDB
|
||||||
|
.databases()
|
||||||
|
.then((r) => {
|
||||||
|
for (var i = 0; i < r.length; i++) window.indexedDB.deleteDatabase(r[i].name);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
alert('All data cleared.');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// queries the DB, and registers the sounds so they can be played
|
||||||
|
export const registerSamplesFromDB = (config, onComplete = () => {}) => {
|
||||||
|
openDB(config, (objectStore) => {
|
||||||
|
let query = objectStore.getAll();
|
||||||
|
query.onsuccess = (event) => {
|
||||||
|
const soundFiles = event.target.result;
|
||||||
|
if (!soundFiles?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sounds = new Map();
|
||||||
|
[...soundFiles]
|
||||||
|
.sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' }))
|
||||||
|
.forEach((soundFile) => {
|
||||||
|
const title = soundFile.title;
|
||||||
|
if (!isAudioFile(title)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const splitRelativePath = soundFile.id?.split('/');
|
||||||
|
const parentDirectory = splitRelativePath[splitRelativePath.length - 2];
|
||||||
|
const soundPath = soundFile.blob;
|
||||||
|
const soundPaths = sounds.get(parentDirectory) ?? new Set();
|
||||||
|
soundPaths.add(soundPath);
|
||||||
|
sounds.set(parentDirectory, soundPaths);
|
||||||
|
});
|
||||||
|
|
||||||
|
sounds.forEach((soundPaths, key) => {
|
||||||
|
const value = Array.from(soundPaths);
|
||||||
|
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
|
||||||
|
type: 'sample',
|
||||||
|
samples: value,
|
||||||
|
baseUrl: undefined,
|
||||||
|
prebake: false,
|
||||||
|
tag: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
logger('imported sounds registered!', 'success');
|
||||||
|
onComplete();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// creates a blob from a buffer that can be read
|
||||||
|
async function bufferToDataUrl(buf) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
var blob = new Blob([buf], { type: 'application/octet-binary' });
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function (event) {
|
||||||
|
resolve(event.target.result);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//open db and initialize it if necessary
|
||||||
|
const openDB = (config, onOpened) => {
|
||||||
|
const { dbName, version, table, columns } = config;
|
||||||
|
if (!('indexedDB' in window)) {
|
||||||
|
console.log('IndexedDB is not supported.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dbOpen = indexedDB.open(dbName, version);
|
||||||
|
|
||||||
|
dbOpen.onupgradeneeded = (_event) => {
|
||||||
|
const db = dbOpen.result;
|
||||||
|
const objectStore = db.createObjectStore(table, { keyPath: 'id', autoIncrement: false });
|
||||||
|
columns.forEach((c) => {
|
||||||
|
objectStore.createIndex(c, c, { unique: false });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
dbOpen.onerror = (err) => {
|
||||||
|
logger('Something went wrong while trying to open the the client DB', 'error');
|
||||||
|
console.error(`indexedDB error: ${err.errorCode}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
dbOpen.onsuccess = () => {
|
||||||
|
const db = dbOpen.result;
|
||||||
|
|
||||||
|
const // lock store for writing
|
||||||
|
writeTransaction = db.transaction([table], 'readwrite'),
|
||||||
|
// get object store
|
||||||
|
objectStore = writeTransaction.objectStore(table);
|
||||||
|
onOpened(objectStore, db);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const processFilesForIDB = async (files) => {
|
||||||
|
return await Promise.all(
|
||||||
|
Array.from(files)
|
||||||
|
.map(async (s) => {
|
||||||
|
const title = s.name;
|
||||||
|
if (!isAudioFile(title)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//create obscured url to file system that can be fetched
|
||||||
|
const sUrl = URL.createObjectURL(s);
|
||||||
|
//fetch the sound and turn it into a buffer array
|
||||||
|
const buf = await fetch(sUrl).then((res) => res.arrayBuffer());
|
||||||
|
//create a url blob containing all of the buffer data
|
||||||
|
const base64 = await bufferToDataUrl(buf);
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
blob: base64,
|
||||||
|
id: s.webkitRelativePath,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean),
|
||||||
|
).catch((error) => {
|
||||||
|
logger('Something went wrong while processing uploaded files', 'error');
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadSamplesToDB = async (config, files) => {
|
||||||
|
await processFilesForIDB(files).then((files) => {
|
||||||
|
const onOpened = (objectStore, _db) => {
|
||||||
|
files.forEach((file) => {
|
||||||
|
if (file == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
objectStore.put(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
openDB(config, onOpened);
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -9,7 +9,7 @@ export function ButtonGroup({ value, onChange, items }) {
|
|||||||
key={key}
|
key={key}
|
||||||
onClick={() => onChange(key)}
|
onClick={() => onChange(key)}
|
||||||
className={cx(
|
className={cx(
|
||||||
'px-2 border-b h-8',
|
'px-2 border-b h-8 whitespace-nowrap',
|
||||||
// i === 0 && 'rounded-l-md',
|
// i === 0 && 'rounded-l-md',
|
||||||
// i === arr.length - 1 && 'rounded-r-md',
|
// i === arr.length - 1 && 'rounded-r-md',
|
||||||
// value === key ? 'bg-background' : 'bg-lineHighlight',
|
// value === key ? 'bg-background' : 'bg-lineHighlight',
|
||||||
|
|||||||
43
website/src/repl/panel/ImportSoundsButton.jsx
Normal file
43
website/src/repl/panel/ImportSoundsButton.jsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { registerSamplesFromDB, uploadSamplesToDB, userSamplesDBConfig } from '../idbutils.mjs';
|
||||||
|
|
||||||
|
//choose a directory to locally import samples
|
||||||
|
export default function ImportSoundsButton({ onComplete }) {
|
||||||
|
let fileUploadRef = React.createRef();
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const onChange = useCallback(async () => {
|
||||||
|
if (!fileUploadRef.current.files?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsUploading(true);
|
||||||
|
await uploadSamplesToDB(userSamplesDBConfig, fileUploadRef.current.files).then(() => {
|
||||||
|
registerSamplesFromDB(userSamplesDBConfig, () => {
|
||||||
|
onComplete();
|
||||||
|
setIsUploading(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
style={{ alignItems: 'center' }}
|
||||||
|
className="flex bg-background ml-2 pl-2 pr-2 max-w-[300px] rounded-md hover:opacity-50 whitespace-nowrap cursor-pointer"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
disabled={isUploading}
|
||||||
|
ref={fileUploadRef}
|
||||||
|
id="audio_file"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
type="file"
|
||||||
|
directory=""
|
||||||
|
webkitdirectory=""
|
||||||
|
multiple
|
||||||
|
accept="audio/*"
|
||||||
|
onChange={() => {
|
||||||
|
onChange();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{isUploading ? 'importing...' : 'import sounds'}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,27 +1,27 @@
|
|||||||
|
import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import * as tunes from '../tunes.mjs';
|
|
||||||
import {
|
import {
|
||||||
useSettings,
|
|
||||||
clearUserPatterns,
|
clearUserPatterns,
|
||||||
newUserPattern,
|
|
||||||
setActivePattern,
|
|
||||||
deleteActivePattern,
|
deleteActivePattern,
|
||||||
duplicateActivePattern,
|
duplicateActivePattern,
|
||||||
|
exportPatterns,
|
||||||
getUserPattern,
|
getUserPattern,
|
||||||
getUserPatterns,
|
importPatterns,
|
||||||
|
newUserPattern,
|
||||||
renameActivePattern,
|
renameActivePattern,
|
||||||
addUserPattern,
|
setActivePattern,
|
||||||
setUserPatterns,
|
useActivePattern,
|
||||||
|
useSettings,
|
||||||
} from '../../settings.mjs';
|
} from '../../settings.mjs';
|
||||||
import { logger } from '@strudel.cycles/core';
|
import * as tunes from '../tunes.mjs';
|
||||||
import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid';
|
|
||||||
|
|
||||||
function classNames(...classes) {
|
function classNames(...classes) {
|
||||||
return classes.filter(Boolean).join(' ');
|
return classes.filter(Boolean).join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PatternsTab({ context }) {
|
export function PatternsTab({ context }) {
|
||||||
const { userPatterns, activePattern } = useSettings();
|
const { userPatterns } = useSettings();
|
||||||
|
const activePattern = useActivePattern();
|
||||||
const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]);
|
const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]);
|
||||||
return (
|
return (
|
||||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-4 pb-4">
|
<div className="px-4 w-full dark:text-white text-stone-900 space-y-4 pb-4">
|
||||||
@ -85,38 +85,11 @@ export function PatternsTab({ context }) {
|
|||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
accept="text/plain,application/json"
|
accept="text/plain,application/json"
|
||||||
onChange={async (e) => {
|
onChange={(e) => importPatterns(e.target.files)}
|
||||||
const files = Array.from(e.target.files);
|
|
||||||
await Promise.all(
|
|
||||||
files.map(async (file, i) => {
|
|
||||||
const content = await file.text();
|
|
||||||
if (file.type === 'application/json') {
|
|
||||||
const userPatterns = getUserPatterns() || {};
|
|
||||||
setUserPatterns({ ...userPatterns, ...JSON.parse(content) });
|
|
||||||
} else if (file.type === 'text/plain') {
|
|
||||||
const name = file.name.replace(/\.[^/.]+$/, '');
|
|
||||||
addUserPattern(name, { code: content });
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
logger(`import done!`);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
import
|
import
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button className="hover:opacity-50" onClick={() => exportPatterns()}>
|
||||||
className="hover:opacity-50"
|
|
||||||
onClick={() => {
|
|
||||||
const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' });
|
|
||||||
const downloadLink = document.createElement('a');
|
|
||||||
downloadLink.href = window.URL.createObjectURL(blob);
|
|
||||||
const date = new Date().toISOString().split('T')[0];
|
|
||||||
downloadLink.download = `strudel_patterns_${date}.json`;
|
|
||||||
document.body.appendChild(downloadLink);
|
|
||||||
downloadLink.click();
|
|
||||||
document.body.removeChild(downloadLink);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
export
|
export
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { getAudioContext, soundMap, connectToDestination } from '@strudel.cycles
|
|||||||
import React, { useMemo, useRef } from 'react';
|
import React, { useMemo, useRef } from 'react';
|
||||||
import { settingsMap, useSettings } from '../../settings.mjs';
|
import { settingsMap, useSettings } from '../../settings.mjs';
|
||||||
import { ButtonGroup } from './Forms.jsx';
|
import { ButtonGroup } from './Forms.jsx';
|
||||||
|
import ImportSoundsButton from './ImportSoundsButton.jsx';
|
||||||
|
|
||||||
const getSamples = (samples) =>
|
const getSamples = (samples) =>
|
||||||
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
|
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
|
||||||
@ -43,7 +44,7 @@ export function SoundsTab() {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900">
|
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900">
|
||||||
<div className="pb-2 flex-none">
|
<div className="pb-2 flex shrink-0 overflow-auto">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
value={soundsFilter}
|
value={soundsFilter}
|
||||||
onChange={(value) => settingsMap.setKey('soundsFilter', value)}
|
onChange={(value) => settingsMap.setKey('soundsFilter', value)}
|
||||||
@ -54,6 +55,7 @@ export function SoundsTab() {
|
|||||||
user: 'User',
|
user: 'User',
|
||||||
}}
|
}}
|
||||||
></ButtonGroup>
|
></ButtonGroup>
|
||||||
|
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal">
|
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal">
|
||||||
{soundEntries.map(([name, { data, onTrigger }]) => (
|
{soundEntries.map(([name, { data, onTrigger }]) => (
|
||||||
|
|||||||
@ -114,6 +114,9 @@ export async function prebake() {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
'github:tidalcycles/Dirt-Samples/master/',
|
'github:tidalcycles/Dirt-Samples/master/',
|
||||||
|
{
|
||||||
|
prebake: true,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
// await samples('github:tidalcycles/Dirt-Samples/master');
|
// await samples('github:tidalcycles/Dirt-Samples/master');
|
||||||
|
|||||||
34
website/src/repl/vanilla/vanilla.css
Normal file
34
website/src/repl/vanilla/vanilla.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
body,
|
||||||
|
input {
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
background-color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#code,
|
||||||
|
.cm-editor,
|
||||||
|
.cm-scroller {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex-col;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings > form > * + * {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
202
website/src/repl/vanilla/vanilla.mjs
Normal file
202
website/src/repl/vanilla/vanilla.mjs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import { logger, getDrawContext, silence, controls, evalScope, hash2code, code2hash } from '@strudel.cycles/core';
|
||||||
|
import { StrudelMirror, initTheme, activateTheme } from '@strudel/codemirror';
|
||||||
|
import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
|
import {
|
||||||
|
getAudioContext,
|
||||||
|
webaudioOutput,
|
||||||
|
registerSynthSounds,
|
||||||
|
registerZZFXSounds,
|
||||||
|
samples,
|
||||||
|
} from '@strudel.cycles/webaudio';
|
||||||
|
import './vanilla.css';
|
||||||
|
|
||||||
|
let editor;
|
||||||
|
const initialSettings = {
|
||||||
|
keybindings: 'codemirror',
|
||||||
|
isLineNumbersDisplayed: true,
|
||||||
|
isActiveLineHighlighted: true,
|
||||||
|
isAutoCompletionEnabled: false,
|
||||||
|
isPatternHighlightingEnabled: true,
|
||||||
|
isFlashEnabled: true,
|
||||||
|
isTooltipEnabled: false,
|
||||||
|
isLineWrappingEnabled: false,
|
||||||
|
theme: 'teletext',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 18,
|
||||||
|
};
|
||||||
|
initTheme(initialSettings.theme);
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const container = document.getElementById('code');
|
||||||
|
if (!container) {
|
||||||
|
console.warn('could not init: no container found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawContext = getDrawContext();
|
||||||
|
const drawTime = [-2, 2];
|
||||||
|
editor = new StrudelMirror({
|
||||||
|
defaultOutput: webaudioOutput,
|
||||||
|
getTime: () => getAudioContext().currentTime,
|
||||||
|
transpiler,
|
||||||
|
root: container,
|
||||||
|
initialCode: '// LOADING',
|
||||||
|
pattern: silence,
|
||||||
|
settings: initialSettings,
|
||||||
|
drawTime,
|
||||||
|
onDraw: (haps, time, frame, painters) => {
|
||||||
|
painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2);
|
||||||
|
painters?.forEach((painter) => {
|
||||||
|
// ctx time haps drawTime paintOptions
|
||||||
|
painter(drawContext, time, haps, drawTime, { clear: false });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
prebake: async () => {
|
||||||
|
// populate scope / lazy load modules
|
||||||
|
const modulesLoading = evalScope(
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
import('@strudel.cycles/tonal'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
// import('@strudel.cycles/xen'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
import('@strudel/codemirror'),
|
||||||
|
/* import('@strudel/hydra'), */
|
||||||
|
// import('@strudel.cycles/serial'),
|
||||||
|
/* import('@strudel.cycles/soundfonts'), */
|
||||||
|
// import('@strudel.cycles/csound'),
|
||||||
|
/* import('@strudel.cycles/midi'), */
|
||||||
|
// import('@strudel.cycles/osc'),
|
||||||
|
controls, // sadly, this cannot be exported from core directly (yet)
|
||||||
|
);
|
||||||
|
// load samples
|
||||||
|
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
|
||||||
|
await Promise.all([
|
||||||
|
modulesLoading,
|
||||||
|
registerSynthSounds(),
|
||||||
|
registerZZFXSounds(),
|
||||||
|
samples(`${ds}/tidal-drum-machines.json`),
|
||||||
|
samples(`${ds}/piano.json`),
|
||||||
|
samples(`${ds}/Dirt-Samples.json`),
|
||||||
|
samples(`${ds}/EmuSP12.json`),
|
||||||
|
samples(`${ds}/vcsl.json`),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
afterEval: ({ code }) => {
|
||||||
|
window.location.hash = '#' + code2hash(code);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// init settings
|
||||||
|
editor.updateSettings(initialSettings);
|
||||||
|
|
||||||
|
logger(`Welcome to Strudel! Click into the editor and then hit ctrl+enter to run the code!`, 'highlight');
|
||||||
|
const codeParam = window.location.href.split('#')[1] || '';
|
||||||
|
|
||||||
|
const initialCode = codeParam
|
||||||
|
? hash2code(codeParam)
|
||||||
|
: `// @date 23-11-30
|
||||||
|
// "teigrührgerät" @by froos
|
||||||
|
|
||||||
|
stack(
|
||||||
|
stack(
|
||||||
|
s("bd(<3!3 5>,6)/2").bank('RolandTR707')
|
||||||
|
,
|
||||||
|
s("~ sd:<0 1>").bank('RolandTR707').room("<0 .5>")
|
||||||
|
.lastOf(8, x=>x.segment("12").end(.2).gain(isaw))
|
||||||
|
,
|
||||||
|
s("[tb ~ tb]").bank('RolandTR707')
|
||||||
|
.clip(0).release(.08).room(.2)
|
||||||
|
).off(-1/6, x=>x.speed(.7).gain(.2).degrade())
|
||||||
|
,
|
||||||
|
stack(
|
||||||
|
note("<g1(<3 4>,6) ~!2 [f1?]*2>")
|
||||||
|
.s("sawtooth").lpf(perlin.range(400,1000))
|
||||||
|
.lpa(.1).lpenv(-3).room(.2)
|
||||||
|
.lpq(8).noise(.2)
|
||||||
|
.add(note("0,.1"))
|
||||||
|
,
|
||||||
|
chord("<~ Gm9 ~!2>")
|
||||||
|
.dict('ireal').voicing()
|
||||||
|
.s("sawtooth").vib("2:.1")
|
||||||
|
.lpf(1000).lpa(.1).lpenv(-4)
|
||||||
|
.room(.5)
|
||||||
|
,
|
||||||
|
n(run(3)).chord("<Gm9 Gm11>/8")
|
||||||
|
.dict('ireal-ext')
|
||||||
|
.off(1/2, add(n(4)))
|
||||||
|
.voicing()
|
||||||
|
.clip(.1).release(.05)
|
||||||
|
.s("sine").jux(rev)
|
||||||
|
.sometimesBy(sine.slow(16), add(note(12)))
|
||||||
|
.room(.75)
|
||||||
|
.lpf(sine.range(200,2000).slow(16))
|
||||||
|
.gain(saw.slow(4).div(2))
|
||||||
|
).add(note(perlin.range(0,.5)))
|
||||||
|
)`;
|
||||||
|
|
||||||
|
editor.setCode(initialCode); // simpler alternative to above init
|
||||||
|
|
||||||
|
// settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key]));
|
||||||
|
onEvent('strudel-toggle-play', () => editor.toggle());
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
function onEvent(key, callback) {
|
||||||
|
const listener = (e) => {
|
||||||
|
if (e.data === key) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('message', listener);
|
||||||
|
return () => window.removeEventListener('message', listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// settings form
|
||||||
|
function getInput(form, name) {
|
||||||
|
return form.querySelector(`input[name=${name}]`) || form.querySelector(`select[name=${name}]`);
|
||||||
|
}
|
||||||
|
function getFormValues(form, initial) {
|
||||||
|
const entries = Object.entries(initial).map(([key, initialValue]) => {
|
||||||
|
const input = getInput(form, key);
|
||||||
|
if (!input) {
|
||||||
|
return [key, initialValue]; // fallback
|
||||||
|
}
|
||||||
|
if (input.type === 'checkbox') {
|
||||||
|
return [key, input.checked];
|
||||||
|
}
|
||||||
|
if (input.type === 'number') {
|
||||||
|
return [key, Number(input.value)];
|
||||||
|
}
|
||||||
|
if (input.tagName === 'SELECT') {
|
||||||
|
return [key, input.value];
|
||||||
|
}
|
||||||
|
return [key, input.value];
|
||||||
|
});
|
||||||
|
return Object.fromEntries(entries);
|
||||||
|
}
|
||||||
|
function setFormValues(form, values) {
|
||||||
|
Object.entries(values).forEach(([key, value]) => {
|
||||||
|
const input = getInput(form, key);
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (input.type === 'checkbox') {
|
||||||
|
input.checked = !!value;
|
||||||
|
} else if (input.type === 'number') {
|
||||||
|
input.value = value;
|
||||||
|
} else if (input.tagName) {
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = document.querySelector('form[name=settings]');
|
||||||
|
setFormValues(form, initialSettings);
|
||||||
|
form.addEventListener('change', () => {
|
||||||
|
const values = getFormValues(form, initialSettings);
|
||||||
|
// console.log('values', values);
|
||||||
|
editor.updateSettings(values);
|
||||||
|
// TODO: only activateTheme when it changes
|
||||||
|
activateTheme(values.theme);
|
||||||
|
});
|
||||||
@ -1,9 +1,13 @@
|
|||||||
import { persistentMap } from '@nanostores/persistent';
|
import { persistentMap, persistentAtom } from '@nanostores/persistent';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { register } from '@strudel.cycles/core';
|
import { register } from '@strudel.cycles/core';
|
||||||
import * as tunes from './repl/tunes.mjs';
|
import * as tunes from './repl/tunes.mjs';
|
||||||
|
|
||||||
import { defaultAudioDeviceName } from './repl/panel/AudioDeviceSelector';
|
import { defaultAudioDeviceName } from './repl/panel/AudioDeviceSelector';
|
||||||
|
|
||||||
|
import { logger } from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
|
||||||
export const defaultSettings = {
|
export const defaultSettings = {
|
||||||
activeFooter: 'intro',
|
activeFooter: 'intro',
|
||||||
keybindings: 'codemirror',
|
keybindings: 'codemirror',
|
||||||
@ -26,6 +30,24 @@ export const defaultSettings = {
|
|||||||
|
|
||||||
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
|
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
|
||||||
|
|
||||||
|
// active pattern is separate, because it shouldn't sync state across tabs
|
||||||
|
// reason: https://github.com/tidalcycles/strudel/issues/857
|
||||||
|
const $activePattern = persistentAtom('activePattern', '', { listen: false });
|
||||||
|
export function setActivePattern(key) {
|
||||||
|
$activePattern.set(key);
|
||||||
|
}
|
||||||
|
export function getActivePattern() {
|
||||||
|
return $activePattern.get();
|
||||||
|
}
|
||||||
|
export function useActivePattern() {
|
||||||
|
return useStore($activePattern);
|
||||||
|
}
|
||||||
|
export function initUserCode(code) {
|
||||||
|
const userPatterns = getUserPatterns();
|
||||||
|
const match = Object.entries(userPatterns).find(([_, pat]) => pat.code === code);
|
||||||
|
setActivePattern(match?.[0] || '');
|
||||||
|
}
|
||||||
|
|
||||||
export function useSettings() {
|
export function useSettings() {
|
||||||
const state = useStore(settingsMap);
|
const state = useStore(settingsMap);
|
||||||
return {
|
return {
|
||||||
@ -118,7 +140,7 @@ export function getUserPattern(key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function renameActivePattern() {
|
export function renameActivePattern() {
|
||||||
let activePattern = getSetting('activePattern');
|
let activePattern = getActivePattern();
|
||||||
let userPatterns = getUserPatterns();
|
let userPatterns = getUserPatterns();
|
||||||
if (!userPatterns[activePattern]) {
|
if (!userPatterns[activePattern]) {
|
||||||
alert('Cannot rename examples');
|
alert('Cannot rename examples');
|
||||||
@ -141,7 +163,7 @@ export function renameActivePattern() {
|
|||||||
|
|
||||||
export function updateUserCode(code) {
|
export function updateUserCode(code) {
|
||||||
const userPatterns = getUserPatterns();
|
const userPatterns = getUserPatterns();
|
||||||
let activePattern = getSetting('activePattern');
|
let activePattern = getActivePattern();
|
||||||
// check if code is that of an example tune
|
// check if code is that of an example tune
|
||||||
const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || [];
|
const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || [];
|
||||||
if (example && (!activePattern || activePattern === example)) {
|
if (example && (!activePattern || activePattern === example)) {
|
||||||
@ -162,7 +184,7 @@ export function updateUserCode(code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function deleteActivePattern() {
|
export function deleteActivePattern() {
|
||||||
let activePattern = getSetting('activePattern');
|
let activePattern = getActivePattern();
|
||||||
if (!activePattern) {
|
if (!activePattern) {
|
||||||
console.warn('cannot delete: no pattern selected');
|
console.warn('cannot delete: no pattern selected');
|
||||||
return;
|
return;
|
||||||
@ -180,7 +202,7 @@ export function deleteActivePattern() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function duplicateActivePattern() {
|
export function duplicateActivePattern() {
|
||||||
let activePattern = getSetting('activePattern');
|
let activePattern = getActivePattern();
|
||||||
let latestCode = getSetting('latestCode');
|
let latestCode = getSetting('latestCode');
|
||||||
if (!activePattern) {
|
if (!activePattern) {
|
||||||
console.warn('cannot duplicate: no pattern selected');
|
console.warn('cannot duplicate: no pattern selected');
|
||||||
@ -192,8 +214,31 @@ export function duplicateActivePattern() {
|
|||||||
setActivePattern(activePattern);
|
setActivePattern(activePattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setActivePattern(key) {
|
export async function importPatterns(fileList) {
|
||||||
settingsMap.setKey('activePattern', key);
|
const files = Array.from(fileList);
|
||||||
|
await Promise.all(
|
||||||
|
files.map(async (file, i) => {
|
||||||
|
const content = await file.text();
|
||||||
|
if (file.type === 'application/json') {
|
||||||
|
const userPatterns = getUserPatterns() || {};
|
||||||
|
setUserPatterns({ ...userPatterns, ...JSON.parse(content) });
|
||||||
|
} else if (file.type === 'text/plain') {
|
||||||
|
const name = file.name.replace(/\.[^/.]+$/, '');
|
||||||
|
addUserPattern(name, { code: content });
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
logger(`import done!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importUserPatternJSON(jsonString) {}
|
export async function exportPatterns() {
|
||||||
|
const userPatterns = getUserPatterns() || {};
|
||||||
|
const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' });
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = window.URL.createObjectURL(blob);
|
||||||
|
const date = new Date().toISOString().split('T')[0];
|
||||||
|
downloadLink.download = `strudel_patterns_${date}.json`;
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
document.body.removeChild(downloadLink);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user