From 87feac447b6488621ea98f2abb050e3398c8c741 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 09:42:25 +0100 Subject: [PATCH 01/97] - make codemirror package more capable - add themes from react package - add Autocomplete from react package - handle dynamic extensions - handle keyboard shortcuts - make StrudelMirror as capable as Repl --- packages/codemirror/Autocomplete.jsx | 88 ++++ packages/codemirror/codemirror.mjs | 209 ++++++-- packages/codemirror/flash.mjs | 2 + packages/codemirror/highlight.mjs | 9 + packages/codemirror/keybindings.mjs | 31 ++ packages/codemirror/package.json | 10 +- packages/codemirror/themes.mjs | 484 +++++++++++++++++++ packages/codemirror/themes/algoboy.mjs | 41 ++ packages/codemirror/themes/blackscreen.mjs | 38 ++ packages/codemirror/themes/bluescreen.mjs | 41 ++ packages/codemirror/themes/one-dark.mjs | 139 ------ packages/codemirror/themes/strudel-theme.mjs | 45 ++ packages/codemirror/themes/teletext.mjs | 50 ++ packages/codemirror/themes/terminal.mjs | 36 ++ packages/codemirror/themes/whitescreen.mjs | 38 ++ pnpm-lock.yaml | 39 ++ 16 files changed, 1120 insertions(+), 180 deletions(-) create mode 100644 packages/codemirror/Autocomplete.jsx create mode 100644 packages/codemirror/keybindings.mjs create mode 100644 packages/codemirror/themes.mjs create mode 100644 packages/codemirror/themes/algoboy.mjs create mode 100644 packages/codemirror/themes/blackscreen.mjs create mode 100644 packages/codemirror/themes/bluescreen.mjs delete mode 100644 packages/codemirror/themes/one-dark.mjs create mode 100644 packages/codemirror/themes/strudel-theme.mjs create mode 100644 packages/codemirror/themes/teletext.mjs create mode 100644 packages/codemirror/themes/terminal.mjs create mode 100644 packages/codemirror/themes/whitescreen.mjs diff --git a/packages/codemirror/Autocomplete.jsx b/packages/codemirror/Autocomplete.jsx new file mode 100644 index 00000000..18f172ee --- /dev/null +++ b/packages/codemirror/Autocomplete.jsx @@ -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 ( +
+

{getDocLabel(doc)}

+
+
    + {doc.params?.map(({ name, type, description }, i) => ( +
  • + {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)} : ''} +
  • + ))} +
+
+ {doc.examples?.map((example, i) => ( +
+
 {
+                console.log('ola!');
+                navigator.clipboard.writeText(example);
+                e.stopPropagation();
+              }}
+            >
+              {example}
+            
+
+ ))} +
+
+ ); +} + +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(); + 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: [] }) +} diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 2094416e..8c64372d 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -1,36 +1,78 @@ -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 { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language'; -import { EditorState } from '@codemirror/state'; -import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view'; -import { Drawer, repl } from '@strudel.cycles/core'; -import { flashField, flash } from './flash.mjs'; -import { highlightExtension, highlightMiniLocations } from './highlight.mjs'; -import { oneDark } from './themes/one-dark'; +import { Compartment, EditorState } from '@codemirror/state'; +import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view'; +import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core'; +import { isAutoCompletionEnabled } from './Autocomplete'; +import { flash, isFlashEnabled } from './flash.mjs'; +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, + isFlashEnabled, + keybindings, +}; +const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); // 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({ doc: initialCode, extensions: [ - theme, + /* search(), + highlightSelectionMatches(), */ + ...initialSettings, javascript(), - lineNumbers(), - highlightExtension, + sliderPlugin, + // indentOnInput(), // works without. already brought with javascript extension? + // bracketMatching(), // does not do anything + closeBrackets(), highlightActiveLineGutter(), + highlightActiveLine(), syntaxHighlighting(defaultHighlightStyle), - keymap.of(defaultKeymap), - flashField, + history(), EditorView.updateListener.of((v) => onChange(v)), keymap.of([ { key: 'Ctrl-Enter', - run: () => onEvaluate(), + run: () => onEvaluate?.(), + }, + { + key: 'Alt-Enter', + run: () => onEvaluate?.(), }, { key: 'Ctrl-.', - run: () => onStop(), + run: () => onStop?.(), }, + { + key: 'Alt-.', + run: (_, e) => { + e.preventDefault(); + onStop?.(); + }, + }, + /* { + key: 'Ctrl-Shift-.', + run: () => (onPanic ? onPanic() : onStop?.()), + }, + { + key: 'Ctrl-Shift-Enter', + run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()), + }, */ ]), ], }); @@ -43,71 +85,158 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the export class StrudelMirror { 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.root = root; + this.miniLocations = []; + this.widgets = []; + this.painters = []; + this.onDraw = onDraw; + const self = this; this.drawer = new Drawer((haps, time) => { const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped); this.highlight(currentFrame, time); - onDraw?.(haps, time, currentFrame); + this.onDraw?.(haps, time, currentFrame, this.painters); }, drawTime); - const prebaked = prebake(); - prebaked.then(async () => { - if (!onDraw) { - return; - } - const { scheduler, evaluate } = await this.repl; - // draw first frame instantly - prebaked.then(async () => { - await evaluate(this.code, false); - this.drawer.invalidate(scheduler); - onDraw?.(this.drawer.visibleHaps, 0, []); - }); - }); + // this approach might not work with multiple repls on screen.. + Pattern.prototype.onPaint = function (onPaint) { + self.painters.push(onPaint); + return this; + }; + + this.prebaked = prebake(); + // this.drawFirstFrame(); this.repl = repl({ ...replOptions, - onToggle: async (started) => { + onToggle: (started) => { replOptions?.onToggle?.(started); - const { scheduler } = await this.repl; if (started) { - this.drawer.start(scheduler); + this.drawer.start(this.repl.scheduler); } else { this.drawer.stop(); + updateMiniLocations(this.editor, []); + cleanupDraw(false); } }, beforeEval: async () => { - await prebaked; + cleanupDraw(); + this.painters = []; + await this.prebaked; + await replOptions?.beforeEval?.(); }, 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); this.drawer.invalidate(); }, }); this.editor = initEditor({ root, + settings, initialCode, onChange: (v) => { - this.code = v.state.doc.toString(); + if (v.docChanged) { + this.code = v.state.doc.toString(); + this.repl.setCode(this.code); + } }, onEvaluate: () => this.evaluate(), onStop: () => this.stop(), }); } + 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() { - const { evaluate } = await this.repl; this.flash(); - await evaluate(this.code); + await this.repl.evaluate(this.code); } async stop() { - const { scheduler } = await this.repl; - scheduler.stop(); + this.repl.scheduler.stop(); + } + async toggle() { + if (this.repl.scheduler.started) { + this.repl.scheduler.stop(); + } else { + this.evaluate(); + } } flash(ms) { flash(this.editor, ms); } 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; + } + 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; +} diff --git a/packages/codemirror/flash.mjs b/packages/codemirror/flash.mjs index 9bc5c593..6b37038f 100644 --- a/packages/codemirror/flash.mjs +++ b/packages/codemirror/flash.mjs @@ -33,3 +33,5 @@ export const flash = (view, ms = 200) => { view.dispatch({ effects: setFlash.of(false) }); }, ms); }; + +export const isFlashEnabled = (on) => (on ? flashField : []); diff --git a/packages/codemirror/highlight.mjs b/packages/codemirror/highlight.mjs index 317c5fdf..79724f8f 100644 --- a/packages/codemirror/highlight.mjs +++ b/packages/codemirror/highlight.mjs @@ -124,3 +124,12 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi }); export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]; + +export const isPatternHighlightingEnabled = (on, config) => { + on && + config && + setTimeout(() => { + updateMiniLocations(config.editor, config.miniLocations); + }, 100); + return on ? highlightExtension : []; +}; diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs new file mode 100644 index 00000000..6fe00eda --- /dev/null +++ b/packages/codemirror/keybindings.mjs @@ -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), +} diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 4e443648..a309efa2 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -33,13 +33,21 @@ }, "homepage": "https://github.com/tidalcycles/strudel#readme", "dependencies": { + "@codemirror/autocomplete": "^6.6.0", "@codemirror/commands": "^6.2.4", "@codemirror/lang-javascript": "^6.1.7", "@codemirror/language": "^6.6.0", + "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.10.0", "@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": { "vite": "^4.3.3" diff --git a/packages/codemirror/themes.mjs b/packages/codemirror/themes.mjs new file mode 100644 index 00000000..1f520623 --- /dev/null +++ b/packages/codemirror/themes.mjs @@ -0,0 +1,484 @@ +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 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); +} + +export const theme = (theme) => themes[theme] || themes.strudelTheme; diff --git a/packages/codemirror/themes/algoboy.mjs b/packages/codemirror/themes/algoboy.mjs new file mode 100644 index 00000000..399370e1 --- /dev/null +++ b/packages/codemirror/themes/algoboy.mjs @@ -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' }, + ], +}); diff --git a/packages/codemirror/themes/blackscreen.mjs b/packages/codemirror/themes/blackscreen.mjs new file mode 100644 index 00000000..135285a3 --- /dev/null +++ b/packages/codemirror/themes/blackscreen.mjs @@ -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' }, + ], +}); diff --git a/packages/codemirror/themes/bluescreen.mjs b/packages/codemirror/themes/bluescreen.mjs new file mode 100644 index 00000000..aa6489d6 --- /dev/null +++ b/packages/codemirror/themes/bluescreen.mjs @@ -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' }, + ], +}); diff --git a/packages/codemirror/themes/one-dark.mjs b/packages/codemirror/themes/one-dark.mjs deleted file mode 100644 index cce83699..00000000 --- a/packages/codemirror/themes/one-dark.mjs +++ /dev/null @@ -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)]; diff --git a/packages/codemirror/themes/strudel-theme.mjs b/packages/codemirror/themes/strudel-theme.mjs new file mode 100644 index 00000000..4ae31060 --- /dev/null +++ b/packages/codemirror/themes/strudel-theme.mjs @@ -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' }, + ], +}); diff --git a/packages/codemirror/themes/teletext.mjs b/packages/codemirror/themes/teletext.mjs new file mode 100644 index 00000000..5fd9a557 --- /dev/null +++ b/packages/codemirror/themes/teletext.mjs @@ -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 }, + ], +}); diff --git a/packages/codemirror/themes/terminal.mjs b/packages/codemirror/themes/terminal.mjs new file mode 100644 index 00000000..1374bb86 --- /dev/null +++ b/packages/codemirror/themes/terminal.mjs @@ -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' }, + ], +}); diff --git a/packages/codemirror/themes/whitescreen.mjs b/packages/codemirror/themes/whitescreen.mjs new file mode 100644 index 00000000..22abad9e --- /dev/null +++ b/packages/codemirror/themes/whitescreen.mjs @@ -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' }, + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98f6441f..f6f446e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -71,6 +75,9 @@ importers: packages/codemirror: 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': specifier: ^6.2.4 version: 6.2.4 @@ -80,6 +87,9 @@ importers: '@codemirror/language': specifier: ^6.6.0 version: 6.6.0 + '@codemirror/search': + specifier: ^6.0.0 + version: 6.2.3 '@codemirror/state': specifier: ^6.2.0 version: 6.2.0 @@ -89,9 +99,27 @@ importers: '@lezer/highlight': specifier: ^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': specifier: workspace:* 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: vite: specifier: ^4.3.3 @@ -5572,6 +5600,7 @@ packages: /b4a@1.6.4: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} + requiresBuild: true optional: true /babel-plugin-add-module-exports@0.2.1: @@ -6154,6 +6183,7 @@ packages: /color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + requiresBuild: true dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 @@ -6166,6 +6196,7 @@ packages: /color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + requiresBuild: true dependencies: color-convert: 2.0.1 color-string: 1.9.1 @@ -6629,6 +6660,7 @@ packages: /detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} + requiresBuild: true optional: true /detective-amd@4.0.1: @@ -7472,6 +7504,7 @@ packages: /fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + requiresBuild: true optional: true /fast-glob@3.2.12: @@ -10579,6 +10612,7 @@ packages: /node-addon-api@6.1.0: resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + requiresBuild: true optional: true /node-domexception@1.0.0: @@ -11875,6 +11909,7 @@ packages: /queue-tick@1.0.1: resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + requiresBuild: true optional: true /quick-lru@4.0.1: @@ -12730,6 +12765,7 @@ packages: /simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + requiresBuild: true dependencies: is-arrayish: 0.3.2 optional: true @@ -12943,6 +12979,7 @@ packages: /streamx@2.15.2: resolution: {integrity: sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg==} + requiresBuild: true dependencies: fast-fifo: 1.3.2 queue-tick: 1.0.1 @@ -13220,6 +13257,7 @@ packages: /tar-fs@3.0.4: resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==} + requiresBuild: true dependencies: mkdirp-classic: 0.5.3 pump: 3.0.0 @@ -13238,6 +13276,7 @@ packages: /tar-stream@3.1.6: resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==} + requiresBuild: true dependencies: b4a: 1.6.4 fast-fifo: 1.3.2 From 99194814a98126ba1347c01496972be744afeaf6 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 10:36:56 +0100 Subject: [PATCH 02/97] move code hashing helpers to core --- packages/core/util.mjs | 28 ++++++++++++++++++++++++++++ website/src/repl/Repl.jsx | 12 ++++++++++-- website/src/repl/helpers.mjs | 25 ------------------------- 3 files changed, 38 insertions(+), 27 deletions(-) delete mode 100644 website/src/repl/helpers.mjs diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 695eaacf..2b190c82 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -274,3 +274,31 @@ export const sol2note = (n, notation = 'letters') => { const oct = Math.floor(n / 12) - 1; 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 || '')); +} diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index a606d67a..d4f85434 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -4,7 +4,16 @@ Copyright (C) 2022 Strudel contributors - see . */ -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 { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; import { createClient } from '@supabase/supabase-js'; @@ -29,7 +38,6 @@ import { } from '../settings.mjs'; import Loader from './Loader'; import { settingPatterns } from '../settings.mjs'; -import { code2hash, hash2code } from './helpers.mjs'; import { isTauri } from '../tauri.mjs'; import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; import { writeText } from '@tauri-apps/api/clipboard'; diff --git a/website/src/repl/helpers.mjs b/website/src/repl/helpers.mjs deleted file mode 100644 index b86e76f1..00000000 --- a/website/src/repl/helpers.mjs +++ /dev/null @@ -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 || '')); -} From 9474ed7d85f17aceee8c87c3a820254d04c3393e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 10:37:16 +0100 Subject: [PATCH 03/97] dedicated strudelmirror example --- packages/codemirror/codemirror.mjs | 3 +- .../examples/strudelmirror/.gitignore | 24 + .../examples/strudelmirror/index.html | 13 + .../codemirror/examples/strudelmirror/main.js | 156 ++++ .../examples/strudelmirror/main_repl.js | 252 ++++++ .../examples/strudelmirror/package-lock.json | 732 ++++++++++++++++++ .../examples/strudelmirror/package.json | 28 + .../examples/strudelmirror/style.css | 13 + pnpm-lock.yaml | 238 +++++- pnpm-workspace.yaml | 1 + 10 files changed, 1449 insertions(+), 11 deletions(-) create mode 100644 packages/codemirror/examples/strudelmirror/.gitignore create mode 100644 packages/codemirror/examples/strudelmirror/index.html create mode 100644 packages/codemirror/examples/strudelmirror/main.js create mode 100644 packages/codemirror/examples/strudelmirror/main_repl.js create mode 100644 packages/codemirror/examples/strudelmirror/package-lock.json create mode 100644 packages/codemirror/examples/strudelmirror/package.json create mode 100644 packages/codemirror/examples/strudelmirror/style.css diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 8c64372d..a7638e9c 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -144,7 +144,8 @@ export class StrudelMirror { onChange: (v) => { if (v.docChanged) { this.code = v.state.doc.toString(); - this.repl.setCode(this.code); + // TODO: repl is still untouched to make sure the old Repl.jsx stays untouched.. + // this.repl.setCode(this.code); } }, onEvaluate: () => this.evaluate(), diff --git a/packages/codemirror/examples/strudelmirror/.gitignore b/packages/codemirror/examples/strudelmirror/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/packages/codemirror/examples/strudelmirror/.gitignore @@ -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? diff --git a/packages/codemirror/examples/strudelmirror/index.html b/packages/codemirror/examples/strudelmirror/index.html new file mode 100644 index 00000000..17b5009b --- /dev/null +++ b/packages/codemirror/examples/strudelmirror/index.html @@ -0,0 +1,13 @@ + + + + + + + StrudelMirror Example + + +
+ + + diff --git a/packages/codemirror/examples/strudelmirror/main.js b/packages/codemirror/examples/strudelmirror/main.js new file mode 100644 index 00000000..3dd4b608 --- /dev/null +++ b/packages/codemirror/examples/strudelmirror/main.js @@ -0,0 +1,156 @@ +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'; + +async function run() { + const container = document.getElementById('code'); + if (!container) { + console.warn('could not init: no container found'); + return; + } + const settings = { + activeFooter: 'intro', + keybindings: 'codemirror', + isLineNumbersDisplayed: true, + isActiveLineHighlighted: true, + isAutoCompletionEnabled: false, + isPatternHighlightingEnabled: true, + isFlashEnabled: true, + isTooltipEnabled: false, + isLineWrappingEnabled: false, + theme: 'teletext', + fontFamily: 'monospace', + fontSize: 18, + latestCode: '', + isZen: false, + soundsFilter: 'all', + panelPosition: 'bottom', + userPatterns: '{}', + }; + + const drawContext = getDrawContext(); + const drawTime = [-2, 2]; + const editor = new StrudelMirror({ + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + root: container, + initialCode: '// LOADING', + pattern: silence, + settings, + 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(settings); + + 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(",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("/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); +} diff --git a/packages/codemirror/examples/strudelmirror/main_repl.js b/packages/codemirror/examples/strudelmirror/main_repl.js new file mode 100644 index 00000000..ad61b807 --- /dev/null +++ b/packages/codemirror/examples/strudelmirror/main_repl.js @@ -0,0 +1,252 @@ +// this file is unused, but still contains the commented parts of the main repl + +import { + /* logger, */ getDrawContext, + silence, + controls, + evalScope /* , hash2code, code2hash */, +} from '@strudel.cycles/core'; +import { StrudelMirror } from '@strudel/codemirror'; +import { getAudioContext, webaudioOutput /* , resetLoadedSounds */ } from '@strudel.cycles/webaudio'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; +import './style.css'; + +//import { prebake, resetSounds } from './prebake.mjs'; +//import { settingsMap } from '@src/settings.mjs'; +//import { setLatestCode } from '../settings.mjs'; +//import { createClient } from '@supabase/supabase-js'; +//import { getRandomTune } from './helpers.mjs'; + +/* const supabase = createClient( + 'https://pidxdsxphlhzjnzmifth.supabase.co', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', +); */ + +/* async function initCodeFromUrl() { + // await new Promise((resolve) => setTimeout(resolve, 2000)); + // load code from url hash (either short hash from database or decode long hash) + try { + const initialUrl = window.location.href; + const hash = initialUrl.split('?')[1]?.split('#')?.[0]; + const codeParam = window.location.href.split('#')[1] || ''; + // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) + if (codeParam) { + // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) + return hash2code(codeParam); + } else if (hash) { + return supabase + .from('code') + .select('code') + .eq('hash', hash) + .then(({ data, error }) => { + if (error) { + console.warn('failed to load hash', error); + } + if (data.length) { + //console.log('load hash from database', hash); + return data[0].code; + } + }); + } + } catch (err) { + console.warn('failed to decode', err); + } +} +const initialCode = initCodeFromUrl(); +*/ + +async function run() { + const container = document.getElementById('code'); + if (!container) { + console.warn('could not init: no container found'); + return; + } + + // Create a single supabase client for interacting with your database + + // const settings = settingsMap.get(); + // defaultSettings from settings.mjs + const settings = { + activeFooter: 'intro', + keybindings: 'codemirror', + isLineNumbersDisplayed: true, + isActiveLineHighlighted: true, + isAutoCompletionEnabled: false, + isPatternHighlightingEnabled: true, + isFlashEnabled: true, + isTooltipEnabled: false, + isLineWrappingEnabled: false, + theme: 'strudelTheme', + fontFamily: 'monospace', + fontSize: 18, + latestCode: '', + isZen: false, + soundsFilter: 'all', + panelPosition: 'bottom', + userPatterns: '{}', + }; + + const drawContext = getDrawContext(); + const drawTime = [-2, 2]; + const editor = new StrudelMirror({ + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + root: container, + initialCode: '// LOADING', + pattern: silence, + settings, + 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 () => { + // prebake() + + // "old" style prebake copied from Repl.jsx + let modules = [ + 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'), + ]; + /* if (isTauri()) { + modules = modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); + } else { */ + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + //} + const modulesLoading = evalScope( + controls, // sadly, this cannot be exported from core direclty + // settingPatterns, + ...modules, + ); + 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 }) => { + // setLatestCode(code); + // window.location.hash = '#' + code2hash(code); + }, + }); + + // init settings + editor.updateSettings(settings); + + /* const decoded = await initialCode; + let msg; + if (decoded) { + editor.setCode(decoded); + msg = `I have loaded the code from the URL.`; + } else if (settings.latestCode) { + editor.setCode(settings.latestCode); + msg = `Your last session has been loaded!`; + } else { + const { code: randomTune, name } = getRandomTune(); + editor.setCode(randomTune); + msg = `A random code snippet named "${name}" has been loaded!`; + } + logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); */ + // setPending(false); + + const initialCode = `// @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(",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("/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()); + /* onEvent('strudel-shuffle', async () => { + const { code, name } = getRandomTune(); + logger(`[repl] ✨ loading random tune "${name}"`); + console.log(code); + editor.setCode(code); + // await resetSounds(); // <-- "new" style + // old style + resetLoadedSounds(); + editor.repl.setCps(1); + editor.repl.evaluate(code, false); + }); */ + + // const isEmbedded = embedded || window.location !== window.parent.location; +} + +/* let inited = false; +onEvent('strudel-container', () => { + if (!inited) { + inited = true; + run(); + } +}); */ +run(); + +function onEvent(key, callback) { + const listener = (e) => { + if (e.data === key) { + callback(); + } + }; + window.addEventListener('message', listener); + return () => window.removeEventListener('message', listener); +} diff --git a/packages/codemirror/examples/strudelmirror/package-lock.json b/packages/codemirror/examples/strudelmirror/package-lock.json new file mode 100644 index 00000000..6ee035f7 --- /dev/null +++ b/packages/codemirror/examples/strudelmirror/package-lock.json @@ -0,0 +1,732 @@ +{ + "name": "strudelmirror", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "strudelmirror", + "version": "0.0.0", + "devDependencies": { + "vite": "^5.0.8" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", + "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", + "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", + "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", + "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", + "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", + "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", + "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", + "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", + "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", + "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", + "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", + "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", + "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", + "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", + "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", + "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", + "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", + "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", + "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", + "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", + "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", + "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz", + "integrity": "sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz", + "integrity": "sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz", + "integrity": "sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz", + "integrity": "sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz", + "integrity": "sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz", + "integrity": "sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz", + "integrity": "sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz", + "integrity": "sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz", + "integrity": "sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz", + "integrity": "sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz", + "integrity": "sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz", + "integrity": "sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz", + "integrity": "sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/esbuild": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", + "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.9", + "@esbuild/android-arm64": "0.19.9", + "@esbuild/android-x64": "0.19.9", + "@esbuild/darwin-arm64": "0.19.9", + "@esbuild/darwin-x64": "0.19.9", + "@esbuild/freebsd-arm64": "0.19.9", + "@esbuild/freebsd-x64": "0.19.9", + "@esbuild/linux-arm": "0.19.9", + "@esbuild/linux-arm64": "0.19.9", + "@esbuild/linux-ia32": "0.19.9", + "@esbuild/linux-loong64": "0.19.9", + "@esbuild/linux-mips64el": "0.19.9", + "@esbuild/linux-ppc64": "0.19.9", + "@esbuild/linux-riscv64": "0.19.9", + "@esbuild/linux-s390x": "0.19.9", + "@esbuild/linux-x64": "0.19.9", + "@esbuild/netbsd-x64": "0.19.9", + "@esbuild/openbsd-x64": "0.19.9", + "@esbuild/sunos-x64": "0.19.9", + "@esbuild/win32-arm64": "0.19.9", + "@esbuild/win32-ia32": "0.19.9", + "@esbuild/win32-x64": "0.19.9" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.0.tgz", + "integrity": "sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "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.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.8.tgz", + "integrity": "sha512-jYMALd8aeqR3yS9xlHd0OzQJndS9fH5ylVgWdB+pxTwxLKdO1pgC5Dlb398BUxpfaBxa4M9oT7j1g503Gaj5IQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "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 + } + } + } + } +} diff --git a/packages/codemirror/examples/strudelmirror/package.json b/packages/codemirror/examples/strudelmirror/package.json new file mode 100644 index 00000000..d7e484ed --- /dev/null +++ b/packages/codemirror/examples/strudelmirror/package.json @@ -0,0 +1,28 @@ +{ + "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/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:*" + } +} diff --git a/packages/codemirror/examples/strudelmirror/style.css b/packages/codemirror/examples/strudelmirror/style.css new file mode 100644 index 00000000..35899df3 --- /dev/null +++ b/packages/codemirror/examples/strudelmirror/style.css @@ -0,0 +1,13 @@ +:root { + --foreground: white; +} + +html, +body, +#code, +.cm-editor, +.cm-scroller { + padding: 0; + margin: 0; + height: 100%; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6f446e1..3cb81a5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,49 @@ importers: specifier: ^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/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: dependencies: fraction.js: @@ -4081,6 +4124,110 @@ packages: rollup: 2.79.1 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: resolution: {integrity: sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -5997,7 +6144,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /chord-voicings@0.0.1: resolution: {integrity: sha512-SutgB/4ynkkuiK6qdQ/k3QvCFcH0Vj8Ch4t6LbRyRQbVzP/TOztiCk3kvXd516UZ6fqk7ijDRELEFcKN+6V8sA==} @@ -7769,8 +7916,8 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true @@ -10554,6 +10701,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 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: resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} engines: {node: ^14 || ^16 || >=18} @@ -11709,6 +11862,15 @@ packages: picocolors: 1.0.0 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: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} engines: {node: '>=10'} @@ -12539,7 +12701,7 @@ packages: engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /rollup@3.21.0: @@ -12547,7 +12709,7 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /rollup@3.28.0: @@ -12555,7 +12717,28 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true 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: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} @@ -14047,7 +14230,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.5(@types/node@18.16.3) + vite: 4.5.0(@types/node@18.16.3) transitivePeerDependencies: - '@types/node' - less @@ -14106,7 +14289,7 @@ packages: postcss: 8.4.23 rollup: 3.21.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /vite@4.4.5(@types/node@18.16.3): @@ -14142,7 +14325,7 @@ packages: postcss: 8.4.27 rollup: 3.28.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /vite@4.5.0(@types/node@18.16.3): @@ -14178,7 +14361,42 @@ packages: postcss: 8.4.31 rollup: 3.28.0 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): resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 615475e4..baafa3c3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,3 +7,4 @@ packages: - "packages/react/examples/nano-repl" - "packages/web/examples/repl-example" - "packages/superdough/example" + - "packages/codemirror/examples/strudelmirror" From 72d1f129cfe0db20bb0236aea6895745d348b517 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 11:29:33 +0100 Subject: [PATCH 04/97] add settings form to codemirror example --- packages/codemirror/codemirror.mjs | 3 +- .../examples/strudelmirror/index.html | 74 ++++++++++++++++ .../codemirror/examples/strudelmirror/main.js | 87 ++++++++++++++----- .../examples/strudelmirror/style.css | 20 +++++ 4 files changed, 160 insertions(+), 24 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index a7638e9c..725dd818 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -19,6 +19,7 @@ const extensions = { theme, isAutoCompletionEnabled, isPatternHighlightingEnabled, + isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []), isFlashEnabled, keybindings, }; @@ -40,8 +41,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, set // indentOnInput(), // works without. already brought with javascript extension? // bracketMatching(), // does not do anything closeBrackets(), - highlightActiveLineGutter(), - highlightActiveLine(), syntaxHighlighting(defaultHighlightStyle), history(), EditorView.updateListener.of((v) => onChange(v)), diff --git a/packages/codemirror/examples/strudelmirror/index.html b/packages/codemirror/examples/strudelmirror/index.html index 17b5009b..68bd57a1 100644 --- a/packages/codemirror/examples/strudelmirror/index.html +++ b/packages/codemirror/examples/strudelmirror/index.html @@ -7,6 +7,80 @@ StrudelMirror Example +
+
+
+
+
+ +
+ +
+ +
+ +
+ +
+ + + +
+
diff --git a/packages/codemirror/examples/strudelmirror/main.js b/packages/codemirror/examples/strudelmirror/main.js index 3dd4b608..676f4b97 100644 --- a/packages/codemirror/examples/strudelmirror/main.js +++ b/packages/codemirror/examples/strudelmirror/main.js @@ -10,42 +10,38 @@ import { } 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 settings = { - activeFooter: 'intro', - keybindings: 'codemirror', - isLineNumbersDisplayed: true, - isActiveLineHighlighted: true, - isAutoCompletionEnabled: false, - isPatternHighlightingEnabled: true, - isFlashEnabled: true, - isTooltipEnabled: false, - isLineWrappingEnabled: false, - theme: 'teletext', - fontFamily: 'monospace', - fontSize: 18, - latestCode: '', - isZen: false, - soundsFilter: 'all', - panelPosition: 'bottom', - userPatterns: '{}', - }; const drawContext = getDrawContext(); const drawTime = [-2, 2]; - const editor = new StrudelMirror({ + editor = new StrudelMirror({ defaultOutput: webaudioOutput, getTime: () => getAudioContext().currentTime, transpiler, root: container, initialCode: '// LOADING', pattern: silence, - settings, + settings: initialSettings, drawTime, onDraw: (haps, time, frame, painters) => { painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2); @@ -90,7 +86,7 @@ async function run() { }); // init settings - editor.updateSettings(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] || ''; @@ -154,3 +150,50 @@ function onEvent(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); +}); diff --git a/packages/codemirror/examples/strudelmirror/style.css b/packages/codemirror/examples/strudelmirror/style.css index 35899df3..fabc795c 100644 --- a/packages/codemirror/examples/strudelmirror/style.css +++ b/packages/codemirror/examples/strudelmirror/style.css @@ -2,6 +2,13 @@ --foreground: white; } +body, +input { + font-family: monospace; + background: black; + color: white; +} + html, body, #code, @@ -11,3 +18,16 @@ body, margin: 0; height: 100%; } + +.settings { + position: fixed; + right: 0; + top: 0; + z-index: 1000; + display: flex-col; + padding: 10px; +} + +.settings > form > * + * { + margin-top: 10px; +} From f6f5ee0375defa7ac4ab18508fabb4a369fc3045 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 11:50:52 +0100 Subject: [PATCH 05/97] fix: linter error --- packages/codemirror/examples/strudelmirror/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/codemirror/examples/strudelmirror/package.json b/packages/codemirror/examples/strudelmirror/package.json index d7e484ed..5c946bff 100644 --- a/packages/codemirror/examples/strudelmirror/package.json +++ b/packages/codemirror/examples/strudelmirror/package.json @@ -14,6 +14,7 @@ "dependencies": { "@strudel/codemirror": "workspace:*", "@strudel.cycles/core":"workspace:*", + "@strudel.cycles/transpiler":"workspace:*", "@strudel.cycles/tonal":"workspace:*", "@strudel.cycles/mini":"workspace:*", "@strudel.cycles/xen":"workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3cb81a5e..a2416a62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,6 +151,9 @@ importers: '@strudel.cycles/tonal': specifier: workspace:* version: link:../../../tonal + '@strudel.cycles/transpiler': + specifier: workspace:* + version: link:../../../transpiler '@strudel.cycles/webaudio': specifier: workspace:* version: link:../../../webaudio From 60e559a91067fb8ab197946fc7930e1a0b56b444 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 14:38:10 +0100 Subject: [PATCH 06/97] add /vanilla route to website + disable autocomplete for now --- packages/codemirror/codemirror.mjs | 4 +- website/src/pages/vanilla/index.astro | 98 +++++++++++++ website/src/repl/vanilla/vanilla.css | 38 +++++ website/src/repl/vanilla/vanilla.mjs | 199 ++++++++++++++++++++++++++ 4 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 website/src/pages/vanilla/index.astro create mode 100644 website/src/repl/vanilla/vanilla.css create mode 100644 website/src/repl/vanilla/vanilla.mjs diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 725dd818..7933600e 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -6,7 +6,7 @@ import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language' import { Compartment, EditorState } from '@codemirror/state'; import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view'; import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core'; -import { isAutoCompletionEnabled } from './Autocomplete'; +// import { isAutoCompletionEnabled } from './Autocomplete'; import { flash, isFlashEnabled } from './flash.mjs'; import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; import { keybindings } from './keybindings.mjs'; @@ -17,7 +17,7 @@ const extensions = { isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []), theme, - isAutoCompletionEnabled, + // isAutoCompletionEnabled, isPatternHighlightingEnabled, isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []), isFlashEnabled, diff --git a/website/src/pages/vanilla/index.astro b/website/src/pages/vanilla/index.astro new file mode 100644 index 00000000..5377c08f --- /dev/null +++ b/website/src/pages/vanilla/index.astro @@ -0,0 +1,98 @@ +--- +import HeadCommon from '../../components/HeadCommon.astro'; +--- + + + + + Strudel Vanilla REPL + + +
+
+
+
+ + +
+ +
+ +
+ +
+ +
+ + + +
+
+
+ + + diff --git a/website/src/repl/vanilla/vanilla.css b/website/src/repl/vanilla/vanilla.css new file mode 100644 index 00000000..7c985be7 --- /dev/null +++ b/website/src/repl/vanilla/vanilla.css @@ -0,0 +1,38 @@ +:root { + --foreground: white; +} + +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; +} diff --git a/website/src/repl/vanilla/vanilla.mjs b/website/src/repl/vanilla/vanilla.mjs new file mode 100644 index 00000000..6e5c54a3 --- /dev/null +++ b/website/src/repl/vanilla/vanilla.mjs @@ -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 './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, +}; + +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(",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("/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); +}); From 37f97e1c178e5ff903d1a07f95851ebdb456c02b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 14:48:39 +0100 Subject: [PATCH 07/97] remove package-lock --- .../examples/strudelmirror/package-lock.json | 732 ------------------ 1 file changed, 732 deletions(-) delete mode 100644 packages/codemirror/examples/strudelmirror/package-lock.json diff --git a/packages/codemirror/examples/strudelmirror/package-lock.json b/packages/codemirror/examples/strudelmirror/package-lock.json deleted file mode 100644 index 6ee035f7..00000000 --- a/packages/codemirror/examples/strudelmirror/package-lock.json +++ /dev/null @@ -1,732 +0,0 @@ -{ - "name": "strudelmirror", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "strudelmirror", - "version": "0.0.0", - "devDependencies": { - "vite": "^5.0.8" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", - "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", - "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", - "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", - "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", - "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", - "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", - "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", - "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", - "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", - "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", - "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", - "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", - "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", - "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", - "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", - "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", - "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", - "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", - "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", - "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", - "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", - "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz", - "integrity": "sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz", - "integrity": "sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz", - "integrity": "sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz", - "integrity": "sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz", - "integrity": "sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz", - "integrity": "sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz", - "integrity": "sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz", - "integrity": "sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz", - "integrity": "sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz", - "integrity": "sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz", - "integrity": "sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz", - "integrity": "sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz", - "integrity": "sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/esbuild": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", - "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.19.9", - "@esbuild/android-arm64": "0.19.9", - "@esbuild/android-x64": "0.19.9", - "@esbuild/darwin-arm64": "0.19.9", - "@esbuild/darwin-x64": "0.19.9", - "@esbuild/freebsd-arm64": "0.19.9", - "@esbuild/freebsd-x64": "0.19.9", - "@esbuild/linux-arm": "0.19.9", - "@esbuild/linux-arm64": "0.19.9", - "@esbuild/linux-ia32": "0.19.9", - "@esbuild/linux-loong64": "0.19.9", - "@esbuild/linux-mips64el": "0.19.9", - "@esbuild/linux-ppc64": "0.19.9", - "@esbuild/linux-riscv64": "0.19.9", - "@esbuild/linux-s390x": "0.19.9", - "@esbuild/linux-x64": "0.19.9", - "@esbuild/netbsd-x64": "0.19.9", - "@esbuild/openbsd-x64": "0.19.9", - "@esbuild/sunos-x64": "0.19.9", - "@esbuild/win32-arm64": "0.19.9", - "@esbuild/win32-ia32": "0.19.9", - "@esbuild/win32-x64": "0.19.9" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.0.tgz", - "integrity": "sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "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.2" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vite": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.8.tgz", - "integrity": "sha512-jYMALd8aeqR3yS9xlHd0OzQJndS9fH5ylVgWdB+pxTwxLKdO1pgC5Dlb398BUxpfaBxa4M9oT7j1g503Gaj5IQ==", - "dev": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "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 - } - } - } - } -} From 7b6f63db05dc0419768cc29d6485f70f1e74aa7d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 14:50:17 +0100 Subject: [PATCH 08/97] comment out fontFamily for now --- packages/codemirror/examples/strudelmirror/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/codemirror/examples/strudelmirror/index.html b/packages/codemirror/examples/strudelmirror/index.html index 68bd57a1..0e1d43ce 100644 --- a/packages/codemirror/examples/strudelmirror/index.html +++ b/packages/codemirror/examples/strudelmirror/index.html @@ -50,7 +50,7 @@
-
+ >
-->
From 49906e98b740561f84e441fc16837050c3c66d68 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 14:50:26 +0100 Subject: [PATCH 09/97] get rid of duplication --- .../examples/strudelmirror/main_repl.js | 252 ------------------ 1 file changed, 252 deletions(-) delete mode 100644 packages/codemirror/examples/strudelmirror/main_repl.js diff --git a/packages/codemirror/examples/strudelmirror/main_repl.js b/packages/codemirror/examples/strudelmirror/main_repl.js deleted file mode 100644 index ad61b807..00000000 --- a/packages/codemirror/examples/strudelmirror/main_repl.js +++ /dev/null @@ -1,252 +0,0 @@ -// this file is unused, but still contains the commented parts of the main repl - -import { - /* logger, */ getDrawContext, - silence, - controls, - evalScope /* , hash2code, code2hash */, -} from '@strudel.cycles/core'; -import { StrudelMirror } from '@strudel/codemirror'; -import { getAudioContext, webaudioOutput /* , resetLoadedSounds */ } from '@strudel.cycles/webaudio'; -import { transpiler } from '@strudel.cycles/transpiler'; -import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; -import './style.css'; - -//import { prebake, resetSounds } from './prebake.mjs'; -//import { settingsMap } from '@src/settings.mjs'; -//import { setLatestCode } from '../settings.mjs'; -//import { createClient } from '@supabase/supabase-js'; -//import { getRandomTune } from './helpers.mjs'; - -/* const supabase = createClient( - 'https://pidxdsxphlhzjnzmifth.supabase.co', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', -); */ - -/* async function initCodeFromUrl() { - // await new Promise((resolve) => setTimeout(resolve, 2000)); - // load code from url hash (either short hash from database or decode long hash) - try { - const initialUrl = window.location.href; - const hash = initialUrl.split('?')[1]?.split('#')?.[0]; - const codeParam = window.location.href.split('#')[1] || ''; - // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) - if (codeParam) { - // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) - return hash2code(codeParam); - } else if (hash) { - return supabase - .from('code') - .select('code') - .eq('hash', hash) - .then(({ data, error }) => { - if (error) { - console.warn('failed to load hash', error); - } - if (data.length) { - //console.log('load hash from database', hash); - return data[0].code; - } - }); - } - } catch (err) { - console.warn('failed to decode', err); - } -} -const initialCode = initCodeFromUrl(); -*/ - -async function run() { - const container = document.getElementById('code'); - if (!container) { - console.warn('could not init: no container found'); - return; - } - - // Create a single supabase client for interacting with your database - - // const settings = settingsMap.get(); - // defaultSettings from settings.mjs - const settings = { - activeFooter: 'intro', - keybindings: 'codemirror', - isLineNumbersDisplayed: true, - isActiveLineHighlighted: true, - isAutoCompletionEnabled: false, - isPatternHighlightingEnabled: true, - isFlashEnabled: true, - isTooltipEnabled: false, - isLineWrappingEnabled: false, - theme: 'strudelTheme', - fontFamily: 'monospace', - fontSize: 18, - latestCode: '', - isZen: false, - soundsFilter: 'all', - panelPosition: 'bottom', - userPatterns: '{}', - }; - - const drawContext = getDrawContext(); - const drawTime = [-2, 2]; - const editor = new StrudelMirror({ - defaultOutput: webaudioOutput, - getTime: () => getAudioContext().currentTime, - transpiler, - root: container, - initialCode: '// LOADING', - pattern: silence, - settings, - 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 () => { - // prebake() - - // "old" style prebake copied from Repl.jsx - let modules = [ - 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'), - ]; - /* if (isTauri()) { - modules = modules.concat([ - import('@strudel/desktopbridge/loggerbridge.mjs'), - import('@strudel/desktopbridge/midibridge.mjs'), - import('@strudel/desktopbridge/oscbridge.mjs'), - ]); - } else { */ - modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); - //} - const modulesLoading = evalScope( - controls, // sadly, this cannot be exported from core direclty - // settingPatterns, - ...modules, - ); - 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 }) => { - // setLatestCode(code); - // window.location.hash = '#' + code2hash(code); - }, - }); - - // init settings - editor.updateSettings(settings); - - /* const decoded = await initialCode; - let msg; - if (decoded) { - editor.setCode(decoded); - msg = `I have loaded the code from the URL.`; - } else if (settings.latestCode) { - editor.setCode(settings.latestCode); - msg = `Your last session has been loaded!`; - } else { - const { code: randomTune, name } = getRandomTune(); - editor.setCode(randomTune); - msg = `A random code snippet named "${name}" has been loaded!`; - } - logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); */ - // setPending(false); - - const initialCode = `// @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(",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("/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()); - /* onEvent('strudel-shuffle', async () => { - const { code, name } = getRandomTune(); - logger(`[repl] ✨ loading random tune "${name}"`); - console.log(code); - editor.setCode(code); - // await resetSounds(); // <-- "new" style - // old style - resetLoadedSounds(); - editor.repl.setCps(1); - editor.repl.evaluate(code, false); - }); */ - - // const isEmbedded = embedded || window.location !== window.parent.location; -} - -/* let inited = false; -onEvent('strudel-container', () => { - if (!inited) { - inited = true; - run(); - } -}); */ -run(); - -function onEvent(key, callback) { - const listener = (e) => { - if (e.data === key) { - callback(); - } - }; - window.addEventListener('message', listener); - return () => window.removeEventListener('message', listener); -} From 8959c630aeea1b7c091f12e9d4d0c2c65784d623 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 22:39:44 +0100 Subject: [PATCH 10/97] move css variable injection to themes + prepare new HeadCommon in extra file --- packages/codemirror/index.mjs | 1 + packages/codemirror/themes.mjs | 39 ++++++++++++++- website/src/components/HeadCommonNew.astro | 58 ++++++++++++++++++++++ website/src/pages/vanilla/index.astro | 4 +- website/src/repl/vanilla/vanilla.css | 4 -- website/src/repl/vanilla/vanilla.mjs | 5 +- 6 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 website/src/components/HeadCommonNew.astro diff --git a/packages/codemirror/index.mjs b/packages/codemirror/index.mjs index c847c32c..8f2d1630 100644 --- a/packages/codemirror/index.mjs +++ b/packages/codemirror/index.mjs @@ -2,3 +2,4 @@ export * from './codemirror.mjs'; export * from './highlight.mjs'; export * from './flash.mjs'; export * from './slider.mjs'; +export * from './themes.mjs'; diff --git a/packages/codemirror/themes.mjs b/packages/codemirror/themes.mjs index 1f520623..71fb7642 100644 --- a/packages/codemirror/themes.mjs +++ b/packages/codemirror/themes.mjs @@ -473,6 +473,9 @@ 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); @@ -481,4 +484,38 @@ export function injectStyle(rule) { return () => styleSheet.deleteRule(ruleIndex); } -export const theme = (theme) => themes[theme] || themes.strudelTheme; +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); + } +} diff --git a/website/src/components/HeadCommonNew.astro b/website/src/components/HeadCommonNew.astro new file mode 100644 index 00000000..9f323a7a --- /dev/null +++ b/website/src/components/HeadCommonNew.astro @@ -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; +--- + + + + + + + + + + + + + + + + + + + + + + + +{pwaInfo && } + + diff --git a/website/src/pages/vanilla/index.astro b/website/src/pages/vanilla/index.astro index 5377c08f..de138380 100644 --- a/website/src/pages/vanilla/index.astro +++ b/website/src/pages/vanilla/index.astro @@ -1,10 +1,10 @@ --- -import HeadCommon from '../../components/HeadCommon.astro'; +import HeadCommonNew from '../../components/HeadCommonNew.astro'; --- - + Strudel Vanilla REPL diff --git a/website/src/repl/vanilla/vanilla.css b/website/src/repl/vanilla/vanilla.css index 7c985be7..584dd924 100644 --- a/website/src/repl/vanilla/vanilla.css +++ b/website/src/repl/vanilla/vanilla.css @@ -1,7 +1,3 @@ -:root { - --foreground: white; -} - body, input { font-family: monospace; diff --git a/website/src/repl/vanilla/vanilla.mjs b/website/src/repl/vanilla/vanilla.mjs index 6e5c54a3..488fcf8c 100644 --- a/website/src/repl/vanilla/vanilla.mjs +++ b/website/src/repl/vanilla/vanilla.mjs @@ -1,5 +1,5 @@ import { logger, getDrawContext, silence, controls, evalScope, hash2code, code2hash } from '@strudel.cycles/core'; -import { StrudelMirror } from '@strudel/codemirror'; +import { StrudelMirror, initTheme, activateTheme } from '@strudel/codemirror'; import { transpiler } from '@strudel.cycles/transpiler'; import { getAudioContext, @@ -24,6 +24,7 @@ const initialSettings = { fontFamily: 'monospace', fontSize: 18, }; +initTheme(initialSettings.theme); async function run() { const container = document.getElementById('code'); @@ -196,4 +197,6 @@ form.addEventListener('change', () => { const values = getFormValues(form, initialSettings); // console.log('values', values); editor.updateSettings(values); + // TODO: only activateTheme when it changes + activateTheme(values.theme); }); From d23cac506c9554bd00f2c0ab42f4cd83ca2b06db Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 22:51:17 +0100 Subject: [PATCH 11/97] fix: fontFamily setting --- packages/codemirror/codemirror.mjs | 4 ++++ website/src/pages/vanilla/index.astro | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 7933600e..ca42658a 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -190,6 +190,10 @@ export class StrudelMirror { } 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]) { diff --git a/website/src/pages/vanilla/index.astro b/website/src/pages/vanilla/index.astro index de138380..e5a935c0 100644 --- a/website/src/pages/vanilla/index.astro +++ b/website/src/pages/vanilla/index.astro @@ -60,10 +60,8 @@ import HeadCommonNew from '../../components/HeadCommonNew.astro';
- +

From 29082808cb340e1b53c4e2183fb76ce623be45c9 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 14 Dec 2023 23:00:18 +0100 Subject: [PATCH 12/97] fix: make canvas visible - automate editor background handling --- packages/codemirror/codemirror.mjs | 5 +++++ website/src/pages/vanilla/index.astro | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index ca42658a..3c17826d 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -150,6 +150,11 @@ export class StrudelMirror { onEvaluate: () => this.evaluate(), 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) { diff --git a/website/src/pages/vanilla/index.astro b/website/src/pages/vanilla/index.astro index e5a935c0..44144bb6 100644 --- a/website/src/pages/vanilla/index.astro +++ b/website/src/pages/vanilla/index.astro @@ -7,7 +7,7 @@ import HeadCommonNew from '../../components/HeadCommonNew.astro'; Strudel Vanilla REPL - +
-
+ + diff --git a/website/src/pages/vanilla/mini.astro b/website/src/pages/vanilla/mini.astro index 77dd58b6..68dccb8a 100644 --- a/website/src/pages/vanilla/mini.astro +++ b/website/src/pages/vanilla/mini.astro @@ -2,13 +2,29 @@ import HeadCommonNew from '../../components/HeadCommonNew.astro'; --- + - + + Strudel Vanilla REPL +

vanilli repl

+

This is a REPL:

,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(",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("/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))) - )`}> - + code={`s("bd")`}> + +

This is another REPL:

+ diff --git a/website/src/repl/vanilla/vanilla.css b/website/src/repl/vanilla/vanilla.css index 584dd924..5387fb04 100644 --- a/website/src/repl/vanilla/vanilla.css +++ b/website/src/repl/vanilla/vanilla.css @@ -12,7 +12,7 @@ select { html, body, -#code, +#editor, .cm-editor, .cm-scroller { padding: 0; diff --git a/website/src/repl/vanilla/vanilla.mjs b/website/src/repl/vanilla/vanilla.mjs index 488fcf8c..721b745c 100644 --- a/website/src/repl/vanilla/vanilla.mjs +++ b/website/src/repl/vanilla/vanilla.mjs @@ -1,13 +1,5 @@ -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 { hash2code, logger } from '@strudel.cycles/core'; +import { activateTheme, initTheme } from '@strudel/codemirror'; import './vanilla.css'; let editor; @@ -27,68 +19,9 @@ const initialSettings = { 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 + const repl = document.getElementById('editor'); + editor = repl.editor; 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] || ''; @@ -195,8 +128,7 @@ 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); + editor?.updateSettings(values); // TODO: only activateTheme when it changes activateTheme(values.theme); }); From f6b1e13113c4d8ad774c0e9b509310c703f9cd57 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 10:14:20 +0100 Subject: [PATCH 17/97] fix: lint errors + wrong color --- packages/repl/repl-component.mjs | 2 +- website/src/pages/vanilla/mini.astro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/repl/repl-component.mjs b/packages/repl/repl-component.mjs index 44220766..a4b14a0d 100644 --- a/packages/repl/repl-component.mjs +++ b/packages/repl/repl-component.mjs @@ -53,7 +53,7 @@ class StrudelRepl extends HTMLElement { attributeChangedCallback(name, oldValue, newValue) { if (name === 'code') { this.code = newValue; - this.editor?.setCode(initialCode); + this.editor?.setCode(newValue); } else if (settingAttributes.includes(name)) { const camel = kebabToCamel(name); this.settings[camel] = parseAttribute(name, newValue); diff --git a/website/src/pages/vanilla/mini.astro b/website/src/pages/vanilla/mini.astro index 68dccb8a..2f8e8599 100644 --- a/website/src/pages/vanilla/mini.astro +++ b/website/src/pages/vanilla/mini.astro @@ -8,7 +8,7 @@ import HeadCommonNew from '../../components/HeadCommonNew.astro'; Strudel Vanilla REPL diff --git a/website/src/repl/vanilla/vanilla.mjs b/website/src/repl/vanilla/vanilla.mjs index 721b745c..3c2a54a5 100644 --- a/website/src/repl/vanilla/vanilla.mjs +++ b/website/src/repl/vanilla/vanilla.mjs @@ -1,5 +1,4 @@ import { hash2code, logger } from '@strudel.cycles/core'; -import { activateTheme, initTheme } from '@strudel/codemirror'; import './vanilla.css'; let editor; @@ -16,7 +15,6 @@ const initialSettings = { fontFamily: 'monospace', fontSize: 18, }; -initTheme(initialSettings.theme); async function run() { const repl = document.getElementById('editor'); @@ -129,6 +127,4 @@ setFormValues(form, initialSettings); form.addEventListener('change', () => { const values = getFormValues(form, initialSettings); editor?.updateSettings(values); - // TODO: only activateTheme when it changes - activateTheme(values.theme); }); From 129327077a1338f78731f0a2e0d52d3a9f0b796e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 11:24:38 +0100 Subject: [PATCH 24/97] bump repl to 0.9.2 --- packages/repl/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/repl/package.json b/packages/repl/package.json index f3c60059..f79d94f3 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "0.9.1", + "version": "0.9.2", "description": "Strudel REPL as a Web Component", "main": "index.mjs", "publishConfig": { From 505ad17447276585e89177acce11772bdb0476a0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 11:25:36 +0100 Subject: [PATCH 25/97] fix: process is not defined --- packages/repl/index.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/repl/index.mjs b/packages/repl/index.mjs index 330cd77d..f771208d 100644 --- a/packages/repl/index.mjs +++ b/packages/repl/index.mjs @@ -1 +1,8 @@ +// nanostores use process.env which kills the browser build +window.process = { + env: { + NODE_ENV: 'development', + }, +}; + export * from './repl-component.mjs'; From 9fac2c3c339f9047b88a3612d2fe6ee928270c5f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 11:33:35 +0100 Subject: [PATCH 26/97] band aid fix for process is not defined --- packages/repl/examples/simple.html | 6 ++++-- packages/repl/package.json | 1 + packages/repl/vite.config.js | 7 ++++++ pnpm-lock.yaml | 34 ++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/repl/examples/simple.html b/packages/repl/examples/simple.html index 17c4bebf..da47d089 100644 --- a/packages/repl/examples/simple.html +++ b/packages/repl/examples/simple.html @@ -1,2 +1,4 @@ - - + + + + diff --git a/packages/repl/package.json b/packages/repl/package.json index f79d94f3..87e8b9c0 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -33,6 +33,7 @@ }, "homepage": "https://github.com/tidalcycles/strudel#readme", "dependencies": { + "@rollup/plugin-replace": "^5.0.5", "@strudel.cycles/core": "workspace:*", "@strudel.cycles/midi": "workspace:*", "@strudel.cycles/mini": "workspace:*", diff --git a/packages/repl/vite.config.js b/packages/repl/vite.config.js index d94cd4d7..49391bf3 100644 --- a/packages/repl/vite.config.js +++ b/packages/repl/vite.config.js @@ -2,6 +2,7 @@ import { defineConfig } from 'vite'; import { dependencies } from './package.json'; import { resolve } from 'path'; // import { visualizer } from 'rollup-plugin-visualizer'; +import replace from '@rollup/plugin-replace'; // https://vitejs.dev/config/ export default defineConfig({ @@ -15,6 +16,12 @@ export default defineConfig({ }, rollupOptions: { // external: [...Object.keys(dependencies)], + plugins: [ + replace({ + 'process.env.NODE_ENV': JSON.stringify('production'), + preventAssignment: true, + }), + ], }, target: 'esnext', }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3161184..344a24bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -470,6 +470,9 @@ importers: packages/repl: dependencies: + '@rollup/plugin-replace': + specifier: ^5.0.5 + version: 5.0.5 '@strudel.cycles/core': specifier: workspace:* version: link:../core @@ -4155,6 +4158,19 @@ packages: rollup: 2.79.1 dev: true + /@rollup/plugin-replace@5.0.5: + resolution: {integrity: sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0 + magic-string: 0.30.5 + dev: false + /@rollup/pluginutils@3.1.0(rollup@2.79.1): resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} @@ -4167,6 +4183,20 @@ packages: rollup: 2.79.1 dev: true + /@rollup/pluginutils@5.1.0: + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.0 + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: false + /@rollup/rollup-android-arm-eabi@4.9.0: resolution: {integrity: sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==} cpu: [arm] @@ -7585,6 +7615,10 @@ packages: resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} dev: true + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: false + /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: From 9b58cf9890488d9ab5a0bebd7f288585a857ac0f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 11:34:14 +0100 Subject: [PATCH 27/97] bump repl to 0.9.3 --- packages/repl/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/repl/package.json b/packages/repl/package.json index 87e8b9c0..9269d706 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "0.9.2", + "version": "0.9.3", "description": "Strudel REPL as a Web Component", "main": "index.mjs", "publishConfig": { From 372e93e8f8155ffb0ce851ae57a7f59bdd0a2658 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 11:50:49 +0100 Subject: [PATCH 28/97] repl now accepts code in innerHTML + bump --- packages/repl/examples/repl.html | 2 ++ packages/repl/package.json | 2 +- packages/repl/repl-component.mjs | 13 +++++++++++- website/src/pages/vanilla/mini.astro | 31 +++++++++++++++++++--------- 4 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 packages/repl/examples/repl.html diff --git a/packages/repl/examples/repl.html b/packages/repl/examples/repl.html new file mode 100644 index 00000000..925b00f1 --- /dev/null +++ b/packages/repl/examples/repl.html @@ -0,0 +1,2 @@ + + diff --git a/packages/repl/package.json b/packages/repl/package.json index 9269d706..aaa95983 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,6 +1,6 @@ { "name": "@strudel/repl", - "version": "0.9.3", + "version": "0.9.4", "description": "Strudel REPL as a Web Component", "main": "index.mjs", "publishConfig": { diff --git a/packages/repl/repl-component.mjs b/packages/repl/repl-component.mjs index d0a2bdf2..ebcca532 100644 --- a/packages/repl/repl-component.mjs +++ b/packages/repl/repl-component.mjs @@ -59,13 +59,24 @@ class StrudelRepl extends HTMLElement { } } connectedCallback() { + // setTimeout makes sure the dom is ready + setTimeout(() => { + const code = (this.innerHTML + '').replace('', '').trim(); + if (code) { + // use comment code in element body if present + this.setAttribute('code', code); + } + }); + // use a separate container for the editor, to make sure the innerHTML stays as is + const container = document.createElement('div'); + this.parentElement.insertBefore(container, this.nextSibling); const drawContext = getDrawContext(); const drawTime = [-2, 2]; this.editor = new StrudelMirror({ defaultOutput: webaudioOutput, getTime: () => getAudioContext().currentTime, transpiler, - root: this, + root: container, initialCode: '// LOADING', pattern: silence, settings: this.settings, diff --git a/website/src/pages/vanilla/mini.astro b/website/src/pages/vanilla/mini.astro index b68e141b..f2a25703 100644 --- a/website/src/pages/vanilla/mini.astro +++ b/website/src/pages/vanilla/mini.astro @@ -1,16 +1,9 @@ ---- -import HeadCommonNew from '../../components/HeadCommonNew.astro'; ---- - - - Strudel Vanilla REPL - -

vanilli repl

+

This is a REPL:

This is another REPL:

- + From 8e35079fd1a85244324e25aaf3c4e05bc59d7f6c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 21:01:45 +0100 Subject: [PATCH 29/97] more code in example --- packages/repl/examples/repl.html | 2 -- packages/repl/examples/simple.html | 33 ++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) delete mode 100644 packages/repl/examples/repl.html diff --git a/packages/repl/examples/repl.html b/packages/repl/examples/repl.html deleted file mode 100644 index 925b00f1..00000000 --- a/packages/repl/examples/repl.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/packages/repl/examples/simple.html b/packages/repl/examples/simple.html index da47d089..849a6368 100644 --- a/packages/repl/examples/simple.html +++ b/packages/repl/examples/simple.html @@ -1,4 +1,33 @@ - + - + + + From 93e8186388cb4344dee35a28102bcce1fb39b82b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 22:23:20 +0100 Subject: [PATCH 30/97] MicroRepl poc --- packages/codemirror/codemirror.mjs | 3 +- packages/core/repl.mjs | 41 +++++++++++- packages/repl/repl-component.mjs | 6 ++ website/src/docs/Icon.jsx | 38 +++++++++++ website/src/docs/MicroRepl.jsx | 91 +++++++++++++++++++++++++++ website/src/pages/vanilla/micro.astro | 32 ++++++++++ 6 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 website/src/docs/Icon.jsx create mode 100644 website/src/docs/MicroRepl.jsx create mode 100644 website/src/pages/vanilla/micro.astro diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 92dd6214..d7d67607 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -146,8 +146,7 @@ export class StrudelMirror { onChange: (v) => { 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); + this.repl.setCode?.(this.code); } }, onEvaluate: () => this.evaluate(), diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 0bd5229d..b3c7a543 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -16,13 +16,36 @@ export function repl({ transpiler, onToggle, editPattern, + onUpdateState, }) { + const state = { + schedulerError: undefined, + evalError: undefined, + code: '// LOADING', + activeCode: '// LOADING', + pattern: undefined, + miniLocations: [], + widgets: [], + pending: true, + started: false, + }; + + const updateState = (update) => { + Object.assign(state, update); + state.isDirty = state.code !== state.activeCode; + state.error = state.evalError || state.schedulerError; + onUpdateState?.(state); + }; + const scheduler = new Cyclist({ interval, onTrigger: getTrigger({ defaultOutput, getTime }), onError: onSchedulerError, getTime, - onToggle, + onToggle: (started) => { + updateState({ started }); + onToggle?.(started); + }, }); let pPatterns = {}; let allTransform; @@ -43,6 +66,7 @@ export function repl({ throw new Error('no code to evaluate'); } try { + updateState({ code, pending: true }); await beforeEval?.({ code }); shouldHush && hush(); let { pattern, meta } = await _evaluate(code, transpiler); @@ -58,17 +82,28 @@ export function repl({ } logger(`[eval] code updated`); setPattern(pattern, autostart); + updateState({ + miniLocations: meta?.miniLocations || [], + widgets: meta?.widgets || [], + activeCode: code, + pattern, + evalError: undefined, + schedulerError: undefined, + pending: false, + }); afterEval?.({ code, pattern, meta }); return pattern; } catch (err) { // console.warn(`[repl] eval error: ${err.message}`); logger(`[eval] error: ${err.message}`, 'error'); + updateState({ evalError: err, pending: false }); onEvalError?.(err); } }; const stop = () => scheduler.stop(); const start = () => scheduler.start(); const pause = () => scheduler.pause(); + const toggle = () => scheduler.toggle(); const setCps = (cps) => scheduler.setCps(cps); const setCpm = (cpm) => scheduler.setCps(cpm / 60); @@ -127,8 +162,8 @@ export function repl({ setCpm, setcpm: setCpm, }); - - return { scheduler, evaluate, start, stop, pause, setCps, setPattern }; + const setCode = (code) => updateState({ code }); + return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state }; } export const getTrigger = diff --git a/packages/repl/repl-component.mjs b/packages/repl/repl-component.mjs index ebcca532..554be3bc 100644 --- a/packages/repl/repl-component.mjs +++ b/packages/repl/repl-component.mjs @@ -92,6 +92,12 @@ class StrudelRepl extends HTMLElement { afterEval: ({ code }) => { // window.location.hash = '#' + code2hash(code); }, + onUpdateState: (state) => { + const event = new CustomEvent('update', { + detail: state, + }); + this.dispatchEvent(event); + }, }); // init settings this.editor.updateSettings(this.settings); diff --git a/website/src/docs/Icon.jsx b/website/src/docs/Icon.jsx new file mode 100644 index 00000000..64d5f88a --- /dev/null +++ b/website/src/docs/Icon.jsx @@ -0,0 +1,38 @@ +export function Icon({ type }) { + return ( + + { + { + refresh: ( + + ), + play: ( + + ), + pause: ( + + ), + stop: ( + + ), + }[type] + } + + ); +} diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx new file mode 100644 index 00000000..b3e2637e --- /dev/null +++ b/website/src/docs/MicroRepl.jsx @@ -0,0 +1,91 @@ +import { useState, useRef, useCallback, useEffect } from 'react'; +import { Icon } from './Icon'; + +export function MicroRepl({ code, hideHeader = false }) { + /* const [ref, isVisible] = useInView({ + threshold: 0.01, + }); */ + const [replState, setReplState] = useState({}); + const { started, isDirty, error } = replState; + const wc = useRef(); + function togglePlay() { + if (wc.current) { + wc.current?.editor.evaluate(); + } + } + const listener = useCallback((e) => setReplState({ ...e.detail }), []); + useEffect(() => { + return () => { + wc.current.removeEventListener('update', listener); + }; + }, []); + return ( +
+ {!hideHeader && ( +
+
+ + +
+
+ )} +
+ { + if (wc.current) { + return; + } + wc.current = el; + el.addEventListener('update', listener); + }} + > + {error &&
{error.message}
} +
+ {/* punchcard && ( + { + if (el && el.width !== el.clientWidth) { + el.width = el.clientWidth; + } + }} + > + ) */} + {/* !!log.length && ( +
+ {log.map(({ message }, i) => ( +
{message}
+ ))} +
+ ) */} +
+ ); +} + +function cx(...classes) { + // : Array + return classes.filter(Boolean).join(' '); +} diff --git a/website/src/pages/vanilla/micro.astro b/website/src/pages/vanilla/micro.astro new file mode 100644 index 00000000..e8414eed --- /dev/null +++ b/website/src/pages/vanilla/micro.astro @@ -0,0 +1,32 @@ +--- +import { MicroRepl } from '../../docs/MicroRepl' +--- + + + + Strudel Micro REPL + + + +
+

MicroRepl:

+ /16") + .dict('ireal') + .voicing() + .when("<1 0> 0@3", sub(note(12))) + .add(note("[12 | 0]*4")) + .attack(slider(0.511)) + .decay(slider(0.466)) + .sustain(slider(0.398)) + .release(slider(0.443)) + .s('sawtooth') + .lpa(sine.range(.1,.25).slow(12)) + .lpenv(sine.range(0,4).slow(16)) + .lpf(perlin.range(400,1000)) + .add(note(perlin.range(0,.25))) + .vib(4).vibmod(.1) + .room(.75).size(8)`} /> +
+ + From b5edcde638c4a44a9ee20803bf267a5a6c31c872 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 23:18:58 +0100 Subject: [PATCH 31/97] cleanup --- website/src/components/HeadCommonNew.astro | 58 ---------------------- website/src/pages/vanilla/index.astro | 5 -- 2 files changed, 63 deletions(-) delete mode 100644 website/src/components/HeadCommonNew.astro diff --git a/website/src/components/HeadCommonNew.astro b/website/src/components/HeadCommonNew.astro deleted file mode 100644 index 9f323a7a..00000000 --- a/website/src/components/HeadCommonNew.astro +++ /dev/null @@ -1,58 +0,0 @@ ---- -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; ---- - - - - - - - - - - - - - - - - - - - - - - - -{pwaInfo && } - - diff --git a/website/src/pages/vanilla/index.astro b/website/src/pages/vanilla/index.astro index d5c41d6d..d4ea40b8 100644 --- a/website/src/pages/vanilla/index.astro +++ b/website/src/pages/vanilla/index.astro @@ -1,10 +1,5 @@ ---- -import HeadCommonNew from '../../components/HeadCommonNew.astro'; ---- - - Strudel Vanilla REPL From 9c13f6bb53df9ad4d7b0bc0d1369d7edb4d779e0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 23:20:21 +0100 Subject: [PATCH 32/97] fix: mini repl play toggle button --- packages/codemirror/codemirror.mjs | 2 +- website/src/docs/MicroRepl.jsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index d7d67607..4b64692c 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -182,7 +182,7 @@ export class StrudelMirror { } async toggle() { if (this.repl.scheduler.started) { - this.repl.scheduler.stop(); + this.repl.stop(); } else { this.evaluate(); } diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index b3e2637e..534db708 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -1,5 +1,6 @@ import { useState, useRef, useCallback, useEffect } from 'react'; import { Icon } from './Icon'; +// import { useInView } from 'react-hook-inview'; export function MicroRepl({ code, hideHeader = false }) { /* const [ref, isVisible] = useInView({ @@ -10,7 +11,7 @@ export function MicroRepl({ code, hideHeader = false }) { const wc = useRef(); function togglePlay() { if (wc.current) { - wc.current?.editor.evaluate(); + wc.current?.editor.toggle(); } } const listener = useCallback((e) => setReplState({ ...e.detail }), []); From 89c06046b525100a3c28832a4460eda9b05a7950 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 23:27:38 +0100 Subject: [PATCH 33/97] fix: repl package import on server --- packages/repl/index.mjs | 7 -- packages/repl/prebake.mjs | 7 +- packages/repl/repl-component.mjs | 132 ++++++++++++++++--------------- 3 files changed, 72 insertions(+), 74 deletions(-) diff --git a/packages/repl/index.mjs b/packages/repl/index.mjs index f771208d..330cd77d 100644 --- a/packages/repl/index.mjs +++ b/packages/repl/index.mjs @@ -1,8 +1 @@ -// nanostores use process.env which kills the browser build -window.process = { - env: { - NODE_ENV: 'development', - }, -}; - export * from './repl-component.mjs'; diff --git a/packages/repl/prebake.mjs b/packages/repl/prebake.mjs index 1038bab3..02638d1a 100644 --- a/packages/repl/prebake.mjs +++ b/packages/repl/prebake.mjs @@ -1,5 +1,4 @@ import { controls, evalScope } from '@strudel.cycles/core'; -import { registerSoundfonts } from '@strudel.cycles/soundfonts'; import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; import * as core from '@strudel.cycles/core'; @@ -26,7 +25,11 @@ export async function prebake() { modulesLoading, registerSynthSounds(), registerZZFXSounds(), - registerSoundfonts(), + //registerSoundfonts(), + // need dynamic import here, because importing @strudel.cycles/soundfonts fails on server: + // => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically + // seems to be a problem with soundfont2 + import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()), samples(`${ds}/tidal-drum-machines.json`), samples(`${ds}/piano.json`), samples(`${ds}/Dirt-Samples.json`), diff --git a/packages/repl/repl-component.mjs b/packages/repl/repl-component.mjs index 554be3bc..2eda358d 100644 --- a/packages/repl/repl-component.mjs +++ b/packages/repl/repl-component.mjs @@ -40,72 +40,74 @@ const parseAttribute = (name, value) => { return value; }; // console.log('attributes', settingAttributes); -class StrudelRepl extends HTMLElement { - static observedAttributes = ['code', ...settingAttributes]; - settings = initialSettings; - editor = null; - constructor() { - super(); - } - attributeChangedCallback(name, oldValue, newValue) { - if (name === 'code') { - this.code = newValue; - this.editor?.setCode(newValue); - } else if (settingAttributes.includes(name)) { - const camel = kebabToCamel(name); - this.settings[camel] = parseAttribute(name, newValue); - // console.log('name', name, newValue, camel, this.settings[camel]); - this.editor?.updateSettings(this.settings); +if (typeof HTMLElement !== 'undefined') { + class StrudelRepl extends HTMLElement { + static observedAttributes = ['code', ...settingAttributes]; + settings = initialSettings; + editor = null; + constructor() { + super(); } - } - connectedCallback() { - // setTimeout makes sure the dom is ready - setTimeout(() => { - const code = (this.innerHTML + '').replace('', '').trim(); - if (code) { - // use comment code in element body if present - this.setAttribute('code', code); + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'code') { + this.code = newValue; + this.editor?.setCode(newValue); + } else if (settingAttributes.includes(name)) { + const camel = kebabToCamel(name); + this.settings[camel] = parseAttribute(name, newValue); + // console.log('name', name, newValue, camel, this.settings[camel]); + this.editor?.updateSettings(this.settings); } - }); - // use a separate container for the editor, to make sure the innerHTML stays as is - const container = document.createElement('div'); - this.parentElement.insertBefore(container, this.nextSibling); - const drawContext = getDrawContext(); - const drawTime = [-2, 2]; - this.editor = new StrudelMirror({ - defaultOutput: webaudioOutput, - getTime: () => getAudioContext().currentTime, - transpiler, - root: container, - initialCode: '// LOADING', - pattern: silence, - settings: this.settings, - 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, - afterEval: ({ code }) => { - // window.location.hash = '#' + code2hash(code); - }, - onUpdateState: (state) => { - const event = new CustomEvent('update', { - detail: state, - }); - this.dispatchEvent(event); - }, - }); - // init settings - this.editor.updateSettings(this.settings); - this.editor.setCode(this.code); - // settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key])); - // onEvent('strudel-toggle-play', () => this.editor.toggle()); + } + connectedCallback() { + // setTimeout makes sure the dom is ready + setTimeout(() => { + const code = (this.innerHTML + '').replace('', '').trim(); + if (code) { + // use comment code in element body if present + this.setAttribute('code', code); + } + }); + // use a separate container for the editor, to make sure the innerHTML stays as is + const container = document.createElement('div'); + this.parentElement.insertBefore(container, this.nextSibling); + const drawContext = getDrawContext(); + const drawTime = [-2, 2]; + this.editor = new StrudelMirror({ + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + root: container, + initialCode: '// LOADING', + pattern: silence, + settings: this.settings, + 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, + afterEval: ({ code }) => { + // window.location.hash = '#' + code2hash(code); + }, + onUpdateState: (state) => { + const event = new CustomEvent('update', { + detail: state, + }); + this.dispatchEvent(event); + }, + }); + // init settings + this.editor.updateSettings(this.settings); + this.editor.setCode(this.code); + // settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key])); + // onEvent('strudel-toggle-play', () => this.editor.toggle()); + } + // Element functionality written in here } - // Element functionality written in here -} -customElements.define('strudel-editor', StrudelRepl); + customElements.define('strudel-editor', StrudelRepl); +} From 20e8a305836e4f43b68b1d40b831983d4c84a139 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 15 Dec 2023 23:30:27 +0100 Subject: [PATCH 34/97] copy recipes page to test new MicroRepl --- website/src/docs/MicroRepl.jsx | 2 + website/src/pages/recipes/recipes-next.mdx | 312 +++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 website/src/pages/recipes/recipes-next.mdx diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index 534db708..a9673142 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -1,5 +1,7 @@ import { useState, useRef, useCallback, useEffect } from 'react'; import { Icon } from './Icon'; +import '@strudel/repl'; + // import { useInView } from 'react-hook-inview'; export function MicroRepl({ code, hideHeader = false }) { diff --git a/website/src/pages/recipes/recipes-next.mdx b/website/src/pages/recipes/recipes-next.mdx new file mode 100644 index 00000000..62a1a5e9 --- /dev/null +++ b/website/src/pages/recipes/recipes-next.mdx @@ -0,0 +1,312 @@ +--- +title: Recipes +layout: ../../layouts/MainLayout.astro +--- + +import { MicroRepl } from '../../docs/MicroRepl'; + +# Recipes + +This page shows possible ways to achieve common (or not so common) musical goals. +There are often many ways to do a thing and there is no right or wrong. +The fun part is that each representation will give you different impulses when improvising. + +## Arpeggios + +An arpeggio is when the notes of a chord are played in sequence. +We can either write the notes by hand: + + + +...or use scales: + + + +...or chord symbols: + + + +...using off: + + + +## Chopping Breaks + +A sample can be looped and chopped like this: + + + +This fits the break into 8 cycles + chops it in 16 pieces. +The chops are not audible yet, because we're not doing any manipulation. +Let's add randmized doubling + reversing: + + + +If we want to specify the order of samples, we can replace `chop` with `slice`: + +") + .cut(1).rarely(ply(2))`} + punchcard +/> + +If we use `splice` instead of `slice`, the speed adjusts to the duration of the event: + +") + .cut(1).rarely(ply(2))`} + punchcard +/> + +Note that we don't need `fit`, because `splice` will do that by itself. + +## Filter Envelopes + +A minimal filter envelope looks like this: + + d2") + .s("sawtooth") + .lpf(400).lpa(.2).lpenv(4) + .scope()`} +/> + +We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`: + + d2") + .s("sawtooth").lpq(8) + .lpf(400).lpa(.2).lpenv(-4) + .scope()`} +/> + +## Layering Sounds + +We can layer sounds by separating them with ",": + +") +.s("sawtooth, square") // <------ +.scope()`} +/> + +We can control the gain of individual sounds like this: + +") +.s("sawtooth, square:0:.5") // <--- "name:number:gain" +.scope()`} +/> + +For more control over each voice, we can use `layer`: + +").layer( + x=>x.s("sawtooth").vib(4), + x=>x.s("square").add(note(12)) +).scope()`} +/> + +Here, we give the sawtooth a vibrato and the square is moved an octave up. +With `layer`, you can use any pattern method available on each voice, so sky is the limit.. + +## Oscillator Detune + +We can fatten a sound by adding a detuned version to itself: + +") +.add(note("0,.1")) // <------ chorus +.s("sawtooth").scope()`} + punchcard +/> + +Try out different values, or add another voice! + +## Polyrhythms + +Here is a simple example of a polyrhythm: + + + +A polyrhythm is when 2 different tempos happen at the same time. + +## Polymeter + +This is a polymeter: + +,").fast(2)`} punchcard /> + +A polymeter is when 2 different bar lengths play at the same tempo. + +## Phasing + +This is a phasing: + +*[6,6.1]").piano()`} punchcard /> + +Phasing happens when the same sequence plays at slightly different tempos. + +## Running through samples + +Using `run` with `n`, we can rush through a sample bank: + + + +This works great with sample banks that contain similar sounds, like in this case different recordings of a tabla. +Often times, you'll hear the beginning of the phrase not where the pattern begins. +In this case, I hear the beginning at the third sample, which can be accounted for with `early`. + + + +Let's add some randomness: + + + +## Tape Warble + +We can emulate a pitch warbling effect like this: + + + +## Sound Duration + +There are a number of ways to change the sound duration. Using clip: + +/2")`} +/> + +The value of clip is relative to the duration of each event. +We can also create overlaps using release: + +/2")`} +/> + +This will smoothly fade out each sound for the given number of seconds. +We could also make the notes shorter with decay / sustain: + +/2").sustain(0)`} +/> + +For now, there is a limitation where decay values that exceed the event duration may cause little cracks, so use higher numbers with caution.. + +When using samples, we also have `.end` to cut relative to the sample length: + +")`} /> + +Compare that to clip: + +")`} /> + +or decay / sustain + +").sustain(0)`} /> + +## Wavetable Synthesis + +You can loop a sample with `loop` / `loopEnd`: + +").s("bd").loop(1).loopEnd(.05).gain(.2)`} /> + +This allows us to play the first 5% of the bass drum as a synth! +To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically: + + + +Running through different wavetables can also give interesting variations: + + + +...adding a filter envelope + reverb: + + From 48e06bd213dace518d3068ad3eb3f0d68b6f74a8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Dec 2023 12:37:16 +0100 Subject: [PATCH 35/97] fix: redundant style injections for multiple repls --- packages/codemirror/themes.mjs | 14 ++++++++++---- website/src/docs/MicroRepl.jsx | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/themes.mjs b/packages/codemirror/themes.mjs index 71fb7642..ee3e05bf 100644 --- a/packages/codemirror/themes.mjs +++ b/packages/codemirror/themes.mjs @@ -484,11 +484,16 @@ export function injectStyle(rule) { return () => styleSheet.deleteRule(ruleIndex); } -let currentTheme, resetThemeStyle, themeStyle; +let currentTheme, + resetThemeStyle, + themeStyle, + styleID = 'strudel-theme-vars'; export function initTheme(theme) { - themeStyle = document.createElement('style'); - themeStyle.id = 'strudel-theme'; - document.head.append(themeStyle); + if (!document.getElementById(styleID)) { + themeStyle = document.createElement('style'); + themeStyle.id = styleID; + document.head.append(themeStyle); + } activateTheme(theme); } @@ -496,6 +501,7 @@ export function activateTheme(name) { if (currentTheme === name) { return; } + currentTheme = name; if (!settings[name]) { console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings'); } diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index a9673142..c0e34637 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -54,6 +54,7 @@ export function MicroRepl({ code, hideHeader = false }) {
{ if (wc.current) { From fc034830d081ecafa8f8580f4eaf1ea70d35b33f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Dec 2023 13:08:09 +0100 Subject: [PATCH 36/97] use StrudelMirror directly in MicroRepl --- packages/repl/index.mjs | 1 + packages/repl/repl-component.mjs | 2 - pnpm-lock.yaml | 3 + website/package.json | 3 +- website/src/docs/MicroRepl.jsx | 134 ++++++++++++++++++++----------- 5 files changed, 93 insertions(+), 50 deletions(-) diff --git a/packages/repl/index.mjs b/packages/repl/index.mjs index 330cd77d..4119059d 100644 --- a/packages/repl/index.mjs +++ b/packages/repl/index.mjs @@ -1 +1,2 @@ export * from './repl-component.mjs'; +export * from './prebake.mjs'; diff --git a/packages/repl/repl-component.mjs b/packages/repl/repl-component.mjs index 2eda358d..b8947640 100644 --- a/packages/repl/repl-component.mjs +++ b/packages/repl/repl-component.mjs @@ -103,8 +103,6 @@ if (typeof HTMLElement !== 'undefined') { // init settings this.editor.updateSettings(this.settings); this.editor.setCode(this.code); - // settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key])); - // onEvent('strudel-toggle-play', () => this.editor.toggle()); } // Element functionality written in here } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 344a24bc..67599cd9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -793,6 +793,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-hook-inview: + specifier: ^4.5.0 + version: 4.5.0(react-dom@18.2.0)(react@18.2.0) rehype-autolink-headings: specifier: ^6.1.1 version: 6.1.1 diff --git a/website/package.json b/website/package.json index b2267a69..b2c5f39a 100644 --- a/website/package.json +++ b/website/package.json @@ -34,9 +34,9 @@ "@strudel.cycles/transpiler": "workspace:*", "@strudel.cycles/webaudio": "workspace:*", "@strudel.cycles/xen": "workspace:*", - "@strudel/hydra": "workspace:*", "@strudel/codemirror": "workspace:*", "@strudel/desktopbridge": "workspace:*", + "@strudel/hydra": "workspace:*", "@strudel/repl": "workspace:*", "@supabase/supabase-js": "^2.21.0", "@tailwindcss/forms": "^0.5.3", @@ -54,6 +54,7 @@ "nanostores": "^0.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-inview": "^4.5.0", "rehype-autolink-headings": "^6.1.1", "rehype-slug": "^5.0.1", "rehype-urls": "^1.1.1", diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index c0e34637..cc401cd6 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -1,32 +1,78 @@ import { useState, useRef, useCallback, useEffect } from 'react'; import { Icon } from './Icon'; -import '@strudel/repl'; +import { getDrawContext, silence } from '@strudel.cycles/core'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import { StrudelMirror } from '@strudel/codemirror'; +import { prebake } from '@strudel/repl'; +import { useInView } from 'react-hook-inview'; -// import { useInView } from 'react-hook-inview'; +const initialSettings = { + keybindings: 'strudelTheme', + isLineNumbersDisplayed: false, + isActiveLineHighlighted: true, + isAutoCompletionEnabled: false, + isPatternHighlightingEnabled: true, + isFlashEnabled: true, + isTooltipEnabled: false, + isLineWrappingEnabled: false, + theme: 'strudelTheme', + fontFamily: 'monospace', + fontSize: 18, +}; -export function MicroRepl({ code, hideHeader = false }) { - /* const [ref, isVisible] = useInView({ +export function MicroRepl({ code, hideHeader = false, canvasHeight = 200, punchcard, punchcardLabels }) { + const init = useCallback(({ code }) => { + const drawContext = getDrawContext(); + const drawTime = [-2, 2]; + const editor = new StrudelMirror({ + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + root: containerRef.current, + 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, + onUpdateState: (state) => { + setReplState({ ...state }); + }, + }); + // init settings + editor.updateSettings(initialSettings); + editor.setCode(code); + editorRef.current = editor; + }, []); + + const [ref, isVisible] = useInView({ threshold: 0.01, - }); */ + onEnter: () => { + if (!editorRef.current) { + init({ code }); + } + }, + }); const [replState, setReplState] = useState({}); const { started, isDirty, error } = replState; - const wc = useRef(); - function togglePlay() { - if (wc.current) { - wc.current?.editor.toggle(); - } - } - const listener = useCallback((e) => setReplState({ ...e.detail }), []); - useEffect(() => { - return () => { - wc.current.removeEventListener('update', listener); - }; - }, []); + const editorRef = useRef(); + const containerRef = useRef(); + + const [canvasId] = useState(Date.now()); + const drawContext = useCallback( + punchcard ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null, + [punchcard], + ); + return ( -
+
{!hideHeader && (
@@ -35,7 +81,7 @@ export function MicroRepl({ code, hideHeader = false }) { 'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background', started ? 'animate-pulse' : '', )} - onClick={() => togglePlay()} + onClick={() => editorRef.current?.toggle()} > @@ -44,40 +90,34 @@ export function MicroRepl({ code, hideHeader = false }) { 'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight', isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed', )} - onClick={() => activateCode()} + onClick={() => editorRef.current?.evaluate()} >
)} -
- { - if (wc.current) { - return; - } - wc.current = el; - el.addEventListener('update', listener); - }} - > +
+
{error &&
{error.message}
}
{/* punchcard && ( - { - if (el && el.width !== el.clientWidth) { - el.width = el.clientWidth; - } - }} - > - ) */} + { + if (el && el.width !== el.clientWidth) { + el.width = el.clientWidth; + } + //const ratio = el.clientWidth / canvasHeight; + //const targetWidth = Math.round(el.width * ratio); + //if (el.width !== targetWidth) { + // el.width = targetWidth; + //} + }} + > + ) */} {/* !!log.length && (
{log.map(({ message }, i) => ( From 7fcd9d8a83c4bd61af6e4064711b069e8c1db470 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Dec 2023 16:14:40 +0100 Subject: [PATCH 37/97] approaching proper draw logic in microrepl --- packages/codemirror/codemirror.mjs | 3 +- packages/core/pianoroll.mjs | 9 ++-- website/src/docs/MicroRepl.jsx | 72 +++++++++++++++++++++--------- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 4b64692c..7cac0888 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -102,7 +102,8 @@ export class StrudelMirror { this.onDraw?.(haps, time, currentFrame, this.painters); }, drawTime); - // this approach might not work with multiple repls on screen.. + // this approach does not work with multiple repls on screen + // TODO: refactor onPaint usages + find fix, maybe remove painters here? Pattern.prototype.onPaint = function (onPaint) { self.painters.push(onPaint); return this; diff --git a/packages/core/pianoroll.mjs b/packages/core/pianoroll.mjs index 254dd94a..4a8fd8db 100644 --- a/packages/core/pianoroll.mjs +++ b/packages/core/pianoroll.mjs @@ -256,10 +256,13 @@ export function getDrawOptions(drawTime, options = {}) { return { fold: 1, ...options, cycles, playhead }; } +export const getPunchcardPainter = + (options = {}) => + (ctx, time, haps, drawTime, paintOptions = {}) => + pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) }); + Pattern.prototype.punchcard = function (options) { - return this.onPaint((ctx, time, haps, drawTime, paintOptions = {}) => - pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) }), - ); + return this.onPaint(getPunchcardPainter(options)); }; /** diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index cc401cd6..54b5e27e 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -1,6 +1,6 @@ -import { useState, useRef, useCallback, useEffect } from 'react'; +import { useState, useRef, useCallback, useMemo } from 'react'; import { Icon } from './Icon'; -import { getDrawContext, silence } from '@strudel.cycles/core'; +import { silence, getPunchcardPainter } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; import { StrudelMirror } from '@strudel/codemirror'; @@ -9,8 +9,8 @@ import { useInView } from 'react-hook-inview'; const initialSettings = { keybindings: 'strudelTheme', - isLineNumbersDisplayed: false, - isActiveLineHighlighted: true, + isLineNumbersDisplayed: true, + isActiveLineHighlighted: false, isAutoCompletionEnabled: false, isPatternHighlightingEnabled: true, isFlashEnabled: true, @@ -21,10 +21,33 @@ const initialSettings = { fontSize: 18, }; -export function MicroRepl({ code, hideHeader = false, canvasHeight = 200, punchcard, punchcardLabels }) { - const init = useCallback(({ code }) => { - const drawContext = getDrawContext(); +export function MicroRepl({ + code, + hideHeader = false, + canvasHeight = 200, + onTrigger, + onPaint, + punchcard, + punchcardLabels = true, +}) { + const id = useMemo(() => s4(), []); + const canvasId = useMemo(() => `canvas-${id}`, [id]); + const shouldDraw = !!punchcard; + + const init = useCallback(({ code, shouldDraw }) => { const drawTime = [-2, 2]; + const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null; + let onDraw; + if (shouldDraw) { + 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 }); + }); + }; + } + const editor = new StrudelMirror({ defaultOutput: webaudioOutput, getTime: () => getAudioContext().currentTime, @@ -34,12 +57,17 @@ export function MicroRepl({ code, hideHeader = false, canvasHeight = 200, punchc 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 }); - }); + onDraw, + editPattern: (pat, id) => { + if (onTrigger) { + pat = pat.onTrigger(onTrigger, false); + } + if (onPaint) { + editor.painters.push(onPaint); + } else if (punchcard) { + editor.painters.push(getPunchcardPainter({ labels: !!punchcardLabels })); + } + return pat; }, prebake, onUpdateState: (state) => { @@ -56,7 +84,7 @@ export function MicroRepl({ code, hideHeader = false, canvasHeight = 200, punchc threshold: 0.01, onEnter: () => { if (!editorRef.current) { - init({ code }); + init({ code, shouldDraw }); } }, }); @@ -65,12 +93,6 @@ export function MicroRepl({ code, hideHeader = false, canvasHeight = 200, punchc const editorRef = useRef(); const containerRef = useRef(); - const [canvasId] = useState(Date.now()); - const drawContext = useCallback( - punchcard ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null, - [punchcard], - ); - return (
{!hideHeader && ( @@ -101,7 +123,7 @@ export function MicroRepl({ code, hideHeader = false, canvasHeight = 200, punchc
{error &&
{error.message}
}
- {/* punchcard && ( + {shouldDraw && ( - ) */} + )} {/* !!log.length && (
{log.map(({ message }, i) => ( @@ -133,3 +155,9 @@ function cx(...classes) { // : Array return classes.filter(Boolean).join(' '); } + +function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); +} From c29d032027e8f336fe2d6271db685ce9f99a2d3d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Dec 2023 16:19:18 +0100 Subject: [PATCH 38/97] add autodraw flag --- packages/codemirror/codemirror.mjs | 6 +++--- website/src/docs/MicroRepl.jsx | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 7cac0888..58a3e261 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -87,7 +87,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, set export class StrudelMirror { constructor(options) { - const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options; + const { root, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, settings, ...replOptions } = options; this.code = initialCode; this.root = root; this.miniLocations = []; @@ -110,7 +110,7 @@ export class StrudelMirror { }; this.prebaked = prebake(); - // this.drawFirstFrame(); + autodraw && this.drawFirstFrame(); this.repl = repl({ ...replOptions, @@ -169,7 +169,7 @@ export class StrudelMirror { try { await this.repl.evaluate(this.code, false); this.drawer.invalidate(this.repl.scheduler); - this.onDraw?.(this.drawer.visibleHaps, 0, []); + this.onDraw?.(this.drawer.visibleHaps, 0, [], this.painters); } catch (err) { console.warn('first frame could not be painted'); } diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index 54b5e27e..1b97d4e6 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -52,6 +52,7 @@ export function MicroRepl({ defaultOutput: webaudioOutput, getTime: () => getAudioContext().currentTime, transpiler, + autodraw: !!shouldDraw, root: containerRef.current, initialCode: '// LOADING', pattern: silence, From 53d6ef04b870d52ede0ef420ba0aa97e7a96a120 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Dec 2023 16:22:28 +0100 Subject: [PATCH 39/97] fix: drawTime + canvasHeight --- website/src/docs/MicroRepl.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index 1b97d4e6..8a2280e3 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -24,7 +24,7 @@ const initialSettings = { export function MicroRepl({ code, hideHeader = false, - canvasHeight = 200, + canvasHeight = 100, onTrigger, onPaint, punchcard, @@ -35,7 +35,7 @@ export function MicroRepl({ const shouldDraw = !!punchcard; const init = useCallback(({ code, shouldDraw }) => { - const drawTime = [-2, 2]; + const drawTime = [0, 4]; const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null; let onDraw; if (shouldDraw) { From 40f3212efa30056320ba15087b01686974ede52d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Dec 2023 17:04:08 +0100 Subject: [PATCH 40/97] fix: first frame active state --- packages/codemirror/codemirror.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 58a3e261..cf0a3f62 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -169,7 +169,8 @@ export class StrudelMirror { try { await this.repl.evaluate(this.code, false); this.drawer.invalidate(this.repl.scheduler); - this.onDraw?.(this.drawer.visibleHaps, 0, [], this.painters); + // draw at -0.001 to avoid haps at 0 to be visualized as active + this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters); } catch (err) { console.warn('first frame could not be painted'); } From 592e54a53f63f6840a104e2ead7316d2fae3a941 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Dec 2023 21:13:48 +0100 Subject: [PATCH 41/97] delete strudelmirror example --- .../examples/strudelmirror/.gitignore | 24 -- .../examples/strudelmirror/index.html | 87 ------- .../codemirror/examples/strudelmirror/main.js | 199 ---------------- .../examples/strudelmirror/package.json | 29 --- .../examples/strudelmirror/style.css | 33 --- pnpm-lock.yaml | 212 +----------------- pnpm-workspace.yaml | 1 - 7 files changed, 6 insertions(+), 579 deletions(-) delete mode 100644 packages/codemirror/examples/strudelmirror/.gitignore delete mode 100644 packages/codemirror/examples/strudelmirror/index.html delete mode 100644 packages/codemirror/examples/strudelmirror/main.js delete mode 100644 packages/codemirror/examples/strudelmirror/package.json delete mode 100644 packages/codemirror/examples/strudelmirror/style.css diff --git a/packages/codemirror/examples/strudelmirror/.gitignore b/packages/codemirror/examples/strudelmirror/.gitignore deleted file mode 100644 index a547bf36..00000000 --- a/packages/codemirror/examples/strudelmirror/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# 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? diff --git a/packages/codemirror/examples/strudelmirror/index.html b/packages/codemirror/examples/strudelmirror/index.html deleted file mode 100644 index 0e1d43ce..00000000 --- a/packages/codemirror/examples/strudelmirror/index.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - StrudelMirror Example - - -
-
-
- - -
- -
- -
- -
- -
- - - -
-
-
- - - diff --git a/packages/codemirror/examples/strudelmirror/main.js b/packages/codemirror/examples/strudelmirror/main.js deleted file mode 100644 index 676f4b97..00000000 --- a/packages/codemirror/examples/strudelmirror/main.js +++ /dev/null @@ -1,199 +0,0 @@ -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(",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("/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); -}); diff --git a/packages/codemirror/examples/strudelmirror/package.json b/packages/codemirror/examples/strudelmirror/package.json deleted file mode 100644 index 5c946bff..00000000 --- a/packages/codemirror/examples/strudelmirror/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "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:*" - } -} diff --git a/packages/codemirror/examples/strudelmirror/style.css b/packages/codemirror/examples/strudelmirror/style.css deleted file mode 100644 index fabc795c..00000000 --- a/packages/codemirror/examples/strudelmirror/style.css +++ /dev/null @@ -1,33 +0,0 @@ -: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; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67599cd9..6fecabff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: '@lezer/highlight': specifier: ^1.1.4 version: 1.1.4 + '@nanostores/persistent': + specifier: ^0.8.0 + version: 0.8.0(nanostores@0.8.1) '@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) @@ -117,6 +120,9 @@ importers: '@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) + nanostores: + specifier: ^0.8.1 + version: 0.8.1 react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) @@ -125,52 +131,6 @@ importers: specifier: ^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: dependencies: fraction.js: @@ -4200,110 +4160,6 @@ packages: picomatch: 2.3.1 dev: false - /@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: resolution: {integrity: sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -12791,27 +12647,6 @@ packages: optionalDependencies: 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: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -14470,41 +14305,6 @@ packages: optionalDependencies: 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): resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} peerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index baafa3c3..615475e4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,4 +7,3 @@ packages: - "packages/react/examples/nano-repl" - "packages/web/examples/repl-example" - "packages/superdough/example" - - "packages/codemirror/examples/strudelmirror" From 1cdb5964c62eb3e383a5ca02e46800253ac3d52d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Dec 2023 21:16:29 +0100 Subject: [PATCH 42/97] settings sync --- packages/codemirror/codemirror.mjs | 24 +++++++++++++++++++++--- packages/codemirror/package.json | 4 +++- packages/repl/repl-component.mjs | 24 ++++-------------------- website/src/docs/MicroRepl.jsx | 16 ---------------- website/src/pages/vanilla/mini.astro | 5 +++-- website/src/repl/vanilla/vanilla.mjs | 19 +++---------------- 6 files changed, 34 insertions(+), 58 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index cf0a3f62..25639a72 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -12,6 +12,7 @@ import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocatio import { keybindings } from './keybindings.mjs'; import { initTheme, activateTheme, theme } from './themes.mjs'; import { updateWidgets, sliderPlugin } from './slider.mjs'; +import { persistentMap } from '@nanostores/persistent'; const extensions = { isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), @@ -25,8 +26,25 @@ const extensions = { }; const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); +export const defaultSettings = { + keybindings: 'codemirror', + isLineNumbersDisplayed: true, + isActiveLineHighlighted: false, + isAutoCompletionEnabled: false, + isPatternHighlightingEnabled: true, + isFlashEnabled: true, + isTooltipEnabled: false, + isLineWrappingEnabled: false, + theme: 'strudelTheme', + fontFamily: 'monospace', + fontSize: 18, +}; + +export const codemirrorSettings = persistentMap('codemirror-settings', defaultSettings); + // https://codemirror.net/docs/guide/ -export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) { +export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root }) { + const settings = codemirrorSettings.get(); const initialSettings = Object.keys(compartments).map((key) => compartments[key].of(extensions[key](parseBooleans(settings[key]))), ); @@ -87,7 +105,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, set export class StrudelMirror { constructor(options) { - const { root, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, settings, ...replOptions } = options; + const { root, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, ...replOptions } = options; this.code = initialCode; this.root = root; this.miniLocations = []; @@ -142,7 +160,6 @@ export class StrudelMirror { }); this.editor = initEditor({ root, - settings, initialCode, onChange: (v) => { if (v.docChanged) { @@ -237,6 +254,7 @@ export class StrudelMirror { for (let key in extensions) { this.reconfigureExtension(key, settings[key]); } + codemirrorSettings.set({ ...codemirrorSettings.get(), ...settings }); } changeSetting(key, value) { if (extensions[key]) { diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index a309efa2..0c57db8b 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -47,7 +47,9 @@ "@strudel.cycles/core": "workspace:*", "@uiw/codemirror-themes": "^4.19.16", "@uiw/codemirror-themes-all": "^4.19.16", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "nanostores": "^0.8.1", + "@nanostores/persistent": "^0.8.0" }, "devDependencies": { "vite": "^4.3.3" diff --git a/packages/repl/repl-component.mjs b/packages/repl/repl-component.mjs index b8947640..b8dc4cb9 100644 --- a/packages/repl/repl-component.mjs +++ b/packages/repl/repl-component.mjs @@ -1,7 +1,7 @@ import { getDrawContext, silence } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; -import { StrudelMirror } from '@strudel/codemirror'; +import { StrudelMirror, defaultSettings, codemirrorSettings } from '@strudel/codemirror'; import { prebake } from './prebake.mjs'; function camelToKebab(camelCaseString) { @@ -13,24 +13,10 @@ function kebabToCamel(kebabCaseString) { }); } -const initialSettings = { - keybindings: 'strudelTheme', - isLineNumbersDisplayed: true, - isActiveLineHighlighted: true, - isAutoCompletionEnabled: false, - isPatternHighlightingEnabled: true, - isFlashEnabled: true, - isTooltipEnabled: false, - isLineWrappingEnabled: false, - theme: 'strudelTheme', - fontFamily: 'monospace', - fontSize: 18, -}; -const settingAttributes = Object.keys(initialSettings).map(camelToKebab); +const settingAttributes = Object.keys(defaultSettings).map(camelToKebab); const parseAttribute = (name, value) => { const camel = kebabToCamel(name); - const type = typeof initialSettings[camel]; - // console.log('type', type, name); + const type = typeof defaultSettings[camel]; if (type === 'boolean') { return ['1', 'true'].includes(value); } @@ -39,11 +25,10 @@ const parseAttribute = (name, value) => { } return value; }; -// console.log('attributes', settingAttributes); if (typeof HTMLElement !== 'undefined') { class StrudelRepl extends HTMLElement { static observedAttributes = ['code', ...settingAttributes]; - settings = initialSettings; + settings = codemirrorSettings.get(); editor = null; constructor() { super(); @@ -55,7 +40,6 @@ if (typeof HTMLElement !== 'undefined') { } else if (settingAttributes.includes(name)) { const camel = kebabToCamel(name); this.settings[camel] = parseAttribute(name, newValue); - // console.log('name', name, newValue, camel, this.settings[camel]); this.editor?.updateSettings(this.settings); } } diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index 8a2280e3..7e97a9bb 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -7,20 +7,6 @@ import { StrudelMirror } from '@strudel/codemirror'; import { prebake } from '@strudel/repl'; import { useInView } from 'react-hook-inview'; -const initialSettings = { - keybindings: 'strudelTheme', - isLineNumbersDisplayed: true, - isActiveLineHighlighted: false, - isAutoCompletionEnabled: false, - isPatternHighlightingEnabled: true, - isFlashEnabled: true, - isTooltipEnabled: false, - isLineWrappingEnabled: false, - theme: 'strudelTheme', - fontFamily: 'monospace', - fontSize: 18, -}; - export function MicroRepl({ code, hideHeader = false, @@ -56,7 +42,6 @@ export function MicroRepl({ root: containerRef.current, initialCode: '// LOADING', pattern: silence, - settings: initialSettings, drawTime, onDraw, editPattern: (pat, id) => { @@ -76,7 +61,6 @@ export function MicroRepl({ }, }); // init settings - editor.updateSettings(initialSettings); editor.setCode(code); editorRef.current = editor; }, []); diff --git a/website/src/pages/vanilla/mini.astro b/website/src/pages/vanilla/mini.astro index f2a25703..989582aa 100644 --- a/website/src/pages/vanilla/mini.astro +++ b/website/src/pages/vanilla/mini.astro @@ -5,7 +5,7 @@

This is a REPL:

- + code={`s("bd")`}> --> +

This is another REPL:

This is another REPL:

From e17df79133d4809eb0eff4441b5495873530556c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 17 Dec 2023 22:42:51 +0100 Subject: [PATCH 45/97] microrepl: ssr static code --- website/src/docs/MicroRepl.jsx | 31 ++++++----- website/src/pages/recipes/recipes-next.mdx | 62 +++++++++++----------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index 7e97a9bb..778e209d 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -1,11 +1,10 @@ -import { useState, useRef, useCallback, useMemo } from 'react'; +import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; import { Icon } from './Icon'; import { silence, getPunchcardPainter } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; import { StrudelMirror } from '@strudel/codemirror'; import { prebake } from '@strudel/repl'; -import { useInView } from 'react-hook-inview'; export function MicroRepl({ code, @@ -65,21 +64,26 @@ export function MicroRepl({ editorRef.current = editor; }, []); - const [ref, isVisible] = useInView({ - threshold: 0.01, - onEnter: () => { - if (!editorRef.current) { - init({ code, shouldDraw }); - } - }, - }); const [replState, setReplState] = useState({}); const { started, isDirty, error } = replState; const editorRef = useRef(); const containerRef = useRef(); + const [client, setClient] = useState(false); + useEffect(() => { + setClient(true); + if (!editorRef.current) { + setTimeout(() => { + init({ code, shouldDraw }); + }); + } + }, []); + + if (!client) { + return
{code}
; + } return ( -
+
{!hideHeader && (
@@ -117,11 +121,6 @@ export function MicroRepl({ if (el && el.width !== el.clientWidth) { el.width = el.clientWidth; } - //const ratio = el.clientWidth / canvasHeight; - //const targetWidth = Math.round(el.width * ratio); - //if (el.width !== targetWidth) { - // el.width = targetWidth; - //} }} > )} diff --git a/website/src/pages/recipes/recipes-next.mdx b/website/src/pages/recipes/recipes-next.mdx index 62a1a5e9..4230063f 100644 --- a/website/src/pages/recipes/recipes-next.mdx +++ b/website/src/pages/recipes/recipes-next.mdx @@ -17,7 +17,7 @@ An arpeggio is when the notes of a chord are played in sequence. We can either write the notes by hand: ") @@ -92,7 +92,7 @@ s("amen/8").fit() If we use `splice` instead of `slice`, the speed adjusts to the duration of the event: ") @@ -107,7 +107,7 @@ Note that we don't need `fit`, because `splice` will do that by itself. A minimal filter envelope looks like this: d2") .s("sawtooth") .lpf(400).lpa(.2).lpenv(4) @@ -117,7 +117,7 @@ A minimal filter envelope looks like this: We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`: d2") .s("sawtooth").lpq(8) .lpf(400).lpa(.2).lpenv(-4) @@ -129,7 +129,7 @@ We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`: We can layer sounds by separating them with ",": ") .s("sawtooth, square") // <------ .scope()`} @@ -138,7 +138,7 @@ We can layer sounds by separating them with ",": We can control the gain of individual sounds like this: ") .s("sawtooth, square:0:.5") // <--- "name:number:gain" .scope()`} @@ -147,7 +147,7 @@ We can control the gain of individual sounds like this: For more control over each voice, we can use `layer`: ").layer( x=>x.s("sawtooth").vib(4), x=>x.s("square").add(note(12)) @@ -162,7 +162,7 @@ With `layer`, you can use any pattern method available on each voice, so sky is We can fatten a sound by adding a detuned version to itself: ") .add(note("0,.1")) // <------ chorus .s("sawtooth").scope()`} @@ -175,7 +175,7 @@ Try out different values, or add another voice! Here is a simple example of a polyrhythm: - + A polyrhythm is when 2 different tempos happen at the same time. @@ -183,7 +183,7 @@ A polyrhythm is when 2 different tempos happen at the same time. This is a polymeter: -,").fast(2)`} punchcard /> +,").fast(2)`} punchcard /> A polymeter is when 2 different bar lengths play at the same tempo. @@ -191,7 +191,7 @@ A polymeter is when 2 different bar lengths play at the same tempo. This is a phasing: -*[6,6.1]").piano()`} punchcard /> +*[6,6.1]").piano()`} punchcard /> Phasing happens when the same sequence plays at slightly different tempos. @@ -200,7 +200,7 @@ Phasing happens when the same sequence plays at slightly different tempos. Using `run` with `n`, we can rush through a sample bank: @@ -219,7 +219,7 @@ In this case, I hear the beginning at the third sample, which can be accounted f Let's add some randomness: /2")`} /> @@ -250,7 +250,7 @@ The value of clip is relative to the duration of each event. We can also create overlaps using release: /2")`} /> @@ -259,7 +259,7 @@ This will smoothly fade out each sound for the given number of seconds. We could also make the notes shorter with decay / sustain: /2").sustain(0)`} /> @@ -268,27 +268,27 @@ For now, there is a limitation where decay values that exceed the event duration When using samples, we also have `.end` to cut relative to the sample length: -")`} /> +")`} /> Compare that to clip: -")`} /> +")`} /> or decay / sustain -").sustain(0)`} /> +").sustain(0)`} /> ## Wavetable Synthesis You can loop a sample with `loop` / `loopEnd`: -").s("bd").loop(1).loopEnd(.05).gain(.2)`} /> +").s("bd").loop(1).loopEnd(.05).gain(.2)`} /> This allows us to play the first 5% of the bass drum as a synth! To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically: @@ -296,7 +296,7 @@ note("c eb g bb").s("wt_dbass").clip(2)`} Running through different wavetables can also give interesting variations: @@ -304,7 +304,7 @@ note("c2*8").s("wt_dbass").n(run(8))`} ...adding a filter envelope + reverb: Date: Sun, 17 Dec 2023 22:51:43 +0100 Subject: [PATCH 46/97] fix: first frame --- packages/codemirror/codemirror.mjs | 2 +- packages/core/draw.mjs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index bbcf0792..02ee37d9 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -188,7 +188,7 @@ export class StrudelMirror { await this.prebaked; try { await this.repl.evaluate(this.code, false); - this.drawer.invalidate(this.repl.scheduler); + this.drawer.invalidate(this.repl.scheduler, -0.001); // draw at -0.001 to avoid haps at 0 to be visualized as active this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters); } catch (err) { diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index c57baa63..ff5359e1 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -145,12 +145,13 @@ export class Drawer { }, ); } - invalidate(scheduler = this.scheduler) { + invalidate(scheduler = this.scheduler, t) { if (!scheduler) { return; } + // TODO: scheduler.now() seems to move even when it's stopped, this hints at a bug... + t = t ?? scheduler.now(); this.scheduler = scheduler; - const t = scheduler.now(); let [_, lookahead] = this.drawTime; const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1]; // remove all future haps From e8e8f888dd6d47fdb7701b7c15794db5164717c3 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 15:21:54 +0100 Subject: [PATCH 47/97] fix: stop other repls on start --- packages/codemirror/codemirror.mjs | 26 +++++++++++++++++++++++++- website/src/docs/MicroRepl.jsx | 4 ++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 02ee37d9..6ad94209 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -108,7 +108,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo export class StrudelMirror { constructor(options) { - const { root, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, ...replOptions } = options; + const { root, id, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, ...replOptions } = options; this.code = initialCode; this.root = root; this.miniLocations = []; @@ -116,6 +116,7 @@ export class StrudelMirror { this.painters = []; this.onDraw = onDraw; const self = this; + this.id = id || s4(); this.drawer = new Drawer((haps, time) => { const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped); @@ -139,6 +140,12 @@ export class StrudelMirror { replOptions?.onToggle?.(started); if (started) { this.drawer.start(this.repl.scheduler); + // stop other repls when this one is started + document.dispatchEvent( + new CustomEvent('start-repl', { + detail: this.id, + }), + ); } else { this.drawer.stop(); updateMiniLocations(this.editor, []); @@ -179,6 +186,13 @@ export class StrudelMirror { this.root.style.backgroundColor = 'var(--background)'; cmEditor.style.backgroundColor = 'transparent'; } + // stop this repl when another repl is started + this.onStartRepl = (e) => { + if (e.detail !== this.id) { + this.stop(); + } + }; + document.addEventListener('start-repl', this.onStartRepl); } async drawFirstFrame() { if (!this.onDraw) { @@ -274,8 +288,18 @@ export class StrudelMirror { const changes = { from: 0, to: this.editor.state.doc.length, insert: code }; this.editor.dispatch({ changes }); } + clear() { + this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl); + } } function parseBooleans(value) { return { true: true, false: false }[value] ?? value; } + +// helper function to generate repl ids +function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); +} diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index 778e209d..72561669 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -34,6 +34,7 @@ export function MicroRepl({ } const editor = new StrudelMirror({ + id, defaultOutput: webaudioOutput, getTime: () => getAudioContext().currentTime, transpiler, @@ -76,6 +77,9 @@ export function MicroRepl({ init({ code, shouldDraw }); }); } + return () => { + editor.clear(); + }; }, []); if (!client) { From 0ed615a312738aad7031ef79cedd108a1abf612d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 15:32:12 +0100 Subject: [PATCH 48/97] fix: add .piano function --- packages/repl/prebake.mjs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/repl/prebake.mjs b/packages/repl/prebake.mjs index 02638d1a..80d2c3bd 100644 --- a/packages/repl/prebake.mjs +++ b/packages/repl/prebake.mjs @@ -1,4 +1,4 @@ -import { controls, evalScope } from '@strudel.cycles/core'; +import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel.cycles/core'; import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; import * as core from '@strudel.cycles/core'; @@ -37,3 +37,18 @@ export async function prebake() { samples(`${ds}/vcsl.json`), ]); } + +const maxPan = noteToMidi('C8'); +const panwidth = (pan, width) => pan * width + (1 - width) / 2; + +Pattern.prototype.piano = function () { + return this.fmap((v) => ({ ...v, clip: v.clip ?? 1 })) // set clip if not already set.. + .s('piano') + .release(0.1) + .fmap((value) => { + const midi = valueToMidi(value); + // pan by pitch + const pan = panwidth(Math.min(Math.round(midi) / maxPan, 1), 0.5); + return { ...value, pan: (value.pan || 1) * pan }; + }); +}; From de4460a0470d572d309ae69fd99251d49847703e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 15:45:49 +0100 Subject: [PATCH 49/97] remove redundant test pages --- website/src/pages/vanilla/micro.astro | 32 --------------------------- website/src/pages/vanilla/mini.astro | 31 -------------------------- 2 files changed, 63 deletions(-) delete mode 100644 website/src/pages/vanilla/micro.astro delete mode 100644 website/src/pages/vanilla/mini.astro diff --git a/website/src/pages/vanilla/micro.astro b/website/src/pages/vanilla/micro.astro deleted file mode 100644 index e8414eed..00000000 --- a/website/src/pages/vanilla/micro.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -import { MicroRepl } from '../../docs/MicroRepl' ---- - - - - Strudel Micro REPL - - - -
-

MicroRepl:

- /16") - .dict('ireal') - .voicing() - .when("<1 0> 0@3", sub(note(12))) - .add(note("[12 | 0]*4")) - .attack(slider(0.511)) - .decay(slider(0.466)) - .sustain(slider(0.398)) - .release(slider(0.443)) - .s('sawtooth') - .lpa(sine.range(.1,.25).slow(12)) - .lpenv(sine.range(0,4).slow(16)) - .lpf(perlin.range(400,1000)) - .add(note(perlin.range(0,.25))) - .vib(4).vibmod(.1) - .room(.75).size(8)`} /> -
- - diff --git a/website/src/pages/vanilla/mini.astro b/website/src/pages/vanilla/mini.astro deleted file mode 100644 index 34438309..00000000 --- a/website/src/pages/vanilla/mini.astro +++ /dev/null @@ -1,31 +0,0 @@ - - - Strudel Vanilla REPL - - - -

This is a REPL:

- - -

This is another REPL:

- - - From d74d50c40a7d31cfaa5dd254c17f893eca1aed94 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 19:37:03 +0100 Subject: [PATCH 50/97] first steps of using StrudelMirror in main repl --- website/src/pages/vanilla/2.astro | 16 +++ website/src/repl/Repl2.jsx | 224 ++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 website/src/pages/vanilla/2.astro create mode 100644 website/src/repl/Repl2.jsx diff --git a/website/src/pages/vanilla/2.astro b/website/src/pages/vanilla/2.astro new file mode 100644 index 00000000..543d5fed --- /dev/null +++ b/website/src/pages/vanilla/2.astro @@ -0,0 +1,16 @@ +--- +import HeadCommon from '../../components/HeadCommon.astro'; +// import { Repl } from '../repl/Repl.jsx'; + +import { Repl2 } from '../../repl/Repl2'; +--- + + + + + Strudel REPL + + + + + diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx new file mode 100644 index 00000000..c4241a49 --- /dev/null +++ b/website/src/repl/Repl2.jsx @@ -0,0 +1,224 @@ +/* +App.js - +Copyright (C) 2022 Strudel contributors - see +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 . +*/ + +import { logger, getDrawContext, silence, evalScope, controls } from '@strudel.cycles/core'; +import { cx } from '@strudel.cycles/react'; +import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { StrudelMirror } from '@strudel/codemirror'; +/* import { writeText } from '@tauri-apps/api/clipboard'; +import { nanoid } from 'nanoid'; */ +import { createContext, useState } from 'react'; +import { useSettings } from '../settings.mjs'; +import { isTauri } from '../tauri.mjs'; +import { Panel } from './panel/Panel'; +import { Header } from './Header'; +import Loader from './Loader'; +import './Repl.css'; +import { useCallback, useRef, useEffect } from 'react'; +// import { prebake } from '@strudel/repl'; +import { prebake /* , resetSounds */ } from './prebake.mjs'; + +export const ReplContext = createContext(null); + +export function Repl2({ embedded = false }) { + //const isEmbedded = embedded || window.location !== window.parent.location; + const isEmbedded = false; + const [lastShared, setLastShared] = useState(); + const { panelPosition, isZen } = useSettings(); + /* const replState = useStore($replstate); + const isDirty = useStore($repldirty); */ + const shouldDraw = true; + + const init = useCallback(({ code, shouldDraw }) => { + const drawTime = [0, 4]; + const drawContext = shouldDraw ? getDrawContext() : null; + let onDraw; + if (shouldDraw) { + 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 }); + }); + }; + } + const editor = new StrudelMirror({ + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + autodraw: false, + root: containerRef.current, + initialCode: '// LOADING', + pattern: silence, + drawTime, + onDraw, + prebake: async () => { + let modules = [ + 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'), + ]; + if (isTauri()) { + modules = modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); + } else { + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + } + + const modulesLoading = evalScope( + controls, // sadly, this cannot be exported from core direclty + // settingPatterns, + ...modules, + ); + await Promise.all([modulesLoading, prebake()]); + }, + onUpdateState: (state) => { + setReplState({ ...state }); + }, + }); + // init settings + editor.setCode(code); + editorRef.current = editor; + }, []); + + const [replState, setReplState] = useState({}); + const { started, isDirty, error, activeCode } = replState; + const editorRef = useRef(); + const containerRef = useRef(); + const [client, setClient] = useState(false); + useEffect(() => { + setClient(true); + if (!editorRef.current) { + setTimeout(() => { + init({ code: 's("bd")', shouldDraw }); + }); + } + return () => { + editor.clear(); + }; + }, []); + + // + // UI Actions + // + + const handleTogglePlay = async () => { + editorRef.current?.toggle(); + /* await getAudioContext().resume(); // fixes no sound in ios webkit + if (!started) { + logger('[repl] started. tip: you can also start by pressing ctrl+enter', 'highlight'); + activateCode(); + } else { + logger('[repl] stopped. tip: you can also stop by pressing ctrl+dot', 'highlight'); + stop(); + } */ + }; + const handleUpdate = () => { + isDirty && activateCode(); + logger('[repl] code updated! tip: you can also update the code by pressing ctrl+enter', 'highlight'); + }; + + const handleShuffle = async () => { + // window.postMessage('strudel-shuffle'); + }; + + /* const handleShare = async () => { + const codeToShare = activeCode || code; + if (lastShared === codeToShare) { + logger(`Link already generated!`, 'error'); + return; + } + // generate uuid in the browser + const hash = nanoid(12); + const shareUrl = window.location.origin + window.location.pathname + '?' + hash; + const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); + if (!error) { + setLastShared(activeCode || code); + // copy shareUrl to clipboard + if (isTauri()) { + await writeText(shareUrl); + } else { + await navigator.clipboard.writeText(shareUrl); + } + const message = `Link copied to clipboard: ${shareUrl}`; + alert(message); + // alert(message); + logger(message, 'highlight'); + } else { + console.log('error', error); + const message = `Error: ${error.message}`; + // alert(message); + logger(message); + } + }; */ + const pending = false; + //const error = undefined; + // const { started, activeCode } = replState; + + const context = { + // scheduler, + embedded, + started, + pending, + isDirty, + lastShared, + activeCode, + // handleChangeCode: codemirror.handleChangeCode, + handleTogglePlay, + handleUpdate, + handleShuffle, + /* handleShare, */ + handleShare: () => {}, + }; + + return ( + // bg-gradient-to-t from-blue-900 to-slate-900 + // bg-gradient-to-t from-green-900 to-slate-900 + +
+ +
+ {/* isEmbedded && !started && ( + + ) */} +
+
+ {panelPosition === 'right' && !isEmbedded && } +
+ {error && ( +
{error.message || 'Unknown Error :-/'}
+ )} + {panelPosition === 'bottom' && !isEmbedded && } +
+
+ ); +} From 7dac1e275d5389cda9bdb958774b10827c9f94d2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 20:08:57 +0100 Subject: [PATCH 51/97] repl2: working update + shuffle buttons + faster loading --- packages/core/repl.mjs | 4 ++ website/src/repl/Repl2.jsx | 129 +++++++++++++++++++++++-------------- 2 files changed, 85 insertions(+), 48 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index b3c7a543..fd0d8a3b 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -62,6 +62,10 @@ export function repl({ }; setTime(() => scheduler.now()); // TODO: refactor? const evaluate = async (code, autostart = true, shouldHush = true) => { + if (code === state.activeCode && state.started) { + logger('[eval] skip: not dirty') + return; + } if (!code) { throw new Error('no code to evaluate'); } diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index c4241a49..d153b80e 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -6,13 +6,24 @@ This program is free software: you can redistribute it and/or modify it under th import { logger, getDrawContext, silence, evalScope, controls } from '@strudel.cycles/core'; import { cx } from '@strudel.cycles/react'; -import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; import { StrudelMirror } from '@strudel/codemirror'; +import { createClient } from '@supabase/supabase-js'; /* import { writeText } from '@tauri-apps/api/clipboard'; import { nanoid } from 'nanoid'; */ import { createContext, useState } from 'react'; -import { useSettings } from '../settings.mjs'; +import { + useSettings, + settingsMap, + setLatestCode, + updateUserCode, + setActivePattern, + getActivePattern, + getUserPattern, + initUserCode, + settingPatterns, +} from '../settings.mjs'; import { isTauri } from '../tauri.mjs'; import { Panel } from './panel/Panel'; import { Header } from './Header'; @@ -21,9 +32,58 @@ import './Repl.css'; import { useCallback, useRef, useEffect } from 'react'; // import { prebake } from '@strudel/repl'; import { prebake /* , resetSounds */ } from './prebake.mjs'; +import * as tunes from './tunes.mjs'; export const ReplContext = createContext(null); +const { latestCode } = settingsMap.get(); + +initAudioOnFirstClick(); + +// Create a single supabase client for interacting with your database +const supabase = createClient( + 'https://pidxdsxphlhzjnzmifth.supabase.co', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', +); + +let modules = [ + 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'), +]; +if (isTauri()) { + modules = modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); +} else { + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); +} + +const modulesLoading = evalScope( + controls, // sadly, this cannot be exported from core direclty + settingPatterns, + ...modules, +); + +const presets = prebake(); + +let drawContext, clearCanvas; +if (typeof window !== 'undefined') { + drawContext = getDrawContext(); + clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); +} + +// const getTime = () => getAudioContext().currentTime; + export function Repl2({ embedded = false }) { //const isEmbedded = embedded || window.location !== window.parent.location; const isEmbedded = false; @@ -56,36 +116,7 @@ export function Repl2({ embedded = false }) { pattern: silence, drawTime, onDraw, - prebake: async () => { - let modules = [ - 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'), - ]; - if (isTauri()) { - modules = modules.concat([ - import('@strudel/desktopbridge/loggerbridge.mjs'), - import('@strudel/desktopbridge/midibridge.mjs'), - import('@strudel/desktopbridge/oscbridge.mjs'), - ]); - } else { - modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); - } - - const modulesLoading = evalScope( - controls, // sadly, this cannot be exported from core direclty - // settingPatterns, - ...modules, - ); - await Promise.all([modulesLoading, prebake()]); - }, + prebake: async () => Promise.all([modulesLoading, presets]), onUpdateState: (state) => { setReplState({ ...state }); }, @@ -116,24 +147,19 @@ export function Repl2({ embedded = false }) { // UI Actions // - const handleTogglePlay = async () => { - editorRef.current?.toggle(); - /* await getAudioContext().resume(); // fixes no sound in ios webkit - if (!started) { - logger('[repl] started. tip: you can also start by pressing ctrl+enter', 'highlight'); - activateCode(); - } else { - logger('[repl] stopped. tip: you can also stop by pressing ctrl+dot', 'highlight'); - stop(); - } */ - }; - const handleUpdate = () => { - isDirty && activateCode(); - logger('[repl] code updated! tip: you can also update the code by pressing ctrl+enter', 'highlight'); - }; - + const handleTogglePlay = async () => editorRef.current?.toggle(); + const handleUpdate = () => editorRef.current?.evaluate(); const handleShuffle = async () => { // window.postMessage('strudel-shuffle'); + const { code, name } = getRandomTune(); + logger(`[repl] ✨ loading random tune "${name}"`); + setActivePattern(name); + clearCanvas(); + resetLoadedSounds(); + editorRef.current.repl.setCps(1); + await prebake(); // declare default samples + editorRef.current.setCode(code); + editorRef.current.repl.evaluate(code); }; /* const handleShare = async () => { @@ -222,3 +248,10 @@ export function Repl2({ embedded = false }) { ); } + +function getRandomTune() { + const allTunes = Object.entries(tunes); + const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const [name, code] = randomItem(allTunes); + return { name, code }; +} From e19f799046de27730e498b79eff669e1399f46fa Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 20:52:00 +0100 Subject: [PATCH 52/97] wire up settings and theming --- packages/codemirror/codemirror.mjs | 4 ++ packages/core/repl.mjs | 4 -- website/src/components/HeadCommonNext.astro | 58 +++++++++++++++++++++ website/src/pages/vanilla/2.astro | 6 +-- website/src/repl/Repl2.jsx | 18 ++++++- 5 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 website/src/components/HeadCommonNext.astro diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 6ad94209..a2c70599 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -186,6 +186,10 @@ export class StrudelMirror { this.root.style.backgroundColor = 'var(--background)'; cmEditor.style.backgroundColor = 'transparent'; } + const settings = codemirrorSettings.get(); + this.setFontSize(settings.fontSize); + this.setFontFamily(settings.fontFamily); + // stop this repl when another repl is started this.onStartRepl = (e) => { if (e.detail !== this.id) { diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index fd0d8a3b..b3c7a543 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -62,10 +62,6 @@ export function repl({ }; setTime(() => scheduler.now()); // TODO: refactor? const evaluate = async (code, autostart = true, shouldHush = true) => { - if (code === state.activeCode && state.started) { - logger('[eval] skip: not dirty') - return; - } if (!code) { throw new Error('no code to evaluate'); } diff --git a/website/src/components/HeadCommonNext.astro b/website/src/components/HeadCommonNext.astro new file mode 100644 index 00000000..9f323a7a --- /dev/null +++ b/website/src/components/HeadCommonNext.astro @@ -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; +--- + + + + + + + + + + + + + + + + + + + + + + + +{pwaInfo && } + + diff --git a/website/src/pages/vanilla/2.astro b/website/src/pages/vanilla/2.astro index 543d5fed..0db75a1d 100644 --- a/website/src/pages/vanilla/2.astro +++ b/website/src/pages/vanilla/2.astro @@ -1,13 +1,11 @@ --- -import HeadCommon from '../../components/HeadCommon.astro'; -// import { Repl } from '../repl/Repl.jsx'; - +import HeadCommonNext from '../../components/HeadCommonNext.astro'; import { Repl2 } from '../../repl/Repl2'; --- - + Strudel REPL diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index d153b80e..afc81198 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -8,7 +8,7 @@ import { logger, getDrawContext, silence, evalScope, controls } from '@strudel.c import { cx } from '@strudel.cycles/react'; import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; -import { StrudelMirror } from '@strudel/codemirror'; +import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; import { createClient } from '@supabase/supabase-js'; /* import { writeText } from '@tauri-apps/api/clipboard'; import { nanoid } from 'nanoid'; */ @@ -33,6 +33,7 @@ import { useCallback, useRef, useEffect } from 'react'; // import { prebake } from '@strudel/repl'; import { prebake /* , resetSounds */ } from './prebake.mjs'; import * as tunes from './tunes.mjs'; +import { useStore } from '@nanostores/react'; export const ReplContext = createContext(null); @@ -139,10 +140,23 @@ export function Repl2({ embedded = false }) { }); } return () => { - editor.clear(); + editorRef.current?.clear(); }; }, []); + // this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly! + // this will be the case when the main repl is being replaced + const _settings = useStore(settingsMap, { keys: Object.keys(defaultSettings) }); + useEffect(() => { + let editorSettings = {}; + Object.keys(defaultSettings).forEach((key) => { + if (_settings.hasOwnProperty(key)) { + editorSettings[key] = _settings[key]; + } + }); + editorRef.current?.updateSettings(editorSettings); + }, [_settings]); + // // UI Actions // From 5c41d6789cc40335c7dd0982ad327b5b10b2918f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 21:11:28 +0100 Subject: [PATCH 53/97] checkbox for isPatternHighlightingEnabled --- website/src/repl/panel/SettingsTab.jsx | 6 ++++++ website/src/settings.mjs | 2 ++ 2 files changed, 8 insertions(+) diff --git a/website/src/repl/panel/SettingsTab.jsx b/website/src/repl/panel/SettingsTab.jsx index cee5b286..3efab8f2 100644 --- a/website/src/repl/panel/SettingsTab.jsx +++ b/website/src/repl/panel/SettingsTab.jsx @@ -78,6 +78,7 @@ export function SettingsTab() { theme, keybindings, isLineNumbersDisplayed, + isPatternHighlightingEnabled, isActiveLineHighlighted, isAutoCompletionEnabled, isTooltipEnabled, @@ -153,6 +154,11 @@ export function SettingsTab() { onChange={(cbEvent) => settingsMap.setKey('isActiveLineHighlighted', cbEvent.target.checked)} value={isActiveLineHighlighted} /> + settingsMap.setKey('isPatternHighlightingEnabled', cbEvent.target.checked)} + value={isPatternHighlightingEnabled} + /> settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)} diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 570b6446..489bd71c 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -12,6 +12,7 @@ export const defaultSettings = { isAutoCompletionEnabled: false, isTooltipEnabled: false, isLineWrappingEnabled: false, + isPatternHighlightingEnabled: true, theme: 'strudelTheme', fontFamily: 'monospace', fontSize: 18, @@ -50,6 +51,7 @@ export function useSettings() { isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false, isActiveLineHighlighted: [true, 'true'].includes(state.isActiveLineHighlighted) ? true : false, isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false, + isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false, isTooltipEnabled: [true, 'true'].includes(state.isTooltipEnabled) ? true : false, isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false, fontSize: Number(state.fontSize), From 8c0065f563f8798ca20c12f0157f1fb0572e9199 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 23:53:54 +0100 Subject: [PATCH 54/97] pull out repl utility functions + repl2 initCode / switching patterns works now --- website/src/repl/Repl.jsx | 103 +---------------------------------- website/src/repl/Repl2.jsx | 91 +++++++++++++++---------------- website/src/repl/util.mjs | 109 +++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 146 deletions(-) create mode 100644 website/src/repl/util.mjs diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index d4f85434..d50995f2 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -42,45 +42,13 @@ import { isTauri } from '../tauri.mjs'; import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; import { writeText } from '@tauri-apps/api/clipboard'; import { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs'; +import { getRandomTune, initCode, loadModules } from './util.mjs'; const { latestCode } = settingsMap.get(); initAudioOnFirstClick(); -// Create a single supabase client for interacting with your database -const supabase = createClient( - 'https://pidxdsxphlhzjnzmifth.supabase.co', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', -); - -let modules = [ - 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'), -]; -if (isTauri()) { - modules = modules.concat([ - import('@strudel/desktopbridge/loggerbridge.mjs'), - import('@strudel/desktopbridge/midibridge.mjs'), - import('@strudel/desktopbridge/oscbridge.mjs'), - ]); -} else { - modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); -} - -const modulesLoading = evalScope( - controls, // sadly, this cannot be exported from core direclty - settingPatterns, - ...modules, -); - +const modulesLoading = loadModules(); const presets = prebake(); let drawContext, clearCanvas; @@ -91,43 +59,6 @@ if (typeof window !== 'undefined') { const getTime = () => getAudioContext().currentTime; -async function initCode() { - // load code from url hash (either short hash from database or decode long hash) - try { - const initialUrl = window.location.href; - const hash = initialUrl.split('?')[1]?.split('#')?.[0]; - const codeParam = window.location.href.split('#')[1] || ''; - // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) - if (codeParam) { - // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) - return hash2code(codeParam); - } else if (hash) { - return supabase - .from('code') - .select('code') - .eq('hash', hash) - .then(({ data, error }) => { - if (error) { - console.warn('failed to load hash', err); - } - if (data.length) { - //console.log('load hash from database', hash); - return data[0].code; - } - }); - } - } catch (err) { - console.warn('failed to decode', err); - } -} - -function getRandomTune() { - const allTunes = Object.entries(tunes); - const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; - const [name, code] = randomItem(allTunes); - return { name, code }; -} - const { code: randomTune, name } = getRandomTune(); export const ReplContext = createContext(null); @@ -289,35 +220,7 @@ export function Repl({ embedded = false }) { await evaluate(code, false); }; - const handleShare = async () => { - const codeToShare = activeCode || code; - if (lastShared === codeToShare) { - logger(`Link already generated!`, 'error'); - return; - } - // generate uuid in the browser - const hash = nanoid(12); - const shareUrl = window.location.origin + window.location.pathname + '?' + hash; - const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); - if (!error) { - setLastShared(activeCode || code); - // copy shareUrl to clipboard - if (isTauri()) { - await writeText(shareUrl); - } else { - await navigator.clipboard.writeText(shareUrl); - } - const message = `Link copied to clipboard: ${shareUrl}`; - alert(message); - // alert(message); - logger(message, 'highlight'); - } else { - console.log('error', error); - const message = `Error: ${error.message}`; - // alert(message); - logger(message); - } - }; + const handleShare = async () => shareCode(activeCode || code); const context = { scheduler, embedded, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index afc81198..070e54e1 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { logger, getDrawContext, silence, evalScope, controls } from '@strudel.cycles/core'; +import { logger, getDrawContext, silence, code2hash } from '@strudel.cycles/core'; import { cx } from '@strudel.cycles/react'; import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; @@ -34,47 +34,16 @@ import { useCallback, useRef, useEffect } from 'react'; import { prebake /* , resetSounds */ } from './prebake.mjs'; import * as tunes from './tunes.mjs'; import { useStore } from '@nanostores/react'; +import { getRandomTune, loadModules, initCode } from './util.mjs'; +const { code: randomTune, name } = getRandomTune(); export const ReplContext = createContext(null); const { latestCode } = settingsMap.get(); initAudioOnFirstClick(); -// Create a single supabase client for interacting with your database -const supabase = createClient( - 'https://pidxdsxphlhzjnzmifth.supabase.co', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', -); - -let modules = [ - 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'), -]; -if (isTauri()) { - modules = modules.concat([ - import('@strudel/desktopbridge/loggerbridge.mjs'), - import('@strudel/desktopbridge/midibridge.mjs'), - import('@strudel/desktopbridge/oscbridge.mjs'), - ]); -} else { - modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); -} - -const modulesLoading = evalScope( - controls, // sadly, this cannot be exported from core direclty - settingPatterns, - ...modules, -); - +const modulesLoading = loadModules(); const presets = prebake(); let drawContext, clearCanvas; @@ -94,7 +63,7 @@ export function Repl2({ embedded = false }) { const isDirty = useStore($repldirty); */ const shouldDraw = true; - const init = useCallback(({ code, shouldDraw }) => { + const init = useCallback(({ shouldDraw }) => { const drawTime = [0, 4]; const drawContext = shouldDraw ? getDrawContext() : null; let onDraw; @@ -121,9 +90,34 @@ export function Repl2({ embedded = false }) { onUpdateState: (state) => { setReplState({ ...state }); }, + afterEval: ({ code }) => { + updateUserCode(code); + // setPending(false); + setLatestCode(code); + window.location.hash = '#' + code2hash(code); + }, }); // init settings - editor.setCode(code); + initCode().then((decoded) => { + console.log('init code'); + let msg; + if (decoded) { + editor.setCode(decoded); + initUserCode(decoded); + msg = `I have loaded the code from the URL.`; + } else if (latestCode) { + editor.setCode(latestCode); + msg = `Your last session has been loaded!`; + } /* if(randomTune) */ else { + editor.setCode(randomTune); + 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'); + // setPending(false); + }); + editorRef.current = editor; }, []); @@ -136,7 +130,7 @@ export function Repl2({ embedded = false }) { setClient(true); if (!editorRef.current) { setTimeout(() => { - init({ code: 's("bd")', shouldDraw }); + init({ shouldDraw }); }); } return () => { @@ -162,7 +156,19 @@ export function Repl2({ embedded = false }) { // const handleTogglePlay = async () => editorRef.current?.toggle(); - const handleUpdate = () => editorRef.current?.evaluate(); + const handleUpdate = async (newCode, reset = false) => { + if (reset) { + clearCanvas(); + resetLoadedSounds(); + editorRef.current.repl.setCps(1); + await prebake(); // declare default samples + } + if (newCode || isDirty) { + editorRef.current.setCode(newCode); + editorRef.current.repl.evaluate(newCode); + } + logger('[repl] code updated!'); + }; const handleShuffle = async () => { // window.postMessage('strudel-shuffle'); const { code, name } = getRandomTune(); @@ -262,10 +268,3 @@ export function Repl2({ embedded = false }) { ); } - -function getRandomTune() { - const allTunes = Object.entries(tunes); - const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; - const [name, code] = randomItem(allTunes); - return { name, code }; -} diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs new file mode 100644 index 00000000..0da28c25 --- /dev/null +++ b/website/src/repl/util.mjs @@ -0,0 +1,109 @@ +import { controls, evalScope, hash2code } from '@strudel.cycles/core'; +import { settingPatterns } from '../settings.mjs'; +import { isTauri } from '../tauri.mjs'; +import './Repl.css'; +import * as tunes from './tunes.mjs'; +import { createClient } from '@supabase/supabase-js'; + +// Create a single supabase client for interacting with your database +const supabase = createClient( + 'https://pidxdsxphlhzjnzmifth.supabase.co', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', +); + +export async function initCode() { + // load code from url hash (either short hash from database or decode long hash) + try { + const initialUrl = window.location.href; + const hash = initialUrl.split('?')[1]?.split('#')?.[0]; + const codeParam = window.location.href.split('#')[1] || ''; + // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) + if (codeParam) { + // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) + return hash2code(codeParam); + } else if (hash) { + return supabase + .from('code') + .select('code') + .eq('hash', hash) + .then(({ data, error }) => { + if (error) { + console.warn('failed to load hash', err); + } + if (data.length) { + //console.log('load hash from database', hash); + return data[0].code; + } + }); + } + } catch (err) { + console.warn('failed to decode', err); + } +} + +export function getRandomTune() { + const allTunes = Object.entries(tunes); + const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const [name, code] = randomItem(allTunes); + return { name, code }; +} + +export function loadModules() { + let modules = [ + 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'), + ]; + if (isTauri()) { + modules = modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); + } else { + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + } + + return evalScope( + controls, // sadly, this cannot be exported from core direclty + settingPatterns, + ...modules, + ); +} + +export async function shareCode(codeToShare) { + // const codeToShare = activeCode || code; + if (lastShared === codeToShare) { + logger(`Link already generated!`, 'error'); + return; + } + // generate uuid in the browser + const hash = nanoid(12); + const shareUrl = window.location.origin + window.location.pathname + '?' + hash; + const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); + if (!error) { + setLastShared(activeCode || code); + // copy shareUrl to clipboard + if (isTauri()) { + await writeText(shareUrl); + } else { + await navigator.clipboard.writeText(shareUrl); + } + const message = `Link copied to clipboard: ${shareUrl}`; + alert(message); + // alert(message); + logger(message, 'highlight'); + } else { + console.log('error', error); + const message = `Error: ${error.message}`; + // alert(message); + logger(message); + } +} From 63ae95186bcf3bf49a43cf800734d10a271bf1a5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 00:06:15 +0100 Subject: [PATCH 55/97] repl2: share + clean up imports --- website/src/repl/Repl.jsx | 56 +++++++++++---------------------- website/src/repl/Repl2.jsx | 64 ++++++++------------------------------ website/src/repl/util.mjs | 5 ++- 3 files changed, 36 insertions(+), 89 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index d50995f2..4cd1696a 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -4,45 +4,28 @@ Copyright (C) 2022 Strudel contributors - see . */ -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 { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; -import { createClient } from '@supabase/supabase-js'; -import { nanoid } from 'nanoid'; -import React, { createContext, useCallback, useEffect, useState, useMemo } from 'react'; -import './Repl.css'; -import { Panel } from './panel/Panel'; -import { Header } from './Header'; -import { prebake } from './prebake.mjs'; -import * as tunes from './tunes.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; -import { themes } from './themes.mjs'; -import { - settingsMap, - useSettings, - setLatestCode, - updateUserCode, - setActivePattern, - getActivePattern, - getUserPattern, - initUserCode, -} from '../settings.mjs'; -import Loader from './Loader'; -import { settingPatterns } from '../settings.mjs'; -import { isTauri } from '../tauri.mjs'; +import { cleanupDraw, cleanupUi, code2hash, getDrawContext, logger } from '@strudel.cycles/core'; +import { CodeMirror, cx, flash, useHighlighting, useKeydown, useStrudel } from '@strudel.cycles/react'; import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; -import { writeText } from '@tauri-apps/api/clipboard'; +import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; +import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import { + initUserCode, + setActivePattern, + setLatestCode, + settingsMap, + updateUserCode, + useSettings, +} from '../settings.mjs'; +import { Header } from './Header'; +import Loader from './Loader'; +import './Repl.css'; import { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs'; -import { getRandomTune, initCode, loadModules } from './util.mjs'; +import { Panel } from './panel/Panel'; +import { prebake } from './prebake.mjs'; +import { themes } from './themes.mjs'; +import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; const { latestCode } = settingsMap.get(); @@ -66,7 +49,6 @@ export const ReplContext = createContext(null); export function Repl({ embedded = false }) { const isEmbedded = embedded || window.location !== window.parent.location; const [view, setView] = useState(); // codemirror view - const [lastShared, setLastShared] = useState(); const [pending, setPending] = useState(true); const { theme, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index 070e54e1..2ea527a2 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -4,37 +4,30 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { logger, getDrawContext, silence, code2hash } from '@strudel.cycles/core'; +import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core'; import { cx } from '@strudel.cycles/react'; -import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; +import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio'; import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; -import { createClient } from '@supabase/supabase-js'; /* import { writeText } from '@tauri-apps/api/clipboard'; import { nanoid } from 'nanoid'; */ -import { createContext, useState } from 'react'; +import { createContext, useCallback, useEffect, useRef, useState } from 'react'; import { - useSettings, - settingsMap, - setLatestCode, - updateUserCode, - setActivePattern, - getActivePattern, - getUserPattern, initUserCode, - settingPatterns, + setActivePattern, + setLatestCode, + settingsMap, + updateUserCode, + useSettings, } from '../settings.mjs'; -import { isTauri } from '../tauri.mjs'; -import { Panel } from './panel/Panel'; import { Header } from './Header'; import Loader from './Loader'; import './Repl.css'; -import { useCallback, useRef, useEffect } from 'react'; +import { Panel } from './panel/Panel'; // import { prebake } from '@strudel/repl'; -import { prebake /* , resetSounds */ } from './prebake.mjs'; -import * as tunes from './tunes.mjs'; import { useStore } from '@nanostores/react'; -import { getRandomTune, loadModules, initCode } from './util.mjs'; +import { prebake /* , resetSounds */ } from './prebake.mjs'; +import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; const { code: randomTune, name } = getRandomTune(); export const ReplContext = createContext(null); @@ -99,7 +92,6 @@ export function Repl2({ embedded = false }) { }); // init settings initCode().then((decoded) => { - console.log('init code'); let msg; if (decoded) { editor.setCode(decoded); @@ -182,35 +174,7 @@ export function Repl2({ embedded = false }) { editorRef.current.repl.evaluate(code); }; - /* const handleShare = async () => { - const codeToShare = activeCode || code; - if (lastShared === codeToShare) { - logger(`Link already generated!`, 'error'); - return; - } - // generate uuid in the browser - const hash = nanoid(12); - const shareUrl = window.location.origin + window.location.pathname + '?' + hash; - const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); - if (!error) { - setLastShared(activeCode || code); - // copy shareUrl to clipboard - if (isTauri()) { - await writeText(shareUrl); - } else { - await navigator.clipboard.writeText(shareUrl); - } - const message = `Link copied to clipboard: ${shareUrl}`; - alert(message); - // alert(message); - logger(message, 'highlight'); - } else { - console.log('error', error); - const message = `Error: ${error.message}`; - // alert(message); - logger(message); - } - }; */ + const handleShare = async () => shareCode(activeCode); const pending = false; //const error = undefined; // const { started, activeCode } = replState; @@ -223,12 +187,10 @@ export function Repl2({ embedded = false }) { isDirty, lastShared, activeCode, - // handleChangeCode: codemirror.handleChangeCode, handleTogglePlay, handleUpdate, handleShuffle, - /* handleShare, */ - handleShare: () => {}, + handleShare, }; return ( diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 0da28c25..ba459a1f 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -4,6 +4,8 @@ import { isTauri } from '../tauri.mjs'; import './Repl.css'; import * as tunes from './tunes.mjs'; import { createClient } from '@supabase/supabase-js'; +import { nanoid } from 'nanoid'; +import { writeText } from '@tauri-apps/api/clipboard'; // Create a single supabase client for interacting with your database const supabase = createClient( @@ -78,6 +80,7 @@ export function loadModules() { ); } +let lastShared; export async function shareCode(codeToShare) { // const codeToShare = activeCode || code; if (lastShared === codeToShare) { @@ -89,7 +92,7 @@ export async function shareCode(codeToShare) { const shareUrl = window.location.origin + window.location.pathname + '?' + hash; const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); if (!error) { - setLastShared(activeCode || code); + lastShared = codeToShare; // copy shareUrl to clipboard if (isTauri()) { await writeText(shareUrl); From 4c337a5114dc4a108650c665c2a2d82291b836fb Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 00:06:59 +0100 Subject: [PATCH 56/97] fix: lint errors --- website/src/repl/util.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index ba459a1f..2b93b619 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -1,4 +1,4 @@ -import { controls, evalScope, hash2code } from '@strudel.cycles/core'; +import { controls, evalScope, hash2code, logger } from '@strudel.cycles/core'; import { settingPatterns } from '../settings.mjs'; import { isTauri } from '../tauri.mjs'; import './Repl.css'; @@ -30,7 +30,7 @@ export async function initCode() { .eq('hash', hash) .then(({ data, error }) => { if (error) { - console.warn('failed to load hash', err); + console.warn('failed to load hash', error); } if (data.length) { //console.log('load hash from database', hash); From 53484db76899e4cf291fbd038303488025a50c53 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 00:29:43 +0100 Subject: [PATCH 57/97] style fixes + remove lastShared state --- packages/codemirror/codemirror.mjs | 16 ++++++++++++++-- website/src/repl/Header.jsx | 3 +-- website/src/repl/Repl.jsx | 1 - website/src/repl/Repl2.jsx | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index a2c70599..e5692a55 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -108,7 +108,17 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo export class StrudelMirror { constructor(options) { - const { root, id, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, ...replOptions } = options; + const { + root, + id, + initialCode = '', + onDraw, + drawTime = [-2, 2], + autodraw, + prebake, + bgFill = true, + ...replOptions + } = options; this.code = initialCode; this.root = root; this.miniLocations = []; @@ -183,7 +193,9 @@ export class StrudelMirror { const cmEditor = this.root.querySelector('.cm-editor'); if (cmEditor) { this.root.style.display = 'block'; - this.root.style.backgroundColor = 'var(--background)'; + if (bgFill) { + this.root.style.backgroundColor = 'var(--background)'; + } cmEditor.style.backgroundColor = 'transparent'; } const settings = codemirrorSettings.get(); diff --git a/website/src/repl/Header.jsx b/website/src/repl/Header.jsx index b6f13060..2592b917 100644 --- a/website/src/repl/Header.jsx +++ b/website/src/repl/Header.jsx @@ -18,7 +18,6 @@ export function Header({ context }) { started, pending, isDirty, - lastShared, activeCode, handleTogglePlay, handleUpdate, @@ -119,7 +118,7 @@ export function Header({ context }) { onClick={handleShare} > - share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''} + share )} {!isEmbedded && ( diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 4cd1696a..eb2b495b 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -209,7 +209,6 @@ export function Repl({ embedded = false }) { started, pending, isDirty, - lastShared, activeCode, handleChangeCode, handleTogglePlay, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index 2ea527a2..ccd46c94 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -28,6 +28,7 @@ import { Panel } from './panel/Panel'; import { useStore } from '@nanostores/react'; import { prebake /* , resetSounds */ } from './prebake.mjs'; import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; +import './Repl.css'; const { code: randomTune, name } = getRandomTune(); export const ReplContext = createContext(null); @@ -50,7 +51,6 @@ if (typeof window !== 'undefined') { export function Repl2({ embedded = false }) { //const isEmbedded = embedded || window.location !== window.parent.location; const isEmbedded = false; - const [lastShared, setLastShared] = useState(); const { panelPosition, isZen } = useSettings(); /* const replState = useStore($replstate); const isDirty = useStore($repldirty); */ @@ -89,6 +89,7 @@ export function Repl2({ embedded = false }) { setLatestCode(code); window.location.hash = '#' + code2hash(code); }, + bgFill: false, }); // init settings initCode().then((decoded) => { @@ -185,7 +186,6 @@ export function Repl2({ embedded = false }) { started, pending, isDirty, - lastShared, activeCode, handleTogglePlay, handleUpdate, From e12421cee842de47d2ae100149e74553d169cfba Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Mon, 25 Dec 2023 20:27:42 -0500 Subject: [PATCH 58/97] still getting too late --- packages/core/cyclist.mjs | 22 ++++++++++++++++++++-- packages/core/zyklus.mjs | 1 - 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs index 303525a2..2c14bfcc 100644 --- a/packages/core/cyclist.mjs +++ b/packages/core/cyclist.mjs @@ -11,10 +11,14 @@ export class Cyclist { constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) { this.started = false; this.cps = 1; + + // this.num_ticks_since_cps_change = 0; this.lastTick = 0; // absolute time when last tick (clock callback) happened this.lastBegin = 0; // query begin of last tick this.lastEnd = 0; // query end of last tick this.getTime = getTime; // get absolute time + this.num_cycles_since_last_cps_change = 0; + this.time_last_cps_change = 0; this.onToggle = onToggle; this.latency = latency; // fixed trigger time offset const round = (x) => Math.round(x * 1000) / 1000; @@ -25,14 +29,20 @@ export class Cyclist { if (tick === 0) { this.origin = phase; } + if (this.num_ticks_since_cps_change === 0) { + this.num_cycles_since_last_cps_change = this.lastEnd; + } + this.num_ticks_since_cps_change++; try { const time = getTime(); const begin = this.lastEnd; this.lastBegin = begin; - const end = round(begin + duration * this.cps); + const eventLength = duration * this.cps; + const end = this.num_cycles_since_last_cps_change + this.num_ticks_since_cps_change * eventLength; + // const end = round(begin + duration * this.cps); this.lastEnd = end; const haps = this.pattern.queryArc(begin, end); - const tickdeadline = phase - time; // time left till phase begins + const tickdeadline = phase - time; // time left till phase tick begins this.lastTick = time + tickdeadline; haps.forEach((hap) => { @@ -59,6 +69,9 @@ export class Cyclist { this.onToggle?.(v); } start() { + // this.time_last_cps_change = 0; + this.num_ticks_since_cps_change = 0; + this.num_cycles_since_last_cps_change = 0; if (!this.pattern) { throw new Error('Scheduler: no pattern set! call .setPattern first.'); } @@ -84,7 +97,12 @@ export class Cyclist { } } setCps(cps = 1) { + if (this.cps === cps) { + return; + } this.cps = cps; + // this.time_last_cps_change = this.getTime(); + this.num_ticks_since_cps_change = 0; } log(begin, end, haps) { const onsets = haps.filter((h) => h.hasOnset()); diff --git a/packages/core/zyklus.mjs b/packages/core/zyklus.mjs index 3d25b054..4bcaa8cd 100644 --- a/packages/core/zyklus.mjs +++ b/packages/core/zyklus.mjs @@ -39,7 +39,6 @@ function createClock( const pause = () => clear(); const stop = () => { tick = 0; - phase = 0; clear(); }; const getPhase = () => phase; From cb57d8f497c5c961ddc66b5dc6ca887a53b7dbf8 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Mon, 25 Dec 2023 20:42:20 -0500 Subject: [PATCH 59/97] working --- packages/core/cyclist.mjs | 15 +++++++-------- packages/core/zyklus.mjs | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs index 2c14bfcc..c835ca76 100644 --- a/packages/core/cyclist.mjs +++ b/packages/core/cyclist.mjs @@ -11,17 +11,14 @@ export class Cyclist { constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) { this.started = false; this.cps = 1; - - // this.num_ticks_since_cps_change = 0; + this.num_ticks_since_cps_change = 0; this.lastTick = 0; // absolute time when last tick (clock callback) happened this.lastBegin = 0; // query begin of last tick this.lastEnd = 0; // query end of last tick this.getTime = getTime; // get absolute time this.num_cycles_since_last_cps_change = 0; - this.time_last_cps_change = 0; this.onToggle = onToggle; this.latency = latency; // fixed trigger time offset - const round = (x) => Math.round(x * 1000) / 1000; this.clock = createClock( getTime, // called slightly before each cycle @@ -37,12 +34,16 @@ export class Cyclist { const time = getTime(); const begin = this.lastEnd; this.lastBegin = begin; + + //convert ticks to cycles, so you can query the pattern for events const eventLength = duration * this.cps; const end = this.num_cycles_since_last_cps_change + this.num_ticks_since_cps_change * eventLength; - // const end = round(begin + duration * this.cps); this.lastEnd = end; + + // query the pattern for events const haps = this.pattern.queryArc(begin, end); - const tickdeadline = phase - time; // time left till phase tick begins + + const tickdeadline = phase - time; // time left until the phase is a whole number this.lastTick = time + tickdeadline; haps.forEach((hap) => { @@ -69,7 +70,6 @@ export class Cyclist { this.onToggle?.(v); } start() { - // this.time_last_cps_change = 0; this.num_ticks_since_cps_change = 0; this.num_cycles_since_last_cps_change = 0; if (!this.pattern) { @@ -101,7 +101,6 @@ export class Cyclist { return; } this.cps = cps; - // this.time_last_cps_change = this.getTime(); this.num_ticks_since_cps_change = 0; } log(begin, end, haps) { diff --git a/packages/core/zyklus.mjs b/packages/core/zyklus.mjs index 4bcaa8cd..3d25b054 100644 --- a/packages/core/zyklus.mjs +++ b/packages/core/zyklus.mjs @@ -39,6 +39,7 @@ function createClock( const pause = () => clear(); const stop = () => { tick = 0; + phase = 0; clear(); }; const getPhase = () => phase; From 1b55a5e3e00674c52f61a88cd9ff51e0e7b739ae Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 17:55:08 +0100 Subject: [PATCH 60/97] add trailing slash --- website/src/pages/technical-manual/docs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/technical-manual/docs.mdx b/website/src/pages/technical-manual/docs.mdx index aa18eab4..386de6ad 100644 --- a/website/src/pages/technical-manual/docs.mdx +++ b/website/src/pages/technical-manual/docs.mdx @@ -9,7 +9,7 @@ The docs page is built ontop of astro's [docs site](https://github.com/withastro ## Adding a new Docs Page -1. add a `.mdx` file in a path under `website/src/pages/`, e.g. [website/src/pages/learn/code.mdx](https://raw.githubusercontent.com/tidalcycles/strudel/main/website/src/pages/learn/code.mdx) will be available under https://strudel.cc/learn/code (or locally under `http://localhost:4321/learn/code`) +1. add a `.mdx` file in a path under `website/src/pages/`, e.g. [website/src/pages/learn/code.mdx](https://raw.githubusercontent.com/tidalcycles/strudel/main/website/src/pages/learn/code.mdx) will be available under https://strudel.cc/learn/code/ (or locally under `http://localhost:4321/learn/code/`) 2. make sure to copy the top part of another existing docs page. Adjust the title accordingly 3. To add a link to the sidebar, add a new entry to `SIDEBAR` to [`config.ts`](https://github.com/tidalcycles/strudel/blob/main/website/src/config.ts) From 47aa4cf1982596ce407688fdc83349a091307b7e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 17:56:07 +0100 Subject: [PATCH 61/97] autocomplete without react --- .../{Autocomplete.jsx => autocomplete.mjs} | 63 +++++++++---------- packages/codemirror/codemirror.mjs | 4 +- packages/codemirror/html.mjs | 17 +++++ 3 files changed, 48 insertions(+), 36 deletions(-) rename packages/codemirror/{Autocomplete.jsx => autocomplete.mjs} (61%) create mode 100644 packages/codemirror/html.mjs diff --git a/packages/codemirror/Autocomplete.jsx b/packages/codemirror/autocomplete.mjs similarity index 61% rename from packages/codemirror/Autocomplete.jsx rename to packages/codemirror/autocomplete.mjs index 18f172ee..05473b5e 100644 --- a/packages/codemirror/Autocomplete.jsx +++ b/packages/codemirror/autocomplete.mjs @@ -1,7 +1,7 @@ -import { createRoot } from 'react-dom/client'; import jsdoc from '../../doc.json'; // import { javascriptLanguage } from '@codemirror/lang-javascript'; import { autocompletion } from '@codemirror/autocomplete'; +import { h } from './html'; const getDocLabel = (doc) => doc.name || doc.longname; const getInnerText = (html) => { @@ -10,36 +10,32 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; -export function Autocomplete({ doc }) { - return ( -
-

{getDocLabel(doc)}

-
-
    - {doc.params?.map(({ name, type, description }, i) => ( -
  • - {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)} : ''} -
  • - ))} -
-
- {doc.examples?.map((example, i) => ( -
-
 {
-                console.log('ola!');
-                navigator.clipboard.writeText(example);
-                e.stopPropagation();
-              }}
-            >
-              {example}
-            
-
- ))} -
-
- ); +function Autocomplete({ doc }) { + return h`
+

${getDocLabel(doc)}

+${doc.description} +
    + ${doc.params?.map( + ({ name, type, description }) => + `
  • ${name} : ${type.names?.join(' | ')} ${description ? ` - ${getInnerText(description)}` : ''}
  • `, + )} +
+
+ ${doc.examples?.map((example) => `
${example}
`)} +
+
`[0]; + /* +
 {
+  console.log('ola!');
+  navigator.clipboard.writeText(example);
+  e.stopPropagation();
+}}
+>
+{example}
+
+*/ } const jsdocCompletions = jsdoc.docs @@ -56,9 +52,8 @@ const jsdocCompletions = jsdoc.docs // 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(); + const ac = Autocomplete({ doc }); + node.appendChild(ac); return node; }, type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index e5692a55..234e8e1a 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -6,7 +6,7 @@ import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language' import { Compartment, EditorState, Prec } from '@codemirror/state'; import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view'; import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core'; -// import { isAutoCompletionEnabled } from './Autocomplete'; +import { isAutoCompletionEnabled } from './autocomplete.mjs'; import { flash, isFlashEnabled } from './flash.mjs'; import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; import { keybindings } from './keybindings.mjs'; @@ -18,7 +18,7 @@ const extensions = { isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []), theme, - // isAutoCompletionEnabled, + isAutoCompletionEnabled, isPatternHighlightingEnabled, isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []), isFlashEnabled, diff --git a/packages/codemirror/html.mjs b/packages/codemirror/html.mjs new file mode 100644 index 00000000..4b4d82e4 --- /dev/null +++ b/packages/codemirror/html.mjs @@ -0,0 +1,17 @@ +const parser = new DOMParser(); +export let html = (string) => { + return parser.parseFromString(string, 'text/html').querySelectorAll('*'); +}; +let parseChunk = (chunk) => { + if (Array.isArray(chunk)) return chunk.flat().join(''); + if (chunk === undefined) return ''; + return chunk; +}; +export let h = (strings, ...vars) => { + let string = ''; + for (let i in strings) { + string += parseChunk(strings[i]); + string += parseChunk(vars[i]); + } + return html(string); +}; From f524dde5aec807f6d8e475182ac6d1e6b5758ee1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 18:03:58 +0100 Subject: [PATCH 62/97] tooltip without react + simplify autocomplete --- packages/codemirror/autocomplete.mjs | 11 ++-- packages/codemirror/codemirror.mjs | 2 + packages/codemirror/tooltip.mjs | 76 ++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 packages/codemirror/tooltip.mjs diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs index 05473b5e..c065c365 100644 --- a/packages/codemirror/autocomplete.mjs +++ b/packages/codemirror/autocomplete.mjs @@ -10,9 +10,9 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; -function Autocomplete({ doc }) { +export function Autocomplete({ doc, label }) { return h`
-

${getDocLabel(doc)}

+

${label || getDocLabel(doc)}

${doc.description}
    ${doc.params?.map( @@ -50,12 +50,7 @@ const jsdocCompletions = jsdoc.docs .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'); - const ac = Autocomplete({ doc }); - node.appendChild(ac); - return node; - }, + info: () => Autocomplete({ doc }), type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type })); diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 234e8e1a..6713ac6f 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -7,6 +7,7 @@ import { Compartment, EditorState, Prec } from '@codemirror/state'; import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view'; import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core'; import { isAutoCompletionEnabled } from './autocomplete.mjs'; +import { isTooltipEnabled } from './tooltip.mjs'; import { flash, isFlashEnabled } from './flash.mjs'; import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; import { keybindings } from './keybindings.mjs'; @@ -19,6 +20,7 @@ const extensions = { isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []), theme, isAutoCompletionEnabled, + isTooltipEnabled, isPatternHighlightingEnabled, isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []), isFlashEnabled, diff --git a/packages/codemirror/tooltip.mjs b/packages/codemirror/tooltip.mjs new file mode 100644 index 00000000..7b7a36db --- /dev/null +++ b/packages/codemirror/tooltip.mjs @@ -0,0 +1,76 @@ +import { hoverTooltip } from '@codemirror/view'; +import jsdoc from '../../doc.json'; +import { Autocomplete } from './autocomplete.mjs'; + +const getDocLabel = (doc) => doc.name || doc.longname; + +let ctrlDown = false; + +// Record Control key event to trigger or block the tooltip depending on the state +window.addEventListener( + 'keyup', + function (e) { + if (e.key == 'Control') { + ctrlDown = false; + } + }, + true, +); + +window.addEventListener( + 'keydown', + function (e) { + if (e.key == 'Control') { + ctrlDown = true; + } + }, + true, +); + +export const strudelTooltip = hoverTooltip( + (view, pos, side) => { + // Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips + if (!ctrlDown) { + return null; + } + let { from, to, text } = view.state.doc.lineAt(pos); + let start = pos, + end = pos; + while (start > from && /\w/.test(text[start - from - 1])) { + start--; + } + while (end < to && /\w/.test(text[end - from])) { + end++; + } + if ((start == pos && side < 0) || (end == pos && side > 0)) { + return null; + } + let word = text.slice(start - from, end - from); + // Get entry from Strudel documentation + let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0]; + if (!entry) { + // Try for synonyms + entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0]; + if (!entry) { + return null; + } + } + + return { + pos: start, + end, + above: false, + arrow: true, + create(view) { + let dom = document.createElement('div'); + dom.className = 'strudel-tooltip'; + const ac = Autocomplete({ doc: entry, label: word }); + dom.appendChild(ac); + return { dom }; + }, + }; + }, + { hoverTime: 10 }, +); + +export const isTooltipEnabled = (on) => (on ? strudelTooltip : []); From f44ceb46cd726ed93bd444434d1449bd702f4e40 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 18:26:51 +0100 Subject: [PATCH 63/97] remove react-dom dependency --- packages/codemirror/package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 0c57db8b..8810a607 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -47,7 +47,6 @@ "@strudel.cycles/core": "workspace:*", "@uiw/codemirror-themes": "^4.19.16", "@uiw/codemirror-themes-all": "^4.19.16", - "react-dom": "^18.2.0", "nanostores": "^0.8.1", "@nanostores/persistent": "^0.8.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fecabff..5892af73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,9 +123,6 @@ importers: nanostores: specifier: ^0.8.1 version: 0.8.1 - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) devDependencies: vite: specifier: ^4.3.3 From d7c2cf562ffdc9eecde8ead2111aa3c143b0fc65 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 11:40:07 +0100 Subject: [PATCH 64/97] fix: hot reloading --- website/src/repl/Repl2.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index ccd46c94..4a899726 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -128,6 +128,7 @@ export function Repl2({ embedded = false }) { } return () => { editorRef.current?.clear(); + delete editorRef.current; }; }, []); From e2a082f748de41cc493de401432ccd0ecf236467 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 11:42:18 +0100 Subject: [PATCH 65/97] move repl2 to /next --- .../{vanilla/2.astro => next/index.astro} | 0 website/src/pages/vanilla/index.astro | 92 -------------- website/src/repl/vanilla/vanilla.css | 34 ----- website/src/repl/vanilla/vanilla.mjs | 117 ------------------ 4 files changed, 243 deletions(-) rename website/src/pages/{vanilla/2.astro => next/index.astro} (100%) delete mode 100644 website/src/pages/vanilla/index.astro delete mode 100644 website/src/repl/vanilla/vanilla.css delete mode 100644 website/src/repl/vanilla/vanilla.mjs diff --git a/website/src/pages/vanilla/2.astro b/website/src/pages/next/index.astro similarity index 100% rename from website/src/pages/vanilla/2.astro rename to website/src/pages/next/index.astro diff --git a/website/src/pages/vanilla/index.astro b/website/src/pages/vanilla/index.astro deleted file mode 100644 index d4ea40b8..00000000 --- a/website/src/pages/vanilla/index.astro +++ /dev/null @@ -1,92 +0,0 @@ - - - Strudel Vanilla REPL - - -
    -
    -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - - - -
    -
    - - - - - diff --git a/website/src/repl/vanilla/vanilla.css b/website/src/repl/vanilla/vanilla.css deleted file mode 100644 index 5387fb04..00000000 --- a/website/src/repl/vanilla/vanilla.css +++ /dev/null @@ -1,34 +0,0 @@ -body, -input { - font-family: monospace; - background-color: black; - color: white; -} - -input, -select { - background-color: black !important; -} - -html, -body, -#editor, -.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; -} diff --git a/website/src/repl/vanilla/vanilla.mjs b/website/src/repl/vanilla/vanilla.mjs deleted file mode 100644 index 6f0cc058..00000000 --- a/website/src/repl/vanilla/vanilla.mjs +++ /dev/null @@ -1,117 +0,0 @@ -import { hash2code, logger } from '@strudel.cycles/core'; -import { codemirrorSettings, defaultSettings } from '@strudel/codemirror'; -import './vanilla.css'; - -let editor; - -async function run() { - const repl = document.getElementById('editor'); - editor = repl.editor; - 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(",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("/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, codemirrorSettings.get()); -form.addEventListener('change', () => { - const values = getFormValues(form, defaultSettings); - editor?.updateSettings(values); -}); From 8bde87246bc8bdb8e8b865b88f3bda3b4f4a13e1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 12:45:27 +0100 Subject: [PATCH 66/97] load imported samples directly in prebake --- website/src/repl/Repl.jsx | 2 -- website/src/repl/idbutils.mjs | 2 +- website/src/repl/prebake.mjs | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index eb2b495b..65cc3473 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -21,7 +21,6 @@ import { import { Header } from './Header'; import Loader from './Loader'; import './Repl.css'; -import { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs'; import { Panel } from './panel/Panel'; import { prebake } from './prebake.mjs'; import { themes } from './themes.mjs'; @@ -117,7 +116,6 @@ export function Repl({ embedded = false }) { 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'); setPending(false); }); diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs index 414006b9..18e15d20 100644 --- a/website/src/repl/idbutils.mjs +++ b/website/src/repl/idbutils.mjs @@ -24,7 +24,7 @@ const clearIDB = () => { }; // queries the DB, and registers the sounds so they can be played -export const registerSamplesFromDB = (config, onComplete = () => {}) => { +export const registerSamplesFromDB = (config = userSamplesDBConfig, onComplete = () => {}) => { openDB(config, (objectStore) => { let query = objectStore.getAll(); query.onsuccess = (event) => { diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 68f6f8a1..96a484e7 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,5 +1,6 @@ import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core'; import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; +import { registerSamplesFromDB } from './idbutils.mjs'; import './piano.mjs'; import './files.mjs'; @@ -12,6 +13,7 @@ export async function prebake() { await Promise.all([ registerSynthSounds(), registerZZFXSounds(), + registerSamplesFromDB(), //registerSoundfonts(), // need dynamic import here, because importing @strudel.cycles/soundfonts fails on server: // => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically From 39e81aa77cd5858a927c7a773c92434b7777acbc Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 12:46:10 +0100 Subject: [PATCH 67/97] reduce drawTime to fix perf (for now) --- packages/codemirror/codemirror.mjs | 2 +- website/src/repl/Repl2.jsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 6713ac6f..29dca867 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -115,7 +115,7 @@ export class StrudelMirror { id, initialCode = '', onDraw, - drawTime = [-2, 2], + drawTime = [0, 0], autodraw, prebake, bgFill = true, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index 4a899726..641d2a91 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -57,7 +57,10 @@ export function Repl2({ embedded = false }) { const shouldDraw = true; const init = useCallback(({ shouldDraw }) => { - const drawTime = [0, 4]; + // TODO: find way to make spiral & punchcard work (if there's any) + // upping the 2nd value leads to slow eval times + // because Drawer.invalidate might query alot at one time + const drawTime = [0, 0]; const drawContext = shouldDraw ? getDrawContext() : null; let onDraw; if (shouldDraw) { @@ -105,8 +108,6 @@ export function Repl2({ embedded = false }) { editor.setCode(randomTune); 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'); // setPending(false); }); From 9b5842b20ef9666ed5d41b575fb10d2e306396c1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 13:28:20 +0100 Subject: [PATCH 68/97] hotfix: fix build error --- packages/codemirror/html.mjs | 4 ++-- packages/codemirror/tooltip.mjs | 40 +++++++++++++++++---------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/codemirror/html.mjs b/packages/codemirror/html.mjs index 4b4d82e4..527275ef 100644 --- a/packages/codemirror/html.mjs +++ b/packages/codemirror/html.mjs @@ -1,6 +1,6 @@ -const parser = new DOMParser(); +const parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null; export let html = (string) => { - return parser.parseFromString(string, 'text/html').querySelectorAll('*'); + return parser?.parseFromString(string, 'text/html').querySelectorAll('*'); }; let parseChunk = (chunk) => { if (Array.isArray(chunk)) return chunk.flat().join(''); diff --git a/packages/codemirror/tooltip.mjs b/packages/codemirror/tooltip.mjs index 7b7a36db..f67e6d14 100644 --- a/packages/codemirror/tooltip.mjs +++ b/packages/codemirror/tooltip.mjs @@ -6,26 +6,28 @@ const getDocLabel = (doc) => doc.name || doc.longname; let ctrlDown = false; -// Record Control key event to trigger or block the tooltip depending on the state -window.addEventListener( - 'keyup', - function (e) { - if (e.key == 'Control') { - ctrlDown = false; - } - }, - true, -); +if (typeof window !== 'undefined') { + // Record Control key event to trigger or block the tooltip depending on the state + window.addEventListener( + 'keyup', + function (e) { + if (e.key == 'Control') { + ctrlDown = false; + } + }, + true, + ); -window.addEventListener( - 'keydown', - function (e) { - if (e.key == 'Control') { - ctrlDown = true; - } - }, - true, -); + window.addEventListener( + 'keydown', + function (e) { + if (e.key == 'Control') { + ctrlDown = true; + } + }, + true, + ); +} export const strudelTooltip = hoverTooltip( (view, pos, side) => { From a9dc0912d0f1857416518666092e926817674296 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 17:54:51 +0100 Subject: [PATCH 69/97] fix: drawer performance issue --- packages/codemirror/codemirror.mjs | 8 ++++++++ packages/core/draw.mjs | 3 +++ website/src/repl/Repl2.jsx | 5 +---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 29dca867..91f9fd8f 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -126,6 +126,7 @@ export class StrudelMirror { this.miniLocations = []; this.widgets = []; this.painters = []; + this.drawTime = drawTime; this.onDraw = onDraw; const self = this; this.id = id || s4(); @@ -151,6 +152,7 @@ export class StrudelMirror { onToggle: (started) => { replOptions?.onToggle?.(started); if (started) { + this.adjustDrawTime(); this.drawer.start(this.repl.scheduler); // stop other repls when this one is started document.dispatchEvent( @@ -177,6 +179,7 @@ export class StrudelMirror { updateWidgets(this.editor, this.widgets); updateMiniLocations(this.editor, this.miniLocations); replOptions?.afterEval?.(options); + this.adjustDrawTime(); this.drawer.invalidate(); }, }); @@ -212,6 +215,11 @@ export class StrudelMirror { }; document.addEventListener('start-repl', this.onStartRepl); } + // adjusts draw time depending on if there are painters + adjustDrawTime() { + // when no painters are set, [0,0] is enough (just highlighting) + this.drawer.setDrawTime(!!this.painters.length ? this.drawTime : [0, 0]); + } async drawFirstFrame() { if (!this.onDraw) { return; diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index ff5359e1..0d2796f1 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -145,6 +145,9 @@ export class Drawer { }, ); } + setDrawTime(drawTime) { + this.drawTime = drawTime; + } invalidate(scheduler = this.scheduler, t) { if (!scheduler) { return; diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index 641d2a91..fff89e52 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -57,10 +57,7 @@ export function Repl2({ embedded = false }) { const shouldDraw = true; const init = useCallback(({ shouldDraw }) => { - // TODO: find way to make spiral & punchcard work (if there's any) - // upping the 2nd value leads to slow eval times - // because Drawer.invalidate might query alot at one time - const drawTime = [0, 0]; + const drawTime = [-2, 2]; const drawContext = shouldDraw ? getDrawContext() : null; let onDraw; if (shouldDraw) { From f96827d052e3d96530e2fe71635eda4712448b09 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 18:03:50 +0100 Subject: [PATCH 70/97] fix: initial highlighting --- packages/core/draw.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index 0d2796f1..941401da 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -111,8 +111,6 @@ export class Framer { // see vite-vanilla-repl-cm6 for an example export class Drawer { constructor(onDraw, drawTime) { - let [lookbehind, lookahead] = drawTime; // e.g. [-2, 2] - lookbehind = Math.abs(lookbehind); this.visibleHaps = []; this.lastFrame = null; this.drawTime = drawTime; @@ -122,6 +120,8 @@ export class Drawer { console.warn('Drawer: no scheduler'); return; } + const lookbehind = Math.abs(this.drawTime[0]); + const lookahead = this.drawTime[1]; // calculate current frame time (think right side of screen for pianoroll) const phase = this.scheduler.now() + lookahead; // first frame just captures the phase From 837f8f318d95b1f7af98bacb41e47bd1746846c7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 19:40:01 +0100 Subject: [PATCH 71/97] microrepl claviature support --- website/src/docs/MicroRepl.jsx | 43 +++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx index 72561669..7d2e260b 100644 --- a/website/src/docs/MicroRepl.jsx +++ b/website/src/docs/MicroRepl.jsx @@ -1,31 +1,35 @@ import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; import { Icon } from './Icon'; -import { silence, getPunchcardPainter } from '@strudel.cycles/core'; +import { silence, getPunchcardPainter, noteToMidi } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; import { StrudelMirror } from '@strudel/codemirror'; import { prebake } from '@strudel/repl'; +import Claviature from '@components/Claviature'; export function MicroRepl({ code, hideHeader = false, canvasHeight = 100, onTrigger, - onPaint, punchcard, punchcardLabels = true, + claviature, + claviatureLabels, }) { const id = useMemo(() => s4(), []); const canvasId = useMemo(() => `canvas-${id}`, [id]); - const shouldDraw = !!punchcard; + const shouldDraw = !!punchcard || !!claviature; + const shouldShowCanvas = !!punchcard; + const drawTime = punchcard ? [0, 4] : [0, 0]; + const [activeNotes, setActiveNotes] = useState([]); const init = useCallback(({ code, shouldDraw }) => { - const drawTime = [0, 4]; const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null; let onDraw; if (shouldDraw) { onDraw = (haps, time, frame, painters) => { - painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2); + 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 }); @@ -48,10 +52,17 @@ export function MicroRepl({ if (onTrigger) { pat = pat.onTrigger(onTrigger, false); } - if (onPaint) { - editor.painters.push(onPaint); - } else if (punchcard) { - editor.painters.push(getPunchcardPainter({ labels: !!punchcardLabels })); + if (claviature) { + editor?.painters.push((ctx, time, haps, drawTime) => { + const active = haps + .map((hap) => hap.value.note) + .filter(Boolean) + .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); + setActiveNotes(active); + }); + } + if (punchcard) { + editor?.painters.push(getPunchcardPainter({ labels: !!punchcardLabels })); } return pat; }, @@ -78,7 +89,7 @@ export function MicroRepl({ }); } return () => { - editor.clear(); + editorRef.current?.clear(); }; }, []); @@ -116,7 +127,7 @@ export function MicroRepl({
    {error &&
    {error.message}
    }
- {shouldDraw && ( + {shouldShowCanvas && ( ) */} + {claviature && ( + + )}
); } From 975a198ee95f4f2e3f6424730c646ba6c4b55bd2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 20:36:23 +0100 Subject: [PATCH 72/97] whole docs now run new repl - move MicroRepl code to MiniRepl - fix a ssr bug --- website/src/docs/MicroRepl.jsx | 172 ---------- website/src/docs/MiniRepl.jsx | 224 +++++++++---- website/src/pages/recipes/recipes-next.mdx | 312 ------------------ .../src/pages/technical-manual/patterns.mdx | 2 +- website/src/repl/idbutils.mjs | 3 + 5 files changed, 158 insertions(+), 555 deletions(-) delete mode 100644 website/src/docs/MicroRepl.jsx delete mode 100644 website/src/pages/recipes/recipes-next.mdx diff --git a/website/src/docs/MicroRepl.jsx b/website/src/docs/MicroRepl.jsx deleted file mode 100644 index 7d2e260b..00000000 --- a/website/src/docs/MicroRepl.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; -import { Icon } from './Icon'; -import { silence, getPunchcardPainter, noteToMidi } from '@strudel.cycles/core'; -import { transpiler } from '@strudel.cycles/transpiler'; -import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; -import { StrudelMirror } from '@strudel/codemirror'; -import { prebake } from '@strudel/repl'; -import Claviature from '@components/Claviature'; - -export function MicroRepl({ - code, - hideHeader = false, - canvasHeight = 100, - onTrigger, - punchcard, - punchcardLabels = true, - claviature, - claviatureLabels, -}) { - const id = useMemo(() => s4(), []); - const canvasId = useMemo(() => `canvas-${id}`, [id]); - const shouldDraw = !!punchcard || !!claviature; - const shouldShowCanvas = !!punchcard; - const drawTime = punchcard ? [0, 4] : [0, 0]; - const [activeNotes, setActiveNotes] = useState([]); - - const init = useCallback(({ code, shouldDraw }) => { - const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null; - let onDraw; - if (shouldDraw) { - 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 }); - }); - }; - } - - const editor = new StrudelMirror({ - id, - defaultOutput: webaudioOutput, - getTime: () => getAudioContext().currentTime, - transpiler, - autodraw: !!shouldDraw, - root: containerRef.current, - initialCode: '// LOADING', - pattern: silence, - drawTime, - onDraw, - editPattern: (pat, id) => { - if (onTrigger) { - pat = pat.onTrigger(onTrigger, false); - } - if (claviature) { - editor?.painters.push((ctx, time, haps, drawTime) => { - const active = haps - .map((hap) => hap.value.note) - .filter(Boolean) - .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); - setActiveNotes(active); - }); - } - if (punchcard) { - editor?.painters.push(getPunchcardPainter({ labels: !!punchcardLabels })); - } - return pat; - }, - prebake, - onUpdateState: (state) => { - setReplState({ ...state }); - }, - }); - // init settings - editor.setCode(code); - editorRef.current = editor; - }, []); - - const [replState, setReplState] = useState({}); - const { started, isDirty, error } = replState; - const editorRef = useRef(); - const containerRef = useRef(); - const [client, setClient] = useState(false); - useEffect(() => { - setClient(true); - if (!editorRef.current) { - setTimeout(() => { - init({ code, shouldDraw }); - }); - } - return () => { - editorRef.current?.clear(); - }; - }, []); - - if (!client) { - return
{code}
; - } - - return ( -
- {!hideHeader && ( -
-
- - -
-
- )} -
-
- {error &&
{error.message}
} -
- {shouldShowCanvas && ( - { - if (el && el.width !== el.clientWidth) { - el.width = el.clientWidth; - } - }} - > - )} - {/* !!log.length && ( -
- {log.map(({ message }, i) => ( -
{message}
- ))} -
- ) */} - {claviature && ( - - )} -
- ); -} - -function cx(...classes) { - // : Array - return classes.filter(Boolean).join(' '); -} - -function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); -} diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index 4b37ebad..3a16f3dd 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -1,84 +1,159 @@ -import { evalScope, controls, noteToMidi } from '@strudel.cycles/core'; -import { initAudioOnFirstClick } from '@strudel.cycles/webaudio'; -import { useEffect, useState } from 'react'; -import { prebake } from '../repl/prebake'; -import { themes, settings } from '../repl/themes.mjs'; -import './MiniRepl.css'; -import { useSettings } from '../settings.mjs'; +import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; +import { Icon } from './Icon'; +import { silence, getPunchcardPainter, noteToMidi } from '@strudel.cycles/core'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import { StrudelMirror } from '@strudel/codemirror'; +// import { prebake } from '@strudel/repl'; +import { prebake } from '../repl/prebake.mjs'; +import { loadModules } from '../repl/util.mjs'; import Claviature from '@components/Claviature'; -let modules; +let prebaked, modulesLoading; if (typeof window !== 'undefined') { - modules = evalScope( - controls, - import('@strudel.cycles/core'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/midi'), - import('@strudel.cycles/xen'), - import('@strudel.cycles/webaudio'), - import('@strudel.cycles/osc'), - import('@strudel.cycles/csound'), - import('@strudel.cycles/soundfonts'), - import('@strudel/hydra'), - ); -} - -if (typeof window !== 'undefined') { - initAudioOnFirstClick(); - prebake(); + prebaked = prebake(); + modulesLoading = loadModules(); } export function MiniRepl({ - tune, - drawTime, + tune: code, + hideHeader = false, + canvasHeight = 100, + onTrigger, punchcard, punchcardLabels = true, - span = [0, 4], - canvasHeight = 100, - hideHeader, claviature, claviatureLabels, }) { - const [Repl, setRepl] = useState(); - const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed, isActiveLineHighlighted } = useSettings(); + const id = useMemo(() => s4(), []); + const canvasId = useMemo(() => `canvas-${id}`, [id]); + const shouldDraw = !!punchcard || !!claviature; + const shouldShowCanvas = !!punchcard; + const drawTime = punchcard ? [0, 4] : [0, 0]; const [activeNotes, setActiveNotes] = useState([]); - useEffect(() => { - // we have to load this package on the client - // because codemirror throws an error on the server - Promise.all([import('@strudel.cycles/react'), modules]) - .then(([res]) => setRepl(() => res.MiniRepl)) - .catch((err) => console.error(err)); - }, []); - return Repl ? ( -
- { - const active = haps - .map((hap) => hap.value.note) - .filter(Boolean) - .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); - setActiveNotes(active); - } - : undefined + + const init = useCallback(({ code, shouldDraw }) => { + const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null; + let onDraw; + if (shouldDraw) { + 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 }); + }); + }; + } + + const editor = new StrudelMirror({ + id, + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + autodraw: !!shouldDraw, + root: containerRef.current, + initialCode: '// LOADING', + pattern: silence, + drawTime, + onDraw, + editPattern: (pat, id) => { + if (onTrigger) { + pat = pat.onTrigger(onTrigger, false); } - /> + if (claviature) { + editor?.painters.push((ctx, time, haps, drawTime) => { + const active = haps + .map((hap) => hap.value.note) + .filter(Boolean) + .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); + setActiveNotes(active); + }); + } + if (punchcard) { + editor?.painters.push(getPunchcardPainter({ labels: !!punchcardLabels })); + } + return pat; + }, + prebake: async () => Promise.all([modulesLoading, prebaked]), + onUpdateState: (state) => { + setReplState({ ...state }); + }, + }); + // init settings + editor.setCode(code); + editorRef.current = editor; + }, []); + + const [replState, setReplState] = useState({}); + const { started, isDirty, error } = replState; + const editorRef = useRef(); + const containerRef = useRef(); + const [client, setClient] = useState(false); + useEffect(() => { + setClient(true); + if (!editorRef.current) { + setTimeout(() => { + init({ code, shouldDraw }); + }); + } + return () => { + editorRef.current?.clear(); + }; + }, []); + + if (!client) { + return
{code}
; + } + + return ( +
+ {!hideHeader && ( +
+
+ + +
+
+ )} +
+
+ {error &&
{error.message}
} +
+ {shouldShowCanvas && ( + { + if (el && el.width !== el.clientWidth) { + el.width = el.clientWidth; + } + }} + > + )} + {/* !!log.length && ( +
+ {log.map(({ message }, i) => ( +
{message}
+ ))} +
+ ) */} {claviature && ( )}
- ) : ( -
{tune}
); } + +function cx(...classes) { + // : Array + return classes.filter(Boolean).join(' '); +} + +function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); +} diff --git a/website/src/pages/recipes/recipes-next.mdx b/website/src/pages/recipes/recipes-next.mdx deleted file mode 100644 index 4230063f..00000000 --- a/website/src/pages/recipes/recipes-next.mdx +++ /dev/null @@ -1,312 +0,0 @@ ---- -title: Recipes -layout: ../../layouts/MainLayout.astro ---- - -import { MicroRepl } from '../../docs/MicroRepl'; - -# Recipes - -This page shows possible ways to achieve common (or not so common) musical goals. -There are often many ways to do a thing and there is no right or wrong. -The fun part is that each representation will give you different impulses when improvising. - -## Arpeggios - -An arpeggio is when the notes of a chord are played in sequence. -We can either write the notes by hand: - - - -...or use scales: - - - -...or chord symbols: - - - -...using off: - - - -## Chopping Breaks - -A sample can be looped and chopped like this: - - - -This fits the break into 8 cycles + chops it in 16 pieces. -The chops are not audible yet, because we're not doing any manipulation. -Let's add randmized doubling + reversing: - - - -If we want to specify the order of samples, we can replace `chop` with `slice`: - -") - .cut(1).rarely(ply(2))`} - punchcard -/> - -If we use `splice` instead of `slice`, the speed adjusts to the duration of the event: - -") - .cut(1).rarely(ply(2))`} - punchcard -/> - -Note that we don't need `fit`, because `splice` will do that by itself. - -## Filter Envelopes - -A minimal filter envelope looks like this: - - d2") - .s("sawtooth") - .lpf(400).lpa(.2).lpenv(4) - .scope()`} -/> - -We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`: - - d2") - .s("sawtooth").lpq(8) - .lpf(400).lpa(.2).lpenv(-4) - .scope()`} -/> - -## Layering Sounds - -We can layer sounds by separating them with ",": - -") -.s("sawtooth, square") // <------ -.scope()`} -/> - -We can control the gain of individual sounds like this: - -") -.s("sawtooth, square:0:.5") // <--- "name:number:gain" -.scope()`} -/> - -For more control over each voice, we can use `layer`: - -").layer( - x=>x.s("sawtooth").vib(4), - x=>x.s("square").add(note(12)) -).scope()`} -/> - -Here, we give the sawtooth a vibrato and the square is moved an octave up. -With `layer`, you can use any pattern method available on each voice, so sky is the limit.. - -## Oscillator Detune - -We can fatten a sound by adding a detuned version to itself: - -") -.add(note("0,.1")) // <------ chorus -.s("sawtooth").scope()`} - punchcard -/> - -Try out different values, or add another voice! - -## Polyrhythms - -Here is a simple example of a polyrhythm: - - - -A polyrhythm is when 2 different tempos happen at the same time. - -## Polymeter - -This is a polymeter: - -,").fast(2)`} punchcard /> - -A polymeter is when 2 different bar lengths play at the same tempo. - -## Phasing - -This is a phasing: - -*[6,6.1]").piano()`} punchcard /> - -Phasing happens when the same sequence plays at slightly different tempos. - -## Running through samples - -Using `run` with `n`, we can rush through a sample bank: - - - -This works great with sample banks that contain similar sounds, like in this case different recordings of a tabla. -Often times, you'll hear the beginning of the phrase not where the pattern begins. -In this case, I hear the beginning at the third sample, which can be accounted for with `early`. - - - -Let's add some randomness: - - - -## Tape Warble - -We can emulate a pitch warbling effect like this: - - - -## Sound Duration - -There are a number of ways to change the sound duration. Using clip: - -/2")`} -/> - -The value of clip is relative to the duration of each event. -We can also create overlaps using release: - -/2")`} -/> - -This will smoothly fade out each sound for the given number of seconds. -We could also make the notes shorter with decay / sustain: - -/2").sustain(0)`} -/> - -For now, there is a limitation where decay values that exceed the event duration may cause little cracks, so use higher numbers with caution.. - -When using samples, we also have `.end` to cut relative to the sample length: - -")`} /> - -Compare that to clip: - -")`} /> - -or decay / sustain - -").sustain(0)`} /> - -## Wavetable Synthesis - -You can loop a sample with `loop` / `loopEnd`: - -").s("bd").loop(1).loopEnd(.05).gain(.2)`} /> - -This allows us to play the first 5% of the bass drum as a synth! -To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically: - - - -Running through different wavetables can also give interesting variations: - - - -...adding a filter envelope + reverb: - - diff --git a/website/src/pages/technical-manual/patterns.mdx b/website/src/pages/technical-manual/patterns.mdx index 45664ad7..49ede526 100644 --- a/website/src/pages/technical-manual/patterns.mdx +++ b/website/src/pages/technical-manual/patterns.mdx @@ -14,7 +14,7 @@ Example: e.show())) silence`} diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs index 18e15d20..7613b767 100644 --- a/website/src/repl/idbutils.mjs +++ b/website/src/repl/idbutils.mjs @@ -77,6 +77,9 @@ async function bufferToDataUrl(buf) { //open db and initialize it if necessary const openDB = (config, onOpened) => { const { dbName, version, table, columns } = config; + if (typeof window === 'undefined') { + return; + } if (!('indexedDB' in window)) { console.log('IndexedDB is not supported.'); return; From 2ed3a5c582c14b570be3217ba0679c5e4dc296e1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 20:36:59 +0100 Subject: [PATCH 73/97] fix: lint --- packages/codemirror/codemirror.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 91f9fd8f..8a887036 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -218,7 +218,7 @@ export class StrudelMirror { // adjusts draw time depending on if there are painters adjustDrawTime() { // when no painters are set, [0,0] is enough (just highlighting) - this.drawer.setDrawTime(!!this.painters.length ? this.drawTime : [0, 0]); + this.drawer.setDrawTime(this.painters.length ? this.drawTime : [0, 0]); } async drawFirstFrame() { if (!this.onDraw) { From 201fe726b614d93b721a3c25db425c9c54e431df Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 20:48:59 +0100 Subject: [PATCH 74/97] replace main repl --- website/src/pages/index.astro | 6 +- website/src/pages/next/index.astro | 14 -- website/src/repl/Repl.jsx | 259 ++++++++++++----------------- website/src/repl/Repl2.jsx | 231 ------------------------- 4 files changed, 113 insertions(+), 397 deletions(-) delete mode 100644 website/src/pages/next/index.astro delete mode 100644 website/src/repl/Repl2.jsx diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 40a1a03f..7db3da94 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -1,11 +1,11 @@ --- -import HeadCommon from '../components/HeadCommon.astro'; -import { Repl } from '../repl/Repl.jsx'; +import HeadCommonNext from '../components/HeadCommonNext.astro'; +import { Repl } from '../repl/Repl'; --- - + Strudel REPL diff --git a/website/src/pages/next/index.astro b/website/src/pages/next/index.astro deleted file mode 100644 index 0db75a1d..00000000 --- a/website/src/pages/next/index.astro +++ /dev/null @@ -1,14 +0,0 @@ ---- -import HeadCommonNext from '../../components/HeadCommonNext.astro'; -import { Repl2 } from '../../repl/Repl2'; ---- - - - - - Strudel REPL - - - - - diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 65cc3473..4aa677ad 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -1,15 +1,17 @@ /* -App.js - +Repl.jsx - Copyright (C) 2022 Strudel contributors - see 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 . */ -import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; -import { cleanupDraw, cleanupUi, code2hash, getDrawContext, logger } from '@strudel.cycles/core'; -import { CodeMirror, cx, flash, useHighlighting, useKeydown, useStrudel } from '@strudel.cycles/react'; -import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; -import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; -import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core'; +import { cx } from '@strudel.cycles/react'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio'; +import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; +/* import { writeText } from '@tauri-apps/api/clipboard'; +import { nanoid } from 'nanoid'; */ +import { createContext, useCallback, useEffect, useRef, useState } from 'react'; import { initUserCode, setActivePattern, @@ -22,9 +24,14 @@ import { Header } from './Header'; import Loader from './Loader'; import './Repl.css'; import { Panel } from './panel/Panel'; -import { prebake } from './prebake.mjs'; -import { themes } from './themes.mjs'; +// import { prebake } from '@strudel/repl'; +import { useStore } from '@nanostores/react'; +import { prebake /* , resetSounds */ } from './prebake.mjs'; import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; +import './Repl.css'; + +const { code: randomTune, name } = getRandomTune(); +export const ReplContext = createContext(null); const { latestCode } = settingsMap.get(); @@ -39,185 +46,151 @@ if (typeof window !== 'undefined') { clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); } -const getTime = () => getAudioContext().currentTime; - -const { code: randomTune, name } = getRandomTune(); - -export const ReplContext = createContext(null); +// const getTime = () => getAudioContext().currentTime; export function Repl({ embedded = false }) { - const isEmbedded = embedded || window.location !== window.parent.location; - const [view, setView] = useState(); // codemirror view - const [pending, setPending] = useState(true); - const { - theme, - keybindings, - fontSize, - fontFamily, - isLineNumbersDisplayed, - isActiveLineHighlighted, - isAutoCompletionEnabled, - isTooltipEnabled, - isLineWrappingEnabled, - panelPosition, - isZen, - } = useSettings(); + //const isEmbedded = embedded || window.location !== window.parent.location; + const isEmbedded = false; + const { panelPosition, isZen } = useSettings(); + /* const replState = useStore($replstate); + const isDirty = useStore($repldirty); */ + const shouldDraw = true; - const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]); - const { setWidgets } = useWidgets(view); - const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } = - useStrudel({ - initialCode: '// LOADING...', + const init = useCallback(({ shouldDraw }) => { + const drawTime = [-2, 2]; + const drawContext = shouldDraw ? getDrawContext() : null; + let onDraw; + if (shouldDraw) { + 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 }); + }); + }; + } + const editor = new StrudelMirror({ defaultOutput: webaudioOutput, - getTime, - beforeEval: async () => { - setPending(true); - await modulesLoading; - cleanupUi(); - cleanupDraw(); + getTime: () => getAudioContext().currentTime, + transpiler, + autodraw: false, + root: containerRef.current, + initialCode: '// LOADING', + pattern: silence, + drawTime, + onDraw, + prebake: async () => Promise.all([modulesLoading, presets]), + onUpdateState: (state) => { + setReplState({ ...state }); }, - afterEval: ({ code, meta }) => { + afterEval: ({ code }) => { updateUserCode(code); - setMiniLocations(meta.miniLocations); - setWidgets(meta.widgets); - setPending(false); + // setPending(false); setLatestCode(code); window.location.hash = '#' + code2hash(code); }, - onEvalError: (err) => { - setPending(false); - }, - onToggle: (play) => { - if (!play) { - cleanupDraw(false); - window.postMessage('strudel-stop'); - } else { - window.postMessage('strudel-start'); - } - }, - drawContext, - // drawTime: [0, 6], - paintOptions, + bgFill: false, }); - - // init code - useEffect(() => { + // init settings initCode().then((decoded) => { let msg; if (decoded) { - setCode(decoded); + editor.setCode(decoded); initUserCode(decoded); msg = `I have loaded the code from the URL.`; } else if (latestCode) { - setCode(latestCode); + editor.setCode(latestCode); msg = `Your last session has been loaded!`; } /* if(randomTune) */ else { - setCode(randomTune); + editor.setCode(randomTune); msg = `A random code snippet named "${name}" has been loaded!`; } - //registers samples that have been saved to the index DB logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); - setPending(false); + // setPending(false); }); + + editorRef.current = editor; }, []); - // keyboard shortcuts - useKeydown( - useCallback( - async (e) => { - if (e.ctrlKey || e.altKey) { - if (e.code === 'Enter') { - if (getAudioContext().state !== 'running') { - alert('please click play to initialize the audio. you can use shortcuts after that!'); - return; - } - e.preventDefault(); - flash(view); - await activateCode(); - } else if (e.key === '.' || e.code === 'Period') { - stop(); - e.preventDefault(); - } - } - }, - [activateCode, stop, view], - ), - ); + const [replState, setReplState] = useState({}); + const { started, isDirty, error, activeCode } = replState; + const editorRef = useRef(); + const containerRef = useRef(); + const [client, setClient] = useState(false); + useEffect(() => { + setClient(true); + if (!editorRef.current) { + setTimeout(() => { + init({ shouldDraw }); + }); + } + return () => { + editorRef.current?.clear(); + delete editorRef.current; + }; + }, []); - // highlighting - const { setMiniLocations } = useHighlighting({ - view, - pattern, - active: started && !activeCode?.includes('strudel disable-highlighting'), - getTime: () => scheduler.now(), - }); + // this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly! + // this will be the case when the main repl is being replaced + const _settings = useStore(settingsMap, { keys: Object.keys(defaultSettings) }); + useEffect(() => { + let editorSettings = {}; + Object.keys(defaultSettings).forEach((key) => { + if (_settings.hasOwnProperty(key)) { + editorSettings[key] = _settings[key]; + } + }); + editorRef.current?.updateSettings(editorSettings); + }, [_settings]); // // UI Actions // - const handleChangeCode = useCallback( - (c) => { - setCode(c); - // started && logger('[edit] code changed. hit ctrl+enter to update'); - }, - [started], - ); - const handleSelectionChange = useCallback((selection) => { - // TODO: scroll to selected function in reference - // console.log('selectino change', selection.ranges[0].from); - }, []); - - const handleTogglePlay = async () => { - await getAudioContext().resume(); // fixes no sound in ios webkit - if (!started) { - logger('[repl] started. tip: you can also start by pressing ctrl+enter', 'highlight'); - activateCode(); - } else { - logger('[repl] stopped. tip: you can also stop by pressing ctrl+dot', 'highlight'); - stop(); - } - }; + const handleTogglePlay = async () => editorRef.current?.toggle(); const handleUpdate = async (newCode, reset = false) => { if (reset) { clearCanvas(); resetLoadedSounds(); - scheduler.setCps(1); + editorRef.current.repl.setCps(1); await prebake(); // declare default samples } - (newCode || isDirty) && activateCode(newCode); + if (newCode || isDirty) { + editorRef.current.setCode(newCode); + editorRef.current.repl.evaluate(newCode); + } logger('[repl] code updated!'); }; - const handleShuffle = async () => { + // window.postMessage('strudel-shuffle'); const { code, name } = getRandomTune(); logger(`[repl] ✨ loading random tune "${name}"`); setActivePattern(name); clearCanvas(); resetLoadedSounds(); - scheduler.setCps(1); + editorRef.current.repl.setCps(1); await prebake(); // declare default samples - await evaluate(code, false); + editorRef.current.setCode(code); + editorRef.current.repl.evaluate(code); }; - const handleShare = async () => shareCode(activeCode || code); + const handleShare = async () => shareCode(activeCode); + const pending = false; + //const error = undefined; + // const { started, activeCode } = replState; + const context = { - scheduler, + // scheduler, embedded, started, pending, isDirty, activeCode, - handleChangeCode, handleTogglePlay, handleUpdate, handleShuffle, handleShare, }; - const currentTheme = useMemo(() => themes[theme] || themes.strudelTheme, [theme]); - const handleViewChanged = useCallback((v) => { - setView(v); - }, []); return ( // bg-gradient-to-t from-blue-900 to-slate-900 @@ -231,7 +204,7 @@ export function Repl({ embedded = false }) { >
- {isEmbedded && !started && ( + {/* isEmbedded && !started && ( - )} + ) */}
-
- -
+
{panelPosition === 'right' && !isEmbedded && }
{error && ( diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx deleted file mode 100644 index fff89e52..00000000 --- a/website/src/repl/Repl2.jsx +++ /dev/null @@ -1,231 +0,0 @@ -/* -App.js - -Copyright (C) 2022 Strudel contributors - see -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 . -*/ - -import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core'; -import { cx } from '@strudel.cycles/react'; -import { transpiler } from '@strudel.cycles/transpiler'; -import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio'; -import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; -/* import { writeText } from '@tauri-apps/api/clipboard'; -import { nanoid } from 'nanoid'; */ -import { createContext, useCallback, useEffect, useRef, useState } from 'react'; -import { - initUserCode, - setActivePattern, - setLatestCode, - settingsMap, - updateUserCode, - useSettings, -} from '../settings.mjs'; -import { Header } from './Header'; -import Loader from './Loader'; -import './Repl.css'; -import { Panel } from './panel/Panel'; -// import { prebake } from '@strudel/repl'; -import { useStore } from '@nanostores/react'; -import { prebake /* , resetSounds */ } from './prebake.mjs'; -import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; -import './Repl.css'; - -const { code: randomTune, name } = getRandomTune(); -export const ReplContext = createContext(null); - -const { latestCode } = settingsMap.get(); - -initAudioOnFirstClick(); - -const modulesLoading = loadModules(); -const presets = prebake(); - -let drawContext, clearCanvas; -if (typeof window !== 'undefined') { - drawContext = getDrawContext(); - clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); -} - -// const getTime = () => getAudioContext().currentTime; - -export function Repl2({ embedded = false }) { - //const isEmbedded = embedded || window.location !== window.parent.location; - const isEmbedded = false; - const { panelPosition, isZen } = useSettings(); - /* const replState = useStore($replstate); - const isDirty = useStore($repldirty); */ - const shouldDraw = true; - - const init = useCallback(({ shouldDraw }) => { - const drawTime = [-2, 2]; - const drawContext = shouldDraw ? getDrawContext() : null; - let onDraw; - if (shouldDraw) { - 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 }); - }); - }; - } - const editor = new StrudelMirror({ - defaultOutput: webaudioOutput, - getTime: () => getAudioContext().currentTime, - transpiler, - autodraw: false, - root: containerRef.current, - initialCode: '// LOADING', - pattern: silence, - drawTime, - onDraw, - prebake: async () => Promise.all([modulesLoading, presets]), - onUpdateState: (state) => { - setReplState({ ...state }); - }, - afterEval: ({ code }) => { - updateUserCode(code); - // setPending(false); - setLatestCode(code); - window.location.hash = '#' + code2hash(code); - }, - bgFill: false, - }); - // init settings - initCode().then((decoded) => { - let msg; - if (decoded) { - editor.setCode(decoded); - initUserCode(decoded); - msg = `I have loaded the code from the URL.`; - } else if (latestCode) { - editor.setCode(latestCode); - msg = `Your last session has been loaded!`; - } /* if(randomTune) */ else { - editor.setCode(randomTune); - msg = `A random code snippet named "${name}" has been loaded!`; - } - logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); - // setPending(false); - }); - - editorRef.current = editor; - }, []); - - const [replState, setReplState] = useState({}); - const { started, isDirty, error, activeCode } = replState; - const editorRef = useRef(); - const containerRef = useRef(); - const [client, setClient] = useState(false); - useEffect(() => { - setClient(true); - if (!editorRef.current) { - setTimeout(() => { - init({ shouldDraw }); - }); - } - return () => { - editorRef.current?.clear(); - delete editorRef.current; - }; - }, []); - - // this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly! - // this will be the case when the main repl is being replaced - const _settings = useStore(settingsMap, { keys: Object.keys(defaultSettings) }); - useEffect(() => { - let editorSettings = {}; - Object.keys(defaultSettings).forEach((key) => { - if (_settings.hasOwnProperty(key)) { - editorSettings[key] = _settings[key]; - } - }); - editorRef.current?.updateSettings(editorSettings); - }, [_settings]); - - // - // UI Actions - // - - const handleTogglePlay = async () => editorRef.current?.toggle(); - const handleUpdate = async (newCode, reset = false) => { - if (reset) { - clearCanvas(); - resetLoadedSounds(); - editorRef.current.repl.setCps(1); - await prebake(); // declare default samples - } - if (newCode || isDirty) { - editorRef.current.setCode(newCode); - editorRef.current.repl.evaluate(newCode); - } - logger('[repl] code updated!'); - }; - const handleShuffle = async () => { - // window.postMessage('strudel-shuffle'); - const { code, name } = getRandomTune(); - logger(`[repl] ✨ loading random tune "${name}"`); - setActivePattern(name); - clearCanvas(); - resetLoadedSounds(); - editorRef.current.repl.setCps(1); - await prebake(); // declare default samples - editorRef.current.setCode(code); - editorRef.current.repl.evaluate(code); - }; - - const handleShare = async () => shareCode(activeCode); - const pending = false; - //const error = undefined; - // const { started, activeCode } = replState; - - const context = { - // scheduler, - embedded, - started, - pending, - isDirty, - activeCode, - handleTogglePlay, - handleUpdate, - handleShuffle, - handleShare, - }; - - return ( - // bg-gradient-to-t from-blue-900 to-slate-900 - // bg-gradient-to-t from-green-900 to-slate-900 - -
- -
- {/* isEmbedded && !started && ( - - ) */} -
-
- {panelPosition === 'right' && !isEmbedded && } -
- {error && ( -
{error.message || 'Unknown Error :-/'}
- )} - {panelPosition === 'bottom' && !isEmbedded && } -
-
- ); -} From 014303b0d56b7d6bfd5692f18cd1a29d6437b8f5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 28 Dec 2023 21:04:29 +0100 Subject: [PATCH 75/97] remove dependencies to @strudel.cycles/react --- pnpm-lock.yaml | 3 - website/package.json | 1 - website/public/dependencygraph.svg | 6 - website/src/components/HeadCommon.astro | 36 +- website/src/components/PitchSlider.jsx | 4 +- website/src/cx.mjs | 10 + website/src/pages/index.astro | 4 +- website/src/repl/Header.jsx | 3 +- website/src/repl/Loader.jsx | 3 +- website/src/repl/Repl.jsx | 2 +- website/src/repl/panel/ConsoleTab.jsx | 3 +- website/src/repl/panel/Forms.jsx | 3 +- website/src/repl/panel/Panel.jsx | 3 +- website/src/repl/panel/SettingsTab.jsx | 3 +- website/src/repl/panel/SoundsTab.jsx | 3 +- website/src/repl/panel/WelcomeTab.jsx | 4 +- website/src/repl/themes.mjs | 482 ------------------------ website/src/useEvent.mjs | 12 + website/src/useFrame.mjs | 43 +++ 19 files changed, 81 insertions(+), 547 deletions(-) create mode 100644 website/src/cx.mjs delete mode 100644 website/src/repl/themes.mjs create mode 100644 website/src/useEvent.mjs create mode 100644 website/src/useFrame.mjs diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5892af73..a9c5f796 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -669,9 +669,6 @@ importers: '@strudel.cycles/osc': specifier: workspace:* version: link:../packages/osc - '@strudel.cycles/react': - specifier: workspace:* - version: link:../packages/react '@strudel.cycles/serial': specifier: workspace:* version: link:../packages/serial diff --git a/website/package.json b/website/package.json index b2c5f39a..7557bdc6 100644 --- a/website/package.json +++ b/website/package.json @@ -27,7 +27,6 @@ "@strudel.cycles/midi": "workspace:*", "@strudel.cycles/mini": "workspace:*", "@strudel.cycles/osc": "workspace:*", - "@strudel.cycles/react": "workspace:*", "@strudel.cycles/serial": "workspace:*", "@strudel.cycles/soundfonts": "workspace:*", "@strudel.cycles/tonal": "workspace:*", diff --git a/website/public/dependencygraph.svg b/website/public/dependencygraph.svg index 62fadac7..27f48833 100644 --- a/website/public/dependencygraph.svg +++ b/website/public/dependencygraph.svg @@ -96,12 +96,6 @@ - - @strudel.cycles/serial@0.6.0 diff --git a/website/src/components/HeadCommon.astro b/website/src/components/HeadCommon.astro index ab18500f..cb3e605a 100644 --- a/website/src/components/HeadCommon.astro +++ b/website/src/components/HeadCommon.astro @@ -46,41 +46,9 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL {pwaInfo && } - - diff --git a/packages/react/examples/nano-repl/package.json b/packages/react/examples/nano-repl/package.json deleted file mode 100644 index c837e4b7..00000000 --- a/packages/react/examples/nano-repl/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@strudel.cycles/nano-repl", - "private": true, - "version": "0.6.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "@strudel.cycles/core": "workspace:*", - "@strudel.cycles/osc": "workspace:*", - "@strudel.cycles/mini": "workspace:*", - "@strudel.cycles/transpiler": "workspace:*", - "@strudel.cycles/soundfonts": "workspace:*", - "@strudel.cycles/webaudio": "workspace:*", - "@strudel.cycles/tonal": "workspace:*", - "@strudel.cycles/react": "workspace:*" - }, - "devDependencies": { - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.1", - "@vitejs/plugin-react": "^4.0.0", - "autoprefixer": "^10.4.14", - "postcss": "^8.4.23", - "tailwindcss": "^3.3.2", - "vite": "^4.3.3" - } -} diff --git a/packages/react/examples/nano-repl/src/App.jsx b/packages/react/examples/nano-repl/src/App.jsx deleted file mode 100644 index 086923ac..00000000 --- a/packages/react/examples/nano-repl/src/App.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import { controls, evalScope } from '@strudel.cycles/core'; -import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react'; -import { - getAudioContext, - initAudioOnFirstClick, - panic, - webaudioOutput, - registerSynthSounds, -} from '@strudel.cycles/webaudio'; -import { registerSoundfonts } from '@strudel.cycles/soundfonts'; -import { useCallback, useState } from 'react'; -import './style.css'; -// import { prebake } from '../../../../../repl/src/prebake.mjs'; - -initAudioOnFirstClick(); - -async function init() { - // TODO: only import stuff when play is pressed? - const loadModules = evalScope( - controls, - import('@strudel.cycles/core'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/xen'), - import('@strudel.cycles/webaudio'), - import('@strudel.cycles/osc'), - ); - - await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]); -} -init(); - -const defaultTune = `samples({ - bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'], - sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'], - hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'], -}, 'github:tidalcycles/Dirt-Samples/master/'); -stack( - s("bd,[~ ],hh*8") // drums - .speed(perlin.range(.7,.9)) // random sample speed variation - //.hush() - ,"" // bassline - .off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps - .add(perlin.range(0,.5)) // random pitch variation - .superimpose(add(.05)) // add second, slightly detuned voice - .note() // wrap in "note" - .decay(.15).sustain(0) // make each note of equal length - .s('sawtooth') // waveform - .gain(.4) // turn down - .cutoff(sine.slow(7).range(300,5000)) // automate cutoff - //.hush() - ,">".voicings('lefthand') // chords - .superimpose(x=>x.add(.04)) // add second, slightly detuned voice - .add(perlin.range(0,.5)) // random pitch variation - .note() // wrap in "n" - .s('square') // waveform - .gain(.16) // turn down - .cutoff(500) // fixed cutoff - .attack(1) // slowly fade in - //.hush() - ,"a4 c5 ".struct("x(5,8)") - .superimpose(x=>x.add(.04)) // add second, slightly detuned voice - .add(perlin.range(0,.5)) // random pitch variation - .note() // wrap in "note" - .decay(.1).sustain(0) // make notes short - .s('triangle') // waveform - .degradeBy(perlin.range(0,.5)) // randomly controlled random removal :) - .echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes - //.hush() -) -.fast(2/3)`; - -// await prebake(); - -const ctx = getAudioContext(); -const getTime = () => ctx.currentTime; -function App() { - const [code, setCode] = useState(defaultTune); - const [view, setView] = useState(); - // const [code, setCode] = useState(`"c3".note().slow(2)`); - const { scheduler, evaluate, schedulerError, evalError, isDirty, activeCode, pattern, started } = useStrudel({ - code, - defaultOutput: webaudioOutput, - getTime, - afterEval: ({ meta }) => setMiniLocations(meta.miniLocations), - }); - - const { setMiniLocations } = useHighlighting({ - view, - pattern, - active: started && !activeCode?.includes('strudel disable-highlighting'), - getTime: () => scheduler.now(), - }); - - const error = evalError || schedulerError; - useKeydown( - useCallback( - async (e) => { - if (e.ctrlKey || e.altKey) { - if (e.code === 'Enter') { - e.preventDefault(); - flash(view); - await evaluate(code); - if (e.shiftKey) { - panic(); - scheduler.stop(); - scheduler.start(); - } - if (!scheduler.started) { - scheduler.start(); - } - } else if (e.code === 'Period') { - scheduler.stop(); - panic(); - e.preventDefault(); - } - } - }, - [scheduler, evaluate, view, code], - ), - ); - return ( -
- - -
- ); -} - -export default App; diff --git a/packages/react/examples/nano-repl/src/main.jsx b/packages/react/examples/nano-repl/src/main.jsx deleted file mode 100644 index 1b794581..00000000 --- a/packages/react/examples/nano-repl/src/main.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; - -ReactDOM.createRoot(document.getElementById('root')).render( - - - , -); diff --git a/packages/react/examples/nano-repl/src/style.css b/packages/react/examples/nano-repl/src/style.css deleted file mode 100644 index e01c06ef..00000000 --- a/packages/react/examples/nano-repl/src/style.css +++ /dev/null @@ -1,19 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --background: #222; - --lineBackground: #22222250; - --foreground: #fff; - --caret: #ffcc00; - --selection: rgba(128, 203, 196, 0.5); - --selectionMatch: #036dd626; - --lineHighlight: #00000050; - --gutterBackground: transparent; - --gutterForeground: #8a919966; -} - -body { - background: #123; -} diff --git a/packages/react/examples/nano-repl/tailwind.config.cjs b/packages/react/examples/nano-repl/tailwind.config.cjs deleted file mode 100644 index b2a25c69..00000000 --- a/packages/react/examples/nano-repl/tailwind.config.cjs +++ /dev/null @@ -1,28 +0,0 @@ -/* -tailwind.config.js - -Copyright (C) 2022 Strudel contributors - see -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 . -*/ - -module.exports = { - // TODO: find out if leaving out tutorial path works now - content: ['./src/**/*.{js,jsx,ts,tsx}', '../../src/**/*.{html,js,jsx,md,mdx,ts,tsx}'], - theme: { - extend: { - colors: { - // codemirror-theme settings - background: 'var(--background)', - lineBackground: 'var(--lineBackground)', - foreground: 'var(--foreground)', - caret: 'var(--caret)', - selection: 'var(--selection)', - selectionMatch: 'var(--selectionMatch)', - gutterBackground: 'var(--gutterBackground)', - gutterForeground: 'var(--gutterForeground)', - gutterBorder: 'var(--gutterBorder)', - lineHighlight: 'var(--lineHighlight)', - }, - }, - }, - plugins: [], -}; diff --git a/packages/react/examples/nano-repl/vite.config.js b/packages/react/examples/nano-repl/vite.config.js deleted file mode 100644 index 627a3196..00000000 --- a/packages/react/examples/nano-repl/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}); diff --git a/packages/react/index.html b/packages/react/index.html deleted file mode 100644 index 83d2bd57..00000000 --- a/packages/react/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Strudel React Components - - -
- - - diff --git a/packages/react/package.json b/packages/react/package.json deleted file mode 100644 index df017d91..00000000 --- a/packages/react/package.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "@strudel.cycles/react", - "version": "0.9.0", - "description": "React components for strudel", - "main": "src/index.js", - "publishConfig": { - "main": "dist/index.js", - "module": "dist/index.mjs" - }, - "scripts": { - "dev": "vite", - "build": "vite build", - "watch": "vite build --watch", - "preview": "vite preview", - "prepublishOnly": "npm run build" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/tidalcycles/strudel.git" - }, - "keywords": [ - "tidalcycles", - "strudel", - "pattern", - "livecoding", - "algorave" - ], - "author": "Felix Roos ", - "license": "AGPL-3.0-or-later", - "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" - }, - "homepage": "https://github.com/tidalcycles/strudel#readme", - "dependencies": { - "@codemirror/autocomplete": "^6.6.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/lang-javascript": "^6.1.7", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.2.0", - "@codemirror/view": "^6.10.0", - "@lezer/highlight": "^1.1.4", - "@replit/codemirror-emacs": "^6.0.1", - "@replit/codemirror-vim": "^6.0.14", - "@replit/codemirror-vscode-keymap": "^6.0.2", - "@strudel.cycles/core": "workspace:*", - "@strudel.cycles/transpiler": "workspace:*", - "@strudel.cycles/webaudio": "workspace:*", - "@strudel/codemirror": "workspace:*", - "@uiw/codemirror-themes": "^4.19.16", - "@uiw/react-codemirror": "^4.19.16", - "react-hook-inview": "^4.5.0" - }, - "peerDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "devDependencies": { - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.1", - "@vitejs/plugin-react": "^4.0.0", - "autoprefixer": "^10.4.14", - "postcss": "^8.4.23", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "tailwindcss": "^3.3.2", - "vite": "^4.3.3" - } -} diff --git a/packages/react/postcss.config.js b/packages/react/postcss.config.js deleted file mode 100644 index b77b9fe4..00000000 --- a/packages/react/postcss.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/* -postcss.config.js - -Copyright (C) 2022 Strudel contributors - see -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 . -*/ - -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/react/src/App.jsx b/packages/react/src/App.jsx deleted file mode 100644 index ba7b1da4..00000000 --- a/packages/react/src/App.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { MiniRepl } from './components/MiniRepl'; -import 'tailwindcss/tailwind.css'; -import { controls, evalScope } from '@strudel.cycles/core'; - -evalScope( - controls, - import('@strudel.cycles/core'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/webaudio'), -); - -function App() { - return ( -
- -
- ); -} - -export default App; diff --git a/packages/react/src/components/Autocomplete.jsx b/packages/react/src/components/Autocomplete.jsx deleted file mode 100644 index 9a0b9de4..00000000 --- a/packages/react/src/components/Autocomplete.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import { createRoot } from 'react-dom/client'; -import jsdoc from '../../../../doc.json'; - -const getDocLabel = (doc) => doc.name || doc.longname; -const getDocSynonyms = (doc) => [getDocLabel(doc), ...(doc.synonyms || [])]; -const getInnerText = (html) => { - var div = document.createElement('div'); - div.innerHTML = html; - return div.textContent || div.innerText || ''; -}; - -export function Autocomplete({ doc, label = getDocLabel(doc) }) { - const synonyms = getDocSynonyms(doc).filter((a) => a !== label); - return ( -
-

{label}

{' '} - {!!synonyms.length && ( - - Synonyms: {synonyms.join(', ')} - - )} -
-
    - {doc.params?.map(({ name, type, description }, i) => ( -
  • - {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)} : ''} -
  • - ))} -
-
- {doc.examples?.map((example, i) => ( -
-
 {
-                navigator.clipboard.writeText(example);
-                e.stopPropagation();
-              }}
-            >
-              {example}
-            
-
- ))} -
-
- ); -} - -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 - .reduce( - (acc, doc) /*: Completion */ => - acc.concat( - [getDocLabel(doc), ...(doc.synonyms || [])].map((label) => ({ - label, - // 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(); - 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' }, - ], */ - }; -}; diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx deleted file mode 100644 index 4852b287..00000000 --- a/packages/react/src/components/CodeMirror6.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import { autocompletion } from '@codemirror/autocomplete'; -import { Prec } from '@codemirror/state'; -import { javascript, javascriptLanguage } from '@codemirror/lang-javascript'; -import { ViewPlugin, EditorView, keymap } from '@codemirror/view'; -import { emacs } from '@replit/codemirror-emacs'; -import { vim } from '@replit/codemirror-vim'; -import { vscodeKeymap } from '@replit/codemirror-vscode-keymap'; -import _CodeMirror from '@uiw/react-codemirror'; -import React, { useCallback, useMemo } from 'react'; -import strudelTheme from '../themes/strudel-theme'; -import { strudelAutocomplete } from './Autocomplete'; -import { strudelTooltip } from './Tooltip'; -import { - highlightExtension, - flashField, - flash, - highlightMiniLocations, - updateMiniLocations, -} from '@strudel/codemirror'; -import './style.css'; -import { sliderPlugin } from '@strudel/codemirror/slider.mjs'; - -export { flash, highlightMiniLocations, updateMiniLocations }; - -const staticExtensions = [javascript(), flashField, highlightExtension, sliderPlugin]; - -export default function CodeMirror({ - value, - onChange, - onViewChanged, - onSelectionChange, - onDocChange, - theme, - keybindings, - isLineNumbersDisplayed, - isActiveLineHighlighted, - isAutoCompletionEnabled, - isTooltipEnabled, - isLineWrappingEnabled, - fontSize = 18, - fontFamily = 'monospace', -}) { - const handleOnChange = useCallback( - (value) => { - onChange?.(value); - }, - [onChange], - ); - - const handleOnCreateEditor = useCallback( - (view) => { - onViewChanged?.(view); - }, - [onViewChanged], - ); - - const handleOnUpdate = useCallback( - (viewUpdate) => { - if (viewUpdate.docChanged && onDocChange) { - onDocChange?.(viewUpdate); - } - if (viewUpdate.selectionSet && onSelectionChange) { - onSelectionChange?.(viewUpdate.state.selection); - } - }, - [onSelectionChange], - ); - - const vscodePlugin = ViewPlugin.fromClass( - class { - constructor(view) {} - }, - { - provide: (plugin) => { - return Prec.highest(keymap.of([...vscodeKeymap])); - }, - }, - ); - - const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []); - - const extensions = useMemo(() => { - let _extensions = [...staticExtensions]; - let bindings = { - vim, - emacs, - vscode: vscodeExtension, - }; - - if (bindings[keybindings]) { - _extensions.push(bindings[keybindings]()); - } - - if (isAutoCompletionEnabled) { - _extensions.push(javascriptLanguage.data.of({ autocomplete: strudelAutocomplete })); - } else { - _extensions.push(autocompletion({ override: [] })); - } - - if (isTooltipEnabled) { - _extensions.push(strudelTooltip); - } - - _extensions.push([keymap.of({})]); - - if (isLineWrappingEnabled) { - _extensions.push(EditorView.lineWrapping); - } - - return _extensions; - }, [keybindings, isAutoCompletionEnabled, isTooltipEnabled, isLineWrappingEnabled]); - - const basicSetup = useMemo( - () => ({ - lineNumbers: isLineNumbersDisplayed, - highlightActiveLine: isActiveLineHighlighted, - }), - [isLineNumbersDisplayed, isActiveLineHighlighted], - ); - - return ( -
- <_CodeMirror - value={value} - theme={theme || strudelTheme} - onChange={handleOnChange} - onCreateEditor={handleOnCreateEditor} - onUpdate={handleOnUpdate} - extensions={extensions} - basicSetup={basicSetup} - /> -
- ); -} diff --git a/packages/react/src/components/Icon.tsx b/packages/react/src/components/Icon.tsx deleted file mode 100644 index 24ec8786..00000000 --- a/packages/react/src/components/Icon.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; - -export function Icon({ type }) { - return ( - - { - { - refresh: ( - - ), - play: ( - - ), - pause: ( - - ), - stop: ( - - ), - }[type] - } - - ); -} diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx deleted file mode 100644 index c7f70907..00000000 --- a/packages/react/src/components/MiniRepl.jsx +++ /dev/null @@ -1,199 +0,0 @@ -import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; -import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react'; -import { useInView } from 'react-hook-inview'; -import 'tailwindcss/tailwind.css'; -import cx from '../cx'; -import useHighlighting from '../hooks/useHighlighting.mjs'; -import useStrudel from '../hooks/useStrudel.mjs'; -import CodeMirror6, { flash } from './CodeMirror6'; -import { Icon } from './Icon'; -import './style.css'; -import { logger } from '@strudel.cycles/core'; -import useEvent from '../hooks/useEvent.mjs'; -import useKeydown from '../hooks/useKeydown.mjs'; - -const getTime = () => getAudioContext().currentTime; - -export function MiniRepl({ - tune, - hideOutsideView = false, - enableKeyboard, - onTrigger, - drawTime, - punchcard, - punchcardLabels, - onPaint, - canvasHeight = 200, - fontSize = 18, - fontFamily, - hideHeader = false, - theme, - keybindings, - isLineNumbersDisplayed, - isActiveLineHighlighted, -}) { - drawTime = drawTime || (punchcard ? [0, 4] : undefined); - const evalOnMount = !!drawTime; - const drawContext = useCallback( - punchcard ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null, - [punchcard], - ); - const { - code, - setCode, - evaluate, - activateCode, - error, - isDirty, - activeCode, - pattern, - started, - scheduler, - togglePlay, - stop, - canvasId, - id: replId, - } = useStrudel({ - initialCode: tune, - defaultOutput: webaudioOutput, - editPattern: (pat, id) => { - //pat = pat.withContext((ctx) => ({ ...ctx, id })); - if (onTrigger) { - pat = pat.onTrigger(onTrigger, false); - } - if (onPaint) { - pat = pat.onPaint(onPaint); - } else if (punchcard) { - pat = pat.punchcard({ labels: punchcardLabels }); - } - return pat; - }, - getTime, - evalOnMount, - drawContext, - drawTime, - afterEval: ({ meta }) => setMiniLocations(meta.miniLocations), - }); - - const [view, setView] = useState(); - const [ref, isVisible] = useInView({ - threshold: 0.01, - }); - const wasVisible = useRef(); - const show = useMemo(() => { - if (isVisible || !hideOutsideView) { - wasVisible.current = true; - } - return isVisible || wasVisible.current; - }, [isVisible, hideOutsideView]); - const { setMiniLocations } = useHighlighting({ - view, - pattern, - active: started && !activeCode?.includes('strudel disable-highlighting'), - getTime: () => scheduler.now(), - }); - - // keyboard shortcuts - useKeydown( - useCallback( - async (e) => { - if (view?.hasFocus) { - if (e.ctrlKey || e.altKey) { - if (e.code === 'Enter') { - e.preventDefault(); - flash(view); - await activateCode(); - } else if (e.key === '.' || e.code === 'Period') { - stop(); - e.preventDefault(); - } - } - } - }, - [activateCode, stop, view], - ), - ); - - const [log, setLog] = useState([]); - useLogger( - useCallback((e) => { - const { data } = e.detail; - const logId = data?.hap?.context?.id; - // const logId = data?.pattern?.meta?.id; - if (logId === replId) { - setLog((l) => { - return l.concat([e.detail]).slice(-8); - }); - } - }, []), - ); - - return ( -
- {!hideHeader && ( -
-
- - -
-
- )} -
- {show && ( - - )} - {error &&
{error.message}
} -
- {punchcard && ( - { - if (el && el.width !== el.clientWidth) { - el.width = el.clientWidth; - } - }} - > - )} - {!!log.length && ( -
- {log.map(({ message }, i) => ( -
{message}
- ))} -
- )} -
- ); -} - -// TODO: dedupe -function useLogger(onTrigger) { - useEvent(logger.key, onTrigger); -} diff --git a/packages/react/src/components/Tooltip.jsx b/packages/react/src/components/Tooltip.jsx deleted file mode 100644 index 43a53476..00000000 --- a/packages/react/src/components/Tooltip.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { createRoot } from 'react-dom/client'; -import { hoverTooltip } from '@codemirror/view'; -import jsdoc from '../../../../doc.json'; -import { Autocomplete } from './Autocomplete'; - -const getDocLabel = (doc) => doc.name || doc.longname; - -let ctrlDown = false; - -// Record Control key event to trigger or block the tooltip depending on the state -window.addEventListener( - 'keyup', - function (e) { - if (e.key == 'Control') { - ctrlDown = false; - } - }, - true, -); - -window.addEventListener( - 'keydown', - function (e) { - if (e.key == 'Control') { - ctrlDown = true; - } - }, - true, -); - -export const strudelTooltip = hoverTooltip( - (view, pos, side) => { - // Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips - if (!ctrlDown) { - return null; - } - let { from, to, text } = view.state.doc.lineAt(pos); - let start = pos, - end = pos; - while (start > from && /\w/.test(text[start - from - 1])) { - start--; - } - while (end < to && /\w/.test(text[end - from])) { - end++; - } - if ((start == pos && side < 0) || (end == pos && side > 0)) { - return null; - } - let word = text.slice(start - from, end - from); - // Get entry from Strudel documentation - let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0]; - if (!entry) { - // Try for synonyms - entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0]; - if (!entry) { - return null; - } - } - - return { - pos: start, - end, - above: false, - arrow: true, - create(view) { - let dom = document.createElement('div'); - dom.className = 'strudel-tooltip'; - createRoot(dom).render(); - return { dom }; - }, - }; - }, - { hoverTime: 10 }, -); diff --git a/packages/react/src/components/style.css b/packages/react/src/components/style.css deleted file mode 100644 index 6336bba3..00000000 --- a/packages/react/src/components/style.css +++ /dev/null @@ -1,34 +0,0 @@ -:root { - --background: #222; - --lineBackground: #22222299; - --foreground: #fff; - --caret: #ffcc00; - --selection: rgba(128, 203, 196, 0.5); - --selectionMatch: #036dd626; - --lineHighlight: #00000050; - --gutterBackground: transparent; - --gutterForeground: #8a919966; -} - -.cm-editor { - background-color: transparent !important; - height: 100%; - z-index: 11; -} - -.cm-theme { - width: 100%; - height: 100%; -} - -.cm-theme-light { - width: 100%; -} - -footer { - z-index: 0 !important; -} - -.strudel-tooltip { - padding: 5px; -} diff --git a/packages/react/src/cx.js b/packages/react/src/cx.js deleted file mode 100644 index 4e4aea08..00000000 --- a/packages/react/src/cx.js +++ /dev/null @@ -1,10 +0,0 @@ -/* -cx.js - -Copyright (C) 2022 Strudel contributors - see -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 . -*/ - -export default function cx(...classes) { - // : Array - return classes.filter(Boolean).join(' '); -} diff --git a/packages/react/src/hooks/useEvent.mjs b/packages/react/src/hooks/useEvent.mjs deleted file mode 100644 index f349c6e7..00000000 --- a/packages/react/src/hooks/useEvent.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import { useEffect } from 'react'; - -function useEvent(name, onTrigger, useCapture = false) { - useEffect(() => { - document.addEventListener(name, onTrigger, useCapture); - return () => { - document.removeEventListener(name, onTrigger, useCapture); - }; - }, [onTrigger]); -} - -export default useEvent; diff --git a/packages/react/src/hooks/useFrame.mjs b/packages/react/src/hooks/useFrame.mjs deleted file mode 100644 index 130609d0..00000000 --- a/packages/react/src/hooks/useFrame.mjs +++ /dev/null @@ -1,43 +0,0 @@ -import { useEffect, useRef } from 'react'; - -function useFrame(callback, autostart = false) { - const requestRef = useRef(); - const previousTimeRef = useRef(); - - const animate = (time) => { - if (previousTimeRef.current !== undefined) { - const deltaTime = time - previousTimeRef.current; - callback(time, deltaTime); - } - previousTimeRef.current = time; - requestRef.current = requestAnimationFrame(animate); - }; - - const start = () => { - requestRef.current = requestAnimationFrame(animate); - }; - const stop = () => { - requestRef.current && cancelAnimationFrame(requestRef.current); - delete requestRef.current; - }; - useEffect(() => { - if (requestRef.current) { - stop(); - start(); - } - }, [callback]); - - useEffect(() => { - if (autostart) { - start(); - } - return stop; - }, []); // Make sure the effect only runs once - - return { - start, - stop, - }; -} - -export default useFrame; diff --git a/packages/react/src/hooks/useHighlighting.mjs b/packages/react/src/hooks/useHighlighting.mjs deleted file mode 100644 index cb2ca114..00000000 --- a/packages/react/src/hooks/useHighlighting.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { highlightMiniLocations, updateMiniLocations } from '../components/CodeMirror6'; -const round = (x) => Math.round(x * 1000) / 1000; - -function useHighlighting({ view, pattern, active, getTime }) { - const highlights = useRef([]); - const lastEnd = useRef(0); - - const [miniLocations, setMiniLocations] = useState([]); - useEffect(() => { - if (view) { - updateMiniLocations(view, miniLocations); - } - }, [view, miniLocations]); - - useEffect(() => { - if (view) { - if (pattern && active) { - lastEnd.current = 0; - let frame = requestAnimationFrame(function updateHighlights() { - try { - const audioTime = getTime(); - // force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away - // see https://github.com/tidalcycles/strudel/issues/108 - const begin = Math.max(lastEnd.current ?? audioTime, audioTime - 1 / 10, -0.01); // negative time seems buggy - const span = [round(begin), round(audioTime + 1 / 60)]; - lastEnd.current = span[1]; - highlights.current = highlights.current.filter((hap) => hap.endClipped > audioTime); // keep only highlights that are still active - const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset()); - highlights.current = highlights.current.concat(haps); // add potential new onsets - highlightMiniLocations(view, begin, highlights.current); - } catch (err) { - highlightMiniLocations(view, 0, []); - } - frame = requestAnimationFrame(updateHighlights); - }); - return () => { - cancelAnimationFrame(frame); - }; - } else { - highlights.current = []; - highlightMiniLocations(view, 0, highlights.current); - } - } - }, [pattern, active, view]); - - return { setMiniLocations }; -} - -export default useHighlighting; diff --git a/packages/react/src/hooks/useKeydown.mjs b/packages/react/src/hooks/useKeydown.mjs deleted file mode 100644 index 88d1cb94..00000000 --- a/packages/react/src/hooks/useKeydown.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { useLayoutEffect } from 'react'; - -// set active pattern on ctrl+enter -const useKeydown = (callback) => - useLayoutEffect(() => { - window.addEventListener('keydown', callback, true); - return () => window.removeEventListener('keydown', callback, true); - }, [callback]); - -export default useKeydown; diff --git a/packages/react/src/hooks/usePatternFrame.mjs b/packages/react/src/hooks/usePatternFrame.mjs deleted file mode 100644 index 725fe0a3..00000000 --- a/packages/react/src/hooks/usePatternFrame.mjs +++ /dev/null @@ -1,48 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; -import 'tailwindcss/tailwind.css'; -import useFrame from '../hooks/useFrame.mjs'; - -function usePatternFrame({ pattern, started, getTime, onDraw, drawTime = [-2, 2] }) { - let [lookbehind, lookahead] = drawTime; - lookbehind = Math.abs(lookbehind); - let visibleHaps = useRef([]); - let lastFrame = useRef(null); - useEffect(() => { - if (pattern && started) { - const t = getTime(); - const futureHaps = pattern.queryArc(Math.max(t, 0), t + lookahead + 0.1); // +0.1 = workaround for weird holes in query.. - visibleHaps.current = visibleHaps.current.filter((h) => h.whole.begin < t); - visibleHaps.current = visibleHaps.current.concat(futureHaps); - } - }, [pattern, started]); - const { start: startFrame, stop: stopFrame } = useFrame( - useCallback(() => { - const phase = getTime() + lookahead; - if (lastFrame.current === null) { - lastFrame.current = phase; - return; - } - const haps = pattern.queryArc(Math.max(lastFrame.current, phase - 1 / 10), phase); - lastFrame.current = phase; - visibleHaps.current = (visibleHaps.current || []) - .filter((h) => h.endClipped >= phase - lookbehind - lookahead) // in frame - .concat(haps.filter((h) => h.hasOnset())); - onDraw(pattern, phase - lookahead, visibleHaps.current, drawTime); - }, [pattern, onDraw]), - ); - useEffect(() => { - if (started) { - startFrame(); - } else { - visibleHaps.current = []; - stopFrame(); - } - }, [started]); - return { - clear: () => { - visibleHaps.current = []; - }, - }; -} - -export default usePatternFrame; diff --git a/packages/react/src/hooks/usePostMessage.mjs b/packages/react/src/hooks/usePostMessage.mjs deleted file mode 100644 index 9d3bc8e7..00000000 --- a/packages/react/src/hooks/usePostMessage.mjs +++ /dev/null @@ -1,17 +0,0 @@ -/* -usePostMessage.mjs - -Copyright (C) 2022 Strudel contributors - see -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 . -*/ - -import { useEffect, useCallback } from 'react'; - -function usePostMessage(listener) { - useEffect(() => { - window.addEventListener('message', listener); - return () => window.removeEventListener('message', listener); - }, [listener]); - return useCallback((data) => window.postMessage(data, '*'), []); -} - -export default usePostMessage; diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs deleted file mode 100644 index dbf2269e..00000000 --- a/packages/react/src/hooks/useStrudel.mjs +++ /dev/null @@ -1,171 +0,0 @@ -import { useRef, useCallback, useEffect, useMemo, useState } from 'react'; -import { repl } from '@strudel.cycles/core'; -import { transpiler } from '@strudel.cycles/transpiler'; -import usePatternFrame from './usePatternFrame'; -import usePostMessage from './usePostMessage.mjs'; - -function useStrudel({ - defaultOutput, - interval, - getTime, - evalOnMount = false, - initialCode = '', - beforeEval, - afterEval, - editPattern, - onEvalError, - onToggle, - canvasId, - drawContext, - drawTime = [-2, 2], - paintOptions = {}, -}) { - const id = useMemo(() => s4(), []); - canvasId = canvasId || `canvas-${id}`; - // scheduler - const [schedulerError, setSchedulerError] = useState(); - const [evalError, setEvalError] = useState(); - const [code, setCode] = useState(initialCode); - const [activeCode, setActiveCode] = useState(); - const [pattern, setPattern] = useState(); - const [started, setStarted] = useState(false); - const isDirty = code !== activeCode; - //const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]); - const shouldPaint = useCallback((pat) => !!pat?.context?.onPaint, []); - - // TODO: make sure this hook reruns when scheduler.started changes - const { scheduler, evaluate, start, stop, pause, setCps } = useMemo( - () => - repl({ - interval, - defaultOutput, - onSchedulerError: setSchedulerError, - onEvalError: (err) => { - setEvalError(err); - onEvalError?.(err); - }, - getTime, - drawContext, - transpiler, - editPattern, - beforeEval: async ({ code }) => { - setCode(code); - await beforeEval?.(); - }, - afterEval: (res) => { - const { pattern: _pattern, code } = res; - setActiveCode(code); - setPattern(_pattern); - setEvalError(); - setSchedulerError(); - afterEval?.(res); - }, - onToggle: (v) => { - setStarted(v); - onToggle?.(v); - }, - }), - [defaultOutput, interval, getTime], - ); - const broadcast = usePostMessage(({ data: { from, type } }) => { - if (type === 'start' && from !== id) { - // console.log('message', from, type); - stop(); - } - }); - const activateCode = useCallback( - async (newCode, autostart = true) => { - if (newCode) { - setCode(code); - } - const res = await evaluate(newCode || code, autostart); - broadcast({ type: 'start', from: id }); - return res; - }, - [evaluate, code], - ); - - const onDraw = useCallback( - (pattern, time, haps, drawTime) => { - const { onPaint } = pattern.context || {}; - const ctx = typeof drawContext === 'function' ? drawContext(canvasId) : drawContext; - onPaint?.(ctx, time, haps, drawTime, paintOptions); - }, - [drawContext, canvasId, paintOptions], - ); - - const drawFirstFrame = useCallback( - (pat) => { - if (shouldPaint(pat)) { - const [_, lookahead] = drawTime; - const haps = pat.queryArc(0, lookahead); - // draw at -0.001 to avoid activating haps at 0 - onDraw(pat, -0.001, haps, drawTime); - } - }, - [drawTime, onDraw, shouldPaint], - ); - - const inited = useRef(); - useEffect(() => { - if (!inited.current && evalOnMount && code) { - inited.current = true; - evaluate(code, false).then((pat) => drawFirstFrame(pat)); - } - }, [evalOnMount, code, evaluate, drawFirstFrame]); - - // this will stop the scheduler when hot reloading in development - useEffect(() => { - return () => { - scheduler.stop(); - }; - }, [scheduler]); - - const togglePlay = async () => { - if (started) { - scheduler.stop(); - drawFirstFrame(pattern); - } else { - await activateCode(); - } - }; - const error = schedulerError || evalError; - - usePatternFrame({ - pattern, - started: shouldPaint(pattern) && started, - getTime: () => scheduler.now(), - drawTime, - onDraw, - }); - - return { - id, - canvasId, - code, - setCode, - error, - schedulerError, - scheduler, - evalError, - evaluate, - activateCode, - activeCode, - isDirty, - pattern, - started, - start, - stop, - pause, - togglePlay, - setCps, - }; -} - -export default useStrudel; - -function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); -} diff --git a/packages/react/src/hooks/useWidgets.mjs b/packages/react/src/hooks/useWidgets.mjs deleted file mode 100644 index e7ca136a..00000000 --- a/packages/react/src/hooks/useWidgets.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { useEffect, useState } from 'react'; -import { updateWidgets } from '@strudel/codemirror'; - -// i know this is ugly.. in the future, repl needs to run without react -export function useWidgets(view) { - const [widgets, setWidgets] = useState([]); - useEffect(() => { - if (view) { - updateWidgets(view, widgets); - } - }, [view, widgets]); - return { widgets, setWidgets }; -} diff --git a/packages/react/src/index.js b/packages/react/src/index.js deleted file mode 100644 index 818d9c95..00000000 --- a/packages/react/src/index.js +++ /dev/null @@ -1,12 +0,0 @@ -// import 'tailwindcss/tailwind.css'; - -export { default as CodeMirror, flash, updateMiniLocations, highlightMiniLocations } from './components/CodeMirror6'; // !SSR -export * from './components/MiniRepl'; // !SSR -export { default as useHighlighting } from './hooks/useHighlighting'; // !SSR -export { default as useStrudel } from './hooks/useStrudel'; // !SSR -export { default as usePostMessage } from './hooks/usePostMessage'; -export { default as useKeydown } from './hooks/useKeydown'; -export { default as useEvent } from './hooks/useEvent'; -export { default as strudelTheme } from './themes/strudel-theme'; -export { default as teletext } from './themes/teletext'; -export { default as cx } from './cx'; diff --git a/packages/react/src/main.jsx b/packages/react/src/main.jsx deleted file mode 100644 index fe0fabf3..00000000 --- a/packages/react/src/main.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import App from './App'; -import { createRoot } from 'react-dom/client'; - -createRoot(document.getElementById('root')).render( - - - , -); diff --git a/packages/react/src/themes/algoboy.js b/packages/react/src/themes/algoboy.js deleted file mode 100644 index 399370e1..00000000 --- a/packages/react/src/themes/algoboy.js +++ /dev/null @@ -1,41 +0,0 @@ -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' }, - ], -}); diff --git a/packages/react/src/themes/blackscreen.js b/packages/react/src/themes/blackscreen.js deleted file mode 100644 index 135285a3..00000000 --- a/packages/react/src/themes/blackscreen.js +++ /dev/null @@ -1,38 +0,0 @@ -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' }, - ], -}); diff --git a/packages/react/src/themes/bluescreen.js b/packages/react/src/themes/bluescreen.js deleted file mode 100644 index aa6489d6..00000000 --- a/packages/react/src/themes/bluescreen.js +++ /dev/null @@ -1,41 +0,0 @@ -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' }, - ], -}); diff --git a/packages/react/src/themes/strudel-theme.js b/packages/react/src/themes/strudel-theme.js deleted file mode 100644 index 4ae31060..00000000 --- a/packages/react/src/themes/strudel-theme.js +++ /dev/null @@ -1,45 +0,0 @@ -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' }, - ], -}); diff --git a/packages/react/src/themes/teletext.js b/packages/react/src/themes/teletext.js deleted file mode 100644 index 5fd9a557..00000000 --- a/packages/react/src/themes/teletext.js +++ /dev/null @@ -1,50 +0,0 @@ -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 }, - ], -}); diff --git a/packages/react/src/themes/terminal.js b/packages/react/src/themes/terminal.js deleted file mode 100644 index 1374bb86..00000000 --- a/packages/react/src/themes/terminal.js +++ /dev/null @@ -1,36 +0,0 @@ -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' }, - ], -}); diff --git a/packages/react/src/themes/whitescreen.js b/packages/react/src/themes/whitescreen.js deleted file mode 100644 index 22abad9e..00000000 --- a/packages/react/src/themes/whitescreen.js +++ /dev/null @@ -1,38 +0,0 @@ -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' }, - ], -}); diff --git a/packages/react/tailwind.config.js b/packages/react/tailwind.config.js deleted file mode 100644 index c5a940fc..00000000 --- a/packages/react/tailwind.config.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = { - content: ['./src/**/*.{js,jsx,ts,tsx}'], - theme: { - extend: { - colors: { - // codemirror-theme settings - background: 'var(--background)', - lineBackground: 'var(--lineBackground)', - foreground: 'var(--foreground)', - caret: 'var(--caret)', - selection: 'var(--selection)', - selectionMatch: 'var(--selectionMatch)', - gutterBackground: 'var(--gutterBackground)', - gutterForeground: 'var(--gutterForeground)', - gutterBorder: 'var(--gutterBorder)', - lineHighlight: 'var(--lineHighlight)', - }, - }, - }, - plugins: [], - corePlugins: { - preflight: false, - }, -}; diff --git a/packages/react/vite.config.js b/packages/react/vite.config.js deleted file mode 100644 index be289dab..00000000 --- a/packages/react/vite.config.js +++ /dev/null @@ -1,47 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import { peerDependencies, dependencies } from './package.json'; -import { resolve } from 'path'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - react({ - jsxRuntime: 'classic', - }), - ], - build: { - lib: { - entry: resolve(__dirname, 'src', 'index.js'), - formats: ['es', 'cjs'], - fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]), - // for UMD name: 'GlobalName' - }, - rollupOptions: { - external: [ - ...Object.keys(peerDependencies), - ...Object.keys(dependencies), - // TODO: find out which of below names are obsolete now - '@strudel.cycles/transpiler', - 'acorn', - '@strudel.cycles/core', - '@strudel.cycles/mini', - '@strudel.cycles/tonal', - '@strudel.cycles/midi', - '@strudel.cycles/xen', - '@strudel.cycles/serial', - '@strudel.cycles/webaudio', - '@codemirror/view', - '@codemirror/lang-javascript', - '@codemirror/state', - '@codemirror/commands', - '@lezer/highlight', - '@codemirror/language', - '@uiw/codemirror-themes', - '@uiw/react-codemirror', - '@lezer/highlight', - ], - }, - target: 'esnext', - }, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9c5f796..f26dd289 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -282,149 +282,6 @@ importers: specifier: ^4.3.3 version: 4.3.3 - packages/react: - 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': - specifier: ^6.0.0 - version: 6.2.4 - '@codemirror/lang-javascript': - specifier: ^6.1.7 - version: 6.1.7 - '@codemirror/language': - specifier: ^6.0.0 - version: 6.6.0 - '@codemirror/lint': - specifier: ^6.0.0 - version: 6.1.0 - '@codemirror/search': - specifier: ^6.0.0 - version: 6.2.3 - '@codemirror/state': - specifier: ^6.2.0 - version: 6.2.0 - '@codemirror/view': - specifier: ^6.10.0 - version: 6.10.0 - '@lezer/highlight': - specifier: ^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': - specifier: workspace:* - version: link:../core - '@strudel.cycles/transpiler': - specifier: workspace:* - version: link:../transpiler - '@strudel.cycles/webaudio': - specifier: workspace:* - version: link:../webaudio - '@strudel/codemirror': - specifier: workspace:* - version: link:../codemirror - '@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/react-codemirror': - specifier: ^4.19.16 - version: 4.19.16(@babel/runtime@7.20.13)(@codemirror/autocomplete@6.6.0)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/theme-one-dark@6.1.0)(@codemirror/view@6.10.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0) - react-hook-inview: - specifier: ^4.5.0 - version: 4.5.0(react-dom@18.2.0)(react@18.2.0) - devDependencies: - '@types/react': - specifier: ^18.2.0 - version: 18.2.0 - '@types/react-dom': - specifier: ^18.2.1 - version: 18.2.1 - '@vitejs/plugin-react': - specifier: ^4.0.0 - version: 4.0.0(vite@4.3.3) - autoprefixer: - specifier: ^10.4.14 - version: 10.4.14(postcss@8.4.23) - postcss: - specifier: ^8.4.23 - version: 8.4.23 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - tailwindcss: - specifier: ^3.3.2 - version: 3.3.2 - vite: - specifier: ^4.3.3 - version: 4.3.3 - - packages/react/examples/nano-repl: - dependencies: - '@strudel.cycles/core': - specifier: workspace:* - version: link:../../../core - '@strudel.cycles/mini': - specifier: workspace:* - version: link:../../../mini - '@strudel.cycles/osc': - specifier: workspace:* - version: link:../../../osc - '@strudel.cycles/react': - specifier: workspace:* - version: link:../.. - '@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 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - devDependencies: - '@types/react': - specifier: ^18.2.0 - version: 18.2.0 - '@types/react-dom': - specifier: ^18.2.1 - version: 18.2.1 - '@vitejs/plugin-react': - specifier: ^4.0.0 - version: 4.0.0(vite@4.3.3) - autoprefixer: - specifier: ^10.4.14 - version: 10.4.14(postcss@8.4.23) - postcss: - specifier: ^8.4.23 - version: 8.4.23 - tailwindcss: - specifier: ^3.3.2 - version: 3.3.2 - vite: - specifier: ^4.3.3 - version: 4.3.3 - packages/repl: dependencies: '@rollup/plugin-replace': @@ -930,6 +787,7 @@ packages: /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + dev: false /@ampproject/remapping@2.2.0: resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} @@ -1074,38 +932,10 @@ packages: '@babel/highlight': 7.22.20 chalk: 2.4.2 - /@babel/compat-data@7.21.5: - resolution: {integrity: sha512-M+XAiQ7GzQ3FDPf0KOLkugzptnIypt0X0ma0wmlTKPR3IchgNFdx2JXxZdvd18JY5s7QkaFD/qyX0dsMpog/Ug==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/compat-data@7.23.2: resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==} engines: {node: '>=6.9.0'} - /@babel/core@7.21.5: - resolution: {integrity: sha512-9M398B/QH5DlfCOTKDZT1ozXr0x8uBEeFd+dJraGUZGiaNpGCDVGCc14hZexsMblw3XxltJ+6kSvogp9J+5a9g==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.5 - '@babel/helper-compilation-targets': 7.21.5(@babel/core@7.21.5) - '@babel/helper-module-transforms': 7.21.5 - '@babel/helpers': 7.21.5 - '@babel/parser': 7.21.5 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/core@7.23.2: resolution: {integrity: sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==} engines: {node: '>=6.9.0'} @@ -1137,16 +967,6 @@ packages: jsesc: 2.5.2 dev: true - /@babel/generator@7.21.5: - resolution: {integrity: sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.17 - jsesc: 2.5.2 - dev: true - /@babel/generator@7.23.0: resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} engines: {node: '>=6.9.0'} @@ -1170,20 +990,6 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.5): - resolution: {integrity: sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.21.5 - '@babel/core': 7.21.5 - '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.5 - lru-cache: 5.1.1 - semver: 6.3.0 - dev: true - /@babel/helper-compilation-targets@7.22.15: resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} engines: {node: '>=6.9.0'} @@ -1240,11 +1046,6 @@ packages: - supports-color dev: true - /@babel/helper-environment-visitor@7.21.5: - resolution: {integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-environment-visitor@7.22.20: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} engines: {node: '>=6.9.0'} @@ -1256,14 +1057,6 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/helper-function-name@7.21.0: - resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.21.5 - dev: true - /@babel/helper-function-name@7.23.0: resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} engines: {node: '>=6.9.0'} @@ -1271,13 +1064,6 @@ packages: '@babel/template': 7.22.15 '@babel/types': 7.23.0 - /@babel/helper-hoist-variables@7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} @@ -1291,35 +1077,12 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/helper-module-imports@7.21.4: - resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/helper-module-imports@7.22.15: resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.23.0 - /@babel/helper-module-transforms@7.21.5: - resolution: {integrity: sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-module-imports': 7.21.4 - '@babel/helper-simple-access': 7.21.5 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.2): resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} engines: {node: '>=6.9.0'} @@ -1340,11 +1103,6 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/helper-plugin-utils@7.20.2: - resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-plugin-utils@7.22.5: resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} engines: {node: '>=6.9.0'} @@ -1378,13 +1136,6 @@ packages: - supports-color dev: true - /@babel/helper-simple-access@7.21.5: - resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/helper-simple-access@7.22.5: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} @@ -1398,13 +1149,6 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/helper-split-export-declaration@7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} @@ -1432,11 +1176,6 @@ packages: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.21.0: - resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-validator-option@7.22.15: resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} engines: {node: '>=6.9.0'} @@ -1453,17 +1192,6 @@ packages: - supports-color dev: true - /@babel/helpers@7.21.5: - resolution: {integrity: sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helpers@7.23.2: resolution: {integrity: sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==} engines: {node: '>=6.9.0'} @@ -1513,6 +1241,7 @@ packages: hasBin: true dependencies: '@babel/types': 7.21.5 + dev: false /@babel/parser@7.23.0: resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} @@ -2150,16 +1879,6 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.5): - resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.5 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==} engines: {node: '>=6.9.0'} @@ -2170,16 +1889,6 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: false - /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.5): - resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.5 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==} engines: {node: '>=6.9.0'} @@ -2401,15 +2110,6 @@ packages: dependencies: regenerator-runtime: 0.13.11 - /@babel/template@7.20.7: - resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/parser': 7.21.5 - '@babel/types': 7.21.5 - dev: true - /@babel/template@7.22.15: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} engines: {node: '>=6.9.0'} @@ -2418,24 +2118,6 @@ packages: '@babel/parser': 7.23.0 '@babel/types': 7.23.0 - /@babel/traverse@7.21.5: - resolution: {integrity: sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.5 - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.21.5 - '@babel/types': 7.21.5 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/traverse@7.23.2: resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} engines: {node: '>=6.9.0'} @@ -2553,15 +2235,6 @@ packages: resolution: {integrity: sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==} dev: false - /@codemirror/theme-one-dark@6.1.0: - resolution: {integrity: sha512-AiTHtFRu8+vWT9wWUWDM+cog6ZwgivJogB1Tm/g40NIpLwph7AnmxrSzWfvJN5fBVufsuwBxecQCNmdcR5D7Aw==} - dependencies: - '@codemirror/language': 6.6.0 - '@codemirror/state': 6.2.0 - '@codemirror/view': 6.10.0 - '@lezer/highlight': 1.1.4 - dev: false - /@codemirror/view@6.10.0: resolution: {integrity: sha512-Oea3rvE4JQLMmLsy2b54yxXQJgJM9xKpUQIpF/LGgKUTH2lA06GAmEtKKWn5OUnbW3jrH1hHeUd8DJEgePMOeQ==} dependencies: @@ -4685,11 +4358,13 @@ packages: /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + dev: false /@types/react-dom@18.2.1: resolution: {integrity: sha512-8QZEV9+Kwy7tXFmjJrp3XUKQSs9LTnE0KnoUb0YCguWBiNW0Yfb2iBMYZ08WPg35IR6P3Z0s00B15SwZnO26+w==} dependencies: '@types/react': 18.2.0 + dev: false /@types/react@18.2.0: resolution: {integrity: sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==} @@ -4697,6 +4372,7 @@ packages: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.2 csstype: 3.1.1 + dev: false /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} @@ -4706,6 +4382,7 @@ packages: /@types/scheduler@0.16.2: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + dev: false /@types/trusted-types@2.0.2: resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} @@ -4761,26 +4438,6 @@ packages: eslint-visitor-keys: 3.3.0 dev: false - /@uiw/codemirror-extensions-basic-setup@4.19.16(@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): - resolution: {integrity: sha512-Xm0RDpyYVZ/8hWqaBs3+wZwi4uLwZUBwp/uCt89X80FeR6mr3BFuC+a+gcDO4dBu3l+WQE3jJdhjKjB2TCY/PQ==} - peerDependencies: - '@codemirror/autocomplete': '>=6.0.0' - '@codemirror/commands': '>=6.0.0' - '@codemirror/language': '>=6.0.0' - '@codemirror/lint': '>=6.0.0' - '@codemirror/search': '>=6.0.0' - '@codemirror/state': '>=6.0.0' - '@codemirror/view': '>=6.0.0' - dependencies: - '@codemirror/autocomplete': 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': 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 - dev: false - /@uiw/codemirror-theme-abcdef@4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0): resolution: {integrity: sha512-vZHLg35Rhz39FF3HgAeHSZxIOV3/PG8q8v/0dcywCvt1FG9J6OuAifXzePo2nrT/P/qkienbehxzF+DyHHzV5g==} dependencies: @@ -5056,33 +4713,6 @@ packages: '@codemirror/view': 6.10.0 dev: false - /@uiw/react-codemirror@4.19.16(@babel/runtime@7.20.13)(@codemirror/autocomplete@6.6.0)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/theme-one-dark@6.1.0)(@codemirror/view@6.10.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-uElraR7Mvwz2oZKrmtF5hmIB8dAlIiU65nfg484e/V9k4PV6/5KtPUQL3JPO4clH2pcd+TQqRTQrFFkP/D25ew==} - peerDependencies: - '@babel/runtime': '>=7.11.0' - '@codemirror/state': '>=6.0.0' - '@codemirror/theme-one-dark': '>=6.0.0' - '@codemirror/view': '>=6.0.0' - codemirror: '>=6.0.0' - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@babel/runtime': 7.20.13 - '@codemirror/commands': 6.2.4 - '@codemirror/state': 6.2.0 - '@codemirror/theme-one-dark': 6.1.0 - '@codemirror/view': 6.10.0 - '@uiw/codemirror-extensions-basic-setup': 4.19.16(@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) - codemirror: 6.0.1(@lezer/common@1.0.2) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - transitivePeerDependencies: - - '@codemirror/autocomplete' - - '@codemirror/language' - - '@codemirror/lint' - - '@codemirror/search' - dev: false - /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -5096,21 +4726,6 @@ packages: vite-plugin-pwa: 0.16.5(vite@4.5.0)(workbox-build@7.0.0)(workbox-window@7.0.0) dev: true - /@vitejs/plugin-react@4.0.0(vite@4.3.3): - resolution: {integrity: sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 - dependencies: - '@babel/core': 7.21.5 - '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.5) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.5) - react-refresh: 0.14.0 - vite: 4.3.3 - transitivePeerDependencies: - - supports-color - dev: true - /@vitejs/plugin-react@4.1.0(vite@4.5.0): resolution: {integrity: sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -5373,6 +4988,7 @@ packages: /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -5413,6 +5029,7 @@ packages: /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: false /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -5624,22 +5241,6 @@ packages: tslib: 2.5.0 dev: false - /autoprefixer@10.4.14(postcss@8.4.23): - resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.5 - caniuse-lite: 1.0.30001481 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - dev: true - /autoprefixer@10.4.16(postcss@8.4.31): resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} engines: {node: ^10 || ^12 || >=14} @@ -5792,17 +5393,6 @@ packages: dependencies: fill-range: 7.0.1 - /browserslist@4.21.5: - resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001481 - electron-to-chromium: 1.4.284 - node-releases: 2.0.8 - update-browserslist-db: 1.0.10(browserslist@4.21.5) - dev: true - /browserslist@4.22.1: resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5945,6 +5535,7 @@ packages: /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + dev: false /camelcase-keys@6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} @@ -5964,10 +5555,6 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - /caniuse-lite@1.0.30001481: - resolution: {integrity: sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==} - dev: true - /caniuse-lite@1.0.30001559: resolution: {integrity: sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==} @@ -6214,20 +5801,6 @@ packages: resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} engines: {node: '>=0.10.0'} - /codemirror@6.0.1(@lezer/common@1.0.2): - resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} - dependencies: - '@codemirror/autocomplete': 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': 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 - transitivePeerDependencies: - - '@lezer/common' - dev: false - /collect-all@1.0.4: resolution: {integrity: sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==} engines: {node: '>=0.10.0'} @@ -6338,6 +5911,7 @@ packages: /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + dev: false /commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} @@ -6478,10 +6052,6 @@ packages: q: 1.5.1 dev: true - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: true - /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -6541,6 +6111,7 @@ packages: /csstype@3.1.1: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + dev: false /d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} @@ -6826,6 +6397,7 @@ packages: /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: false /diff-sequences@29.4.3: resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} @@ -6920,10 +6492,6 @@ packages: jake: 10.8.5 dev: true - /electron-to-chromium@1.4.284: - resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} - dev: true - /electron-to-chromium@1.4.574: resolution: {integrity: sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg==} @@ -7789,6 +7357,7 @@ packages: /fraction.js@4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} + dev: false /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -8080,6 +7649,7 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: false /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -9107,6 +8677,7 @@ packages: /jiti@1.18.2: resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} hasBin: true + dev: false /js-sdsl@4.3.0: resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} @@ -9452,10 +9023,12 @@ packages: /lilconfig@2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} engines: {node: '>=10'} + dev: false /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} + dev: false /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -9591,6 +9164,7 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 + dev: false /loupe@2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} @@ -10617,6 +10191,7 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: false /nan@2.17.0: resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==} @@ -10761,10 +10336,6 @@ packages: /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} - /node-releases@2.0.8: - resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==} - dev: true - /node-source-walk@4.3.0: resolution: {integrity: sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA==} engines: {node: '>=6.0'} @@ -10848,6 +10419,7 @@ packages: /normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + dev: false /npm-bundled@1.1.2: resolution: {integrity: sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==} @@ -11123,6 +10695,7 @@ packages: /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + dev: false /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} @@ -11609,6 +11182,7 @@ packages: /pirates@4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} + dev: false /pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} @@ -11679,6 +11253,7 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.2 + dev: false /postcss-js@4.0.1(postcss@8.4.23): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -11688,6 +11263,7 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.23 + dev: false /postcss-load-config@4.0.1(postcss@8.4.23): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} @@ -11704,6 +11280,7 @@ packages: lilconfig: 2.0.6 postcss: 8.4.23 yaml: 2.2.2 + dev: false /postcss-load-config@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} @@ -11730,6 +11307,7 @@ packages: dependencies: postcss: 8.4.23 postcss-selector-parser: 6.0.11 + dev: false /postcss-selector-parser@6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} @@ -11748,6 +11326,7 @@ packages: /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: false /postcss-values-parser@6.0.2(postcss@8.4.23): resolution: {integrity: sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==} @@ -12049,6 +11628,7 @@ packages: loose-envify: 1.4.0 react: 18.2.0 scheduler: 0.23.0 + dev: false /react-hook-inview@4.5.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Hm61BK32/K2Cc3bjBe2bQkndHbQP6NhHvWVX7zYitaitB6T28uUV+wlgxbXU9twxUt7+17HyHq6aezpMUCijQQ==} @@ -12067,17 +11647,20 @@ packages: /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} + dev: false /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 + dev: false /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: pify: 2.3.0 + dev: false /read-cmd-shim@3.0.0: resolution: {integrity: sha512-KQDVjGqhZk92PPNRj9ZEXEuqg8bUobSKRw+q0YQ3TKI5xkce7bUJobL4Z/OtiEbAAv70yEpYIXp4iQ9L8oPVog==} @@ -12695,6 +12278,7 @@ packages: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: loose-envify: 1.4.0 + dev: false /section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} @@ -13267,6 +12851,7 @@ packages: mz: 2.7.0 pirates: 4.0.5 ts-interface-checker: 0.1.13 + dev: false /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} @@ -13325,6 +12910,7 @@ packages: sucrase: 3.32.0 transitivePeerDependencies: - ts-node + dev: false /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} @@ -13477,11 +13063,13 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: false /thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: false /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} @@ -13570,6 +13158,7 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: false /tsconfck@3.0.0(typescript@4.9.4): resolution: {integrity: sha512-w3wnsIrJNi7avf4Zb0VjOoodoO0woEqGgZGQm+LHH9przdUI+XDKsWAXwxHA1DaRTjeuZNcregSzr7RaA8zG9A==} @@ -13965,17 +13554,6 @@ packages: engines: {node: '>=4'} dev: true - /update-browserslist-db@1.0.10(browserslist@4.21.5): - resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.5 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - /update-browserslist-db@1.0.13(browserslist@4.22.1): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -14815,6 +14393,7 @@ packages: /yaml@2.2.2: resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} engines: {node: '>= 14'} + dev: false /yargs-parser@20.2.4: resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} From b0bdd090322d3f4b845a1b9db697d8efabb39319 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 29 Dec 2023 00:30:46 +0100 Subject: [PATCH 77/97] fix: dumb react ssr workaround --- website/src/docs/MiniRepl.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index 3a16f3dd..aab37fd2 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; +import { useState, useRef, useCallback, useMemo, useEffect, useLayoutEffect } from 'react'; import { Icon } from './Icon'; import { silence, getPunchcardPainter, noteToMidi } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; @@ -9,6 +9,9 @@ import { prebake } from '../repl/prebake.mjs'; import { loadModules } from '../repl/util.mjs'; import Claviature from '@components/Claviature'; +// https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 +export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; + let prebaked, modulesLoading; if (typeof window !== 'undefined') { prebaked = prebake(); @@ -89,7 +92,8 @@ export function MiniRepl({ const editorRef = useRef(); const containerRef = useRef(); const [client, setClient] = useState(false); - useEffect(() => { + + useIsomorphicLayoutEffect(() => { setClient(true); if (!editorRef.current) { setTimeout(() => { From 9974311344dd22ab6a5aeae942dba1695cea0d4f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 29 Dec 2023 00:55:14 +0100 Subject: [PATCH 78/97] better repl init + a bit of ssr for main repl --- website/src/docs/MiniRepl.jsx | 29 +++++++++++------------------ website/src/pages/index.astro | 2 +- website/src/repl/Header.jsx | 2 +- website/src/repl/Repl.jsx | 32 ++++++++++---------------------- website/src/repl/panel/Panel.jsx | 16 ++++++++++++---- website/src/useClient.mjs | 9 +++++++++ 6 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 website/src/useClient.mjs diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index aab37fd2..5e915641 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, useMemo, useEffect, useLayoutEffect } from 'react'; +import { useState, useRef, useCallback, useMemo, useEffect } from 'react'; import { Icon } from './Icon'; import { silence, getPunchcardPainter, noteToMidi } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; @@ -8,9 +8,7 @@ import { StrudelMirror } from '@strudel/codemirror'; import { prebake } from '../repl/prebake.mjs'; import { loadModules } from '../repl/util.mjs'; import Claviature from '@components/Claviature'; - -// https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 -export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; +import useClient from '@src/useClient.mjs'; let prebaked, modulesLoading; if (typeof window !== 'undefined') { @@ -91,19 +89,7 @@ export function MiniRepl({ const { started, isDirty, error } = replState; const editorRef = useRef(); const containerRef = useRef(); - const [client, setClient] = useState(false); - - useIsomorphicLayoutEffect(() => { - setClient(true); - if (!editorRef.current) { - setTimeout(() => { - init({ code, shouldDraw }); - }); - } - return () => { - editorRef.current?.clear(); - }; - }, []); + const client = useClient(); if (!client) { return
{code}
; @@ -136,7 +122,14 @@ export function MiniRepl({
)}
-
+
{ + if (!editorRef.current) { + containerRef.current = el; + init({ code, shouldDraw }); + } + }} + >
{error &&
{error.message}
}
{shouldShowCanvas && ( diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 6630ef32..53a522e8 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -9,6 +9,6 @@ import { Repl } from '../repl/Repl'; Strudel REPL - + diff --git a/website/src/repl/Header.jsx b/website/src/repl/Header.jsx index 9af54b12..774d0142 100644 --- a/website/src/repl/Header.jsx +++ b/website/src/repl/Header.jsx @@ -23,7 +23,7 @@ export function Header({ context }) { handleShuffle, handleShare, } = context; - const isEmbedded = embedded || window.location !== window.parent.location; + const isEmbedded = typeof window !== 'undefined' && (embedded || window.location !== window.parent.location); const { isZen } = useSettings(); return ( diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 4a350d55..0ab201a1 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -35,19 +35,15 @@ export const ReplContext = createContext(null); const { latestCode } = settingsMap.get(); -initAudioOnFirstClick(); - -const modulesLoading = loadModules(); -const presets = prebake(); - -let drawContext, clearCanvas; +let modulesLoading, presets, drawContext, clearCanvas; if (typeof window !== 'undefined') { + initAudioOnFirstClick(); + modulesLoading = loadModules(); + presets = prebake(); drawContext = getDrawContext(); clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); } -// const getTime = () => getAudioContext().currentTime; - export function Repl({ embedded = false }) { //const isEmbedded = embedded || window.location !== window.parent.location; const isEmbedded = false; @@ -116,19 +112,6 @@ export function Repl({ embedded = false }) { const { started, isDirty, error, activeCode } = replState; const editorRef = useRef(); const containerRef = useRef(); - const [client, setClient] = useState(false); - useEffect(() => { - setClient(true); - if (!editorRef.current) { - setTimeout(() => { - init({ shouldDraw }); - }); - } - return () => { - editorRef.current?.clear(); - delete editorRef.current; - }; - }, []); // this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly! // this will be the case when the main repl is being replaced @@ -217,7 +200,12 @@ export function Repl({ embedded = false }) {
{ + containerRef.current = el; + if (!editorRef.current) { + init({ shouldDraw }); + } + }} >
{panelPosition === 'right' && !isEmbedded && }
diff --git a/website/src/repl/panel/Panel.jsx b/website/src/repl/panel/Panel.jsx index c2dbd443..8d824d26 100644 --- a/website/src/repl/panel/Panel.jsx +++ b/website/src/repl/panel/Panel.jsx @@ -3,7 +3,7 @@ import { logger } from '@strudel.cycles/core'; import useEvent from '@src/useEvent.mjs'; import cx from '@src/cx.mjs'; import { nanoid } from 'nanoid'; -import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; +import { useCallback, useLayoutEffect, useEffect, useRef, useState } from 'react'; import { setActiveFooter, useSettings } from '../../settings.mjs'; import { ConsoleTab } from './ConsoleTab'; import { FilesTab } from './FilesTab'; @@ -12,21 +12,25 @@ import { SettingsTab } from './SettingsTab'; import { SoundsTab } from './SoundsTab'; import { WelcomeTab } from './WelcomeTab'; import { PatternsTab } from './PatternsTab'; +import useClient from '@src/useClient.mjs'; -const TAURI = window.__TAURI__; +// https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 +export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; + +const TAURI = typeof window !== 'undefined' && window.__TAURI__; export function Panel({ context }) { const footerContent = useRef(); const [log, setLog] = useState([]); const { activeFooter, isZen, panelPosition } = useSettings(); - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (footerContent.current && activeFooter === 'console') { // scroll log box to bottom when log changes footerContent.current.scrollTop = footerContent.current?.scrollHeight; } }, [log, activeFooter]); - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (!footerContent.current) { } else if (activeFooter === 'console') { footerContent.current.scrollTop = footerContent.current?.scrollHeight; @@ -80,6 +84,10 @@ export function Panel({ context }) { right: cx('max-w-full flex-grow-0 flex-none overflow-hidden', isActive ? 'w-[600px] h-full' : 'absolute right-0'), bottom: cx('relative', isActive ? 'h-[360px] min-h-[360px]' : ''), }; + const client = useClient(); + if (!client) { + return null; + } return (