diff --git a/packages/core/examples/vite-vanilla-repl-cm6/.gitignore b/packages/core/examples/vite-vanilla-repl-cm6/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/.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/core/examples/vite-vanilla-repl-cm6/README.md b/packages/core/examples/vite-vanilla-repl-cm6/README.md
new file mode 100644
index 00000000..70d18e09
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/README.md
@@ -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
+```
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/codemirror.js b/packages/core/examples/vite-vanilla-repl-cm6/codemirror.js
new file mode 100644
index 00000000..7ba377ab
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/codemirror.js
@@ -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 }) });
+}
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/highlighter.js b/packages/core/examples/vite-vanilla-repl-cm6/highlighter.js
new file mode 100644
index 00000000..0a306e70
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/highlighter.js
@@ -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([]);
+ }
+ }
+}
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/index.html b/packages/core/examples/vite-vanilla-repl-cm6/index.html
new file mode 100644
index 00000000..32381f03
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Vite Vanilla Strudel REPL
+
+
+
+
+
+
+
+
+
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/main.js b/packages/core/examples/vite-vanilla-repl-cm6/main.js
new file mode 100644
index 00000000..814f3609
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/main.js
@@ -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();
+});
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/package.json b/packages/core/examples/vite-vanilla-repl-cm6/package.json
new file mode 100644
index 00000000..f84b23ed
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/package.json
@@ -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:*"
+ }
+}
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/strudel.js b/packages/core/examples/vite-vanilla-repl-cm6/strudel.js
new file mode 100644
index 00000000..a1e999ee
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/strudel.js
@@ -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
+ });
+}
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/style.css b/packages/core/examples/vite-vanilla-repl-cm6/style.css
new file mode 100644
index 00000000..20970778
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/style.css
@@ -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;
+}
diff --git a/packages/core/examples/vite-vanilla-repl-cm6/tunes.mjs b/packages/core/examples/vite-vanilla-repl-cm6/tunes.mjs
new file mode 100644
index 00000000..4b0155e4
--- /dev/null
+++ b/packages/core/examples/vite-vanilla-repl-cm6/tunes.mjs
@@ -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("")
+ // wait for it...
+).fast(2/3)
+ //.crush(6) // remove "//" if you dare`;
diff --git a/packages/core/examples/vite-vanilla-repl/README.md b/packages/core/examples/vite-vanilla-repl/README.md
index ac2ed20f..d7f65720 100644
--- a/packages/core/examples/vite-vanilla-repl/README.md
+++ b/packages/core/examples/vite-vanilla-repl/README.md
@@ -6,5 +6,3 @@ This folder demonstrates how to set up a strudel repl using vite and vanilla JS.
npm i
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)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a4df850b..40a6442f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -101,6 +101,46 @@ importers:
specifier: ^4.3.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:
dependencies:
'@csound/browser':
@@ -220,10 +260,10 @@ importers:
version: 1.1.4
'@replit/codemirror-emacs':
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':
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':
specifier: workspace:*
version: link:../core
@@ -2268,8 +2308,8 @@ packages:
'@lezer/common': 1.0.2
dev: false
- /@codemirror/commands@6.2.0:
- resolution: {integrity: sha512-+00smmZBradoGFEkRjliN7BjqPh/Hx0KCHWOEibUmflUqZz2RwBTU0MrVovEEHozhx3AUSGcO/rl3/5f9e9Biw==}
+ /@codemirror/commands@6.2.4:
+ resolution: {integrity: sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==}
dependencies:
'@codemirror/language': 6.6.0
'@codemirror/state': 6.2.0
@@ -3451,7 +3491,7 @@ packages:
escalade: 3.1.1
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==}
peerDependencies:
'@codemirror/autocomplete': ^6.0.2
@@ -3461,13 +3501,13 @@ packages:
'@codemirror/view': ^6.3.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.0
+ '@codemirror/commands': 6.2.4
'@codemirror/search': 6.2.3
'@codemirror/state': 6.2.0
'@codemirror/view': 6.10.0
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==}
peerDependencies:
'@codemirror/commands': ^6.0.0
@@ -3476,7 +3516,7 @@ packages:
'@codemirror/state': ^6.0.1
'@codemirror/view': ^6.0.3
dependencies:
- '@codemirror/commands': 6.2.0
+ '@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0
'@codemirror/search': 6.2.3
'@codemirror/state': 6.2.0
@@ -4069,7 +4109,7 @@ 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.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==}
peerDependencies:
'@codemirror/autocomplete': '>=6.0.0'
@@ -4081,7 +4121,7 @@ packages:
'@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.0
+ '@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0
'@codemirror/lint': 6.1.0
'@codemirror/search': 6.2.3
@@ -4376,11 +4416,11 @@ packages:
react-dom: '>=16.8.0'
dependencies:
'@babel/runtime': 7.20.13
- '@codemirror/commands': 6.2.0
+ '@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.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)
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==}
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.0
+ '@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0
'@codemirror/lint': 6.1.0
'@codemirror/search': 6.2.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 3b068aa6..4b821d33 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -3,4 +3,5 @@ packages:
- "packages/*"
- "website/"
- "packages/core/examples/vite-vanilla-repl"
+ - "packages/core/examples/vite-vanilla-repl-cm6"
- "packages/react/examples/nano-repl"
\ No newline at end of file