From b2c6d876338a2209c2a7e6324d77581f16e078a0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 13 Jan 2023 15:13:39 +0100 Subject: [PATCH] MiniRepl: add punchcard flag for implicit vis + reintroduce editPattern + add punchcards to mini-notation.mdx --- packages/core/repl.mjs | 3 +- packages/react/dist/index.cjs.js | 2 +- packages/react/dist/index.es.js | 542 ++++++++++--------- packages/react/src/components/MiniRepl.jsx | 4 +- packages/react/src/hooks/usePatternFrame.mjs | 2 +- packages/react/src/hooks/useStrudel.mjs | 2 + website/src/docs/MiniRepl.jsx | 10 +- website/src/pages/learn/getting-started.mdx | 2 +- website/src/pages/learn/mini-notation.mdx | 58 +- website/src/pages/technical-manual/docs.mdx | 8 +- 10 files changed, 323 insertions(+), 310 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 83d3e51a..045f2102 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -13,7 +13,7 @@ export function repl({ getTime, transpiler, onToggle, - drawContext, + editPattern, }) { const scheduler = new Cyclist({ interval, @@ -45,6 +45,7 @@ export function repl({ let { pattern } = await _evaluate(code, transpiler); logger(`[eval] code updated`); + pattern = editPattern?.(pattern) || pattern; scheduler.setPattern(pattern, autostart); afterEval?.({ code, pattern }); return pattern; diff --git a/packages/react/dist/index.cjs.js b/packages/react/dist/index.cjs.js index 58d94e08..272a8138 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"),ie=require("@uiw/react-codemirror"),y=require("@codemirror/view"),P=require("@codemirror/state"),ue=require("@codemirror/lang-javascript"),i=require("@lezer/highlight"),de=require("@uiw/codemirror-themes"),G=require("@strudel.cycles/webaudio"),fe=require("react-hook-inview"),Q=require("@strudel.cycles/core"),ge=require("@strudel.cycles/transpiler"),X=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},u=X(t),me=X(ie),he=de.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:i.tags.keyword,color:"#c792ea"},{tag:i.tags.operator,color:"#89ddff"},{tag:i.tags.special(i.tags.variableName),color:"#eeffff"},{tag:i.tags.typeName,color:"#c3e88d"},{tag:i.tags.atom,color:"#f78c6c"},{tag:i.tags.number,color:"#c3e88d"},{tag:i.tags.definition(i.tags.variableName),color:"#82aaff"},{tag:i.tags.string,color:"#c3e88d"},{tag:i.tags.special(i.tags.string),color:"#c3e88d"},{tag:i.tags.comment,color:"#7d8799"},{tag:i.tags.variableName,color:"#c792ea"},{tag:i.tags.tagName,color:"#c3e88d"},{tag:i.tags.bracket,color:"#525154"},{tag:i.tags.meta,color:"#ffcb6b"},{tag:i.tags.attributeName,color:"#c792ea"},{tag:i.tags.propertyName,color:"#c792ea"},{tag:i.tags.className,color:"#decb6b"},{tag:i.tags.invalid,color:"#ffffff"}]});const T=P.StateEffect.define(),pe=P.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(T))if(r.value){const a=y.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=y.Decoration.set([a.range(0,o.newDoc.length)])}else e=y.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>y.EditorView.decorations.from(e)}),Y=e=>{e.dispatch({effects:T.of(!0)}),setTimeout(()=>{e.dispatch({effects:T.of(!1)})},200)},z=P.StateEffect.define(),ve=P.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(z)){const a=r.value.map(d=>(d.context.locations||[]).map(({start:m,end:f})=>{const s=d.context.color||"#FFCA28";let c=o.newDoc.line(m.line).from+m.column,h=o.newDoc.line(f.line).from+f.column;const p=o.newDoc.length;return c>p||h>p?void 0:y.Decoration.mark({attributes:{style:`outline: 1.5px solid ${s};`}}).range(c,h)})).flat().filter(Boolean)||[];e=y.Decoration.set(a,!0)}return e}catch{return y.Decoration.set([])}},provide:e=>y.EditorView.decorations.from(e)}),be=[ue.javascript(),he,ve,pe];function Z({value:e,onChange:o,onViewChanged:r,onSelectionChange:a,options:d,editorDidMount:m}){const f=t.useCallback(h=>{o?.(h)},[o]),s=t.useCallback(h=>{r?.(h)},[r]),c=t.useCallback(h=>{h.selectionSet&&a&&a?.(h.state.selection)},[a]);return u.default.createElement(u.default.Fragment,null,u.default.createElement(me.default,{value:e,onChange:f,onCreateEditor:s,onUpdate:c,extensions:be}))}function U(...e){return e.filter(Boolean).join(" ")}function ee({view:e,pattern:o,active:r,getTime:a}){const d=t.useRef([]),m=t.useRef();t.useEffect(()=>{if(e)if(o&&r){let f=requestAnimationFrame(function s(){try{const c=a(),p=[Math.max(m.current||c,c-1/10,0),c+1/60];m.current=p[1],d.current=d.current.filter(v=>v.whole.end>c);const n=o.queryArc(...p).filter(v=>v.hasOnset());d.current=d.current.concat(n),e.dispatch({effects:z.of(d.current)})}catch{e.dispatch({effects:z.of([])})}f=requestAnimationFrame(s)});return()=>{cancelAnimationFrame(f)}}else d.current=[],e.dispatch({effects:z.of([])})},[o,r,e])}function Ee(e,o=!1){const r=t.useRef(),a=t.useRef(),d=s=>{if(a.current!==void 0){const c=s-a.current;e(s,c)}a.current=s,r.current=requestAnimationFrame(d)},m=()=>{r.current=requestAnimationFrame(d)},f=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(f(),m())},[e]),t.useEffect(()=>(o&&m(),f),[]),{start:m,stop:f}}function ye({pattern:e,started:o,getTime:r,onDraw:a,drawTime:d=[-2,2]}){let[m,f]=d;m=Math.abs(m);let s=t.useRef([]),c=t.useRef(null);t.useEffect(()=>{if(e){const n=r(),v=e.queryArc(n,n+f);s.current=s.current.filter(E=>E.whole.begin{const n=r()+f;if(c.current===null){c.current=n;return}const v=e.queryArc(Math.max(c.current,n-1/10),n);c.current=n,s.current=(s.current||[]).filter(E=>E.whole.end>=n-m-f).concat(v.filter(E=>E.hasOnset())),a(e,n-f,s.current,d)},[e]));return t.useEffect(()=>{o?h():(s.current=[],p())},[o]),{clear:()=>{s.current=[]}}}function te(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(o=>window.postMessage(o,"*"),[])}function re({defaultOutput:e,interval:o,getTime:r,evalOnMount:a=!1,initialCode:d="",autolink:m=!1,beforeEval:f,afterEval:s,onEvalError:c,onToggle:h,canvasId:p,drawContext:n,drawTime:v=[-2,2]}){const E=t.useMemo(()=>we(),[]);p=p||`canvas-${E}`;const[q,S]=t.useState(),[F,A]=t.useState(),[w,C]=t.useState(d),[N,V]=t.useState(),[M,L]=t.useState(),[D,H]=t.useState(!1),O=w!==N,{scheduler:l,evaluate:b,start:W,stop:x,pause:oe}=t.useMemo(()=>Q.repl({interval:o,defaultOutput:e,onSchedulerError:S,onEvalError:g=>{A(g),c?.(g)},getTime:r,drawContext:n,transpiler:ge.transpiler,beforeEval:({code:g})=>{C(g),f?.()},afterEval:({pattern:g,code:k})=>{V(k),L(g),A(),S(),m&&(window.location.hash="#"+encodeURIComponent(btoa(k))),s?.()},onToggle:g=>{H(g),h?.(g)}}),[e,o,r]),ne=te(({data:{from:g,type:k}})=>{k==="start"&&g!==E&&x()}),j=t.useCallback(async(g=!0)=>{const k=await b(w,g);return ne({type:"start",from:E}),k},[b,w]),R=t.useCallback((g,k,I,K)=>{const{onPaint:ce}=g.context||{},le=typeof n=="function"?n(p):n;ce?.(le,k,I,K)},[n,p]),B=t.useCallback(g=>{if(n&&R){const[k,I]=v,K=g.queryArc(0,I);R(g,0,K,v)}},[v,R]),$=t.useRef();t.useEffect(()=>{!$.current&&n&&R&&a&&w&&($.current=!0,b(w,!1).then(g=>B(g)))},[j,a,w,B]),t.useEffect(()=>()=>{l.stop()},[l]);const ae=async()=>{D?(l.stop(),B(M)):await j()},se=q||F;return ye({pattern:M,started:n&&D,getTime:()=>l.now(),drawTime:v,onDraw:R}),{id:E,canvasId:p,code:w,setCode:C,error:se,schedulerError:q,scheduler:l,evalError:F,evaluate:b,activateCode:j,activeCode:N,isDirty:O,pattern:M,started:D,start:W,stop:x,pause:oe,togglePlay:ae}}function we(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function J({type:e}){return u.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:u.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:u.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:u.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"}),stop:u.default.createElement("path",{fillRule:"evenodd",d:"M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z",clipRule:"evenodd"})}[e])}const ke="_container_3i85k_1",_e="_header_3i85k_5",Me="_buttons_3i85k_9",Ce="_button_3i85k_9",Fe="_buttonDisabled_3i85k_17",De="_error_3i85k_21",Re="_body_3i85k_25",_={container:ke,header:_e,buttons:Me,button:Ce,buttonDisabled:Fe,error:De,body:Re},qe=()=>G.getAudioContext().currentTime;function Se({tune:e,hideOutsideView:o=!1,enableKeyboard:r,drawTime:a,canvasHeight:d=200}){const{code:m,setCode:f,evaluate:s,activateCode:c,error:h,isDirty:p,activeCode:n,pattern:v,started:E,scheduler:q,togglePlay:S,stop:F,canvasId:A,id:w}=re({initialCode:e,defaultOutput:G.webaudioOutput,getTime:qe,evalOnMount:!!a,drawContext:a?l=>document.querySelector("#"+l)?.getContext("2d"):null,drawTime:a}),[C,N]=t.useState(),[V,M]=fe.useInView({threshold:.01}),L=t.useRef(),D=t.useMemo(()=>((M||!o)&&(L.current=!0),M||L.current),[M,o]);ee({view:C,pattern:v,active:E&&!n?.includes("strudel disable-highlighting"),getTime:()=>q.now()}),t.useLayoutEffect(()=>{if(r){const l=async b=>{(b.ctrlKey||b.altKey)&&(b.code==="Enter"?(b.preventDefault(),Y(C),await c()):b.code==="Period"&&(F(),b.preventDefault()))};return window.addEventListener("keydown",l,!0),()=>window.removeEventListener("keydown",l,!0)}},[r,v,m,s,F,C]);const[H,O]=t.useState([]);return Ae(t.useCallback(l=>{const{data:b}=l.detail;b?.hap?.context?.id===w&&O(x=>x.concat([l.detail]).slice(-10))},[])),u.default.createElement("div",{className:_.container,ref:V},u.default.createElement("div",{className:_.header},u.default.createElement("div",{className:_.buttons},u.default.createElement("button",{className:U(_.button,E?"sc-animate-pulse":""),onClick:()=>S()},u.default.createElement(J,{type:E?"stop":"play"})),u.default.createElement("button",{className:U(p?_.button:_.buttonDisabled),onClick:()=>c()},u.default.createElement(J,{type:"refresh"}))),h&&u.default.createElement("div",{className:_.error},h.message)),u.default.createElement("div",{className:_.body},D&&u.default.createElement(Z,{value:m,onChange:f,onViewChanged:N})),a&&u.default.createElement("canvas",{id:A,className:"w-full pointer-events-none",height:d,ref:l=>{l&&l.width!==l.clientWidth&&(l.width=l.clientWidth)}}),!!H.length&&u.default.createElement("div",{className:"sc-bg-gray-800 sc-rounded-md sc-p-2"},H.map(({message:l},b)=>u.default.createElement("div",{key:b},l))))}function Ae(e){Ne(Q.logger.key,e)}function Ne(e,o,r=!1){t.useEffect(()=>(document.addEventListener(e,o,r),()=>{document.removeEventListener(e,o,r)}),[o])}const Le=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=Z;exports.MiniRepl=Se;exports.cx=U;exports.flash=Y;exports.useHighlighting=ee;exports.useKeydown=Le;exports.usePostMessage=te;exports.useStrudel=re; +"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),ue=require("@uiw/react-codemirror"),y=require("@codemirror/view"),V=require("@codemirror/state"),de=require("@codemirror/lang-javascript"),d=require("@lezer/highlight"),fe=require("@uiw/codemirror-themes"),T=require("@strudel.cycles/webaudio"),ge=require("react-hook-inview"),X=require("@strudel.cycles/core"),me=require("@strudel.cycles/transpiler"),Y=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},f=Y(t),he=Y(ue),pe=fe.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:d.tags.keyword,color:"#c792ea"},{tag:d.tags.operator,color:"#89ddff"},{tag:d.tags.special(d.tags.variableName),color:"#eeffff"},{tag:d.tags.typeName,color:"#c3e88d"},{tag:d.tags.atom,color:"#f78c6c"},{tag:d.tags.number,color:"#c3e88d"},{tag:d.tags.definition(d.tags.variableName),color:"#82aaff"},{tag:d.tags.string,color:"#c3e88d"},{tag:d.tags.special(d.tags.string),color:"#c3e88d"},{tag:d.tags.comment,color:"#7d8799"},{tag:d.tags.variableName,color:"#c792ea"},{tag:d.tags.tagName,color:"#c3e88d"},{tag:d.tags.bracket,color:"#525154"},{tag:d.tags.meta,color:"#ffcb6b"},{tag:d.tags.attributeName,color:"#c792ea"},{tag:d.tags.propertyName,color:"#c792ea"},{tag:d.tags.className,color:"#decb6b"},{tag:d.tags.invalid,color:"#ffffff"}]});const W=V.StateEffect.define(),ve=V.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(W))if(r.value){const n=y.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=y.Decoration.set([n.range(0,o.newDoc.length)])}else e=y.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>y.EditorView.decorations.from(e)}),Z=e=>{e.dispatch({effects:W.of(!0)}),setTimeout(()=>{e.dispatch({effects:W.of(!1)})},200)},z=V.StateEffect.define(),be=V.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(z)){const n=r.value.map(c=>(c.context.locations||[]).map(({start:m,end:l})=>{const s=c.context.color||"#FFCA28";let i=o.newDoc.line(m.line).from+m.column,p=o.newDoc.line(l.line).from+l.column;const E=o.newDoc.length;return i>E||p>E?void 0:y.Decoration.mark({attributes:{style:`outline: 1.5px solid ${s};`}}).range(i,p)})).flat().filter(Boolean)||[];e=y.Decoration.set(n,!0)}return e}catch{return y.Decoration.set([])}},provide:e=>y.EditorView.decorations.from(e)}),Ee=[de.javascript(),pe,be,ve];function ee({value:e,onChange:o,onViewChanged:r,onSelectionChange:n,options:c,editorDidMount:m}){const l=t.useCallback(p=>{o?.(p)},[o]),s=t.useCallback(p=>{r?.(p)},[r]),i=t.useCallback(p=>{p.selectionSet&&n&&n?.(p.state.selection)},[n]);return f.default.createElement(f.default.Fragment,null,f.default.createElement(he.default,{value:e,onChange:l,onCreateEditor:s,onUpdate:i,extensions:Ee}))}function $(...e){return e.filter(Boolean).join(" ")}function te({view:e,pattern:o,active:r,getTime:n}){const c=t.useRef([]),m=t.useRef();t.useEffect(()=>{if(e)if(o&&r){let l=requestAnimationFrame(function s(){try{const i=n(),E=[Math.max(m.current||i,i-1/10,0),i+1/60];m.current=E[1],c.current=c.current.filter(h=>h.whole.end>i);const u=o.queryArc(...E).filter(h=>h.hasOnset());c.current=c.current.concat(u),e.dispatch({effects:z.of(c.current)})}catch{e.dispatch({effects:z.of([])})}l=requestAnimationFrame(s)});return()=>{cancelAnimationFrame(l)}}else c.current=[],e.dispatch({effects:z.of([])})},[o,r,e])}function ye(e,o=!1){const r=t.useRef(),n=t.useRef(),c=s=>{if(n.current!==void 0){const i=s-n.current;e(s,i)}n.current=s,r.current=requestAnimationFrame(c)},m=()=>{r.current=requestAnimationFrame(c)},l=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(l(),m())},[e]),t.useEffect(()=>(o&&m(),l),[]),{start:m,stop:l}}function we({pattern:e,started:o,getTime:r,onDraw:n,drawTime:c=[-2,2]}){let[m,l]=c;m=Math.abs(m);let s=t.useRef([]),i=t.useRef(null);t.useEffect(()=>{if(e){const u=r(),h=e.queryArc(u,u+l+.1);s.current=s.current.filter(b=>b.whole.begin{const u=r()+l;if(i.current===null){i.current=u;return}const h=e.queryArc(Math.max(i.current,u-1/10),u);i.current=u,s.current=(s.current||[]).filter(b=>b.whole.end>=u-m-l).concat(h.filter(b=>b.hasOnset())),n(e,u-l,s.current,c)},[e]));return t.useEffect(()=>{o?p():(s.current=[],E())},[o]),{clear:()=>{s.current=[]}}}function re(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(o=>window.postMessage(o,"*"),[])}function oe({defaultOutput:e,interval:o,getTime:r,evalOnMount:n=!1,initialCode:c="",autolink:m=!1,beforeEval:l,editPattern:s,afterEval:i,onEvalError:p,onToggle:E,canvasId:u,drawContext:h,drawTime:b=[-2,2]}){const _=t.useMemo(()=>ke(),[]);u=u||`canvas-${_}`;const[S,A]=t.useState(),[D,N]=t.useState(),[w,F]=t.useState(c),[L,O]=t.useState(),[C,H]=t.useState(),[R,P]=t.useState(!1),j=w!==L,{scheduler:a,evaluate:v,start:J,stop:x,pause:ne}=t.useMemo(()=>X.repl({interval:o,defaultOutput:e,onSchedulerError:A,onEvalError:g=>{N(g),p?.(g)},getTime:r,drawContext:h,transpiler:me.transpiler,editPattern:s,beforeEval:({code:g})=>{F(g),l?.()},afterEval:({pattern:g,code:k})=>{O(k),H(g),N(),A(),m&&(window.location.hash="#"+encodeURIComponent(btoa(k))),i?.()},onToggle:g=>{P(g),E?.(g)}}),[e,o,r]),ae=re(({data:{from:g,type:k}})=>{k==="start"&&g!==_&&x()}),B=t.useCallback(async(g=!0)=>{const k=await v(w,g);return ae({type:"start",from:_}),k},[v,w]),q=t.useCallback((g,k,K,U)=>{const{onPaint:le}=g.context||{},ie=typeof h=="function"?h(u):h;le?.(ie,k,K,U)},[h,u]),I=t.useCallback(g=>{if(h&&q){const[k,K]=b,U=g.queryArc(0,K);q(g,0,U,b)}},[b,q]),G=t.useRef();t.useEffect(()=>{!G.current&&h&&q&&n&&w&&(G.current=!0,v(w,!1).then(g=>I(g)))},[B,n,w,I]),t.useEffect(()=>()=>{a.stop()},[a]);const se=async()=>{R?(a.stop(),I(C)):await B()},ce=S||D;return we({pattern:C,started:h&&R,getTime:()=>a.now(),drawTime:b,onDraw:q}),{id:_,canvasId:u,code:w,setCode:F,error:ce,schedulerError:S,scheduler:a,evalError:D,evaluate:v,activateCode:B,activeCode:L,isDirty:j,pattern:C,started:R,start:J,stop:x,pause:ne,togglePlay:se}}function ke(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function Q({type:e}){return f.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:f.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:f.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:f.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"}),stop:f.default.createElement("path",{fillRule:"evenodd",d:"M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z",clipRule:"evenodd"})}[e])}const _e="_container_3i85k_1",Me="_header_3i85k_5",Ce="_buttons_3i85k_9",Fe="_button_3i85k_9",De="_buttonDisabled_3i85k_17",Re="_error_3i85k_21",qe="_body_3i85k_25",M={container:_e,header:Me,buttons:Ce,button:Fe,buttonDisabled:De,error:Re,body:qe},Se=()=>T.getAudioContext().currentTime;function Ae({tune:e,hideOutsideView:o=!1,enableKeyboard:r,drawTime:n,punchcard:c,canvasHeight:m=200}){n=n||(c?[0,4]:void 0);const{code:l,setCode:s,evaluate:i,activateCode:p,error:E,isDirty:u,activeCode:h,pattern:b,started:_,scheduler:S,togglePlay:A,stop:D,canvasId:N,id:w}=oe({initialCode:e,defaultOutput:T.webaudioOutput,editPattern:a=>c?a.punchcard():a,getTime:Se,evalOnMount:!!n,drawContext:n?a=>document.querySelector("#"+a)?.getContext("2d"):null,drawTime:n}),[F,L]=t.useState(),[O,C]=ge.useInView({threshold:.01}),H=t.useRef(),R=t.useMemo(()=>((C||!o)&&(H.current=!0),C||H.current),[C,o]);te({view:F,pattern:b,active:_&&!h?.includes("strudel disable-highlighting"),getTime:()=>S.now()}),t.useLayoutEffect(()=>{if(r){const a=async v=>{(v.ctrlKey||v.altKey)&&(v.code==="Enter"?(v.preventDefault(),Z(F),await p()):v.code==="Period"&&(D(),v.preventDefault()))};return window.addEventListener("keydown",a,!0),()=>window.removeEventListener("keydown",a,!0)}},[r,b,l,i,D,F]);const[P,j]=t.useState([]);return Ne(t.useCallback(a=>{const{data:v}=a.detail;v?.hap?.context?.id===w&&j(x=>x.concat([a.detail]).slice(-10))},[])),f.default.createElement("div",{className:M.container,ref:O},f.default.createElement("div",{className:M.header},f.default.createElement("div",{className:M.buttons},f.default.createElement("button",{className:$(M.button,_?"sc-animate-pulse":""),onClick:()=>A()},f.default.createElement(Q,{type:_?"stop":"play"})),f.default.createElement("button",{className:$(u?M.button:M.buttonDisabled),onClick:()=>p()},f.default.createElement(Q,{type:"refresh"}))),E&&f.default.createElement("div",{className:M.error},E.message)),f.default.createElement("div",{className:M.body},R&&f.default.createElement(ee,{value:l,onChange:s,onViewChanged:L})),n&&f.default.createElement("canvas",{id:N,className:"w-full pointer-events-none",height:m,ref:a=>{a&&a.width!==a.clientWidth&&(a.width=a.clientWidth)}}),!!P.length&&f.default.createElement("div",{className:"sc-bg-gray-800 sc-rounded-md sc-p-2"},P.map(({message:a},v)=>f.default.createElement("div",{key:v},a))))}function Ne(e){Le(X.logger.key,e)}function Le(e,o,r=!1){t.useEffect(()=>(document.addEventListener(e,o,r),()=>{document.removeEventListener(e,o,r)}),[o])}const He=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=ee;exports.MiniRepl=Ae;exports.cx=$;exports.flash=Z;exports.useHighlighting=te;exports.useKeydown=He;exports.usePostMessage=re;exports.useStrudel=oe; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index eca17af6..20cbfecd 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -1,15 +1,15 @@ -import l, { useCallback as w, useRef as A, useEffect as k, useMemo as $, useState as _, useLayoutEffect as Y } from "react"; -import ie from "@uiw/react-codemirror"; -import { Decoration as M, EditorView as Z } from "@codemirror/view"; -import { StateEffect as ee, StateField as te } from "@codemirror/state"; -import { javascript as le } from "@codemirror/lang-javascript"; -import { tags as i } from "@lezer/highlight"; -import { createTheme as ue } from "@uiw/codemirror-themes"; -import { webaudioOutput as de, getAudioContext as fe } from "@strudel.cycles/webaudio"; -import { useInView as me } from "react-hook-inview"; -import { repl as he, logger as ge } from "@strudel.cycles/core"; -import { transpiler as pe } from "@strudel.cycles/transpiler"; -const ve = ue({ +import d, { useCallback as w, useRef as N, useEffect as k, useMemo as G, useState as M, useLayoutEffect as T } from "react"; +import le from "@uiw/react-codemirror"; +import { Decoration as A, EditorView as ee } from "@codemirror/view"; +import { StateEffect as te, StateField as re } from "@codemirror/state"; +import { javascript as ue } from "@codemirror/lang-javascript"; +import { tags as u } from "@lezer/highlight"; +import { createTheme as de } from "@uiw/codemirror-themes"; +import { webaudioOutput as fe, getAudioContext as me } from "@strudel.cycles/webaudio"; +import { useInView as he } from "react-hook-inview"; +import { repl as ge, logger as pe } from "@strudel.cycles/core"; +import { transpiler as ve } from "@strudel.cycles/transpiler"; +const be = de({ theme: "dark", settings: { background: "#222", @@ -22,413 +22,417 @@ const ve = ue({ gutterForeground: "#8a919966" }, styles: [ - { tag: i.keyword, color: "#c792ea" }, - { tag: i.operator, color: "#89ddff" }, - { tag: i.special(i.variableName), color: "#eeffff" }, - { tag: i.typeName, color: "#c3e88d" }, - { tag: i.atom, color: "#f78c6c" }, - { tag: i.number, color: "#c3e88d" }, - { tag: i.definition(i.variableName), color: "#82aaff" }, - { tag: i.string, color: "#c3e88d" }, - { tag: i.special(i.string), color: "#c3e88d" }, - { tag: i.comment, color: "#7d8799" }, - { tag: i.variableName, color: "#c792ea" }, - { tag: i.tagName, color: "#c3e88d" }, - { tag: i.bracket, color: "#525154" }, - { tag: i.meta, color: "#ffcb6b" }, - { tag: i.attributeName, color: "#c792ea" }, - { tag: i.propertyName, color: "#c792ea" }, - { tag: i.className, color: "#decb6b" }, - { tag: i.invalid, color: "#ffffff" } + { tag: u.keyword, color: "#c792ea" }, + { tag: u.operator, color: "#89ddff" }, + { tag: u.special(u.variableName), color: "#eeffff" }, + { tag: u.typeName, color: "#c3e88d" }, + { tag: u.atom, color: "#f78c6c" }, + { tag: u.number, color: "#c3e88d" }, + { tag: u.definition(u.variableName), color: "#82aaff" }, + { tag: u.string, color: "#c3e88d" }, + { tag: u.special(u.string), color: "#c3e88d" }, + { tag: u.comment, color: "#7d8799" }, + { tag: u.variableName, color: "#c792ea" }, + { tag: u.tagName, color: "#c3e88d" }, + { tag: u.bracket, color: "#525154" }, + { tag: u.meta, color: "#ffcb6b" }, + { tag: u.attributeName, color: "#c792ea" }, + { tag: u.propertyName, color: "#c792ea" }, + { tag: u.className, color: "#decb6b" }, + { tag: u.invalid, color: "#ffffff" } ] }); -const G = ee.define(), be = te.define({ +const J = te.define(), Ee = re.define({ create() { - return M.none; + return A.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(G)) + if (t.is(J)) if (t.value) { - const o = M.mark({ attributes: { style: "background-color: #FFCA2880" } }); - e = M.set([o.range(0, r.newDoc.length)]); + const n = A.mark({ attributes: { style: "background-color: #FFCA2880" } }); + e = A.set([n.range(0, r.newDoc.length)]); } else - e = M.set([]); + e = A.set([]); return e; } catch (t) { return console.warn("flash error", t), e; } }, - provide: (e) => Z.decorations.from(e) -}), Ee = (e) => { - e.dispatch({ effects: G.of(!0) }), setTimeout(() => { - e.dispatch({ effects: G.of(!1) }); + provide: (e) => ee.decorations.from(e) +}), ye = (e) => { + e.dispatch({ effects: J.of(!0) }), setTimeout(() => { + e.dispatch({ effects: J.of(!1) }); }, 200); -}, O = ee.define(), ye = te.define({ +}, B = te.define(), we = re.define({ create() { - return M.none; + return A.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(O)) { - const o = t.value.map( - (u) => (u.context.locations || []).map(({ start: m, end: d }) => { - const a = u.context.color || "#FFCA28"; - let c = r.newDoc.line(m.line).from + m.column, h = r.newDoc.line(d.line).from + d.column; - const g = r.newDoc.length; - return c > g || h > g ? void 0 : M.mark({ attributes: { style: `outline: 1.5px solid ${a};` } }).range(c, h); + if (t.is(B)) { + const n = t.value.map( + (c) => (c.context.locations || []).map(({ start: m, end: s }) => { + const a = c.context.color || "#FFCA28"; + let i = r.newDoc.line(m.line).from + m.column, g = r.newDoc.line(s.line).from + s.column; + const b = r.newDoc.length; + return i > b || g > b ? void 0 : A.mark({ attributes: { style: `outline: 1.5px solid ${a};` } }).range(i, g); }) ).flat().filter(Boolean) || []; - e = M.set(o, !0); + e = A.set(n, !0); } return e; } catch { - return M.set([]); + return A.set([]); } }, - provide: (e) => Z.decorations.from(e) -}), we = [le(), ve, ye, be]; -function ke({ value: e, onChange: r, onViewChanged: t, onSelectionChange: o, options: u, editorDidMount: m }) { - const d = w( - (h) => { - r?.(h); + provide: (e) => ee.decorations.from(e) +}), ke = [ue(), be, we, Ee]; +function Fe({ value: e, onChange: r, onViewChanged: t, onSelectionChange: n, options: c, editorDidMount: m }) { + const s = w( + (g) => { + r?.(g); }, [r] ), a = w( - (h) => { - t?.(h); + (g) => { + t?.(g); }, [t] - ), c = w( - (h) => { - h.selectionSet && o && o?.(h.state.selection); + ), i = w( + (g) => { + g.selectionSet && n && n?.(g.state.selection); }, - [o] + [n] ); - return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(ie, { + return /* @__PURE__ */ d.createElement(d.Fragment, null, /* @__PURE__ */ d.createElement(le, { value: e, - onChange: d, + onChange: s, onCreateEditor: a, - onUpdate: c, - extensions: we + onUpdate: i, + extensions: ke })); } -function T(...e) { +function Y(...e) { return e.filter(Boolean).join(" "); } -function Fe({ view: e, pattern: r, active: t, getTime: o }) { - const u = A([]), m = A(); +function _e({ view: e, pattern: r, active: t, getTime: n }) { + const c = N([]), m = N(); k(() => { if (e) if (r && t) { - let d = requestAnimationFrame(function a() { + let s = requestAnimationFrame(function a() { try { - const c = o(), g = [Math.max(m.current || c, c - 1 / 10, 0), c + 1 / 60]; - m.current = g[1], u.current = u.current.filter((p) => p.whole.end > c); - const n = r.queryArc(...g).filter((p) => p.hasOnset()); - u.current = u.current.concat(n), e.dispatch({ effects: O.of(u.current) }); + const i = n(), b = [Math.max(m.current || i, i - 1 / 10, 0), i + 1 / 60]; + m.current = b[1], c.current = c.current.filter((h) => h.whole.end > i); + const l = r.queryArc(...b).filter((h) => h.hasOnset()); + c.current = c.current.concat(l), e.dispatch({ effects: B.of(c.current) }); } catch { - e.dispatch({ effects: O.of([]) }); + e.dispatch({ effects: B.of([]) }); } - d = requestAnimationFrame(a); + s = requestAnimationFrame(a); }); return () => { - cancelAnimationFrame(d); + cancelAnimationFrame(s); }; } else - u.current = [], e.dispatch({ effects: O.of([]) }); + c.current = [], e.dispatch({ effects: B.of([]) }); }, [r, t, e]); } -function _e(e, r = !1) { - const t = A(), o = A(), u = (a) => { - if (o.current !== void 0) { - const c = a - o.current; - e(a, c); +function Me(e, r = !1) { + const t = N(), n = N(), c = (a) => { + if (n.current !== void 0) { + const i = a - n.current; + e(a, i); } - o.current = a, t.current = requestAnimationFrame(u); + n.current = a, t.current = requestAnimationFrame(c); }, m = () => { - t.current = requestAnimationFrame(u); - }, d = () => { + t.current = requestAnimationFrame(c); + }, s = () => { t.current && cancelAnimationFrame(t.current), delete t.current; }; return k(() => { - t.current && (d(), m()); - }, [e]), k(() => (r && m(), d), []), { + t.current && (s(), m()); + }, [e]), k(() => (r && m(), s), []), { start: m, - stop: d + stop: s }; } -function Me({ pattern: e, started: r, getTime: t, onDraw: o, drawTime: u = [-2, 2] }) { - let [m, d] = u; +function Ae({ pattern: e, started: r, getTime: t, onDraw: n, drawTime: c = [-2, 2] }) { + let [m, s] = c; m = Math.abs(m); - let a = A([]), c = A(null); + let a = N([]), i = N(null); k(() => { if (e) { - const n = t(), p = e.queryArc(n, n + d); - a.current = a.current.filter((b) => b.whole.begin < n), a.current = a.current.concat(p); + const l = t(), h = e.queryArc(l, l + s + 0.1); + a.current = a.current.filter((v) => v.whole.begin < l), a.current = a.current.concat(h); } }, [e]); - const { start: h, stop: g } = _e( + const { start: g, stop: b } = Me( w(() => { - const n = t() + d; - if (c.current === null) { - c.current = n; + const l = t() + s; + if (i.current === null) { + i.current = l; return; } - const p = e.queryArc(Math.max(c.current, n - 1 / 10), n); - c.current = n, a.current = (a.current || []).filter((b) => b.whole.end >= n - m - d).concat(p.filter((b) => b.hasOnset())), o(e, n - d, a.current, u); + const h = e.queryArc(Math.max(i.current, l - 1 / 10), l); + i.current = l, a.current = (a.current || []).filter((v) => v.whole.end >= l - m - s).concat(h.filter((v) => v.hasOnset())), n(e, l - s, a.current, c); }, [e]) ); return k(() => { - r ? h() : (a.current = [], g()); + r ? g() : (a.current = [], b()); }, [r]), { clear: () => { a.current = []; } }; } -function Ae(e) { +function Ne(e) { return k(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), w((r) => window.postMessage(r, "*"), []); } -function Ne({ +function De({ defaultOutput: e, interval: r, getTime: t, - evalOnMount: o = !1, - initialCode: u = "", + evalOnMount: n = !1, + initialCode: c = "", autolink: m = !1, - beforeEval: d, - afterEval: a, - onEvalError: c, - onToggle: h, - canvasId: g, - drawContext: n, - drawTime: p = [-2, 2] + beforeEval: s, + editPattern: a, + afterEval: i, + onEvalError: g, + onToggle: b, + canvasId: l, + drawContext: h, + drawTime: v = [-2, 2] }) { - const b = $(() => De(), []); - g = g || `canvas-${b}`; - const [q, x] = _(), [C, z] = _(), [E, D] = _(u), [H, B] = _(), [N, P] = _(), [R, S] = _(!1), I = E !== H, { scheduler: s, evaluate: v, start: J, stop: V, pause: re } = $( - () => he({ + const F = G(() => Ce(), []); + l = l || `canvas-${F}`; + const [x, z] = M(), [R, H] = M(), [E, C] = M(c), [P, I] = M(), [D, S] = M(), [L, V] = M(!1), K = E !== P, { scheduler: o, evaluate: p, start: Q, stop: O, pause: ne } = G( + () => ge({ interval: r, defaultOutput: e, - onSchedulerError: x, + onSchedulerError: z, onEvalError: (f) => { - z(f), c?.(f); + H(f), g?.(f); }, getTime: t, - drawContext: n, - transpiler: pe, + drawContext: h, + transpiler: ve, + editPattern: a, beforeEval: ({ code: f }) => { - D(f), d?.(); + C(f), s?.(); }, afterEval: ({ pattern: f, code: y }) => { - B(y), P(f), z(), x(), m && (window.location.hash = "#" + encodeURIComponent(btoa(y))), a?.(); + I(y), S(f), H(), z(), m && (window.location.hash = "#" + encodeURIComponent(btoa(y))), i?.(); }, onToggle: (f) => { - S(f), h?.(f); + V(f), b?.(f); } }), [e, r, t] - ), ne = Ae(({ data: { from: f, type: y } }) => { - y === "start" && f !== b && V(); - }), K = w( + ), oe = Ne(({ data: { from: f, type: y } }) => { + y === "start" && f !== F && O(); + }), j = w( async (f = !0) => { - const y = await v(E, f); - return ne({ type: "start", from: b }), y; + const y = await p(E, f); + return oe({ type: "start", from: F }), y; }, - [v, E] - ), L = w( - (f, y, U, W) => { - const { onPaint: ce } = f.context || {}, se = typeof n == "function" ? n(g) : n; - ce?.(se, y, U, W); + [p, E] + ), q = w( + (f, y, W, $) => { + const { onPaint: se } = f.context || {}, ie = typeof h == "function" ? h(l) : h; + se?.(ie, y, W, $); }, - [n, g] - ), j = w( + [h, l] + ), U = w( (f) => { - if (n && L) { - const [y, U] = p, W = f.queryArc(0, U); - L(f, 0, W, p); + if (h && q) { + const [y, W] = v, $ = f.queryArc(0, W); + q(f, 0, $, v); } }, - [p, L] - ), Q = A(); + [v, q] + ), X = N(); k(() => { - !Q.current && n && L && o && E && (Q.current = !0, v(E, !1).then((f) => j(f))); - }, [K, o, E, j]), k(() => () => { - s.stop(); - }, [s]); - const oe = async () => { - R ? (s.stop(), j(N)) : await K(); - }, ae = q || C; - return Me({ - pattern: N, - started: n && R, - getTime: () => s.now(), - drawTime: p, - onDraw: L + !X.current && h && q && n && E && (X.current = !0, p(E, !1).then((f) => U(f))); + }, [j, n, E, U]), k(() => () => { + o.stop(); + }, [o]); + const ae = async () => { + L ? (o.stop(), U(D)) : await j(); + }, ce = x || R; + return Ae({ + pattern: D, + started: h && L, + getTime: () => o.now(), + drawTime: v, + onDraw: q }), { - id: b, - canvasId: g, + id: F, + canvasId: l, code: E, - setCode: D, - error: ae, - schedulerError: q, - scheduler: s, - evalError: C, - evaluate: v, - activateCode: K, - activeCode: H, - isDirty: I, - pattern: N, - started: R, - start: J, - stop: V, - pause: re, - togglePlay: oe + setCode: C, + error: ce, + schedulerError: x, + scheduler: o, + evalError: R, + evaluate: p, + activateCode: j, + activeCode: P, + isDirty: K, + pattern: D, + started: L, + start: Q, + stop: O, + pause: ne, + togglePlay: ae }; } -function De() { +function Ce() { return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); } -function X({ type: e }) { - return /* @__PURE__ */ l.createElement("svg", { +function Z({ type: e }) { + return /* @__PURE__ */ d.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "sc-h-5 sc-w-5", viewBox: "0 0 20 20", fill: "currentColor" }, { - refresh: /* @__PURE__ */ l.createElement("path", { + refresh: /* @__PURE__ */ d.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: /* @__PURE__ */ l.createElement("path", { + play: /* @__PURE__ */ d.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: /* @__PURE__ */ l.createElement("path", { + pause: /* @__PURE__ */ d.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" }), - stop: /* @__PURE__ */ l.createElement("path", { + stop: /* @__PURE__ */ d.createElement("path", { fillRule: "evenodd", d: "M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z", clipRule: "evenodd" }) }[e]); } -const Ce = "_container_3i85k_1", Re = "_header_3i85k_5", Le = "_buttons_3i85k_9", qe = "_button_3i85k_9", xe = "_buttonDisabled_3i85k_17", ze = "_error_3i85k_21", He = "_body_3i85k_25", F = { - container: Ce, - header: Re, - buttons: Le, - button: qe, - buttonDisabled: xe, - error: ze, - body: He -}, Pe = () => fe().currentTime; -function Te({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, drawTime: o, canvasHeight: u = 200 }) { +const Re = "_container_3i85k_1", Le = "_header_3i85k_5", qe = "_buttons_3i85k_9", xe = "_button_3i85k_9", ze = "_buttonDisabled_3i85k_17", He = "_error_3i85k_21", Pe = "_body_3i85k_25", _ = { + container: Re, + header: Le, + buttons: qe, + button: xe, + buttonDisabled: ze, + error: He, + body: Pe +}, Se = () => me().currentTime; +function Ye({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, drawTime: n, punchcard: c, canvasHeight: m = 200 }) { + n = n || (c ? [0, 4] : void 0); const { - code: m, - setCode: d, - evaluate: a, - activateCode: c, - error: h, - isDirty: g, - activeCode: n, - pattern: p, - started: b, - scheduler: q, - togglePlay: x, - stop: C, - canvasId: z, + code: s, + setCode: a, + evaluate: i, + activateCode: g, + error: b, + isDirty: l, + activeCode: h, + pattern: v, + started: F, + scheduler: x, + togglePlay: z, + stop: R, + canvasId: H, id: E - } = Ne({ + } = De({ initialCode: e, - defaultOutput: de, - getTime: Pe, - evalOnMount: !!o, - drawContext: o ? (s) => document.querySelector("#" + s)?.getContext("2d") : null, - drawTime: o - }), [D, H] = _(), [B, N] = me({ + defaultOutput: fe, + editPattern: (o) => c ? o.punchcard() : o, + getTime: Se, + evalOnMount: !!n, + drawContext: n ? (o) => document.querySelector("#" + o)?.getContext("2d") : null, + drawTime: n + }), [C, P] = M(), [I, D] = he({ threshold: 0.01 - }), P = A(), R = $(() => ((N || !r) && (P.current = !0), N || P.current), [N, r]); - Fe({ - view: D, - pattern: p, - active: b && !n?.includes("strudel disable-highlighting"), - getTime: () => q.now() - }), Y(() => { + }), S = N(), L = G(() => ((D || !r) && (S.current = !0), D || S.current), [D, r]); + _e({ + view: C, + pattern: v, + active: F && !h?.includes("strudel disable-highlighting"), + getTime: () => x.now() + }), T(() => { if (t) { - const s = async (v) => { - (v.ctrlKey || v.altKey) && (v.code === "Enter" ? (v.preventDefault(), Ee(D), await c()) : v.code === "Period" && (C(), v.preventDefault())); + const o = async (p) => { + (p.ctrlKey || p.altKey) && (p.code === "Enter" ? (p.preventDefault(), ye(C), await g()) : p.code === "Period" && (R(), p.preventDefault())); }; - return window.addEventListener("keydown", s, !0), () => window.removeEventListener("keydown", s, !0); + return window.addEventListener("keydown", o, !0), () => window.removeEventListener("keydown", o, !0); } - }, [t, p, m, a, C, D]); - const [S, I] = _([]); - return Se( - w((s) => { - const { data: v } = s.detail; - v?.hap?.context?.id === E && I((V) => V.concat([s.detail]).slice(-10)); + }, [t, v, s, i, R, C]); + const [V, K] = M([]); + return Ve( + w((o) => { + const { data: p } = o.detail; + p?.hap?.context?.id === E && K((O) => O.concat([o.detail]).slice(-10)); }, []) - ), /* @__PURE__ */ l.createElement("div", { - className: F.container, - ref: B - }, /* @__PURE__ */ l.createElement("div", { - className: F.header - }, /* @__PURE__ */ l.createElement("div", { - className: F.buttons - }, /* @__PURE__ */ l.createElement("button", { - className: T(F.button, b ? "sc-animate-pulse" : ""), - onClick: () => x() - }, /* @__PURE__ */ l.createElement(X, { - type: b ? "stop" : "play" - })), /* @__PURE__ */ l.createElement("button", { - className: T(g ? F.button : F.buttonDisabled), - onClick: () => c() - }, /* @__PURE__ */ l.createElement(X, { + ), /* @__PURE__ */ d.createElement("div", { + className: _.container, + ref: I + }, /* @__PURE__ */ d.createElement("div", { + className: _.header + }, /* @__PURE__ */ d.createElement("div", { + className: _.buttons + }, /* @__PURE__ */ d.createElement("button", { + className: Y(_.button, F ? "sc-animate-pulse" : ""), + onClick: () => z() + }, /* @__PURE__ */ d.createElement(Z, { + type: F ? "stop" : "play" + })), /* @__PURE__ */ d.createElement("button", { + className: Y(l ? _.button : _.buttonDisabled), + onClick: () => g() + }, /* @__PURE__ */ d.createElement(Z, { type: "refresh" - }))), h && /* @__PURE__ */ l.createElement("div", { - className: F.error - }, h.message)), /* @__PURE__ */ l.createElement("div", { - className: F.body - }, R && /* @__PURE__ */ l.createElement(ke, { - value: m, - onChange: d, - onViewChanged: H - })), o && /* @__PURE__ */ l.createElement("canvas", { - id: z, + }))), b && /* @__PURE__ */ d.createElement("div", { + className: _.error + }, b.message)), /* @__PURE__ */ d.createElement("div", { + className: _.body + }, L && /* @__PURE__ */ d.createElement(Fe, { + value: s, + onChange: a, + onViewChanged: P + })), n && /* @__PURE__ */ d.createElement("canvas", { + id: H, className: "w-full pointer-events-none", - height: u, - ref: (s) => { - s && s.width !== s.clientWidth && (s.width = s.clientWidth); + height: m, + ref: (o) => { + o && o.width !== o.clientWidth && (o.width = o.clientWidth); } - }), !!S.length && /* @__PURE__ */ l.createElement("div", { + }), !!V.length && /* @__PURE__ */ d.createElement("div", { className: "sc-bg-gray-800 sc-rounded-md sc-p-2" - }, S.map(({ message: s }, v) => /* @__PURE__ */ l.createElement("div", { - key: v - }, s)))); + }, V.map(({ message: o }, p) => /* @__PURE__ */ d.createElement("div", { + key: p + }, o)))); } -function Se(e) { - Ve(ge.key, e); +function Ve(e) { + Oe(pe.key, e); } -function Ve(e, r, t = !1) { +function Oe(e, r, t = !1) { k(() => (document.addEventListener(e, r, t), () => { document.removeEventListener(e, r, t); }), [r]); } -const Xe = (e) => Y(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); +const Ze = (e) => T(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); export { - ke as CodeMirror, - Te as MiniRepl, - T as cx, - Ee as flash, - Fe as useHighlighting, - Xe as useKeydown, - Ae as usePostMessage, - Ne as useStrudel + Fe as CodeMirror, + Ye as MiniRepl, + Y as cx, + ye as flash, + _e as useHighlighting, + Ze as useKeydown, + Ne as usePostMessage, + De as useStrudel }; diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index 3496b9d3..dc03b467 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -13,7 +13,8 @@ import { logger } from '@strudel.cycles/core'; const getTime = () => getAudioContext().currentTime; -export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTime, canvasHeight = 200 }) { +export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTime, punchcard, canvasHeight = 200 }) { + drawTime = drawTime || (punchcard ? [0, 4] : undefined); const { code, setCode, @@ -32,6 +33,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTi } = useStrudel({ initialCode: tune, defaultOutput: webaudioOutput, + editPattern: (pat) => (punchcard ? pat.punchcard() : pat), getTime, evalOnMount: !!drawTime, drawContext: !!drawTime ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null, diff --git a/packages/react/src/hooks/usePatternFrame.mjs b/packages/react/src/hooks/usePatternFrame.mjs index 711af7df..175097fd 100644 --- a/packages/react/src/hooks/usePatternFrame.mjs +++ b/packages/react/src/hooks/usePatternFrame.mjs @@ -10,7 +10,7 @@ function usePatternFrame({ pattern, started, getTime, onDraw, drawTime = [-2, 2] useEffect(() => { if (pattern) { const t = getTime(); - const futureHaps = pattern.queryArc(t, t + lookahead); + const futureHaps = pattern.queryArc(t, 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); } diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index 738076b5..85d1a4cd 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -12,6 +12,7 @@ function useStrudel({ initialCode = '', autolink = false, beforeEval, + editPattern, afterEval, onEvalError, onToggle, @@ -44,6 +45,7 @@ function useStrudel({ getTime, drawContext, transpiler, + editPattern, beforeEval: ({ code }) => { setCode(code); beforeEval?.(); diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index d18c3ab4..425d18a3 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -22,7 +22,7 @@ if (typeof window !== 'undefined') { prebake(); } -export function MiniRepl({ tune, drawTime }) { +export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) { const [Repl, setRepl] = useState(); useEffect(() => { // we have to load this package on the client @@ -31,5 +31,11 @@ export function MiniRepl({ tune, drawTime }) { setRepl(() => res.MiniRepl); }); }, []); - return Repl ? :
{tune}
; + return Repl ? ( +
+ +
+ ) : ( +
{tune}
+ ); } diff --git a/website/src/pages/learn/getting-started.mdx b/website/src/pages/learn/getting-started.mdx index be858ed5..2ad890e3 100644 --- a/website/src/pages/learn/getting-started.mdx +++ b/website/src/pages/learn/getting-started.mdx @@ -28,7 +28,7 @@ Strudel however runs directly in your web browser, does not require any custom s The main place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/) ([what is a REPL?](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)), but in these pages you will also encounter interactive "MiniREPLs" where you can listen to and edit Strudel patterns. Try clicking the play icon below: - + Then edit the text so it reads `s("bd sd cp hh")` and click the refresh icon. Congratulations, you have now live coded your first Strudel pattern! diff --git a/website/src/pages/learn/mini-notation.mdx b/website/src/pages/learn/mini-notation.mdx index 873ac404..4c0a0242 100644 --- a/website/src/pages/learn/mini-notation.mdx +++ b/website/src/pages/learn/mini-notation.mdx @@ -51,12 +51,12 @@ If you do just want to get a regular string that is _not_ parsed as mini-notatio We can play more notes by separating them with spaces: - + Here, those four notes are squashed into one cycle, so each note is a quarter second long. Try adding or removing notes and notice how the tempo changes! - + Note that the overall duration of time does not change, and instead each note length descreases. This is a key idea, as it illustrates the 'Cycle' in TidalCycles! @@ -72,28 +72,28 @@ But, it will begin to make sense as we go through more elements of mini-notation We can slow the sequence down by enclosing it in brackets and dividing it by a number (`/2`): - + The division by two means that the sequence will be played over the course of two cycles. You can also use decimal numbers for any tempo you like (`/2.75`). - + ## Angle Brackets Using angle brackets `<>`, we can define the sequence length based on the number of events: -")`} /> +")`} punchcard /> The above snippet is the same as: - + The advantage of the angle brackets, is that we can add more events without needing to change the number at the end. -")`} /> +")`} punchcard /> -")`} /> +")`} punchcard /> This is more similar to traditional music sequencers and piano rolls, where adding a note increases the perceived overall duration. @@ -101,15 +101,13 @@ This is more similar to traditional music sequencers and piano rolls, where addi Contrary to division, a sequence can be sped up by multiplying it by a number using the asterisk symbol (`*`): - + The multiplication by two here means that the sequence will play twice a cycle. As with divisions, multiplications can be decimal (`*2.75`): - - -Actually, this is not true, but this will be [fixed](https://github.com/tidalcycles/strudel/issues/314) :) + ## Subdividing time with bracket nesting @@ -133,7 +131,7 @@ Well, what this means is that in TidalCycles, not only can you divide time any w The "~" represents a rest, and will create silence between other events: - + ## Parallel / polyphony @@ -141,17 +139,17 @@ Using commas, we can play chords. The following are the same: - + But to play multiple chords in a sequence, we have to wrap them in brackets: -")`} /> +")`} punchcard /> ## Elongation With the "@" symbol, we can specify temporal "weight" of a sequence child: -")`} /> +")`} punchcard /> Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1. @@ -159,7 +157,7 @@ Here, the first chord has a weight of 2, making it twice the length of the other Using "!" we can repeat without speeding up: -")`} /> +")`} punchcard /> In essence, the `x!n` is like a shortcut for `[x*n]@n`. @@ -181,24 +179,24 @@ Using round brackets after an event, we can create rhythmical sub-divisions base This algorithm can be found in many different types of music software, and is often referred to as a [Euclidean rhythm](https://en.wikipedia.org/wiki/Euclidean_rhythm) sequencer, after computer scientist Godfriend Toussaint. Why is it interesting? Well, consider the following simple example: - + Sound familiar? This is a popular Euclidian rhythm going by various names, such as "Pop Clave". These rhythms can be found in all musical cultures, and the Euclidian rhythm algorithm allows us to express them extremely easily. Writing this rhythm out in full require describing: - + But using the Euclidian rhythm notation, we only need to express "3 beats over 8 segments, starting on position 1". This makes it easy to write patterns with interesting rhythmic structures and variations that still sound familiar: - + Note that since the example above does not use the third `offset` parameter, it can be written simply as `"(3,8)"`. - + Let's look at those three parameters in detail. @@ -207,26 +205,26 @@ Let's look at those three parameters in detail. `beats`: the first parameter controls how may beats will be played. Compare these: - - - + + + ### Segments `segments`: the second parameter controls the total amount of segments the beats will be distributed over: - - - + + + ### Offsets `offset`: the third (optional) parameter controls the starting position for distributing the beats. We need a secondary rhythm to hear the difference: - - - + + + ## Mini-notation exercise diff --git a/website/src/pages/technical-manual/docs.mdx b/website/src/pages/technical-manual/docs.mdx index 48f8903d..502702af 100644 --- a/website/src/pages/technical-manual/docs.mdx +++ b/website/src/pages/technical-manual/docs.mdx @@ -29,11 +29,11 @@ add a mini repl with - `client:idle` is required to tell astro that the repl should be interactive, see [Client Directive](https://docs.astro.build/en/reference/directives-reference/#client-directives) - `tune`: be any valid pattern code -- `drawTime`: time window for drawing. Use together with `.punchcard()`. Example: +- `punchcard`: if added, a punchcard / pianoroll visualization is renderd +- `drawTime`: time window for drawing, defaults to `[0, 4]` +- `canvasHeight`: height of the canvas, defaults to 100px -```jsx - -``` +See `mini-notation.mdx` for usage examples ## In-Source Documentation