From 1ac784dc7a933d72e9645cf03463454f6b3e1af8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 13 Jan 2023 12:03:34 +0100 Subject: [PATCH] localize draw logic --- packages/core/draw.mjs | 6 + packages/core/pattern.mjs | 1 + packages/core/pianoroll.mjs | 10 + packages/core/repl.mjs | 5 +- packages/react/dist/index.cjs.js | 2 +- packages/react/dist/index.es.js | 553 ++++++++++--------- packages/react/src/components/Icon.tsx | 7 + packages/react/src/components/MiniRepl.jsx | 29 +- packages/react/src/hooks/usePatternFrame.mjs | 19 +- packages/react/src/hooks/useStrudel.mjs | 47 +- website/src/docs/MiniRepl.jsx | 4 +- website/src/pages/learn/getting-started.mdx | 2 +- website/src/pages/learn/mini-notation.mdx | 4 +- website/src/pages/technical-manual/docs.mdx | 6 +- website/src/repl/Repl.jsx | 10 +- 15 files changed, 397 insertions(+), 308 deletions(-) diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index 7260e654..4bfd3257 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -59,3 +59,9 @@ export const cleanupDraw = (clearScreen = true) => { clearInterval(window.strudelScheduler); } }; + +Pattern.prototype.onPaint = function (onPaint) { + // this is evil! TODO: add pattern.context + this.context = { onPaint }; + return this; +}; diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index a11841a6..5e8bc786 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2060,6 +2060,7 @@ export const velocity = register('velocity', function (velocity, pat) { */ // TODO - fix export const legato = register('legato', function (value, pat) { + value = Fraction(value); return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value)))); }); diff --git a/packages/core/pianoroll.mjs b/packages/core/pianoroll.mjs index 34ce1d4b..b8a597f1 100644 --- a/packages/core/pianoroll.mjs +++ b/packages/core/pianoroll.mjs @@ -283,3 +283,13 @@ export function pianoroll({ ctx.stroke(); return this; } + +Pattern.prototype.noteroll = function (options = { fold: 1 }) { + return this.onPaint((ctx, time, haps, drawTime) => { + let [lookbehind, lookahead] = drawTime; + lookbehind = Math.abs(lookbehind); + const cycles = lookahead + lookbehind; + const playhead = lookbehind / cycles; + pianoroll({ ctx, time, haps, ...options, cycles, playhead }); + }); +}; diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index c71019ac..83d3e51a 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -12,8 +12,8 @@ export function repl({ afterEval, getTime, transpiler, - editPattern, onToggle, + drawContext, }) { const scheduler = new Cyclist({ interval, @@ -35,7 +35,7 @@ export function repl({ getTime, onToggle, }); - setTime(() => scheduler.getPhase()); // TODO: refactor? + setTime(() => scheduler.now()); // TODO: refactor? const evaluate = async (code, autostart = true) => { if (!code) { throw new Error('no code to evaluate'); @@ -45,7 +45,6 @@ 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 5e9d79dc..86132d69 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"),ee=require("@uiw/react-codemirror"),E=require("@codemirror/view"),H=require("@codemirror/state"),te=require("@codemirror/lang-javascript"),i=require("@lezer/highlight"),re=require("@uiw/codemirror-themes"),B=require("@strudel.cycles/core"),W=require("@strudel.cycles/webaudio"),ae=require("react-hook-inview"),oe=require("@strudel.cycles/transpiler"),$=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},u=$(t),ne=$(ee),se=re.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 j=H.StateEffect.define(),ce=H.StateField.define({create(){return E.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(j))if(r.value){const s=E.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=E.Decoration.set([s.range(0,a.newDoc.length)])}else e=E.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>E.EditorView.decorations.from(e)}),J=e=>{e.dispatch({effects:j.of(!0)}),setTimeout(()=>{e.dispatch({effects:j.of(!1)})},200)},P=H.StateEffect.define(),ie=H.StateField.define({create(){return E.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(P)){const s=r.value.map(n=>(n.context.locations||[]).map(({start:l,end:d})=>{const m=n.context.color||"#FFCA28";let o=a.newDoc.line(l.line).from+l.column,f=a.newDoc.line(d.line).from+d.column;const b=a.newDoc.length;return o>b||f>b?void 0:E.Decoration.mark({attributes:{style:`outline: 1.5px solid ${m};`}}).range(o,f)})).flat().filter(Boolean)||[];e=E.Decoration.set(s,!0)}return e}catch{return E.Decoration.set([])}},provide:e=>E.EditorView.decorations.from(e)}),le=[te.javascript(),se,ie,ce];function G({value:e,onChange:a,onViewChanged:r,onSelectionChange:s,options:n,editorDidMount:l}){const d=t.useCallback(f=>{a?.(f)},[a]),m=t.useCallback(f=>{r?.(f)},[r]),o=t.useCallback(f=>{f.selectionSet&&s&&s?.(f.state.selection)},[s]);return u.default.createElement(u.default.Fragment,null,u.default.createElement(ne.default,{value:e,onChange:d,onCreateEditor:m,onUpdate:o,extensions:le}))}function O(...e){return e.filter(Boolean).join(" ")}function Q({view:e,pattern:a,active:r,getTime:s}){const n=t.useRef([]),l=t.useRef();t.useEffect(()=>{if(e)if(a&&r){let d=requestAnimationFrame(function m(){try{const o=s(),b=[Math.max(l.current||o,o-1/10,0),o+1/60];l.current=b[1],n.current=n.current.filter(p=>p.whole.end>o);const v=a.queryArc(...b).filter(p=>p.hasOnset());n.current=n.current.concat(v),e.dispatch({effects:P.of(n.current)})}catch{e.dispatch({effects:P.of([])})}d=requestAnimationFrame(m)});return()=>{cancelAnimationFrame(d)}}else n.current=[],e.dispatch({effects:P.of([])})},[a,r,e])}function ue(e,a=!1){const r=t.useRef(),s=t.useRef(),n=m=>{if(s.current!==void 0){const o=m-s.current;e(m,o)}s.current=m,r.current=requestAnimationFrame(n)},l=()=>{r.current=requestAnimationFrame(n)},d=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(d(),l())},[e]),t.useEffect(()=>(a&&l(),d),[]),{start:l,stop:d}}function de({pattern:e,started:a,getTime:r,onDraw:s}){let n=t.useRef([]),l=t.useRef(null);const{start:d,stop:m}=ue(t.useCallback(()=>{const o=r();if(l.current===null){l.current=o;return}const f=e.queryArc(Math.max(l.current,o-1/10),o),b=4;l.current=o,n.current=(n.current||[]).filter(v=>v.whole.end>o-b).concat(f.filter(v=>v.hasOnset())),s(o,n.current)},[e]));t.useEffect(()=>{a?d():(n.current=[],m())},[a])}function X(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(a=>window.postMessage(a,"*"),[])}function Y({defaultOutput:e,interval:a,getTime:r,evalOnMount:s=!1,initialCode:n="",autolink:l=!1,beforeEval:d,afterEval:m,editPattern:o,onEvalError:f,onToggle:b,canvasId:v}){const p=t.useMemo(()=>fe(),[]);v=v||`canvas-${p}`;const[k,D]=t.useState(),[q,F]=t.useState(),[w,N]=t.useState(n),[C,T]=t.useState(),[z,R]=t.useState(),[S,V]=t.useState(!1),x=w!==C,{scheduler:M,evaluate:c,start:g,stop:_,pause:I}=t.useMemo(()=>B.repl({interval:a,defaultOutput:e,onSchedulerError:D,onEvalError:h=>{F(h),f?.(h)},getTime:r,transpiler:oe.transpiler,beforeEval:({code:h})=>{N(h),d?.()},editPattern:o?h=>o(h,p):void 0,afterEval:({pattern:h,code:L})=>{T(L),R(h),F(),D(),l&&(window.location.hash="#"+encodeURIComponent(btoa(L))),m?.()},onToggle:h=>{V(h),b?.(h)}}),[e,a,r]),Z=X(({data:{from:h,type:L}})=>{L==="start"&&h!==p&&_()}),A=t.useCallback(async(h=!0)=>{await c(w,h),Z({type:"start",from:p})},[c,w]),K=t.useRef();return t.useEffect(()=>{!K.current&&s&&w&&(K.current=!0,A())},[A,s,w]),t.useEffect(()=>()=>{M.stop()},[M]),{id:p,canvasId:v,code:w,setCode:N,error:k||q,schedulerError:k,scheduler:M,evalError:q,evaluate:c,activateCode:A,activeCode:C,isDirty:x,pattern:z,started:S,start:g,stop:_,pause:I,togglePlay:async()=>{S?M.pause():await A()}}}function fe(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function U({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"})}[e])}const ge="_container_3i85k_1",me="_header_3i85k_5",he="_buttons_3i85k_9",pe="_button_3i85k_9",ve="_buttonDisabled_3i85k_17",be="_error_3i85k_21",Ee="_body_3i85k_25",y={container:ge,header:me,buttons:he,button:pe,buttonDisabled:ve,error:be,body:Ee},we=()=>W.getAudioContext().currentTime;function ye({tune:e,hideOutsideView:a=!1,enableKeyboard:r,withCanvas:s=!1,canvasHeight:n=200}){const{code:l,setCode:d,evaluate:m,activateCode:o,error:f,isDirty:b,activeCode:v,pattern:p,started:k,scheduler:D,togglePlay:q,stop:F,canvasId:w,id:N}=Y({initialCode:e,defaultOutput:W.webaudioOutput,getTime:we,editPattern:(c,g)=>c.withContext(_=>({..._,id:g}))});de({pattern:p,started:s&&k,getTime:()=>D.now(),onDraw:(c,g)=>{const _=document.querySelector("#"+w).getContext("2d");B.pianoroll({ctx:_,time:c,haps:g,autorange:1,fold:1,playhead:1})}});const[C,T]=t.useState(),[z,R]=ae.useInView({threshold:.01}),S=t.useRef(),V=t.useMemo(()=>((R||!a)&&(S.current=!0),R||S.current),[R,a]);Q({view:C,pattern:p,active:k&&!v?.includes("strudel disable-highlighting"),getTime:()=>D.getPhase()}),t.useLayoutEffect(()=>{if(r){const c=async g=>{(g.ctrlKey||g.altKey)&&(g.code==="Enter"?(g.preventDefault(),J(C),await o()):g.code==="Period"&&(F(),g.preventDefault()))};return window.addEventListener("keydown",c,!0),()=>window.removeEventListener("keydown",c,!0)}},[r,p,l,m,F,C]);const[x,M]=t.useState([]);return ke(t.useCallback(c=>{const{data:g}=c.detail;g?.hap?.context?.id===N&&M(I=>I.concat([c.detail]).slice(-10))},[])),u.default.createElement("div",{className:y.container,ref:z},u.default.createElement("div",{className:y.header},u.default.createElement("div",{className:y.buttons},u.default.createElement("button",{className:O(y.button,k?"sc-animate-pulse":""),onClick:()=>q()},u.default.createElement(U,{type:k?"pause":"play"})),u.default.createElement("button",{className:O(b?y.button:y.buttonDisabled),onClick:()=>o()},u.default.createElement(U,{type:"refresh"}))),f&&u.default.createElement("div",{className:y.error},f.message)),u.default.createElement("div",{className:y.body},V&&u.default.createElement(G,{value:l,onChange:d,onViewChanged:T})),s&&u.default.createElement("canvas",{id:w,className:"w-full pointer-events-none",height:n,ref:c=>{c&&c.width!==c.clientWidth&&(c.width=c.clientWidth)}}),!!x.length&&u.default.createElement("div",{className:"sc-bg-gray-800 sc-rounded-md sc-p-2"},x.map(({message:c},g)=>u.default.createElement("div",{key:g},c))))}function ke(e){_e(B.logger.key,e)}function _e(e,a,r=!1){t.useEffect(()=>(document.addEventListener(e,a,r),()=>{document.removeEventListener(e,a,r)}),[a])}const Ce=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=G;exports.MiniRepl=ye;exports.cx=O;exports.flash=J;exports.useHighlighting=Q;exports.useKeydown=Ce;exports.usePostMessage=X;exports.useStrudel=Y; +"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]));t.useEffect(()=>{o?h():(s.current=[],p())},[o])}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; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index d0027424..c72163df 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -1,15 +1,15 @@ -import l, { useCallback as N, useRef as k, useEffect as _, useMemo as K, useState as w, useLayoutEffect as G } from "react"; -import Z from "@uiw/react-codemirror"; -import { Decoration as y, EditorView as J } from "@codemirror/view"; -import { StateEffect as Q, StateField as X } from "@codemirror/state"; -import { javascript as ee } from "@codemirror/lang-javascript"; -import { tags as s } from "@lezer/highlight"; -import { createTheme as te } from "@uiw/codemirror-themes"; -import { repl as re, logger as ne, pianoroll as oe } from "@strudel.cycles/core"; -import { webaudioOutput as ae, getAudioContext as ce } from "@strudel.cycles/webaudio"; -import { useInView as se } from "react-hook-inview"; -import { transpiler as ie } from "@strudel.cycles/transpiler"; -const le = te({ +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({ theme: "dark", settings: { background: "#222", @@ -22,241 +22,273 @@ const le = te({ gutterForeground: "#8a919966" }, styles: [ - { tag: s.keyword, color: "#c792ea" }, - { tag: s.operator, color: "#89ddff" }, - { tag: s.special(s.variableName), color: "#eeffff" }, - { tag: s.typeName, color: "#c3e88d" }, - { tag: s.atom, color: "#f78c6c" }, - { tag: s.number, color: "#c3e88d" }, - { tag: s.definition(s.variableName), color: "#82aaff" }, - { tag: s.string, color: "#c3e88d" }, - { tag: s.special(s.string), color: "#c3e88d" }, - { tag: s.comment, color: "#7d8799" }, - { tag: s.variableName, color: "#c792ea" }, - { tag: s.tagName, color: "#c3e88d" }, - { tag: s.bracket, color: "#525154" }, - { tag: s.meta, color: "#ffcb6b" }, - { tag: s.attributeName, color: "#c792ea" }, - { tag: s.propertyName, color: "#c792ea" }, - { tag: s.className, color: "#decb6b" }, - { tag: s.invalid, color: "#ffffff" } + { 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" } ] }); -const j = Q.define(), ue = X.define({ +const G = ee.define(), be = te.define({ create() { - return y.none; + return M.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(j)) + if (t.is(G)) if (t.value) { - const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } }); - e = y.set([a.range(0, r.newDoc.length)]); + const o = M.mark({ attributes: { style: "background-color: #FFCA2880" } }); + e = M.set([o.range(0, r.newDoc.length)]); } else - e = y.set([]); + e = M.set([]); return e; } catch (t) { return console.warn("flash error", t), e; } }, - provide: (e) => J.decorations.from(e) -}), de = (e) => { - e.dispatch({ effects: j.of(!0) }), setTimeout(() => { - e.dispatch({ effects: j.of(!1) }); + provide: (e) => Z.decorations.from(e) +}), Ee = (e) => { + e.dispatch({ effects: G.of(!0) }), setTimeout(() => { + e.dispatch({ effects: G.of(!1) }); }, 200); -}, z = Q.define(), fe = X.define({ +}, O = ee.define(), ye = te.define({ create() { - return y.none; + return M.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(z)) { - const a = t.value.map( - (o) => (o.context.locations || []).map(({ start: i, end: u }) => { - const m = o.context.color || "#FFCA28"; - let n = r.newDoc.line(i.line).from + i.column, d = r.newDoc.line(u.line).from + u.column; - const v = r.newDoc.length; - return n > v || d > v ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${m};` } }).range(n, d); + 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); }) ).flat().filter(Boolean) || []; - e = y.set(a, !0); + e = M.set(o, !0); } return e; } catch { - return y.set([]); + return M.set([]); } }, - provide: (e) => J.decorations.from(e) -}), me = [ee(), le, fe, ue]; -function ge({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: o, editorDidMount: i }) { - const u = N( - (d) => { - r?.(d); + 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); }, [r] - ), m = N( - (d) => { - t?.(d); + ), a = w( + (h) => { + t?.(h); }, [t] - ), n = N( - (d) => { - d.selectionSet && a && a?.(d.state.selection); + ), c = w( + (h) => { + h.selectionSet && o && o?.(h.state.selection); }, - [a] + [o] ); - return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(Z, { + return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(ie, { value: e, - onChange: u, - onCreateEditor: m, - onUpdate: n, - extensions: me + onChange: d, + onCreateEditor: a, + onUpdate: c, + extensions: we })); } -function W(...e) { +function T(...e) { return e.filter(Boolean).join(" "); } -function pe({ view: e, pattern: r, active: t, getTime: a }) { - const o = k([]), i = k(); - _(() => { +function Fe({ view: e, pattern: r, active: t, getTime: o }) { + const u = A([]), m = A(); + k(() => { if (e) if (r && t) { - let u = requestAnimationFrame(function m() { + let d = requestAnimationFrame(function a() { try { - const n = a(), v = [Math.max(i.current || n, n - 1 / 10, 0), n + 1 / 60]; - i.current = v[1], o.current = o.current.filter((p) => p.whole.end > n); - const h = r.queryArc(...v).filter((p) => p.hasOnset()); - o.current = o.current.concat(h), e.dispatch({ effects: z.of(o.current) }); + 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) }); } catch { - e.dispatch({ effects: z.of([]) }); + e.dispatch({ effects: O.of([]) }); } - u = requestAnimationFrame(m); + d = requestAnimationFrame(a); }); return () => { - cancelAnimationFrame(u); + cancelAnimationFrame(d); }; } else - o.current = [], e.dispatch({ effects: z.of([]) }); + u.current = [], e.dispatch({ effects: O.of([]) }); }, [r, t, e]); } -function he(e, r = !1) { - const t = k(), a = k(), o = (m) => { - if (a.current !== void 0) { - const n = m - a.current; - e(m, n); +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); } - a.current = m, t.current = requestAnimationFrame(o); - }, i = () => { - t.current = requestAnimationFrame(o); - }, u = () => { + o.current = a, t.current = requestAnimationFrame(u); + }, m = () => { + t.current = requestAnimationFrame(u); + }, d = () => { t.current && cancelAnimationFrame(t.current), delete t.current; }; - return _(() => { - t.current && (u(), i()); - }, [e]), _(() => (r && i(), u), []), { - start: i, - stop: u + return k(() => { + t.current && (d(), m()); + }, [e]), k(() => (r && m(), d), []), { + start: m, + stop: d }; } -function ve({ pattern: e, started: r, getTime: t, onDraw: a }) { - let o = k([]), i = k(null); - const { start: u, stop: m } = he( - N(() => { - const n = t(); - if (i.current === null) { - i.current = n; +function Me({ pattern: e, started: r, getTime: t, onDraw: o, drawTime: u = [-2, 2] }) { + let [m, d] = u; + m = Math.abs(m); + let a = A([]), c = A(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); + } + }, [e]); + const { start: h, stop: g } = _e( + w(() => { + const n = t() + d; + if (c.current === null) { + c.current = n; return; } - const d = e.queryArc(Math.max(i.current, n - 1 / 10), n), v = 4; - i.current = n, o.current = (o.current || []).filter((h) => h.whole.end > n - v).concat(d.filter((h) => h.hasOnset())), a(n, o.current); + 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); }, [e]) ); - _(() => { - r ? u() : (o.current = [], m()); + k(() => { + r ? h() : (a.current = [], g()); }, [r]); } -function be(e) { - return _(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []); +function Ae(e) { + return k(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), w((r) => window.postMessage(r, "*"), []); } -function Ee({ +function Ne({ defaultOutput: e, interval: r, getTime: t, - evalOnMount: a = !1, - initialCode: o = "", - autolink: i = !1, - beforeEval: u, - afterEval: m, - editPattern: n, - onEvalError: d, - onToggle: v, - canvasId: h + evalOnMount: o = !1, + initialCode: u = "", + autolink: m = !1, + beforeEval: d, + afterEval: a, + onEvalError: c, + onToggle: h, + canvasId: g, + drawContext: n, + drawTime: p = [-2, 2] }) { - const p = K(() => we(), []); - h = h || `canvas-${p}`; - const [F, A] = w(), [P, D] = w(), [b, q] = w(o), [x, V] = w(), [I, R] = w(), [L, B] = w(!1), H = b !== x, { scheduler: M, evaluate: c, start: f, stop: C, pause: O } = K( - () => re({ + 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({ interval: r, defaultOutput: e, - onSchedulerError: A, - onEvalError: (g) => { - D(g), d?.(g); + onSchedulerError: x, + onEvalError: (f) => { + z(f), c?.(f); }, getTime: t, - transpiler: ie, - beforeEval: ({ code: g }) => { - q(g), u?.(); + drawContext: n, + transpiler: pe, + beforeEval: ({ code: f }) => { + D(f), d?.(); }, - editPattern: n ? (g) => n(g, p) : void 0, - afterEval: ({ pattern: g, code: T }) => { - V(T), R(g), D(), A(), i && (window.location.hash = "#" + encodeURIComponent(btoa(T))), m?.(); + afterEval: ({ pattern: f, code: y }) => { + B(y), P(f), z(), x(), m && (window.location.hash = "#" + encodeURIComponent(btoa(y))), a?.(); }, - onToggle: (g) => { - B(g), v?.(g); + onToggle: (f) => { + S(f), h?.(f); } }), [e, r, t] - ), Y = be(({ data: { from: g, type: T } }) => { - T === "start" && g !== p && C(); - }), S = N( - async (g = !0) => { - await c(b, g), Y({ type: "start", from: p }); + ), ne = Ae(({ data: { from: f, type: y } }) => { + y === "start" && f !== b && V(); + }), K = w( + async (f = !0) => { + const y = await v(E, f); + return ne({ type: "start", from: b }), y; }, - [c, b] - ), U = k(); - return _(() => { - !U.current && a && b && (U.current = !0, S()); - }, [S, a, b]), _(() => () => { - M.stop(); - }, [M]), { - id: p, - canvasId: h, - code: b, - setCode: q, - error: F || P, - schedulerError: F, - scheduler: M, - evalError: P, - evaluate: c, - activateCode: S, - activeCode: x, - isDirty: H, - pattern: I, - started: L, - start: f, - stop: C, - pause: O, - togglePlay: async () => { - L ? M.pause() : await S(); - } + [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); + }, + [n, g] + ), j = w( + (f) => { + if (n && L) { + const [y, U] = p, W = f.queryArc(0, U); + L(f, 0, W, p); + } + }, + [p, L] + ), Q = A(); + 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 + }), { + id: b, + canvasId: g, + 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 }; } -function we() { +function De() { return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); } -function $({ type: e }) { +function X({ type: e }) { return /* @__PURE__ */ l.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "sc-h-5 sc-w-5", @@ -277,125 +309,122 @@ function $({ type: e }) { 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", { + 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 ye = "_container_3i85k_1", ke = "_header_3i85k_5", _e = "_buttons_3i85k_9", Fe = "_button_3i85k_9", Ce = "_buttonDisabled_3i85k_17", Ne = "_error_3i85k_21", xe = "_body_3i85k_25", E = { - container: ye, - header: ke, - buttons: _e, - button: Fe, - buttonDisabled: Ce, - error: Ne, - body: xe -}, Me = () => ce().currentTime; -function je({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: o = 200 }) { +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 { - code: i, - setCode: u, - evaluate: m, - activateCode: n, - error: d, - isDirty: v, - activeCode: h, + code: m, + setCode: d, + evaluate: a, + activateCode: c, + error: h, + isDirty: g, + activeCode: n, pattern: p, - started: F, - scheduler: A, - togglePlay: P, - stop: D, - canvasId: b, - id: q - } = Ee({ + started: b, + scheduler: q, + togglePlay: x, + stop: C, + canvasId: z, + id: E + } = Ne({ initialCode: e, - defaultOutput: ae, - getTime: Me, - editPattern: (c, f) => c.withContext((C) => ({ ...C, id: f })) - }); - ve({ - pattern: p, - started: a && F, - getTime: () => A.now(), - onDraw: (c, f) => { - const C = document.querySelector("#" + b).getContext("2d"); - oe({ ctx: C, time: c, haps: f, autorange: 1, fold: 1, playhead: 1 }); - } - }); - const [x, V] = w(), [I, R] = se({ + defaultOutput: de, + getTime: Pe, + evalOnMount: !!o, + drawContext: o ? (s) => document.querySelector("#" + s)?.getContext("2d") : null, + drawTime: o + }), [D, H] = _(), [B, N] = me({ threshold: 0.01 - }), L = k(), B = K(() => ((R || !r) && (L.current = !0), R || L.current), [R, r]); - pe({ - view: x, + }), P = A(), R = $(() => ((N || !r) && (P.current = !0), N || P.current), [N, r]); + Fe({ + view: D, pattern: p, - active: F && !h?.includes("strudel disable-highlighting"), - getTime: () => A.getPhase() - }), G(() => { + active: b && !n?.includes("strudel disable-highlighting"), + getTime: () => q.now() + }), Y(() => { if (t) { - const c = async (f) => { - (f.ctrlKey || f.altKey) && (f.code === "Enter" ? (f.preventDefault(), de(x), await n()) : f.code === "Period" && (D(), f.preventDefault())); + const s = async (v) => { + (v.ctrlKey || v.altKey) && (v.code === "Enter" ? (v.preventDefault(), Ee(D), await c()) : v.code === "Period" && (C(), v.preventDefault())); }; - return window.addEventListener("keydown", c, !0), () => window.removeEventListener("keydown", c, !0); + return window.addEventListener("keydown", s, !0), () => window.removeEventListener("keydown", s, !0); } - }, [t, p, i, m, D, x]); - const [H, M] = w([]); - return Ae( - N((c) => { - const { data: f } = c.detail; - f?.hap?.context?.id === q && M((O) => O.concat([c.detail]).slice(-10)); + }, [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)); }, []) ), /* @__PURE__ */ l.createElement("div", { - className: E.container, - ref: I + className: F.container, + ref: B }, /* @__PURE__ */ l.createElement("div", { - className: E.header + className: F.header }, /* @__PURE__ */ l.createElement("div", { - className: E.buttons + className: F.buttons }, /* @__PURE__ */ l.createElement("button", { - className: W(E.button, F ? "sc-animate-pulse" : ""), - onClick: () => P() - }, /* @__PURE__ */ l.createElement($, { - type: F ? "pause" : "play" + className: T(F.button, b ? "sc-animate-pulse" : ""), + onClick: () => x() + }, /* @__PURE__ */ l.createElement(X, { + type: b ? "stop" : "play" })), /* @__PURE__ */ l.createElement("button", { - className: W(v ? E.button : E.buttonDisabled), - onClick: () => n() - }, /* @__PURE__ */ l.createElement($, { + className: T(g ? F.button : F.buttonDisabled), + onClick: () => c() + }, /* @__PURE__ */ l.createElement(X, { type: "refresh" - }))), d && /* @__PURE__ */ l.createElement("div", { - className: E.error - }, d.message)), /* @__PURE__ */ l.createElement("div", { - className: E.body - }, B && /* @__PURE__ */ l.createElement(ge, { - value: i, - onChange: u, - onViewChanged: V - })), a && /* @__PURE__ */ l.createElement("canvas", { - id: b, + }))), 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, className: "w-full pointer-events-none", - height: o, - ref: (c) => { - c && c.width !== c.clientWidth && (c.width = c.clientWidth); + height: u, + ref: (s) => { + s && s.width !== s.clientWidth && (s.width = s.clientWidth); } - }), !!H.length && /* @__PURE__ */ l.createElement("div", { + }), !!S.length && /* @__PURE__ */ l.createElement("div", { className: "sc-bg-gray-800 sc-rounded-md sc-p-2" - }, H.map(({ message: c }, f) => /* @__PURE__ */ l.createElement("div", { - key: f - }, c)))); + }, S.map(({ message: s }, v) => /* @__PURE__ */ l.createElement("div", { + key: v + }, s)))); } -function Ae(e) { - De(ne.key, e); +function Se(e) { + Ve(ge.key, e); } -function De(e, r, t = !1) { - _(() => (document.addEventListener(e, r, t), () => { +function Ve(e, r, t = !1) { + k(() => (document.addEventListener(e, r, t), () => { document.removeEventListener(e, r, t); }), [r]); } -const Ue = (e) => G(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); +const Xe = (e) => Y(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); export { - ge as CodeMirror, - je as MiniRepl, - W as cx, - de as flash, - pe as useHighlighting, - Ue as useKeydown, - be as usePostMessage, - Ee as useStrudel + ke as CodeMirror, + Te as MiniRepl, + T as cx, + Ee as flash, + Fe as useHighlighting, + Xe as useKeydown, + Ae as usePostMessage, + Ne as useStrudel }; diff --git a/packages/react/src/components/Icon.tsx b/packages/react/src/components/Icon.tsx index 0ae722a3..28a8a374 100644 --- a/packages/react/src/components/Icon.tsx +++ b/packages/react/src/components/Icon.tsx @@ -26,6 +26,13 @@ export function Icon({ type }) { clipRule="evenodd" /> ), + stop: ( + + ), }[type] } diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index 70ebe22c..3496b9d3 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -1,11 +1,9 @@ -import { pianoroll } from '@strudel.cycles/core'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react'; import { useInView } from 'react-hook-inview'; import 'tailwindcss/tailwind.css'; import cx from '../cx'; import useHighlighting from '../hooks/useHighlighting.mjs'; -import usePatternFrame from '../hooks/usePatternFrame.mjs'; import useStrudel from '../hooks/useStrudel.mjs'; import CodeMirror6, { flash } from './CodeMirror6'; import { Icon } from './Icon'; @@ -15,7 +13,7 @@ import { logger } from '@strudel.cycles/core'; const getTime = () => getAudioContext().currentTime; -export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCanvas = false, canvasHeight = 200 }) { +export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTime, canvasHeight = 200 }) { const { code, setCode, @@ -35,24 +33,11 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa initialCode: tune, defaultOutput: webaudioOutput, getTime, - editPattern: (pat, id) => { - return pat.withContext((ctx) => ({ ...ctx, id })); - }, + evalOnMount: !!drawTime, + drawContext: !!drawTime ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null, + drawTime, }); - usePatternFrame({ - pattern, - started: withCanvas && started, - getTime: () => scheduler.now(), - onDraw: (time, haps) => { - const ctx = document.querySelector('#' + canvasId).getContext('2d'); - pianoroll({ ctx, time, haps, autorange: 1, fold: 1, playhead: 1 }); - }, - }); - - /* useEffect(() => { - init && activateCode(); - }, [init, activateCode]); */ const [view, setView] = useState(); const [ref, isVisible] = useInView({ threshold: 0.01, @@ -68,7 +53,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa view, pattern, active: started && !activeCode?.includes('strudel disable-highlighting'), - getTime: () => scheduler.getPhase(), + getTime: () => scheduler.now(), }); // set active pattern on ctrl+enter @@ -110,7 +95,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa