From f1ae8a17cf0701862cbd6c8e3a892a7aee630a7f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 17 Nov 2022 10:07:19 +0100 Subject: [PATCH 1/8] fix: could play multiple mini repl at once --- packages/react/dist/index.cjs.js | 2 +- packages/react/dist/index.es.js | 302 ++++++++++++------------ packages/react/src/hooks/useStrudel.mjs | 22 +- tutorial/MiniRepl.jsx | 2 +- 4 files changed, 179 insertions(+), 149 deletions(-) diff --git a/packages/react/dist/index.cjs.js b/packages/react/dist/index.cjs.js index ca537205..cd672e9e 100644 --- a/packages/react/dist/index.cjs.js +++ b/packages/react/dist/index.cjs.js @@ -1 +1 @@ -"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),G=require("@uiw/react-codemirror"),b=require("@codemirror/view"),R=require("@codemirror/state"),Q=require("@codemirror/lang-javascript"),o=require("@lezer/highlight"),W=require("@uiw/codemirror-themes"),X=require("react-hook-inview"),V=require("@strudel.cycles/webaudio"),Y=require("@strudel.cycles/core"),Z=require("@strudel.cycles/transpiler"),j=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},n=j(t),ee=j(G),te=W.createTheme({theme:"dark",settings:{background:"#222",foreground:"#75baff",caret:"#ffcc00",selection:"rgba(128, 203, 196, 0.5)",selectionMatch:"#036dd626",lineHighlight:"#00000050",gutterBackground:"transparent",gutterForeground:"#8a919966"},styles:[{tag:o.tags.keyword,color:"#c792ea"},{tag:o.tags.operator,color:"#89ddff"},{tag:o.tags.special(o.tags.variableName),color:"#eeffff"},{tag:o.tags.typeName,color:"#c3e88d"},{tag:o.tags.atom,color:"#f78c6c"},{tag:o.tags.number,color:"#c3e88d"},{tag:o.tags.definition(o.tags.variableName),color:"#82aaff"},{tag:o.tags.string,color:"#c3e88d"},{tag:o.tags.special(o.tags.string),color:"#c3e88d"},{tag:o.tags.comment,color:"#7d8799"},{tag:o.tags.variableName,color:"#c792ea"},{tag:o.tags.tagName,color:"#c3e88d"},{tag:o.tags.bracket,color:"#525154"},{tag:o.tags.meta,color:"#ffcb6b"},{tag:o.tags.attributeName,color:"#c792ea"},{tag:o.tags.propertyName,color:"#c792ea"},{tag:o.tags.className,color:"#decb6b"},{tag:o.tags.invalid,color:"#ffffff"}]});const P=R.StateEffect.define(),re=R.StateField.define({create(){return b.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(P))if(a.value){const s=b.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=b.Decoration.set([s.range(0,r.newDoc.length)])}else e=b.Decoration.set([]);return e}catch(a){return console.warn("flash error",a),e}},provide:e=>b.EditorView.decorations.from(e)}),B=e=>{e.dispatch({effects:P.of(!0)}),setTimeout(()=>{e.dispatch({effects:P.of(!1)})},200)},N=R.StateEffect.define(),oe=R.StateField.define({create(){return b.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(N)){const s=a.value.map(c=>(c.context.locations||[]).map(({start:m,end:u})=>{const d=c.context.color||"#FFCA28";let i=r.newDoc.line(m.line).from+m.column,l=r.newDoc.line(u.line).from+u.column;const h=r.newDoc.length;return i>h||l>h?void 0:b.Decoration.mark({attributes:{style:`outline: 1.5px solid ${d};`}}).range(i,l)})).flat().filter(Boolean)||[];e=b.Decoration.set(s,!0)}return e}catch{return b.Decoration.set([])}},provide:e=>b.EditorView.decorations.from(e)}),ae=[Q.javascript(),te,oe,re];function I({value:e,onChange:r,onViewChanged:a,onSelectionChange:s,options:c,editorDidMount:m}){const u=t.useCallback(l=>{r?.(l)},[r]),d=t.useCallback(l=>{a?.(l)},[a]),i=t.useCallback(l=>{l.selectionSet&&s&&s?.(l.state.selection)},[s]);return n.default.createElement(n.default.Fragment,null,n.default.createElement(ee.default,{value:e,onChange:u,onCreateEditor:d,onUpdate:i,extensions:ae}))}function H(...e){return e.filter(Boolean).join(" ")}function K({view:e,pattern:r,active:a,getTime:s}){const c=t.useRef([]),m=t.useRef();t.useEffect(()=>{if(e)if(r&&a){let d=function(){try{const i=s(),h=[Math.max(m.current||i,i-1/10,0),i+1/60];m.current=h[1],c.current=c.current.filter(p=>p.whole.end>i);const v=r.queryArc(...h).filter(p=>p.hasOnset());c.current=c.current.concat(v),e.dispatch({effects:N.of(c.current)})}catch{e.dispatch({effects:N.of([])})}u=requestAnimationFrame(d)},u=requestAnimationFrame(d);return()=>{cancelAnimationFrame(u)}}else c.current=[],e.dispatch({effects:N.of([])})},[r,a,e])}const ne="_container_3i85k_1",se="_header_3i85k_5",ce="_buttons_3i85k_9",ie="_button_3i85k_9",le="_buttonDisabled_3i85k_17",ue="_error_3i85k_21",de="_body_3i85k_25",E={container:ne,header:se,buttons:ce,button:ie,buttonDisabled:le,error:ue,body:de};function z({type:e}){return n.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:n.default.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:n.default.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:n.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"})}[e])}function O({defaultOutput:e,interval:r,getTime:a,evalOnMount:s=!1,initialCode:c="",autolink:m=!1,beforeEval:u,afterEval:d,onEvalError:i,onToggle:l}){const[h,v]=t.useState(),[p,D]=t.useState(),[w,k]=t.useState(c),[y,q]=t.useState(),[F,_]=t.useState(),[C,A]=t.useState(!1),M=w!==y,{scheduler:f,evaluate:x,start:U,stop:J,pause:$}=t.useMemo(()=>Y.repl({interval:r,defaultOutput:e,onSchedulerError:v,onEvalError:g=>{D(g),i?.(g)},getTime:a,transpiler:Z.transpiler,beforeEval:({code:g})=>{k(g),u?.()},afterEval:({pattern:g,code:T})=>{q(T),_(g),D(),v(),m&&(window.location.hash="#"+encodeURIComponent(btoa(T))),d?.()},onToggle:g=>{A(g),l?.(g)}}),[e,r,a]),S=t.useCallback(async(g=!0)=>x(w,g),[x,w]),L=t.useRef();return t.useEffect(()=>{!L.current&&s&&w&&(L.current=!0,S())},[S,s,w]),t.useEffect(()=>()=>{f.stop()},[f]),{code:w,setCode:k,error:h||p,schedulerError:h,scheduler:f,evalError:p,evaluate:x,activateCode:S,activeCode:y,isDirty:M,pattern:F,started:C,start:U,stop:J,pause:$,togglePlay:async()=>{C?f.pause():await S()}}}const fe=()=>V.getAudioContext().currentTime;function ge({tune:e,hideOutsideView:r=!1,init:a,enableKeyboard:s}){const{code:c,setCode:m,evaluate:u,activateCode:d,error:i,isDirty:l,activeCode:h,pattern:v,started:p,scheduler:D,togglePlay:w,stop:k}=O({initialCode:e,defaultOutput:V.webaudioOutput,getTime:fe}),[y,q]=t.useState(),[F,_]=X.useInView({threshold:.01}),C=t.useRef(),A=t.useMemo(()=>((_||!r)&&(C.current=!0),_||C.current),[_,r]);return K({view:y,pattern:v,active:p&&!h?.includes("strudel disable-highlighting"),getTime:()=>D.getPhase()}),t.useLayoutEffect(()=>{if(s){const M=async f=>{(f.ctrlKey||f.altKey)&&(f.code==="Enter"?(f.preventDefault(),B(y),await d()):f.code==="Period"&&(k(),f.preventDefault()))};return window.addEventListener("keydown",M,!0),()=>window.removeEventListener("keydown",M,!0)}},[s,v,c,u,k,y]),n.default.createElement("div",{className:E.container,ref:F},n.default.createElement("div",{className:E.header},n.default.createElement("div",{className:E.buttons},n.default.createElement("button",{className:H(E.button,p?"sc-animate-pulse":""),onClick:()=>w()},n.default.createElement(z,{type:p?"pause":"play"})),n.default.createElement("button",{className:H(l?E.button:E.buttonDisabled),onClick:()=>d()},n.default.createElement(z,{type:"refresh"}))),i&&n.default.createElement("div",{className:E.error},i.message)),n.default.createElement("div",{className:E.body},A&&n.default.createElement(I,{value:c,onChange:m,onViewChanged:q})))}function me(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(r=>window.postMessage(r,"*"),[])}const he=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=I;exports.MiniRepl=ge;exports.cx=H;exports.flash=B;exports.useHighlighting=K;exports.useKeydown=he;exports.usePostMessage=me;exports.useStrudel=O; +"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),X=require("@uiw/react-codemirror"),p=require("@codemirror/view"),A=require("@codemirror/state"),Y=require("@codemirror/lang-javascript"),o=require("@lezer/highlight"),Z=require("@uiw/codemirror-themes"),ee=require("react-hook-inview"),B=require("@strudel.cycles/webaudio"),te=require("@strudel.cycles/core"),re=require("@strudel.cycles/transpiler"),I=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},n=I(t),oe=I(X),ae=Z.createTheme({theme:"dark",settings:{background:"#222",foreground:"#75baff",caret:"#ffcc00",selection:"rgba(128, 203, 196, 0.5)",selectionMatch:"#036dd626",lineHighlight:"#00000050",gutterBackground:"transparent",gutterForeground:"#8a919966"},styles:[{tag:o.tags.keyword,color:"#c792ea"},{tag:o.tags.operator,color:"#89ddff"},{tag:o.tags.special(o.tags.variableName),color:"#eeffff"},{tag:o.tags.typeName,color:"#c3e88d"},{tag:o.tags.atom,color:"#f78c6c"},{tag:o.tags.number,color:"#c3e88d"},{tag:o.tags.definition(o.tags.variableName),color:"#82aaff"},{tag:o.tags.string,color:"#c3e88d"},{tag:o.tags.special(o.tags.string),color:"#c3e88d"},{tag:o.tags.comment,color:"#7d8799"},{tag:o.tags.variableName,color:"#c792ea"},{tag:o.tags.tagName,color:"#c3e88d"},{tag:o.tags.bracket,color:"#525154"},{tag:o.tags.meta,color:"#ffcb6b"},{tag:o.tags.attributeName,color:"#c792ea"},{tag:o.tags.propertyName,color:"#c792ea"},{tag:o.tags.className,color:"#decb6b"},{tag:o.tags.invalid,color:"#ffffff"}]});const L=A.StateEffect.define(),ne=A.StateField.define({create(){return p.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(L))if(a.value){const s=p.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=p.Decoration.set([s.range(0,r.newDoc.length)])}else e=p.Decoration.set([]);return e}catch(a){return console.warn("flash error",a),e}},provide:e=>p.EditorView.decorations.from(e)}),K=e=>{e.dispatch({effects:L.of(!0)}),setTimeout(()=>{e.dispatch({effects:L.of(!1)})},200)},x=A.StateEffect.define(),se=A.StateField.define({create(){return p.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(x)){const s=a.value.map(c=>(c.context.locations||[]).map(({start:g,end:d})=>{const f=c.context.color||"#FFCA28";let i=r.newDoc.line(g.line).from+g.column,l=r.newDoc.line(d.line).from+d.column;const m=r.newDoc.length;return i>m||l>m?void 0:p.Decoration.mark({attributes:{style:`outline: 1.5px solid ${f};`}}).range(i,l)})).flat().filter(Boolean)||[];e=p.Decoration.set(s,!0)}return e}catch{return p.Decoration.set([])}},provide:e=>p.EditorView.decorations.from(e)}),ce=[Y.javascript(),ae,se,ne];function O({value:e,onChange:r,onViewChanged:a,onSelectionChange:s,options:c,editorDidMount:g}){const d=t.useCallback(l=>{r?.(l)},[r]),f=t.useCallback(l=>{a?.(l)},[a]),i=t.useCallback(l=>{l.selectionSet&&s&&s?.(l.state.selection)},[s]);return n.default.createElement(n.default.Fragment,null,n.default.createElement(oe.default,{value:e,onChange:d,onCreateEditor:f,onUpdate:i,extensions:ce}))}function T(...e){return e.filter(Boolean).join(" ")}function U({view:e,pattern:r,active:a,getTime:s}){const c=t.useRef([]),g=t.useRef();t.useEffect(()=>{if(e)if(r&&a){let f=function(){try{const i=s(),m=[Math.max(g.current||i,i-1/10,0),i+1/60];g.current=m[1],c.current=c.current.filter(h=>h.whole.end>i);const v=r.queryArc(...m).filter(h=>h.hasOnset());c.current=c.current.concat(v),e.dispatch({effects:x.of(c.current)})}catch{e.dispatch({effects:x.of([])})}d=requestAnimationFrame(f)},d=requestAnimationFrame(f);return()=>{cancelAnimationFrame(d)}}else c.current=[],e.dispatch({effects:x.of([])})},[r,a,e])}const ie="_container_3i85k_1",le="_header_3i85k_5",ue="_buttons_3i85k_9",de="_button_3i85k_9",fe="_buttonDisabled_3i85k_17",ge="_error_3i85k_21",me="_body_3i85k_25",E={container:ie,header:le,buttons:ue,button:de,buttonDisabled:fe,error:ge,body:me};function j({type:e}){return n.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:n.default.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:n.default.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:n.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"})}[e])}function J(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(r=>window.postMessage(r,"*"),[])}function $({defaultOutput:e,interval:r,getTime:a,evalOnMount:s=!1,initialCode:c="",autolink:g=!1,beforeEval:d,afterEval:f,onEvalError:i,onToggle:l}){const m=t.useMemo(()=>he(),[]),[v,h]=t.useState(),[_,C]=t.useState(),[b,y]=t.useState(c),[M,P]=t.useState(),[k,D]=t.useState(),[S,N]=t.useState(!1),w=b!==M,{scheduler:R,evaluate:H,start:G,stop:z,pause:Q}=t.useMemo(()=>te.repl({interval:r,defaultOutput:e,onSchedulerError:h,onEvalError:u=>{C(u),i?.(u)},getTime:a,transpiler:re.transpiler,beforeEval:({code:u})=>{y(u),d?.()},afterEval:({pattern:u,code:F})=>{P(F),D(u),C(),h(),g&&(window.location.hash="#"+encodeURIComponent(btoa(F))),f?.()},onToggle:u=>{N(u),l?.(u)}}),[e,r,a]),W=J(({data:{from:u,type:F}})=>{F==="start"&&u!==m&&z()}),q=t.useCallback(async(u=!0)=>{await H(b,u),W({type:"start",from:m})},[H,b]),V=t.useRef();return t.useEffect(()=>{!V.current&&s&&b&&(V.current=!0,q())},[q,s,b]),t.useEffect(()=>()=>{R.stop()},[R]),{code:b,setCode:y,error:v||_,schedulerError:v,scheduler:R,evalError:_,evaluate:H,activateCode:q,activeCode:M,isDirty:w,pattern:k,started:S,start:G,stop:z,pause:Q,togglePlay:async()=>{S?R.pause():await q()}}}function he(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}const pe=()=>B.getAudioContext().currentTime;function be({tune:e,hideOutsideView:r=!1,init:a,enableKeyboard:s}){const{code:c,setCode:g,evaluate:d,activateCode:f,error:i,isDirty:l,activeCode:m,pattern:v,started:h,scheduler:_,togglePlay:C,stop:b}=$({initialCode:e,defaultOutput:B.webaudioOutput,getTime:pe}),[y,M]=t.useState(),[P,k]=ee.useInView({threshold:.01}),D=t.useRef(),S=t.useMemo(()=>((k||!r)&&(D.current=!0),k||D.current),[k,r]);return U({view:y,pattern:v,active:h&&!m?.includes("strudel disable-highlighting"),getTime:()=>_.getPhase()}),t.useLayoutEffect(()=>{if(s){const N=async w=>{(w.ctrlKey||w.altKey)&&(w.code==="Enter"?(w.preventDefault(),K(y),await f()):w.code==="Period"&&(b(),w.preventDefault()))};return window.addEventListener("keydown",N,!0),()=>window.removeEventListener("keydown",N,!0)}},[s,v,c,d,b,y]),n.default.createElement("div",{className:E.container,ref:P},n.default.createElement("div",{className:E.header},n.default.createElement("div",{className:E.buttons},n.default.createElement("button",{className:T(E.button,h?"sc-animate-pulse":""),onClick:()=>C()},n.default.createElement(j,{type:h?"pause":"play"})),n.default.createElement("button",{className:T(l?E.button:E.buttonDisabled),onClick:()=>f()},n.default.createElement(j,{type:"refresh"}))),i&&n.default.createElement("div",{className:E.error},i.message)),n.default.createElement("div",{className:E.body},S&&n.default.createElement(O,{value:c,onChange:g,onViewChanged:M})))}const ve=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=O;exports.MiniRepl=be;exports.cx=T;exports.flash=K;exports.useHighlighting=U;exports.useKeydown=ve;exports.usePostMessage=J;exports.useStrudel=$; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index bbd3b758..499895c3 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -1,15 +1,15 @@ -import n, { useCallback as N, useRef as x, useEffect as R, useState as w, useMemo as I, useLayoutEffect as K } from "react"; -import Q from "@uiw/react-codemirror"; -import { Decoration as E, EditorView as O } from "@codemirror/view"; -import { StateEffect as j, StateField as U } from "@codemirror/state"; -import { javascript as W } from "@codemirror/lang-javascript"; +import n, { useCallback as _, useRef as H, useEffect as L, useMemo as V, useState as w, useLayoutEffect as j } from "react"; +import X from "@uiw/react-codemirror"; +import { Decoration as E, EditorView as U } from "@codemirror/view"; +import { StateEffect as $, StateField as G } from "@codemirror/state"; +import { javascript as Y } from "@codemirror/lang-javascript"; import { tags as r } from "@lezer/highlight"; -import { createTheme as X } from "@uiw/codemirror-themes"; -import { useInView as Y } from "react-hook-inview"; -import { webaudioOutput as Z, getAudioContext as ee } from "@strudel.cycles/webaudio"; -import { repl as te } from "@strudel.cycles/core"; -import { transpiler as re } from "@strudel.cycles/transpiler"; -const oe = X({ +import { createTheme as Z } from "@uiw/codemirror-themes"; +import { useInView as ee } from "react-hook-inview"; +import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio"; +import { repl as oe } from "@strudel.cycles/core"; +import { transpiler as ne } from "@strudel.cycles/transpiler"; +const ae = Z({ theme: "dark", settings: { background: "#222", @@ -42,14 +42,14 @@ const oe = X({ { tag: r.invalid, color: "#ffffff" } ] }); -const T = j.define(), ne = U.define({ +const B = $.define(), se = G.define({ create() { return E.none; }, update(e, t) { try { for (let o of t.effects) - if (o.is(T)) + if (o.is(B)) if (o.value) { const a = E.mark({ attributes: { style: "background-color: #FFCA2880" } }); e = E.set([a.range(0, t.newDoc.length)]); @@ -60,25 +60,25 @@ const T = j.define(), ne = U.define({ return console.warn("flash error", o), e; } }, - provide: (e) => O.decorations.from(e) -}), ae = (e) => { - e.dispatch({ effects: T.of(!0) }), setTimeout(() => { - e.dispatch({ effects: T.of(!1) }); + provide: (e) => U.decorations.from(e) +}), ce = (e) => { + e.dispatch({ effects: B.of(!0) }), setTimeout(() => { + e.dispatch({ effects: B.of(!1) }); }, 200); -}, A = j.define(), se = U.define({ +}, z = $.define(), ie = G.define({ create() { return E.none; }, update(e, t) { try { for (let o of t.effects) - if (o.is(A)) { + if (o.is(z)) { const a = o.value.map( - (s) => (s.context.locations || []).map(({ start: m, end: l }) => { - const d = s.context.color || "#FFCA28"; - let c = t.newDoc.line(m.line).from + m.column, i = t.newDoc.line(l.line).from + l.column; - const g = t.newDoc.length; - return c > g || i > g ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(c, i); + (s) => (s.context.locations || []).map(({ start: f, end: d }) => { + const u = s.context.color || "#FFCA28"; + let c = t.newDoc.line(f.line).from + f.column, i = t.newDoc.line(d.line).from + d.column; + const m = t.newDoc.length; + return c > m || i > m ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${u};` } }).range(c, i); }) ).flat().filter(Boolean) || []; e = E.set(a, !0); @@ -88,69 +88,69 @@ const T = j.define(), ne = U.define({ return E.set([]); } }, - provide: (e) => O.decorations.from(e) -}), ce = [W(), oe, se, ne]; -function ie({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: m }) { - const l = N( + provide: (e) => U.decorations.from(e) +}), le = [Y(), ae, ie, se]; +function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: f }) { + const d = _( (i) => { t?.(i); }, [t] - ), d = N( + ), u = _( (i) => { o?.(i); }, [o] - ), c = N( + ), c = _( (i) => { i.selectionSet && a && a?.(i.state.selection); }, [a] ); - return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(Q, { + return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(X, { value: e, - onChange: l, - onCreateEditor: d, + onChange: d, + onCreateEditor: u, onUpdate: c, - extensions: ce + extensions: le })); } -function B(...e) { +function K(...e) { return e.filter(Boolean).join(" "); } -function le({ view: e, pattern: t, active: o, getTime: a }) { - const s = x([]), m = x(); - R(() => { +function ue({ view: e, pattern: t, active: o, getTime: a }) { + const s = H([]), f = H(); + L(() => { if (e) if (t && o) { - let d = function() { + let u = function() { try { - const c = a(), g = [Math.max(m.current || c, c - 1 / 10, 0), c + 1 / 60]; - m.current = g[1], s.current = s.current.filter((p) => p.whole.end > c); - const h = t.queryArc(...g).filter((p) => p.hasOnset()); - s.current = s.current.concat(h), e.dispatch({ effects: A.of(s.current) }); + const c = a(), m = [Math.max(f.current || c, c - 1 / 10, 0), c + 1 / 60]; + f.current = m[1], s.current = s.current.filter((g) => g.whole.end > c); + const h = t.queryArc(...m).filter((g) => g.hasOnset()); + s.current = s.current.concat(h), e.dispatch({ effects: z.of(s.current) }); } catch { - e.dispatch({ effects: A.of([]) }); + e.dispatch({ effects: z.of([]) }); } - l = requestAnimationFrame(d); - }, l = requestAnimationFrame(d); + d = requestAnimationFrame(u); + }, d = requestAnimationFrame(u); return () => { - cancelAnimationFrame(l); + cancelAnimationFrame(d); }; } else - s.current = [], e.dispatch({ effects: A.of([]) }); + s.current = [], e.dispatch({ effects: z.of([]) }); }, [t, o, e]); } -const de = "_container_3i85k_1", ue = "_header_3i85k_5", fe = "_buttons_3i85k_9", me = "_button_3i85k_9", ge = "_buttonDisabled_3i85k_17", pe = "_error_3i85k_21", he = "_body_3i85k_25", b = { - container: de, - header: ue, - buttons: fe, - button: me, - buttonDisabled: ge, - error: pe, - body: he +const fe = "_container_3i85k_1", me = "_header_3i85k_5", ge = "_buttons_3i85k_9", pe = "_button_3i85k_9", he = "_buttonDisabled_3i85k_17", be = "_error_3i85k_21", ve = "_body_3i85k_25", v = { + container: fe, + header: me, + buttons: ge, + button: pe, + buttonDisabled: he, + error: be, + body: ve }; -function q({ type: e }) { +function O({ type: e }) { return /* @__PURE__ */ n.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "sc-h-5 sc-w-5", @@ -174,137 +174,147 @@ function q({ type: e }) { }) }[e]); } -function ve({ +function Ee(e) { + return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []); +} +function we({ defaultOutput: e, interval: t, getTime: o, evalOnMount: a = !1, initialCode: s = "", - autolink: m = !1, - beforeEval: l, - afterEval: d, + autolink: f = !1, + beforeEval: d, + afterEval: u, onEvalError: c, onToggle: i }) { - const [g, h] = w(), [p, D] = w(), [v, k] = w(s), [y, P] = w(), [z, _] = w(), [C, H] = w(!1), F = v !== y, { scheduler: u, evaluate: L, start: $, stop: G, pause: J } = I( - () => te({ + const m = V(() => ye(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V( + () => oe({ interval: t, defaultOutput: e, - onSchedulerError: h, - onEvalError: (f) => { - D(f), c?.(f); + onSchedulerError: g, + onEvalError: (l) => { + N(l), c?.(l); }, getTime: o, - transpiler: re, - beforeEval: ({ code: f }) => { - k(f), l?.(); + transpiler: ne, + beforeEval: ({ code: l }) => { + y(l), d?.(); }, - afterEval: ({ pattern: f, code: S }) => { - P(S), _(f), D(), h(), m && (window.location.hash = "#" + encodeURIComponent(btoa(S))), d?.(); + afterEval: ({ pattern: l, code: P }) => { + S(P), D(l), N(), g(), f && (window.location.hash = "#" + encodeURIComponent(btoa(P))), u?.(); }, - onToggle: (f) => { - H(f), i?.(f); + onToggle: (l) => { + x(l), i?.(l); } }), [e, t, o] - ), M = N(async (f = !0) => L(v, f), [L, v]), V = x(); - return R(() => { - !V.current && a && v && (V.current = !0, M()); - }, [M, a, v]), R(() => () => { - u.stop(); - }, [u]), { - code: v, - setCode: k, - error: g || p, - schedulerError: g, - scheduler: u, - evalError: p, - evaluate: L, - activateCode: M, - activeCode: y, - isDirty: F, - pattern: z, - started: C, - start: $, - stop: G, - pause: J, + ), W = Ee(({ data: { from: l, type: P } }) => { + P === "start" && l !== m && q(); + }), R = _( + async (l = !0) => { + await T(p, l), W({ type: "start", from: m }); + }, + [T, p] + ), I = H(); + return L(() => { + !I.current && a && p && (I.current = !0, R()); + }, [R, a, p]), L(() => () => { + A.stop(); + }, [A]), { + code: p, + setCode: y, + error: h || C, + schedulerError: h, + scheduler: A, + evalError: C, + evaluate: T, + activateCode: R, + activeCode: M, + isDirty: b, + pattern: k, + started: F, + start: J, + stop: q, + pause: Q, togglePlay: async () => { - C ? u.pause() : await M(); + F ? A.pause() : await R(); } }; } -const be = () => ee().currentTime; -function Pe({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) { +function ye() { + return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); +} +const ke = () => re().currentTime; +function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) { const { code: s, - setCode: m, - evaluate: l, - activateCode: d, + setCode: f, + evaluate: d, + activateCode: u, error: c, isDirty: i, - activeCode: g, + activeCode: m, pattern: h, - started: p, - scheduler: D, - togglePlay: v, - stop: k - } = ve({ + started: g, + scheduler: C, + togglePlay: N, + stop: p + } = we({ initialCode: e, - defaultOutput: Z, - getTime: be - }), [y, P] = w(), [z, _] = Y({ + defaultOutput: te, + getTime: ke + }), [y, M] = w(), [S, k] = ee({ threshold: 0.01 - }), C = x(), H = I(() => ((_ || !t) && (C.current = !0), _ || C.current), [_, t]); - return le({ + }), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]); + return ue({ view: y, pattern: h, - active: p && !g?.includes("strudel disable-highlighting"), - getTime: () => D.getPhase() - }), K(() => { + active: g && !m?.includes("strudel disable-highlighting"), + getTime: () => C.getPhase() + }), j(() => { if (a) { - const F = async (u) => { - (u.ctrlKey || u.altKey) && (u.code === "Enter" ? (u.preventDefault(), ae(y), await d()) : u.code === "Period" && (k(), u.preventDefault())); + const x = async (b) => { + (b.ctrlKey || b.altKey) && (b.code === "Enter" ? (b.preventDefault(), ce(y), await u()) : b.code === "Period" && (p(), b.preventDefault())); }; - return window.addEventListener("keydown", F, !0), () => window.removeEventListener("keydown", F, !0); + return window.addEventListener("keydown", x, !0), () => window.removeEventListener("keydown", x, !0); } - }, [a, h, s, l, k, y]), /* @__PURE__ */ n.createElement("div", { - className: b.container, - ref: z + }, [a, h, s, d, p, y]), /* @__PURE__ */ n.createElement("div", { + className: v.container, + ref: S }, /* @__PURE__ */ n.createElement("div", { - className: b.header + className: v.header }, /* @__PURE__ */ n.createElement("div", { - className: b.buttons + className: v.buttons }, /* @__PURE__ */ n.createElement("button", { - className: B(b.button, p ? "sc-animate-pulse" : ""), - onClick: () => v() - }, /* @__PURE__ */ n.createElement(q, { - type: p ? "pause" : "play" + className: K(v.button, g ? "sc-animate-pulse" : ""), + onClick: () => N() + }, /* @__PURE__ */ n.createElement(O, { + type: g ? "pause" : "play" })), /* @__PURE__ */ n.createElement("button", { - className: B(i ? b.button : b.buttonDisabled), - onClick: () => d() - }, /* @__PURE__ */ n.createElement(q, { + className: K(i ? v.button : v.buttonDisabled), + onClick: () => u() + }, /* @__PURE__ */ n.createElement(O, { type: "refresh" }))), c && /* @__PURE__ */ n.createElement("div", { - className: b.error + className: v.error }, c.message)), /* @__PURE__ */ n.createElement("div", { - className: b.body - }, H && /* @__PURE__ */ n.createElement(ie, { + className: v.body + }, F && /* @__PURE__ */ n.createElement(de, { value: s, - onChange: m, - onViewChanged: P + onChange: f, + onViewChanged: M }))); } -function ze(e) { - return R(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((t) => window.postMessage(t, "*"), []); -} -const He = (e) => K(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); +const Te = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); export { - ie as CodeMirror, - Pe as MiniRepl, - B as cx, - ae as flash, - le as useHighlighting, - He as useKeydown, - ze as usePostMessage, - ve as useStrudel + de as CodeMirror, + Se as MiniRepl, + K as cx, + ce as flash, + ue as useHighlighting, + Te as useKeydown, + Ee as usePostMessage, + we as useStrudel }; diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index 14fabfb4..82326415 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -1,6 +1,7 @@ import { useRef, useCallback, useEffect, useMemo, useState } from 'react'; import { repl } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; +import usePostMessage from './usePostMessage.mjs'; function useStrudel({ defaultOutput, @@ -14,6 +15,7 @@ function useStrudel({ onEvalError, onToggle, }) { + const id = useMemo(() => s4(), []); // scheduler const [schedulerError, setSchedulerError] = useState(); const [evalError, setEvalError] = useState(); @@ -57,7 +59,19 @@ function useStrudel({ }), [defaultOutput, interval, getTime], ); - const activateCode = useCallback(async (autostart = true) => evaluate(code, autostart), [evaluate, code]); + const broadcast = usePostMessage(({ data: { from, type } }) => { + if (type === 'start' && from !== id) { + // console.log('message', from, type); + stop(); + } + }); + const activateCode = useCallback( + async (autostart = true) => { + await evaluate(code, autostart); + broadcast({ type: 'start', from: id }); + }, + [evaluate, code], + ); const inited = useRef(); useEffect(() => { @@ -103,3 +117,9 @@ function useStrudel({ } export default useStrudel; + +function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); +} diff --git a/tutorial/MiniRepl.jsx b/tutorial/MiniRepl.jsx index ef63d563..c86a83fd 100644 --- a/tutorial/MiniRepl.jsx +++ b/tutorial/MiniRepl.jsx @@ -19,7 +19,7 @@ evalScope( import('@strudel.cycles/osc'), ); -prebake(); +// prebake(); export function MiniRepl({ tune }) { return <_MiniRepl tune={tune} hideOutsideView={true} />; From 94a594c7773dbbebfb85ef51d142d098644dacad Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 17 Nov 2022 10:15:59 +0100 Subject: [PATCH 2/8] fix sample examples --- tutorial/tutorial.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index de12742e..d62f3e79 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -253,7 +253,7 @@ Using round brackets, we can create rhythmical sub-divisions based on three para The first parameter controls how may beats will be played. The second parameter controls the total amount of segments the beats will be distributed over. The third (optional) parameter controls the starting position for distributing the beats. -One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,1)" or simply "(3,8)", +One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,0)" or simply "(3,8)", resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segments, starting on position 1). @@ -358,7 +358,7 @@ For pitched sounds, you can use `note`, just like with synths: @2").s('gtr').gain(.5)`} /> @@ -368,7 +368,7 @@ If we want them to behave more like a synth, we can add `clip(1)`: @2").s('gtr').clip(1) .gain(.5)`} @@ -380,8 +380,8 @@ If we have 2 samples with different base pitches, we can make them in tune by sp @2").s("gtr,moog").clip(1) .gain(.5)`} @@ -393,7 +393,7 @@ We can also declare different samples for different regions of the keyboard: Date: Thu, 17 Nov 2022 10:16:29 +0100 Subject: [PATCH 3/8] add s for sharp (fixes notes example) --- packages/core/util.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 7b8a72d1..f6b7d8b8 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ // returns true if the given string is a note -export const isNote = (name) => /^[a-gA-G][#b]*[0-9]$/.test(name); +export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name); export const tokenizeNote = (note) => { if (typeof note !== 'string') { return []; From f279c617920934ffd463e404d7895d11cb2086d4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 17 Nov 2022 10:19:04 +0100 Subject: [PATCH 4/8] fix: each example --- packages/core/pattern.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index c3211ee7..2cab88c3 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1050,7 +1050,7 @@ export class Pattern { * @param {function} func function to apply * @returns Pattern * @example - * note("c3 d3 e3 g3").every(4, x=>x.rev()) + * note("c3 d3 e3 g3").each(4, x=>x.rev()) */ each(n, func) { const pat = this; From 83263968f2d8c0f86ff0cc01c119b756e097a7cf Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 17 Nov 2022 10:41:32 +0100 Subject: [PATCH 5/8] fix: rootNotes example --- tutorial/tutorial.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index d62f3e79..031aa07c 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -769,9 +769,9 @@ Together with layer, struct and voicings, this can be used to create a basic bac ".layer( - x => x.voicings(['d3','g4']).struct("~ x"), - x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out())) -).note()`} + x => x.voicings(['d3','g4']).struct("~ x").note(), + x => x.rootNotes(2).note().s('sawtooth').cutoff(800) +)`} /> From 483c3f61b56e1093241b3f8635f1252225a65351 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 17 Nov 2022 10:49:44 +0100 Subject: [PATCH 6/8] midi now works in the tutorial --- tutorial/tutorial.mdx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 031aa07c..76bc31e1 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -786,17 +786,15 @@ Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi). ### midi(outputName?) -Make sure to have a midi device connected or to use an IAC Driver. +Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages. If no outputName is given, it uses the first midi output it finds. -Midi is currently not supported by the mini repl used here, but you can [open the midi example in the repl](https://strudel.tidalcycles.org/#c3RhY2soIjxDXjcgQTcgRG03IEc3PiIubS52b2ljaW5ncygpLCAnPEMzIEEyIEQzIEcyPicubSkKICAubWlkaSgp). - -In the REPL, you will se a log of the available MIDI devices. - - +/> + +In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".` # Superdirt API From 2293f1ba1547cee03b1cf2b6c450038da8777d50 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 17 Nov 2022 10:52:07 +0100 Subject: [PATCH 7/8] fix: worklets now work in the tutorial again --- tutorial/Tutorial.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tutorial/Tutorial.jsx b/tutorial/Tutorial.jsx index 66117b46..1ec51187 100644 --- a/tutorial/Tutorial.jsx +++ b/tutorial/Tutorial.jsx @@ -10,6 +10,9 @@ import Tutorial from './tutorial.rendered.mdx'; // import ApiDoc from './ApiDoc'; import './style.scss'; import '@strudel.cycles/react/dist/style.css'; +import { initAudioOnFirstClick } from '@strudel.cycles/webaudio'; + +initAudioOnFirstClick(); ReactDOM.render( From 874633051bae74d07f8e1a1c0aadc3882732f97f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 17 Nov 2022 10:56:46 +0100 Subject: [PATCH 8/8] fix: snapshot --- package.json | 2 +- repl/package.json | 1 - .../test/__snapshots__/examples.test.mjs.snap | 16 ++++++++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index c299daf1..41225628 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test-coverage": "vitest --coverage", "bootstrap": "lerna bootstrap", "setup": "npm i && npm run bootstrap && cd repl && npm i && cd ../tutorial && npm i", - "snapshot": "cd repl && npm run snapshot", + "snapshot": "vitest run -u --silent", "repl": "cd repl && npm run dev", "osc": "cd packages/osc && npm run server", "build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build", diff --git a/repl/package.json b/repl/package.json index 88a5b1c0..97ac8dbd 100644 --- a/repl/package.json +++ b/repl/package.json @@ -11,7 +11,6 @@ "build": "vite build", "preview": "vite preview", "test": "vitest run --reporter verbose -v --no-isolate", - "snapshot": "vitest run -u --silent", "add-license": "cat etc/agpl-header.txt ../docs/static/js/*LICENSE.txt > /tmp/strudel-license.txt && cp /tmp/strudel-license.txt ../docs/static/js/*LICENSE.txt", "predeploy": "npm run build", "deploy": "gh-pages -d ../docs", diff --git a/tutorial/test/__snapshots__/examples.test.mjs.snap b/tutorial/test/__snapshots__/examples.test.mjs.snap index 305f7279..e97d20b7 100644 --- a/tutorial/test/__snapshots__/examples.test.mjs.snap +++ b/tutorial/test/__snapshots__/examples.test.mjs.snap @@ -767,10 +767,10 @@ exports[`runs examples > example "dry" example index 0 1`] = ` exports[`runs examples > example "each" example index 0 1`] = ` [ - "3/4 -> 1/1: {\\"note\\":\\"c3\\"}", - "1/2 -> 3/4: {\\"note\\":\\"d3\\"}", - "1/4 -> 1/2: {\\"note\\":\\"e3\\"}", - "0/1 -> 1/4: {\\"note\\":\\"g3\\"}", + "0/1 -> 1/4: {\\"note\\":\\"c3\\"}", + "1/4 -> 1/2: {\\"note\\":\\"d3\\"}", + "1/2 -> 3/4: {\\"note\\":\\"e3\\"}", + "3/4 -> 1/1: {\\"note\\":\\"g3\\"}", "1/1 -> 5/4: {\\"note\\":\\"c3\\"}", "5/4 -> 3/2: {\\"note\\":\\"d3\\"}", "3/2 -> 7/4: {\\"note\\":\\"e3\\"}", @@ -779,10 +779,10 @@ exports[`runs examples > example "each" example index 0 1`] = ` "9/4 -> 5/2: {\\"note\\":\\"d3\\"}", "5/2 -> 11/4: {\\"note\\":\\"e3\\"}", "11/4 -> 3/1: {\\"note\\":\\"g3\\"}", - "3/1 -> 13/4: {\\"note\\":\\"c3\\"}", - "13/4 -> 7/2: {\\"note\\":\\"d3\\"}", - "7/2 -> 15/4: {\\"note\\":\\"e3\\"}", - "15/4 -> 4/1: {\\"note\\":\\"g3\\"}", + "15/4 -> 4/1: {\\"note\\":\\"c3\\"}", + "7/2 -> 15/4: {\\"note\\":\\"d3\\"}", + "13/4 -> 7/2: {\\"note\\":\\"e3\\"}", + "3/1 -> 13/4: {\\"note\\":\\"g3\\"}", ] `;