From 69ecb7b54fc9b967b725fc9951bacd42ea278085 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Jan 2023 23:26:20 +0100 Subject: [PATCH] support logs in mini repl + pass hap to logger + add editPattern hook to repl + useStrudel + do not throw when webaudio gets plain values --- packages/core/pattern.mjs | 21 +- packages/core/repl.mjs | 5 +- packages/react/dist/index.cjs.js | 2 +- packages/react/dist/index.es.js | 461 +++++++++++---------- packages/react/dist/style.css | 2 +- packages/react/src/components/MiniRepl.jsx | 43 +- packages/react/src/hooks/useStrudel.mjs | 2 + packages/webaudio/webaudio.mjs | 9 +- 8 files changed, 317 insertions(+), 228 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 17187bd6..d3fc7b4d 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -776,8 +776,10 @@ export class Pattern { ); } - log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`) { - return this.onTrigger((...args) => logger(func(...args)), false); + log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) { + return this.onTrigger((...args) => { + logger(func(...args), undefined, getData(...args)); + }, false); } logValues(func = id) { @@ -1045,7 +1047,12 @@ Pattern.prototype.factories = { // Elemental patterns -// Nothing +/** + * Does absolutely nothing.. + * @name silence + * @example + * silence // "~" + */ export const silence = new Pattern(() => []); /** A discrete value that repeats once per cycle. @@ -1372,7 +1379,11 @@ export const round = register('round', function (pat) { * Assumes a numerical pattern. Returns a new pattern with all values set to * their mathematical floor. E.g. `3.7` replaced with to `3`, and `-4.2` * replaced with `-5`. + * @name floor + * @memberof Pattern * @returns Pattern + * @example + * "42 42.1 42.5 43".floor().note() */ export const floor = register('floor', function (pat) { return pat.asNumber().fmap((v) => Math.floor(v)); @@ -1382,7 +1393,11 @@ export const floor = register('floor', function (pat) { * Assumes a numerical pattern. Returns a new pattern with all values set to * their mathematical ceiling. E.g. `3.2` replaced with `4`, and `-4.2` * replaced with `-4`. + * @name ceil + * @memberof Pattern * @returns Pattern + * @example + * "42 42.1 42.5 43".ceil().note() */ export const ceil = register('ceil', function (pat) { return pat.asNumber().fmap((v) => Math.ceil(v)); diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 6bef319b..c71019ac 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -12,6 +12,7 @@ export function repl({ afterEval, getTime, transpiler, + editPattern, onToggle, }) { const scheduler = new Cyclist({ @@ -41,8 +42,10 @@ export function repl({ } try { beforeEval?.({ code }); - const { pattern } = await _evaluate(code, transpiler); + 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 bdcd41d6..5e9d79dc 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"),Z=require("@uiw/react-codemirror"),w=require("@codemirror/view"),x=require("@codemirror/state"),ee=require("@codemirror/lang-javascript"),c=require("@lezer/highlight"),te=require("@uiw/codemirror-themes"),B=require("@strudel.cycles/core"),K=require("@strudel.cycles/webaudio"),re=require("react-hook-inview"),ae=require("@strudel.cycles/transpiler"),I=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},l=I(t),oe=I(Z),ne=te.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:c.tags.keyword,color:"#c792ea"},{tag:c.tags.operator,color:"#89ddff"},{tag:c.tags.special(c.tags.variableName),color:"#eeffff"},{tag:c.tags.typeName,color:"#c3e88d"},{tag:c.tags.atom,color:"#f78c6c"},{tag:c.tags.number,color:"#c3e88d"},{tag:c.tags.definition(c.tags.variableName),color:"#82aaff"},{tag:c.tags.string,color:"#c3e88d"},{tag:c.tags.special(c.tags.string),color:"#c3e88d"},{tag:c.tags.comment,color:"#7d8799"},{tag:c.tags.variableName,color:"#c792ea"},{tag:c.tags.tagName,color:"#c3e88d"},{tag:c.tags.bracket,color:"#525154"},{tag:c.tags.meta,color:"#ffcb6b"},{tag:c.tags.attributeName,color:"#c792ea"},{tag:c.tags.propertyName,color:"#c792ea"},{tag:c.tags.className,color:"#decb6b"},{tag:c.tags.invalid,color:"#ffffff"}]});const L=x.StateEffect.define(),se=x.StateField.define({create(){return w.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(L))if(r.value){const s=w.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=w.Decoration.set([s.range(0,a.newDoc.length)])}else e=w.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>w.EditorView.decorations.from(e)}),U=e=>{e.dispatch({effects:L.of(!0)}),setTimeout(()=>{e.dispatch({effects:L.of(!1)})},200)},A=x.StateEffect.define(),ce=x.StateField.define({create(){return w.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(A)){const s=r.value.map(n=>(n.context.locations||[]).map(({start:i,end:u})=>{const f=n.context.color||"#FFCA28";let o=a.newDoc.line(i.line).from+i.column,d=a.newDoc.line(u.line).from+u.column;const g=a.newDoc.length;return o>g||d>g?void 0:w.Decoration.mark({attributes:{style:`outline: 1.5px solid ${f};`}}).range(o,d)})).flat().filter(Boolean)||[];e=w.Decoration.set(s,!0)}return e}catch{return w.Decoration.set([])}},provide:e=>w.EditorView.decorations.from(e)}),ie=[ee.javascript(),ne,ce,se];function W({value:e,onChange:a,onViewChanged:r,onSelectionChange:s,options:n,editorDidMount:i}){const u=t.useCallback(d=>{a?.(d)},[a]),f=t.useCallback(d=>{r?.(d)},[r]),o=t.useCallback(d=>{d.selectionSet&&s&&s?.(d.state.selection)},[s]);return l.default.createElement(l.default.Fragment,null,l.default.createElement(oe.default,{value:e,onChange:u,onCreateEditor:f,onUpdate:o,extensions:ie}))}function z(...e){return e.filter(Boolean).join(" ")}function $({view:e,pattern:a,active:r,getTime:s}){const n=t.useRef([]),i=t.useRef();t.useEffect(()=>{if(e)if(a&&r){let u=requestAnimationFrame(function f(){try{const o=s(),g=[Math.max(i.current||o,o-1/10,0),o+1/60];i.current=g[1],n.current=n.current.filter(v=>v.whole.end>o);const m=a.queryArc(...g).filter(v=>v.hasOnset());n.current=n.current.concat(m),e.dispatch({effects:A.of(n.current)})}catch{e.dispatch({effects:A.of([])})}u=requestAnimationFrame(f)});return()=>{cancelAnimationFrame(u)}}else n.current=[],e.dispatch({effects:A.of([])})},[a,r,e])}function le(e,a=!1){const r=t.useRef(),s=t.useRef(),n=f=>{if(s.current!==void 0){const o=f-s.current;e(f,o)}s.current=f,r.current=requestAnimationFrame(n)},i=()=>{r.current=requestAnimationFrame(n)},u=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(u(),i())},[e]),t.useEffect(()=>(a&&i(),u),[]),{start:i,stop:u}}function ue({pattern:e,started:a,getTime:r,onDraw:s}){let n=t.useRef([]),i=t.useRef(null);const{start:u,stop:f}=le(t.useCallback(()=>{const o=r();if(i.current===null){i.current=o;return}const d=e.queryArc(Math.max(i.current,o-1/10),o),g=4;i.current=o,n.current=(n.current||[]).filter(m=>m.whole.end>o-g).concat(d.filter(m=>m.hasOnset())),s(o,n.current)},[e]));t.useEffect(()=>{a?u():(n.current=[],f())},[a])}function J(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(a=>window.postMessage(a,"*"),[])}function G({defaultOutput:e,interval:a,getTime:r,evalOnMount:s=!1,initialCode:n="",autolink:i=!1,beforeEval:u,afterEval:f,onEvalError:o,onToggle:d,canvasId:g}){const m=t.useMemo(()=>de(),[]);g=g||`canvas-${m}`;const[v,k]=t.useState(),[C,q]=t.useState(),[E,M]=t.useState(n),[_,T]=t.useState(),[P,D]=t.useState(),[F,H]=t.useState(!1),b=E!==_,{scheduler:h,evaluate:R,start:Q,stop:V,pause:X}=t.useMemo(()=>B.repl({interval:a,defaultOutput:e,onSchedulerError:k,onEvalError:p=>{q(p),o?.(p)},getTime:r,transpiler:ae.transpiler,beforeEval:({code:p})=>{M(p),u?.()},afterEval:({pattern:p,code:N})=>{T(N),D(p),q(),k(),i&&(window.location.hash="#"+encodeURIComponent(btoa(N))),f?.()},onToggle:p=>{H(p),d?.(p)}}),[e,a,r]),Y=J(({data:{from:p,type:N}})=>{N==="start"&&p!==m&&V()}),S=t.useCallback(async(p=!0)=>{await R(E,p),Y({type:"start",from:m})},[R,E]),j=t.useRef();return t.useEffect(()=>{!j.current&&s&&E&&(j.current=!0,S())},[S,s,E]),t.useEffect(()=>()=>{h.stop()},[h]),{id:m,canvasId:g,code:E,setCode:M,error:v||C,schedulerError:v,scheduler:h,evalError:C,evaluate:R,activateCode:S,activeCode:_,isDirty:b,pattern:P,started:F,start:Q,stop:V,pause:X,togglePlay:async()=>{F?h.pause():await S()}}}function de(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function O({type:e}){return l.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:l.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:l.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:l.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 fe="_container_3i85k_1",ge="_header_3i85k_5",me="_buttons_3i85k_9",he="_button_3i85k_9",pe="_buttonDisabled_3i85k_17",be="_error_3i85k_21",ve="_body_3i85k_25",y={container:fe,header:ge,buttons:me,button:he,buttonDisabled:pe,error:be,body:ve},we=()=>K.getAudioContext().currentTime;function Ee({tune:e,hideOutsideView:a=!1,enableKeyboard:r,withCanvas:s=!1,canvasHeight:n=200}){const{code:i,setCode:u,evaluate:f,activateCode:o,error:d,isDirty:g,activeCode:m,pattern:v,started:k,scheduler:C,togglePlay:q,stop:E,canvasId:M}=G({initialCode:e,defaultOutput:K.webaudioOutput,getTime:we});ue({pattern:v,started:s&&k,getTime:()=>C.now(),onDraw:(b,h)=>{const R=document.querySelector("#"+M).getContext("2d");B.pianoroll({ctx:R,time:b,haps:h,autorange:1,fold:1,playhead:1})}});const[_,T]=t.useState(),[P,D]=re.useInView({threshold:.01}),F=t.useRef(),H=t.useMemo(()=>((D||!a)&&(F.current=!0),D||F.current),[D,a]);return $({view:_,pattern:v,active:k&&!m?.includes("strudel disable-highlighting"),getTime:()=>C.getPhase()}),t.useLayoutEffect(()=>{if(r){const b=async h=>{(h.ctrlKey||h.altKey)&&(h.code==="Enter"?(h.preventDefault(),U(_),await o()):h.code==="Period"&&(E(),h.preventDefault()))};return window.addEventListener("keydown",b,!0),()=>window.removeEventListener("keydown",b,!0)}},[r,v,i,f,E,_]),l.default.createElement("div",{className:y.container,ref:P},l.default.createElement("div",{className:y.header},l.default.createElement("div",{className:y.buttons},l.default.createElement("button",{className:z(y.button,k?"sc-animate-pulse":""),onClick:()=>q()},l.default.createElement(O,{type:k?"pause":"play"})),l.default.createElement("button",{className:z(g?y.button:y.buttonDisabled),onClick:()=>o()},l.default.createElement(O,{type:"refresh"}))),d&&l.default.createElement("div",{className:y.error},d.message)),l.default.createElement("div",{className:y.body},H&&l.default.createElement(W,{value:i,onChange:u,onViewChanged:T})),s&&l.default.createElement("canvas",{id:M,className:"w-full pointer-events-none",height:n,ref:b=>{b&&b.width!==b.clientWidth&&(b.width=b.clientWidth)}}))}const ye=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=W;exports.MiniRepl=Ee;exports.cx=z;exports.flash=U;exports.useHighlighting=$;exports.useKeydown=ye;exports.usePostMessage=J;exports.useStrudel=G; +"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; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index 6545e0a7..d0027424 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -1,15 +1,15 @@ -import i, { useCallback as N, useRef as E, useEffect as F, useMemo as V, useState as _, useLayoutEffect as U } from "react"; -import Y from "@uiw/react-codemirror"; -import { Decoration as y, EditorView as W } from "@codemirror/view"; -import { StateEffect as $, StateField as G } from "@codemirror/state"; -import { javascript as Z } from "@codemirror/lang-javascript"; +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 ee } from "@uiw/codemirror-themes"; -import { repl as te, pianoroll as re } from "@strudel.cycles/core"; -import { webaudioOutput as oe, getAudioContext as ne } from "@strudel.cycles/webaudio"; -import { useInView as ae } from "react-hook-inview"; -import { transpiler as se } from "@strudel.cycles/transpiler"; -const ce = ee({ +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({ theme: "dark", settings: { background: "#222", @@ -42,14 +42,14 @@ const ce = ee({ { tag: s.invalid, color: "#ffffff" } ] }); -const B = $.define(), ie = G.define({ +const j = Q.define(), ue = X.define({ create() { return y.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(B)) + if (t.is(j)) if (t.value) { const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } }); e = y.set([a.range(0, r.newDoc.length)]); @@ -60,25 +60,25 @@ const B = $.define(), ie = G.define({ return console.warn("flash error", t), e; } }, - provide: (e) => W.decorations.from(e) -}), le = (e) => { - e.dispatch({ effects: B.of(!0) }), setTimeout(() => { - e.dispatch({ effects: B.of(!1) }); + provide: (e) => J.decorations.from(e) +}), de = (e) => { + e.dispatch({ effects: j.of(!0) }), setTimeout(() => { + e.dispatch({ effects: j.of(!1) }); }, 200); -}, H = $.define(), ue = G.define({ +}, z = Q.define(), fe = X.define({ create() { return y.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(H)) { + if (t.is(z)) { const a = t.value.map( - (n) => (n.context.locations || []).map(({ start: c, end: l }) => { - const d = n.context.color || "#FFCA28"; - let o = r.newDoc.line(c.line).from + c.column, u = r.newDoc.line(l.line).from + l.column; - const f = r.newDoc.length; - return o > f || u > f ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(o, u); + (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); }) ).flat().filter(Boolean) || []; e = y.set(a, !0); @@ -88,291 +88,314 @@ const B = $.define(), ie = G.define({ return y.set([]); } }, - provide: (e) => W.decorations.from(e) -}), de = [Z(), ce, ue, ie]; -function fe({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: n, editorDidMount: c }) { - const l = N( - (u) => { - r?.(u); + 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); }, [r] - ), d = N( - (u) => { - t?.(u); + ), m = N( + (d) => { + t?.(d); }, [t] - ), o = N( - (u) => { - u.selectionSet && a && a?.(u.state.selection); + ), n = N( + (d) => { + d.selectionSet && a && a?.(d.state.selection); }, [a] ); - return /* @__PURE__ */ i.createElement(i.Fragment, null, /* @__PURE__ */ i.createElement(Y, { + return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(Z, { value: e, - onChange: l, - onCreateEditor: d, - onUpdate: o, - extensions: de + onChange: u, + onCreateEditor: m, + onUpdate: n, + extensions: me })); } -function j(...e) { +function W(...e) { return e.filter(Boolean).join(" "); } -function me({ view: e, pattern: r, active: t, getTime: a }) { - const n = E([]), c = E(); - F(() => { +function pe({ view: e, pattern: r, active: t, getTime: a }) { + const o = k([]), i = k(); + _(() => { if (e) if (r && t) { - let l = requestAnimationFrame(function d() { + let u = requestAnimationFrame(function m() { try { - const o = a(), f = [Math.max(c.current || o, o - 1 / 10, 0), o + 1 / 60]; - c.current = f[1], n.current = n.current.filter((v) => v.whole.end > o); - const m = r.queryArc(...f).filter((v) => v.hasOnset()); - n.current = n.current.concat(m), e.dispatch({ effects: H.of(n.current) }); + 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) }); } catch { - e.dispatch({ effects: H.of([]) }); + e.dispatch({ effects: z.of([]) }); } - l = requestAnimationFrame(d); + u = requestAnimationFrame(m); }); return () => { - cancelAnimationFrame(l); + cancelAnimationFrame(u); }; } else - n.current = [], e.dispatch({ effects: H.of([]) }); + o.current = [], e.dispatch({ effects: z.of([]) }); }, [r, t, e]); } -function ge(e, r = !1) { - const t = E(), a = E(), n = (d) => { +function he(e, r = !1) { + const t = k(), a = k(), o = (m) => { if (a.current !== void 0) { - const o = d - a.current; - e(d, o); + const n = m - a.current; + e(m, n); } - a.current = d, t.current = requestAnimationFrame(n); - }, c = () => { - t.current = requestAnimationFrame(n); - }, l = () => { + a.current = m, t.current = requestAnimationFrame(o); + }, i = () => { + t.current = requestAnimationFrame(o); + }, u = () => { t.current && cancelAnimationFrame(t.current), delete t.current; }; - return F(() => { - t.current && (l(), c()); - }, [e]), F(() => (r && c(), l), []), { - start: c, - stop: l + return _(() => { + t.current && (u(), i()); + }, [e]), _(() => (r && i(), u), []), { + start: i, + stop: u }; } -function pe({ pattern: e, started: r, getTime: t, onDraw: a }) { - let n = E([]), c = E(null); - const { start: l, stop: d } = ge( +function ve({ pattern: e, started: r, getTime: t, onDraw: a }) { + let o = k([]), i = k(null); + const { start: u, stop: m } = he( N(() => { - const o = t(); - if (c.current === null) { - c.current = o; + const n = t(); + if (i.current === null) { + i.current = n; return; } - const u = e.queryArc(Math.max(c.current, o - 1 / 10), o), f = 4; - c.current = o, n.current = (n.current || []).filter((m) => m.whole.end > o - f).concat(u.filter((m) => m.hasOnset())), a(o, n.current); + 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); }, [e]) ); - F(() => { - r ? l() : (n.current = [], d()); + _(() => { + r ? u() : (o.current = [], m()); }, [r]); } -function he(e) { - return F(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []); +function be(e) { + return _(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []); } -function ve({ +function Ee({ defaultOutput: e, interval: r, getTime: t, evalOnMount: a = !1, - initialCode: n = "", - autolink: c = !1, - beforeEval: l, - afterEval: d, - onEvalError: o, - onToggle: u, - canvasId: f + initialCode: o = "", + autolink: i = !1, + beforeEval: u, + afterEval: m, + editPattern: n, + onEvalError: d, + onToggle: v, + canvasId: h }) { - const m = V(() => be(), []); - f = f || `canvas-${m}`; - const [v, k] = _(), [M, T] = _(), [b, A] = _(n), [C, S] = _(), [z, D] = _(), [x, L] = _(!1), h = b !== C, { scheduler: g, evaluate: R, start: J, stop: O, pause: Q } = V( - () => te({ + 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({ interval: r, defaultOutput: e, - onSchedulerError: k, - onEvalError: (p) => { - T(p), o?.(p); + onSchedulerError: A, + onEvalError: (g) => { + D(g), d?.(g); }, getTime: t, - transpiler: se, - beforeEval: ({ code: p }) => { - A(p), l?.(); + transpiler: ie, + beforeEval: ({ code: g }) => { + q(g), u?.(); }, - afterEval: ({ pattern: p, code: q }) => { - S(q), D(p), T(), k(), c && (window.location.hash = "#" + encodeURIComponent(btoa(q))), 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?.(); }, - onToggle: (p) => { - L(p), u?.(p); + onToggle: (g) => { + B(g), v?.(g); } }), [e, r, t] - ), X = he(({ data: { from: p, type: q } }) => { - q === "start" && p !== m && O(); - }), P = N( - async (p = !0) => { - await R(b, p), X({ type: "start", from: m }); + ), 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 }); }, - [R, b] - ), K = E(); - return F(() => { - !K.current && a && b && (K.current = !0, P()); - }, [P, a, b]), F(() => () => { - g.stop(); - }, [g]), { - id: m, - canvasId: f, + [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: A, - error: v || M, - schedulerError: v, - scheduler: g, - evalError: M, - evaluate: R, - activateCode: P, - activeCode: C, - isDirty: h, - pattern: z, - started: x, - start: J, - stop: O, - pause: Q, + 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 () => { - x ? g.pause() : await P(); + L ? M.pause() : await S(); } }; } -function be() { +function we() { return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); } -function I({ type: e }) { - return /* @__PURE__ */ i.createElement("svg", { +function $({ type: e }) { + return /* @__PURE__ */ l.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__ */ i.createElement("path", { + refresh: /* @__PURE__ */ l.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__ */ i.createElement("path", { + play: /* @__PURE__ */ l.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__ */ i.createElement("path", { + pause: /* @__PURE__ */ l.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 we = "_container_3i85k_1", ye = "_header_3i85k_5", Ee = "_buttons_3i85k_9", ke = "_button_3i85k_9", _e = "_buttonDisabled_3i85k_17", Fe = "_error_3i85k_21", Ce = "_body_3i85k_25", w = { - container: we, - header: ye, - buttons: Ee, - button: ke, - buttonDisabled: _e, - error: Fe, - body: Ce -}, Ne = () => ne().currentTime; -function Be({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: n = 200 }) { +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 { - code: c, - setCode: l, - evaluate: d, - activateCode: o, - error: u, - isDirty: f, - activeCode: m, - pattern: v, - started: k, - scheduler: M, - togglePlay: T, - stop: b, - canvasId: A - } = ve({ + code: i, + setCode: u, + evaluate: m, + activateCode: n, + error: d, + isDirty: v, + activeCode: h, + pattern: p, + started: F, + scheduler: A, + togglePlay: P, + stop: D, + canvasId: b, + id: q + } = Ee({ initialCode: e, - defaultOutput: oe, - getTime: Ne + defaultOutput: ae, + getTime: Me, + editPattern: (c, f) => c.withContext((C) => ({ ...C, id: f })) }); - pe({ - pattern: v, - started: a && k, - getTime: () => M.now(), - onDraw: (h, g) => { - const R = document.querySelector("#" + A).getContext("2d"); - re({ ctx: R, time: h, haps: g, autorange: 1, fold: 1, playhead: 1 }); + 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 [C, S] = _(), [z, D] = ae({ + const [x, V] = w(), [I, R] = se({ threshold: 0.01 - }), x = E(), L = V(() => ((D || !r) && (x.current = !0), D || x.current), [D, r]); - return me({ - view: C, - pattern: v, - active: k && !m?.includes("strudel disable-highlighting"), - getTime: () => M.getPhase() - }), U(() => { + }), L = k(), B = K(() => ((R || !r) && (L.current = !0), R || L.current), [R, r]); + pe({ + view: x, + pattern: p, + active: F && !h?.includes("strudel disable-highlighting"), + getTime: () => A.getPhase() + }), G(() => { if (t) { - const h = async (g) => { - (g.ctrlKey || g.altKey) && (g.code === "Enter" ? (g.preventDefault(), le(C), await o()) : g.code === "Period" && (b(), g.preventDefault())); + const c = async (f) => { + (f.ctrlKey || f.altKey) && (f.code === "Enter" ? (f.preventDefault(), de(x), await n()) : f.code === "Period" && (D(), f.preventDefault())); }; - return window.addEventListener("keydown", h, !0), () => window.removeEventListener("keydown", h, !0); + return window.addEventListener("keydown", c, !0), () => window.removeEventListener("keydown", c, !0); } - }, [t, v, c, d, b, C]), /* @__PURE__ */ i.createElement("div", { - className: w.container, - ref: z - }, /* @__PURE__ */ i.createElement("div", { - className: w.header - }, /* @__PURE__ */ i.createElement("div", { - className: w.buttons - }, /* @__PURE__ */ i.createElement("button", { - className: j(w.button, k ? "sc-animate-pulse" : ""), - onClick: () => T() - }, /* @__PURE__ */ i.createElement(I, { - type: k ? "pause" : "play" - })), /* @__PURE__ */ i.createElement("button", { - className: j(f ? w.button : w.buttonDisabled), - onClick: () => o() - }, /* @__PURE__ */ i.createElement(I, { + }, [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)); + }, []) + ), /* @__PURE__ */ l.createElement("div", { + className: E.container, + ref: I + }, /* @__PURE__ */ l.createElement("div", { + className: E.header + }, /* @__PURE__ */ l.createElement("div", { + className: E.buttons + }, /* @__PURE__ */ l.createElement("button", { + className: W(E.button, F ? "sc-animate-pulse" : ""), + onClick: () => P() + }, /* @__PURE__ */ l.createElement($, { + type: F ? "pause" : "play" + })), /* @__PURE__ */ l.createElement("button", { + className: W(v ? E.button : E.buttonDisabled), + onClick: () => n() + }, /* @__PURE__ */ l.createElement($, { type: "refresh" - }))), u && /* @__PURE__ */ i.createElement("div", { - className: w.error - }, u.message)), /* @__PURE__ */ i.createElement("div", { - className: w.body - }, L && /* @__PURE__ */ i.createElement(fe, { - value: c, - onChange: l, - onViewChanged: S - })), a && /* @__PURE__ */ i.createElement("canvas", { - id: A, + }))), 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, className: "w-full pointer-events-none", - height: n, - ref: (h) => { - h && h.width !== h.clientWidth && (h.width = h.clientWidth); + height: o, + ref: (c) => { + c && c.width !== c.clientWidth && (c.width = c.clientWidth); } - })); + }), !!H.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)))); } -const Oe = (e) => U(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); +function Ae(e) { + De(ne.key, e); +} +function De(e, r, t = !1) { + _(() => (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]); export { - fe as CodeMirror, - Be as MiniRepl, - j as cx, - le as flash, - me as useHighlighting, - Oe as useKeydown, - he as usePostMessage, - ve as useStrudel + ge as CodeMirror, + je as MiniRepl, + W as cx, + de as flash, + pe as useHighlighting, + Ue as useKeydown, + be as usePostMessage, + Ee as useStrudel }; diff --git a/packages/react/dist/style.css b/packages/react/dist/style.css index b28aca62..6e937139 100644 --- a/packages/react/dist/style.css +++ b/packages/react/dist/style.css @@ -1 +1 @@ -.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto} +.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.sc-rounded-md{border-radius:.375rem}.sc-bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.sc-p-2{padding:.5rem}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto} diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index a7874e55..70ebe22c 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -1,6 +1,6 @@ import { pianoroll } from '@strudel.cycles/core'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; -import React, { useLayoutEffect, useMemo, useRef, useState } from 'react'; +import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react'; import { useInView } from 'react-hook-inview'; import 'tailwindcss/tailwind.css'; import cx from '../cx'; @@ -11,6 +11,7 @@ import CodeMirror6, { flash } from './CodeMirror6'; import { Icon } from './Icon'; import styles from './MiniRepl.module.css'; import './style.css'; +import { logger } from '@strudel.cycles/core'; const getTime = () => getAudioContext().currentTime; @@ -29,10 +30,14 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa togglePlay, stop, canvasId, + id: replId, } = useStrudel({ initialCode: tune, defaultOutput: webaudioOutput, getTime, + editPattern: (pat, id) => { + return pat.withContext((ctx) => ({ ...ctx, id })); + }, }); usePatternFrame({ @@ -86,6 +91,20 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa } }, [enableKeyboard, pattern, code, evaluate, stop, view]); + const [log, setLog] = useState([]); + useLogger( + useCallback((e) => { + const { data } = e.detail; + const logId = data?.hap?.context?.id; + // const logId = data?.pattern?.meta?.id; + if (logId === replId) { + setLog((l) => { + return l.concat([e.detail]).slice(-10); + }); + } + }, []), + ); + return (
@@ -114,6 +133,28 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa }} > )} + {!!log.length && ( +
+ {log.map(({ message }, i) => ( +
{message}
+ ))} +
+ )}
); } + +// TODO: dedupe +function useLogger(onTrigger) { + useEvent(logger.key, onTrigger); +} + +// TODO: dedupe +function useEvent(name, onTrigger, useCapture = false) { + useEffect(() => { + document.addEventListener(name, onTrigger, useCapture); + return () => { + document.removeEventListener(name, onTrigger, useCapture); + }; + }, [onTrigger]); +} diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index 5d475e1e..6308cf5e 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -12,6 +12,7 @@ function useStrudel({ autolink = false, beforeEval, afterEval, + editPattern, onEvalError, onToggle, canvasId, @@ -44,6 +45,7 @@ function useStrudel({ setCode(code); beforeEval?.(); }, + editPattern: editPattern ? (pat) => editPattern(pat, id) : undefined, afterEval: ({ pattern: _pattern, code }) => { setActiveCode(code); setPattern(_pattern); diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 11eb170b..f89d3d3a 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -195,9 +195,14 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => { hap.value = { note: hap.value }; } */ if (typeof hap.value !== 'object') { - throw new Error( - `hap.value ${hap.value} is not supported by webaudio output. Hint: append .note() or .s() to the end`, + logger( + `hap.value "${hap.value}" is not supported by webaudio output. Hint: append .note() or .s() to the end`, + 'error', ); + /* throw new Error( + `hap.value "${hap.value}"" is not supported by webaudio output. Hint: append .note() or .s() to the end`, + ); */ + return; } // calculate correct time (tone.js workaround) let t = ac.currentTime + deadline;