add vite-vanilla-repl-cm6

This commit is contained in:
Felix Roos 2023-05-05 08:55:43 +02:00
parent a035412956
commit 1494cc38fc
13 changed files with 387 additions and 15 deletions

View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,8 @@
# vite-vanilla-repl-cm6
This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using:
```sh
npm i
npm run dev
```

View File

@ -0,0 +1,83 @@
import { EditorState } from '@codemirror/state';
import { EditorView, keymap, Decoration, lineNumbers, highlightActiveLineGutter } from '@codemirror/view';
import { defaultKeymap } from '@codemirror/commands';
import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
import { javascript } from '@codemirror/lang-javascript';
import { StateField, StateEffect } from '@codemirror/state';
import './style.css';
// https://codemirror.net/docs/guide/
export function initEditor(initialCode, onUpdate) {
let state = EditorState.create({
doc: initialCode,
extensions: [
javascript(),
lineNumbers(),
/*gutter({
class: "cm-mygutter"
}),*/
highlightField,
highlightActiveLineGutter(),
//markLineGutter,
syntaxHighlighting(defaultHighlightStyle),
keymap.of(defaultKeymap),
//flashField,
EditorView.updateListener.of((v) => {
onUpdate(v);
}),
],
});
return new EditorView({
state,
parent: document.getElementById('editor'),
});
}
// codemirror specific highlighting logic
export const setHighlights = StateEffect.define();
export const highlightField = StateField.define({
create() {
return Decoration.none;
},
update(highlights, tr) {
try {
for (let e of tr.effects) {
if (e.is(setHighlights)) {
const { haps } = e.value;
const marks =
haps
.map((hap) =>
(hap.context.locations || []).map(({ start, end }) => {
// const color = hap.context.color || e.value.color || '#FFCA28';
let from = tr.newDoc.line(start.line).from + start.column;
let to = tr.newDoc.line(end.line).from + end.column;
const l = tr.newDoc.length;
if (from > l || to > l) {
return; // dont mark outside of range, as it will throw an error
}
const mark = Decoration.mark({
attributes: { style: `outline: 2px solid #FFCA28;` },
});
return mark.range(from, to);
}),
)
.flat()
.filter(Boolean) || [];
highlights = Decoration.set(marks, true);
}
}
return highlights;
} catch (err) {
// console.warn('highlighting error', err);
return Decoration.set([]);
}
},
provide: (f) => EditorView.decorations.from(f),
});
// helper to simply trigger highlighting for given haps
export function highlightHaps(view, haps) {
view.dispatch({ effects: setHighlights.of({ haps }) });
}

View File

@ -0,0 +1,44 @@
const round = (x) => Math.round(x * 1000) / 1000;
// this class can be used to create a code highlighter
// it is encapsulated from the editor via the onUpdate callback
// the scheduler is expected to be an instance of Cyclist
export class Highlighter {
constructor(onUpdate) {
this.onUpdate = onUpdate;
}
start(scheduler) {
let highlights = [];
let lastEnd = 0;
this.stop();
const self = this;
let frame = requestAnimationFrame(function updateHighlights() {
try {
const time = scheduler.now();
// 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 ?? time, time - 1 / 10, -0.01); // negative time seems buggy
const span = [round(begin), round(time + 1 / 60)];
lastEnd = span[1];
highlights = highlights.filter((hap) => hap.whole.end > time); // keep only highlights that are still active
const haps = scheduler.pattern
.queryArc(...span)
.filter((hap) => hap.hasOnset());
highlights = highlights.concat(haps); // add potential new onsets
self.onUpdate(highlights); // highlight all still active + new active haps
} catch (err) {
self.onUpdate([]);
}
frame = requestAnimationFrame(updateHighlights);
});
self.cancel = () => {
cancelAnimationFrame(frame);
};
}
stop() {
if (this.cancel) {
this.cancel();
this.onUpdate([]);
}
}
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite Vanilla Strudel REPL</title>
</head>
<body>
<main>
<nav>
<button id="play">eval</button>
<button id="stop">stop</button>
</nav>
<div class="container">
<div id="editor"></div>
<div id="output"></div>
</div>
</main>
<script type="module" src="./main.js"></script>
</body>
</html>

View File

@ -0,0 +1,32 @@
// moved from sandbox: https://codesandbox.io/s/vanilla-codemirror-strudel-2wb7yw?file=/index.html:114-186
import { initEditor, highlightHaps } from './codemirror';
import { initStrudel } from './strudel';
import { Highlighter } from './highlighter';
import { bumpStreet } from './tunes';
let code = bumpStreet;
const view = initEditor(code, (v) => {
code = v.state.doc.toString();
});
const repl = initStrudel();
let highlighter = new Highlighter((haps) => highlightHaps(view, haps));
document.getElementById('play').addEventListener('click', async () => {
const { evaluate, scheduler } = await repl;
if (!scheduler.started) {
scheduler.stop();
scheduler.lastEnd = 0;
await evaluate(code);
highlighter.start(scheduler);
} else {
await evaluate(code);
}
});
document.getElementById('stop').addEventListener('click', async () => {
const { stop } = await repl;
stop();
highlighter.stop();
});

View File

@ -0,0 +1,27 @@
{
"name": "vite-vanilla-repl-cm6",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^4.3.2"
},
"dependencies": {
"@codemirror/commands": "^6.2.4",
"@codemirror/lang-javascript": "^6.1.7",
"@codemirror/language": "^6.6.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.10.0",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*"
}
}

View File

@ -0,0 +1,38 @@
import { controls, repl, evalScope } from "@strudel.cycles/core";
import { transpiler } from "@strudel.cycles/transpiler";
import {
getAudioContext,
webaudioOutput,
initAudioOnFirstClick,
registerSynthSounds
} from "@strudel.cycles/webaudio";
import { registerSoundfonts } from "@strudel.cycles/soundfonts";
const initAudio = initAudioOnFirstClick();
const ctx = getAudioContext();
const loadModules = (scope = {}) =>
evalScope(
controls,
import("@strudel.cycles/core"),
import("@strudel.cycles/mini"),
import("@strudel.cycles/tonal"),
import("@strudel.cycles/webaudio"),
scope
);
export async function initStrudel(options = {}) {
await Promise.all([
initAudio,
loadModules(),
registerSynthSounds(),
registerSoundfonts()
]);
return repl({
defaultOutput: webaudioOutput,
getTime: () => ctx.currentTime,
transpiler,
...options
});
}

View File

@ -0,0 +1,24 @@
body,
html {
margin: 0;
height: 100%;
}
#editor {
overflow: auto;
height: 100%;
}
.cm-editor {
height: 100%;
}
main {
height: 100%;
display: flex;
flex-direction: column;
}
.container {
flex-grow: 1;
}

View File

@ -0,0 +1,32 @@
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
await samples('github:felixroos/samples/main')
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
"<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
// make it 22 edo
.fmap(v => Math.pow(2,v/22))
// mess with the base frequency
.mul("<300 [300@3 200]>/8").freq()
.layer(
// chords
x=>x.div(freq(2)).s("flute").euclidLegato("<3 2>",8)
.shape(.4).lpf(sine.range(800,4000).slow(8)),
// adlibs
x=>x.arp("{0 3 2 [1 3]}%1.5")
.s('xylo').mul(freq(2))
.delay(.5).delayfeedback(.4).juxBy(.5, rev)
.hpf(sine.range(200,3000).slow(8)),
// bass
x=>x.arp("[0 [2 1?]](5,8)").s('sawtooth').div(freq(4))
.lpf(sine.range(400,2000).slow(8)).lpq(8).shape(.4)
.off(1/8, x=>x.mul(freq(2)).degradeBy(.5)).gain(.3)
).clip(1).release(.2)
.stack(
// drums
s("bd sd:<2 1>, [~ hh]*2, [~ rim]").bank('RolandTR909')
.off(1/8, x=>x.speed(2).gain(.4)).sometimes(ply(2)).gain(.8)
.mask("<0@4 1@12>/4")
.reset("<x@15 [x(3,8) x*[4 8]]>")
// wait for it...
).fast(2/3)
//.crush(6) // remove "//" if you dare`;

View File

@ -6,5 +6,3 @@ This folder demonstrates how to set up a strudel repl using vite and vanilla JS.
npm i npm i
npm run dev npm run dev
``` ```
or view it [live on githack](https://rawcdn.githack.com/tidalcycles/strudel/5fb36acb046ead7cd6ad3cd10f532e7f585f536a/packages/core/examples/vite-vanilla-repl/dist/index.html)

66
pnpm-lock.yaml generated
View File

@ -101,6 +101,46 @@ importers:
specifier: ^4.3.3 specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3) version: 4.3.3(@types/node@18.16.3)
packages/core/examples/vite-vanilla-repl-cm6:
dependencies:
'@codemirror/commands':
specifier: ^6.2.4
version: 6.2.4
'@codemirror/lang-javascript':
specifier: ^6.1.7
version: 6.1.7
'@codemirror/language':
specifier: ^6.6.0
version: 6.6.0
'@codemirror/state':
specifier: ^6.2.0
version: 6.2.0
'@codemirror/view':
specifier: ^6.10.0
version: 6.10.0
'@strudel.cycles/core':
specifier: workspace:*
version: link:../..
'@strudel.cycles/mini':
specifier: workspace:*
version: link:../../../mini
'@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
devDependencies:
vite:
specifier: ^4.3.2
version: 4.3.3(@types/node@18.16.3)
packages/csound: packages/csound:
dependencies: dependencies:
'@csound/browser': '@csound/browser':
@ -220,10 +260,10 @@ importers:
version: 1.1.4 version: 1.1.4
'@replit/codemirror-emacs': '@replit/codemirror-emacs':
specifier: ^6.0.1 specifier: ^6.0.1
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) 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': '@replit/codemirror-vim':
specifier: ^6.0.14 specifier: ^6.0.14
version: 6.0.14(@codemirror/commands@6.2.0)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) 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)
'@strudel.cycles/core': '@strudel.cycles/core':
specifier: workspace:* specifier: workspace:*
version: link:../core version: link:../core
@ -2268,8 +2308,8 @@ packages:
'@lezer/common': 1.0.2 '@lezer/common': 1.0.2
dev: false dev: false
/@codemirror/commands@6.2.0: /@codemirror/commands@6.2.4:
resolution: {integrity: sha512-+00smmZBradoGFEkRjliN7BjqPh/Hx0KCHWOEibUmflUqZz2RwBTU0MrVovEEHozhx3AUSGcO/rl3/5f9e9Biw==} resolution: {integrity: sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==}
dependencies: dependencies:
'@codemirror/language': 6.6.0 '@codemirror/language': 6.6.0
'@codemirror/state': 6.2.0 '@codemirror/state': 6.2.0
@ -3451,7 +3491,7 @@ packages:
escalade: 3.1.1 escalade: 3.1.1
dev: false dev: false
/@replit/codemirror-emacs@6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0): /@replit/codemirror-emacs@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):
resolution: {integrity: sha512-2WYkODZGH1QVAXWuOxTMCwktkoZyv/BjYdJi2A5w4fRrmOQFuIACzb6pO9dgU3J+Pm2naeiX2C8veZr/3/r6AA==} resolution: {integrity: sha512-2WYkODZGH1QVAXWuOxTMCwktkoZyv/BjYdJi2A5w4fRrmOQFuIACzb6pO9dgU3J+Pm2naeiX2C8veZr/3/r6AA==}
peerDependencies: peerDependencies:
'@codemirror/autocomplete': ^6.0.2 '@codemirror/autocomplete': ^6.0.2
@ -3461,13 +3501,13 @@ packages:
'@codemirror/view': ^6.3.0 '@codemirror/view': ^6.3.0
dependencies: 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/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.0 '@codemirror/commands': 6.2.4
'@codemirror/search': 6.2.3 '@codemirror/search': 6.2.3
'@codemirror/state': 6.2.0 '@codemirror/state': 6.2.0
'@codemirror/view': 6.10.0 '@codemirror/view': 6.10.0
dev: false dev: false
/@replit/codemirror-vim@6.0.14(@codemirror/commands@6.2.0)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0): /@replit/codemirror-vim@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):
resolution: {integrity: sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA==} resolution: {integrity: sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA==}
peerDependencies: peerDependencies:
'@codemirror/commands': ^6.0.0 '@codemirror/commands': ^6.0.0
@ -3476,7 +3516,7 @@ packages:
'@codemirror/state': ^6.0.1 '@codemirror/state': ^6.0.1
'@codemirror/view': ^6.0.3 '@codemirror/view': ^6.0.3
dependencies: dependencies:
'@codemirror/commands': 6.2.0 '@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0 '@codemirror/language': 6.6.0
'@codemirror/search': 6.2.3 '@codemirror/search': 6.2.3
'@codemirror/state': 6.2.0 '@codemirror/state': 6.2.0
@ -4069,7 +4109,7 @@ packages:
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: false dev: false
/@uiw/codemirror-extensions-basic-setup@4.19.16(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@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): /@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==} resolution: {integrity: sha512-Xm0RDpyYVZ/8hWqaBs3+wZwi4uLwZUBwp/uCt89X80FeR6mr3BFuC+a+gcDO4dBu3l+WQE3jJdhjKjB2TCY/PQ==}
peerDependencies: peerDependencies:
'@codemirror/autocomplete': '>=6.0.0' '@codemirror/autocomplete': '>=6.0.0'
@ -4081,7 +4121,7 @@ packages:
'@codemirror/view': '>=6.0.0' '@codemirror/view': '>=6.0.0'
dependencies: 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/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.0 '@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0 '@codemirror/language': 6.6.0
'@codemirror/lint': 6.1.0 '@codemirror/lint': 6.1.0
'@codemirror/search': 6.2.3 '@codemirror/search': 6.2.3
@ -4376,11 +4416,11 @@ packages:
react-dom: '>=16.8.0' react-dom: '>=16.8.0'
dependencies: dependencies:
'@babel/runtime': 7.20.13 '@babel/runtime': 7.20.13
'@codemirror/commands': 6.2.0 '@codemirror/commands': 6.2.4
'@codemirror/state': 6.2.0 '@codemirror/state': 6.2.0
'@codemirror/theme-one-dark': 6.1.0 '@codemirror/theme-one-dark': 6.1.0
'@codemirror/view': 6.10.0 '@codemirror/view': 6.10.0
'@uiw/codemirror-extensions-basic-setup': 4.19.16(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@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) '@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) codemirror: 6.0.1(@lezer/common@1.0.2)
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
@ -5441,7 +5481,7 @@ packages:
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
dependencies: 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/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.0 '@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0 '@codemirror/language': 6.6.0
'@codemirror/lint': 6.1.0 '@codemirror/lint': 6.1.0
'@codemirror/search': 6.2.3 '@codemirror/search': 6.2.3

View File

@ -3,4 +3,5 @@ packages:
- "packages/*" - "packages/*"
- "website/" - "website/"
- "packages/core/examples/vite-vanilla-repl" - "packages/core/examples/vite-vanilla-repl"
- "packages/core/examples/vite-vanilla-repl-cm6"
- "packages/react/examples/nano-repl" - "packages/react/examples/nano-repl"