From 55a3ef9a80feff21101faf07f0ce9138c6112cda Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 30 Dec 2024 22:48:49 +0100 Subject: [PATCH] highlighting --- package.json | 17 +++++----- pnpm-lock.yaml | 3 ++ src/main.js | 5 ++- src/strudel.js | 88 ++++++++++++++++++++++++++++++++++++-------------- src/style.css | 4 +++ 5 files changed, 84 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index c3e7b82..d754d47 100644 --- a/package.json +++ b/package.json @@ -18,19 +18,20 @@ "@codemirror/view": "^6.9.3", "@flok-editor/cm-eval": "^1.0.1", "@flok-editor/session": "^1.1.0", - "codemirror": "^6.0.1", - "y-codemirror.next": "^0.3.2", - "y-indexeddb": "^9.0.9", - "y-protocols": "^1.0.5", - "y-webrtc": "^10.2.5", - "y-websocket": "^1.5.0", - "yjs": "^13.5.51", "@strudel/codemirror": "^1.1.0", "@strudel/core": "^1.1.0", + "@strudel/draw": "^1.1.0", "@strudel/mini": "^1.1.0", "@strudel/soundfonts": "^1.1.0", "@strudel/tonal": "^1.1.0", "@strudel/transpiler": "^1.1.0", - "@strudel/webaudio": "^1.1.0" + "@strudel/webaudio": "^1.1.0", + "codemirror": "^6.0.1", + "y-codemirror.next": "^0.3.2", + "y-indexeddb": "^9.0.9", + "y-protocols": "^1.0.5", + "y-webrtc": "^10.2.5", + "y-websocket": "^1.5.0", + "yjs": "^13.5.51" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 442ea64..ac918f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@strudel/core': specifier: ^1.1.0 version: 1.1.0 + '@strudel/draw': + specifier: ^1.1.0 + version: 1.1.0 '@strudel/mini': specifier: ^1.1.0 version: 1.1.0 diff --git a/src/main.js b/src/main.js index cd549f4..8e188e9 100644 --- a/src/main.js +++ b/src/main.js @@ -6,7 +6,8 @@ import { yCollab } from "y-codemirror.next"; import { Session } from "@flok-editor/session"; import { flashField, evalKeymap, remoteEvalFlash } from "@flok-editor/cm-eval"; import { UndoManager } from "yjs"; -import { StrudelSession } from "./strudel"; +import { highlightExtension } from "@strudel/codemirror"; +import { StrudelSession, editorViews } from "./strudel"; import "./style.css"; @@ -23,6 +24,7 @@ const flokBasicSetup = (doc) => { return [ flashField(), + highlightExtension, remoteEvalFlash(doc), Prec.high(evalKeymap(doc, { web, defaultMode: "document" })), yCollab(text, doc.session.awareness, { undoManager }), @@ -53,6 +55,7 @@ const createEditor = (doc) => { state, parent: editorEl, }); + editorViews.set(doc.id, view); const targetEl = document.querySelector(`#${doc.id} .target`); targetEl.value = doc.target; diff --git a/src/strudel.js b/src/strudel.js index 191b120..c51de2a 100644 --- a/src/strudel.js +++ b/src/strudel.js @@ -6,7 +6,7 @@ import { evaluate, silence, } from "@strudel/core"; -// import { Framer } from "@strudel/draw"; +import { Framer } from "@strudel/draw"; import { registerSoundfonts } from "@strudel/soundfonts"; import { transpiler } from "@strudel/transpiler"; import { @@ -16,12 +16,17 @@ import { samples, webaudioOutput, } from "@strudel/webaudio"; +import { + highlightMiniLocations, + updateMiniLocations, +} from "@strudel/codemirror"; + +export const editorViews = new Map(); +controls.createParam("docId"); export class StrudelSession { constructor({ onError }) { - this.init().then(() => { - console.log("strudel init done", this.repl); - }); + this.init(); this.patterns = {}; this.pPatterns = {}; this.allTransform = undefined; @@ -62,20 +67,51 @@ export class StrudelSession { this.repl = repl({ defaultOutput: webaudioOutput, - afterEval: (options) => { - // assumes docId is injected at end end as a comment - /* const docId = options.code.split("//").slice(-1)[0]; - if (!docId) return; - const miniLocations = options.meta?.miniLocations; - updateDocumentsContext(docId, { miniLocations }); */ - }, - beforeEval: () => {}, onSchedulerError: (e) => this.onError(`${e}`), - onEvalError: (e) => this.onError(`${e}`), getTime: () => getAudioContext().currentTime, transpiler, }); this.injectPatternMethods(); + + this.initHighlighting(); + } + + initHighlighting() { + let lastFrame /* : number | null */ = null; + this.framer = new Framer( + () => { + const phase = this.repl.scheduler.now(); + if (lastFrame === null) { + lastFrame = phase; + return; + } + if (!this.repl.scheduler.pattern) { + return; + } + // queries the stack of strudel patterns for the current time + const allHaps = this.repl.scheduler.pattern.queryArc( + Math.max(lastFrame, phase - 1 / 10), // make sure query is not larger than 1/10 s + phase + ); + // filter out haps that are not active right now + const currentFrame = allHaps.filter( + (hap) => phase >= hap.whole.begin && phase <= hap.endClipped + ); + // iterate over each strudel doc + Object.keys(this.patterns).forEach((docId) => { + // filter out haps belonging to this document (docId is set in eval) + const haps = currentFrame.filter((h) => h.value.docId === docId); + // update codemirror view to highlight this frame's haps + const view = editorViews.get(docId); + console.log(docId, haps); + highlightMiniLocations(view, phase || 0, haps || []); + }); + }, + (err) => { + console.error("[strudel] draw error", err); + } + ); + this.framer.start(); // tbd allow disabling highlighting } hush() { @@ -93,7 +129,6 @@ export class StrudelSession { // allows muting a pattern x with x_ or _x return silence; } - console.log("p", id); if (id === "$") { // allows adding anonymous patterns with $: id = `$${self.anonymousIndex}`; @@ -134,14 +169,16 @@ export class StrudelSession { !conversational && this.hush(); const { body: code, docId } = msg; // little hack that injects the docId at the end of the code to make it available in afterEval - /* const { pattern } = */ await evaluate( - //`${code}//${docId}`, + let { pattern, meta } = await evaluate( code, transpiler // { id: '?' } ); - let pattern = silence; + const view = editorViews.get(docId); + updateMiniLocations(view, meta?.miniLocations || []); + + // let pattern = silence; if (Object.keys(this.pPatterns).length) { let patterns = Object.values(this.pPatterns); pattern = stack(...patterns); @@ -150,14 +187,17 @@ export class StrudelSession { pattern = this.allTransform(pattern); } - console.log("eval done", this.pPatterns); - if (pattern) { - //this.patterns[docId] = pattern.docId(docId); // docId is needed for highlighting - this.patterns[docId] = pattern; // docId is needed for highlighting - console.log("this.patterns", this.patterns); - const allPatterns = stack(...Object.values(this.patterns)); - await this.repl.scheduler.setPattern(allPatterns, true); + if (!pattern) { + return; } + console.log("evaluated patterns", this.pPatterns); + this.patterns[docId] = pattern.docId(docId); // docId is needed for highlighting + console.log("this.patterns", this.patterns); + const allPatterns = stack(...Object.values(this.patterns)); + + await this.repl.scheduler.setPattern(allPatterns, true); + + console.log("afterEval", meta); } catch (err) { console.error(err); this.onError(`${err}`); diff --git a/src/style.css b/src/style.css index ba4c48f..fcbda7e 100644 --- a/src/style.css +++ b/src/style.css @@ -20,3 +20,7 @@ body { .slot .title { font-weight: 600; } + +:root { + --foreground: #eeeeee80; +}