From ed35f967b0539e36e478cf50230e7a9a2cbfec42 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 17 May 2022 22:27:22 +0200 Subject: [PATCH] migrate repl + move imports out of minirepl --- packages/midi/index.mjs | 2 + packages/react/dist/index.cjs.js | 6 +- packages/react/dist/index.es.js | 153 ++++++++------------- packages/react/src/components/MiniRepl.jsx | 23 +--- packages/react/src/index.js | 8 +- packages/tone/draw.mjs | 2 +- packages/tone/ui.mjs | 2 +- repl/src/App.jsx | 71 +++------- tutorial/MiniRepl.jsx | 25 ++++ tutorial/Tutorial.jsx | 3 +- tutorial/tutorial.mdx | 2 +- 11 files changed, 114 insertions(+), 183 deletions(-) create mode 100644 tutorial/MiniRepl.jsx diff --git a/packages/midi/index.mjs b/packages/midi/index.mjs index 5583aa56..399227f7 100644 --- a/packages/midi/index.mjs +++ b/packages/midi/index.mjs @@ -1 +1,3 @@ import './midi.mjs'; + +export * from './midi.mjs'; diff --git a/packages/react/dist/index.cjs.js b/packages/react/dist/index.cjs.js index 684f64de..fa6da3e9 100644 --- a/packages/react/dist/index.cjs.js +++ b/packages/react/dist/index.cjs.js @@ -1,5 +1,3 @@ -"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var s=require("react"),re=require("react-codemirror6"),x=require("@codemirror/view"),G=require("@codemirror/state"),oe=require("@codemirror/lang-javascript"),i=require("@codemirror/highlight"),ae=require("react-hook-inview"),Q=require("@strudel.cycles/eval"),ne=require("@strudel.cycles/core/util.mjs"),f=require("@strudel.cycles/tone"),A=require("@strudel.cycles/core");function ce(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}function M(e){if(e&&e.__esModule)return e;var o=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});return e&&Object.keys(e).forEach(function(a){if(a!=="default"){var t=Object.getOwnPropertyDescriptor(e,a);Object.defineProperty(o,a,t.get?t:{enumerable:!0,get:function(){return e[a]}})}}),o.default=e,Object.freeze(o)}var m=ce(s);const se="#abb2bf",le="#7d8799",ie="#ffffff",ue="#21252b",j="rgba(0, 0, 0, 0.5)",de="transparent",B="#353a42",fe="rgba(128, 203, 196, 0.2)",z="#ffcc00",ge=x.EditorView.theme({"&":{color:"#ffffff",backgroundColor:de,fontSize:"15px","z-index":11},".cm-content":{caretColor:z,lineHeight:"22px"},".cm-line":{background:"#2C323699"},"&.cm-focused .cm-cursor":{borderLeftColor:z},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":{backgroundColor:fe},".cm-panels":{backgroundColor:ue,color:"#ffffff"},".cm-panels.cm-panels-top":{borderBottom:"2px solid black"},".cm-panels.cm-panels-bottom":{borderTop:"2px solid black"},".cm-searchMatch":{backgroundColor:"#72a1ff59",outline:"1px solid #457dff"},".cm-searchMatch.cm-searchMatch-selected":{backgroundColor:"#6199ff2f"},".cm-activeLine":{backgroundColor:j},".cm-selectionMatch":{backgroundColor:"#aafe661a"},"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bad0f847",outline:"1px solid #515a6b"},".cm-gutters":{background:"#2C323699",color:"#676e95",border:"none"},".cm-activeLineGutter":{backgroundColor:j},".cm-foldPlaceholder":{backgroundColor:"transparent",border:"none",color:"#ddd"},".cm-tooltip":{border:"none",backgroundColor:B},".cm-tooltip .cm-tooltip-arrow:before":{borderTopColor:"transparent",borderBottomColor:"transparent"},".cm-tooltip .cm-tooltip-arrow:after":{borderTopColor:B,borderBottomColor:B},".cm-tooltip-autocomplete":{"& > ul > li[aria-selected]":{backgroundColor:j,color:se}}},{dark:!0}),me=i.HighlightStyle.define([{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:"#f07178"},{tag:i.tags.atom,color:"#f78c6c"},{tag:i.tags.number,color:"#ff5370"},{tag:i.tags.definition(i.tags.variableName),color:"#82aaff"},{tag:i.tags.string,color:"#c3e88d"},{tag:i.tags.special(i.tags.string),color:"#f07178"},{tag:i.tags.comment,color:le},{tag:i.tags.variableName,color:"#f07178"},{tag:i.tags.tagName,color:"#ff5370"},{tag:i.tags.bracket,color:"#a2a1a4"},{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:ie}]),he=[ge,me],D=G.StateEffect.define(),be=G.StateField.define({create(){return x.Decoration.none},update(e,o){try{for(let a of o.effects)a.is(D)&&(e=x.Decoration.set(a.value.flatMap(t=>(t.context.locations||[]).map(({start:r,end:c})=>{const u=t.context.color||"#FFCA28";let g=o.newDoc.line(r.line).from+r.column,l=o.newDoc.line(c.line).from+c.column;const h=o.newDoc.length;return g>h||l>h?void 0:x.Decoration.mark({attributes:{style:`outline: 1px solid ${u}`}}).range(g,l)})).filter(Boolean),!0));return e}catch{return e}},provide:e=>x.EditorView.decorations.from(e)});function U({value:e,onChange:o,onViewChanged:a,onCursor:t,options:r,editorDidMount:c}){return m.default.createElement(m.default.Fragment,null,m.default.createElement(re.CodeMirror,{onViewChange:a,style:{display:"flex",flexDirection:"column",flex:"1 0 auto"},value:e,onChange:o,extensions:[oe.javascript(),he,be]}))}let F;const pe=(e,o)=>{const a=e.getDoc().getValue(),t=W(a,o);F?.clear(),F=e.getDoc().markText(...t,{css:"background-color: #00007720"})};function J(e,o){const a=o.split(` -`);let t=0,r=0;for(let c=0;ca.length)return 0;let t=0;for(let r=0;r0&&(e[r-1]==="("?t--:e[r-1]===")"&&t++,t!==-1);)r--;for(c=r,r=a,t=0;rJ(g,e))}var ve=Object.freeze(Object.defineProperty({__proto__:null,setHighlights:D,default:U,markParens:pe,offsetToPosition:J,positionToOffset:K,getCurrentParenArea:W},Symbol.toStringTag,{value:"Module"}));function ye(e){const{onEvent:o,onQuery:a,onSchedule:t,ready:r=!0,onDraw:c}=e,[u,g]=s.useState(!1),l=1,h=()=>Math.floor(f.Tone.getTransport().seconds/l),y=(v=h())=>{const q=new A.TimeSpan(v,v+1),R=a?.(new A.State(q))||[];t?.(R,v);const O=q.begin.valueOf();f.Tone.getTransport().cancel(O);const P=(v+1)*l-.5,N=Math.max(f.Tone.getTransport().seconds,P)+.1;f.Tone.getTransport().schedule(()=>{y(v+1)},N),R?.filter(p=>p.part.begin.equals(p.whole?.begin)).forEach(p=>{f.Tone.getTransport().schedule(w=>{o(w,p,f.Tone.getContext().currentTime),f.Tone.Draw.schedule(()=>{c?.(w,p)},w)},p.part.begin.valueOf())})};s.useEffect(()=>{r&&y()},[o,t,a,c,r]);const E=async()=>{g(!0),await f.Tone.start(),f.Tone.getTransport().start("+0.1")},b=()=>{f.Tone.getTransport().pause(),g(!1)};return{start:E,stop:b,onEvent:o,started:u,setStarted:g,toggle:()=>u?b():E(),query:y,activeCycle:h}}function Ce(e){return s.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),s.useCallback(o=>window.postMessage(o,"*"),[])}let we=()=>Math.floor((1+Math.random())*65536).toString(16).substring(1);const ke=e=>encodeURIComponent(btoa(e));function Te({tune:e,defaultSynth:o,autolink:a=!0,onEvent:t,onDraw:r}){const c=s.useMemo(()=>we(),[]),[u,g]=s.useState(e),[l,h]=s.useState(),[y,E]=s.useState(""),[b,C]=s.useState(),[v,q]=s.useState(!1),[R,O]=s.useState(""),[P,N]=s.useState(),p=s.useMemo(()=>u!==l||b,[u,l,b]),w=s.useCallback(d=>E(n=>n+`${n?` +"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var o=require("react"),te=require("react-codemirror6"),q=require("@codemirror/view"),I=require("@codemirror/state"),oe=require("@codemirror/lang-javascript"),n=require("@codemirror/highlight"),ae=require("react-hook-inview"),re=require("@strudel.cycles/eval"),ne=require("@strudel.cycles/core/util.mjs"),b=require("@strudel.cycles/tone"),O=require("@strudel.cycles/core"),v=require("@strudel.cycles/midi");function ce(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}var g=ce(o);const se="#abb2bf",le="#7d8799",ie="#ffffff",ue="#21252b",P="rgba(0, 0, 0, 0.5)",de="transparent",W="#353a42",fe="rgba(128, 203, 196, 0.2)",z="#ffcc00",ge=q.EditorView.theme({"&":{color:"#ffffff",backgroundColor:de,fontSize:"15px","z-index":11},".cm-content":{caretColor:z,lineHeight:"22px"},".cm-line":{background:"#2C323699"},"&.cm-focused .cm-cursor":{borderLeftColor:z},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":{backgroundColor:fe},".cm-panels":{backgroundColor:ue,color:"#ffffff"},".cm-panels.cm-panels-top":{borderBottom:"2px solid black"},".cm-panels.cm-panels-bottom":{borderTop:"2px solid black"},".cm-searchMatch":{backgroundColor:"#72a1ff59",outline:"1px solid #457dff"},".cm-searchMatch.cm-searchMatch-selected":{backgroundColor:"#6199ff2f"},".cm-activeLine":{backgroundColor:P},".cm-selectionMatch":{backgroundColor:"#aafe661a"},"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bad0f847",outline:"1px solid #515a6b"},".cm-gutters":{background:"#2C323699",color:"#676e95",border:"none"},".cm-activeLineGutter":{backgroundColor:P},".cm-foldPlaceholder":{backgroundColor:"transparent",border:"none",color:"#ddd"},".cm-tooltip":{border:"none",backgroundColor:W},".cm-tooltip .cm-tooltip-arrow:before":{borderTopColor:"transparent",borderBottomColor:"transparent"},".cm-tooltip .cm-tooltip-arrow:after":{borderTopColor:W,borderBottomColor:W},".cm-tooltip-autocomplete":{"& > ul > li[aria-selected]":{backgroundColor:P,color:se}}},{dark:!0}),me=n.HighlightStyle.define([{tag:n.tags.keyword,color:"#c792ea"},{tag:n.tags.operator,color:"#89ddff"},{tag:n.tags.special(n.tags.variableName),color:"#eeffff"},{tag:n.tags.typeName,color:"#f07178"},{tag:n.tags.atom,color:"#f78c6c"},{tag:n.tags.number,color:"#ff5370"},{tag:n.tags.definition(n.tags.variableName),color:"#82aaff"},{tag:n.tags.string,color:"#c3e88d"},{tag:n.tags.special(n.tags.string),color:"#f07178"},{tag:n.tags.comment,color:le},{tag:n.tags.variableName,color:"#f07178"},{tag:n.tags.tagName,color:"#ff5370"},{tag:n.tags.bracket,color:"#a2a1a4"},{tag:n.tags.meta,color:"#ffcb6b"},{tag:n.tags.attributeName,color:"#c792ea"},{tag:n.tags.propertyName,color:"#c792ea"},{tag:n.tags.className,color:"#decb6b"},{tag:n.tags.invalid,color:ie}]),be=[ge,me],B=I.StateEffect.define(),pe=I.StateField.define({create(){return q.Decoration.none},update(e,a){try{for(let i of a.effects)i.is(B)&&(e=q.Decoration.set(i.value.flatMap(u=>(u.context.locations||[]).map(({start:m,end:c})=>{const s=u.context.color||"#FFCA28";let d=a.newDoc.line(m.line).from+m.column,r=a.newDoc.line(c.line).from+c.column;const l=a.newDoc.length;return d>l||r>l?void 0:q.Decoration.mark({attributes:{style:`outline: 1px solid ${s}`}}).range(d,r)})).filter(Boolean),!0));return e}catch{return e}},provide:e=>q.EditorView.decorations.from(e)});function $({value:e,onChange:a,onViewChanged:i,onCursor:u,options:m,editorDidMount:c}){return g.default.createElement(g.default.Fragment,null,g.default.createElement(te.CodeMirror,{onViewChange:i,style:{display:"flex",flexDirection:"column",flex:"1 0 auto"},value:e,onChange:a,extensions:[oe.javascript(),be,pe]}))}function Q(e){const{onEvent:a,onQuery:i,onSchedule:u,ready:m=!0,onDraw:c}=e,[s,d]=o.useState(!1),r=1,l=()=>Math.floor(b.Tone.getTransport().seconds/r),C=(p=l())=>{const E=new O.TimeSpan(p,p+1),D=i?.(new O.State(E))||[];u?.(D,p);const H=E.begin.valueOf();b.Tone.getTransport().cancel(H);const S=(p+1)*r-.5,R=Math.max(b.Tone.getTransport().seconds,S)+.1;b.Tone.getTransport().schedule(()=>{C(p+1)},R),D?.filter(h=>h.part.begin.equals(h.whole?.begin)).forEach(h=>{b.Tone.getTransport().schedule(k=>{a(k,h,b.Tone.getContext().currentTime),b.Tone.Draw.schedule(()=>{c?.(k,h)},k)},h.part.begin.valueOf())})};o.useEffect(()=>{m&&C()},[a,u,i,c,m]);const x=async()=>{d(!0),await b.Tone.start(),b.Tone.getTransport().start("+0.1")},w=()=>{b.Tone.getTransport().pause(),d(!1)};return{start:x,stop:w,onEvent:a,started:s,setStarted:d,toggle:()=>s?w():x(),query:C,activeCycle:l}}function U(e){return o.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),o.useCallback(a=>window.postMessage(a,"*"),[])}let he=()=>Math.floor((1+Math.random())*65536).toString(16).substring(1);const ve=e=>encodeURIComponent(btoa(e));function G({tune:e,defaultSynth:a,autolink:i=!0,onEvent:u,onDraw:m}){const c=o.useMemo(()=>he(),[]),[s,d]=o.useState(e),[r,l]=o.useState(),[C,x]=o.useState(""),[w,y]=o.useState(),[p,E]=o.useState(!1),[D,H]=o.useState(""),[S,R]=o.useState(),h=o.useMemo(()=>s!==r||w,[s,r,w]),k=o.useCallback(f=>x(t=>t+`${t?` -`:""}${d}`),[]),X=s.useMemo(()=>{if(l&&!l.includes("strudel disable-highlighting"))return(d,n)=>r?.(d,n,l)},[l,r]),T=ye({onDraw:X,onEvent:s.useCallback((d,n,Z)=>{try{t?.(n),n.context.logs?.length&&n.context.logs.forEach(w);const{onTrigger:_,velocity:ee}=n.context;if(_)_(d,n,Z,1,n.wholeOrPart().begin.valueOf(),n.duration.valueOf());else if(o){const te=ne.getPlayableNoteValue(n);o.triggerAttackRelease(te,n.duration.valueOf(),d,ee)}else throw new Error("no defaultSynth passed to useRepl.")}catch(_){console.warn(_),_.message="unplayable event: "+_?.message,w(_.message)}},[t,w,o]),onQuery:s.useCallback(d=>{try{return P?.query(d)||[]}catch(n){return console.warn(n),n.message="query error: "+n.message,C(n),[]}},[P]),onSchedule:s.useCallback((d,n)=>Y(d,n),[]),ready:!!P&&!!l}),H=Ce(({data:{from:d,type:n}})=>{n==="start"&&d!==c&&(T.setStarted(!1),h(void 0))}),V=s.useCallback(async(d=u)=>{if(l&&!p){C(void 0),T.start();return}try{q(!0);const n=await Q.evaluate(d);T.start(),H({type:"start",from:c}),N(()=>n.pattern),a&&(window.location.hash="#"+encodeURIComponent(btoa(u))),O(ke(u)),C(void 0),h(d),q(!1)}catch(n){n.message="evaluation error: "+n.message,console.warn(n),C(n)}},[l,p,u,T,a,c,H]),Y=(d,n)=>{d.length};return{pending:v,code:u,setCode:g,pattern:P,error:b,cycle:T,setPattern:N,dirty:p,log:y,togglePlay:()=>{T.started?T.stop():V()},setActiveCode:h,activateCode:V,activeCode:l,pushLog:w,hash:R}}function L(...e){return e.filter(Boolean).join(" ")}let S=[],I;function _e({view:e,pattern:o,active:a}){s.useEffect(()=>{if(e)if(o&&a){let r=function(){try{const c=f.Tone.getTransport().seconds,u=[I||c,c+1/60];I=c+1/60,S=S.filter(l=>l.whole.end>c);const g=o.queryArc(...u).filter(l=>l.hasOnset());S=S.concat(g),e.dispatch({effects:D.of(S)})}catch{e.dispatch({effects:D.of([])})}t=requestAnimationFrame(r)},t=requestAnimationFrame(r);return()=>{cancelAnimationFrame(t)}}else S=[],e.dispatch({effects:D.of([])})},[o,a,e])}const Me="_container_10e1g_1",Ee="_header_10e1g_5",Pe="_buttons_10e1g_9",Se="_button_10e1g_9",qe="_buttonDisabled_10e1g_17",xe="_error_10e1g_21",De="_body_10e1g_25";var k={container:Me,header:Ee,buttons:Pe,button:Se,buttonDisabled:qe,error:xe,body:De};function $({type:e}){return React.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:React.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:React.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:React.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])}Q.evalScope(f.Tone,Promise.resolve().then(function(){return M(require("@strudel.cycles/core"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/tone"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/tonal"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/mini"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/midi"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/xen"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/webaudio"))}));const Re=new f.Tone.PolySynth().chain(new f.Tone.Gain(.5),f.Tone.Destination).set({oscillator:{type:"triangle"},envelope:{release:.01}});function Ne({tune:e}){const{code:o,setCode:a,pattern:t,activateCode:r,error:c,cycle:u,dirty:g,togglePlay:l}=Te({tune:e,defaultSynth:Re,autolink:!1}),[h,y]=s.useState(),[E,b]=ae.useInView({threshold:.01}),C=s.useRef(),v=s.useMemo(()=>(b&&(C.current=!0),b||C.current),[b]);return _e({view:h,pattern:t,active:u.started}),m.default.createElement("div",{className:k.container,ref:E},m.default.createElement("div",{className:k.header},m.default.createElement("div",{className:k.buttons},m.default.createElement("button",{className:L(k.button,u.started?"sc-animate-pulse":""),onClick:()=>l()},m.default.createElement($,{type:u.started?"pause":"play"})),m.default.createElement("button",{className:L(g?k.button:k.buttonDisabled),onClick:()=>r()},m.default.createElement($,{type:"refresh"}))),c&&m.default.createElement("div",{className:k.error},c.message)),m.default.createElement("div",{className:k.body},v&&m.default.createElement(U,{value:o,onChange:a,onViewChanged:y})))}exports.CodeMirror=ve;exports.MiniRepl=Ne; +`:""}${f}`),[]),K=o.useMemo(()=>{if(r&&!r.includes("strudel disable-highlighting"))return(f,t)=>m?.(f,t,r)},[r,m]),T=Q({onDraw:K,onEvent:o.useCallback((f,t,Y)=>{try{u?.(t),t.context.logs?.length&&t.context.logs.forEach(k);const{onTrigger:_,velocity:Z}=t.context;if(_)_(f,t,Y,1,t.wholeOrPart().begin.valueOf(),t.duration.valueOf());else if(a){const ee=ne.getPlayableNoteValue(t);a.triggerAttackRelease(ee,t.duration.valueOf(),f,Z)}else throw new Error("no defaultSynth passed to useRepl.")}catch(_){console.warn(_),_.message="unplayable event: "+_?.message,k(_.message)}},[u,k,a]),onQuery:o.useCallback(f=>{try{return S?.query(f)||[]}catch(t){return console.warn(t),t.message="query error: "+t.message,y(t),[]}},[S]),onSchedule:o.useCallback((f,t)=>X(f,t),[]),ready:!!S&&!!r}),L=U(({data:{from:f,type:t}})=>{t==="start"&&f!==c&&(T.setStarted(!1),l(void 0))}),A=o.useCallback(async(f=s)=>{if(r&&!h){y(void 0),T.start();return}try{E(!0);const t=await re.evaluate(f);T.start(),L({type:"start",from:c}),R(()=>t.pattern),i&&(window.location.hash="#"+encodeURIComponent(btoa(s))),H(ve(s)),y(void 0),l(f),E(!1)}catch(t){t.message="evaluation error: "+t.message,console.warn(t),y(t)}},[r,h,s,T,i,c,L]),X=(f,t)=>{f.length};return{pending:p,code:s,setCode:d,pattern:S,error:w,cycle:T,setPattern:R,dirty:h,log:C,togglePlay:()=>{T.started?T.stop():A()},setActiveCode:l,activateCode:A,activeCode:r,pushLog:k,hash:D}}function V(...e){return e.filter(Boolean).join(" ")}let N=[],F;function J({view:e,pattern:a,active:i}){o.useEffect(()=>{if(e)if(a&&i){let m=function(){try{const c=b.Tone.getTransport().seconds,s=[F||c,c+1/60];F=c+1/60,N=N.filter(r=>r.whole.end>c);const d=a.queryArc(...s).filter(r=>r.hasOnset());N=N.concat(d),e.dispatch({effects:B.of(N)})}catch{e.dispatch({effects:B.of([])})}u=requestAnimationFrame(m)},u=requestAnimationFrame(m);return()=>{cancelAnimationFrame(u)}}else N=[],e.dispatch({effects:B.of([])})},[a,i,e])}const ye="_container_10e1g_1",Ce="_header_10e1g_5",we="_buttons_10e1g_9",ke="_button_10e1g_9",Me="_buttonDisabled_10e1g_17",Ee="_error_10e1g_21",Te="_body_10e1g_25";var M={container:ye,header:Ce,buttons:we,button:ke,buttonDisabled:Me,error:Ee,body:Te};function j({type:e}){return g.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:g.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:g.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:g.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"})}[e])}function _e({tune:e,defaultSynth:a}){const{code:i,setCode:u,pattern:m,activateCode:c,error:s,cycle:d,dirty:r,togglePlay:l}=G({tune:e,defaultSynth:a,autolink:!1}),[C,x]=o.useState(),[w,y]=ae.useInView({threshold:.01}),p=o.useRef(),E=o.useMemo(()=>(y&&(p.current=!0),y||p.current),[y]);return J({view:C,pattern:m,active:d.started}),g.default.createElement("div",{className:M.container,ref:w},g.default.createElement("div",{className:M.header},g.default.createElement("div",{className:M.buttons},g.default.createElement("button",{className:V(M.button,d.started?"sc-animate-pulse":""),onClick:()=>l()},g.default.createElement(j,{type:d.started?"pause":"play"})),g.default.createElement("button",{className:V(r?M.button:M.buttonDisabled),onClick:()=>c()},g.default.createElement(j,{type:"refresh"}))),s&&g.default.createElement("div",{className:M.error},s.message)),g.default.createElement("div",{className:M.body},E&&g.default.createElement($,{value:i,onChange:u,onViewChanged:x})))}function xe(e){const{ready:a,connected:i,disconnected:u}=e,[m,c]=o.useState(!0),[s,d]=o.useState(v.WebMidi?.outputs||[]);return o.useEffect(()=>{v.enableWebMidi().then(()=>{v.WebMidi.addListener("connected",l=>{d([...v.WebMidi.outputs]),i?.(v.WebMidi,l)}),v.WebMidi.addListener("disconnected",l=>{d([...v.WebMidi.outputs]),u?.(v.WebMidi,l)}),a?.(v.WebMidi),c(!1)}).catch(l=>{if(l){console.error(l),console.warn("Web Midi could not be enabled..");return}})},[a,i,u,s]),{loading:m,outputs:s,outputByName:l=>v.WebMidi.getOutputByName(l)}}exports.CodeMirror=$;exports.MiniRepl=_e;exports.cx=V;exports.useCycle=Q;exports.useHighlighting=J;exports.usePostMessage=U;exports.useRepl=G;exports.useWebMidi=xe; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index e27a6f66..467aec66 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -1,14 +1,15 @@ -import React$1, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { CodeMirror as CodeMirror$1 } from 'react-codemirror6'; import { EditorView, Decoration } from '@codemirror/view'; import { StateEffect, StateField } from '@codemirror/state'; import { javascript } from '@codemirror/lang-javascript'; import { HighlightStyle, tags } from '@codemirror/highlight'; import { useInView } from 'react-hook-inview'; -import { evaluate, evalScope } from '@strudel.cycles/eval'; +import { evaluate } from '@strudel.cycles/eval'; import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs'; import { Tone } from '@strudel.cycles/tone'; import { TimeSpan, State } from '@strudel.cycles/core'; +import { WebMidi, enableWebMidi } from '@strudel.cycles/midi'; /* Credits for color palette: @@ -173,7 +174,7 @@ const highlightField = StateField.define({ provide: (f) => EditorView.decorations.from(f) }); function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorDidMount }) { - return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement(CodeMirror$1, { + return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(CodeMirror$1, { onViewChange: onViewChanged, style: { display: "flex", @@ -189,82 +190,6 @@ function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorD ] })); } -let parenMark; -const markParens = (editor, data) => { - const v = editor.getDoc().getValue(); - const marked = getCurrentParenArea(v, data); - parenMark?.clear(); - parenMark = editor.getDoc().markText(...marked, { css: "background-color: #00007720" }); -}; -function offsetToPosition(offset, code) { - const lines = code.split("\n"); - let line = 0; - let ch = 0; - for (let i = 0; i < offset; i++) { - if (ch === lines[line].length) { - line++; - ch = 0; - } else { - ch++; - } - } - return { line, ch }; -} -function positionToOffset(position, code) { - const lines = code.split("\n"); - if (position.line > lines.length) { - return 0; - } - let offset = 0; - for (let i = 0; i < position.line; i++) { - offset += lines[i].length + 1; - } - offset += position.ch; - return offset; -} -function getCurrentParenArea(code, caretPosition) { - const caret = positionToOffset(caretPosition, code); - let open, i, begin, end; - i = caret; - open = 0; - while (i > 0) { - if (code[i - 1] === "(") { - open--; - } else if (code[i - 1] === ")") { - open++; - } - if (open === -1) { - break; - } - i--; - } - begin = i; - i = caret; - open = 0; - while (i < code.length) { - if (code[i] === "(") { - open--; - } else if (code[i] === ")") { - open++; - } - if (open === 1) { - break; - } - i++; - } - end = i; - return [begin, end].map((o) => offsetToPosition(o, code)); -} - -var CodeMirror6 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ - __proto__: null, - setHighlights: setHighlights, - 'default': CodeMirror, - markParens: markParens, - offsetToPosition: offsetToPosition, - positionToOffset: positionToOffset, - getCurrentParenArea: getCurrentParenArea -}, Symbol.toStringTag, { value: 'Module' })); /* useCycle.mjs - @@ -607,14 +532,7 @@ function Icon({ type }) { }[type]); } -evalScope(Tone, import('@strudel.cycles/core'), import('@strudel.cycles/tone'), import('@strudel.cycles/tonal'), import('@strudel.cycles/mini'), import('@strudel.cycles/midi'), import('@strudel.cycles/xen'), import('@strudel.cycles/webaudio')); -const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({ - oscillator: { type: "triangle" }, - envelope: { - release: 0.01 - } -}); -function MiniRepl({ tune }) { +function MiniRepl({ tune, defaultSynth }) { const { code, setCode, pattern, activateCode, error, cycle, dirty, togglePlay } = useRepl({ tune, defaultSynth, @@ -632,32 +550,71 @@ function MiniRepl({ tune }) { return isVisible || wasVisible.current; }, [isVisible]); useHighlighting({ view, pattern, active: cycle.started }); - return /* @__PURE__ */ React$1.createElement("div", { + return /* @__PURE__ */ React.createElement("div", { className: styles.container, ref - }, /* @__PURE__ */ React$1.createElement("div", { + }, /* @__PURE__ */ React.createElement("div", { className: styles.header - }, /* @__PURE__ */ React$1.createElement("div", { + }, /* @__PURE__ */ React.createElement("div", { className: styles.buttons - }, /* @__PURE__ */ React$1.createElement("button", { + }, /* @__PURE__ */ React.createElement("button", { className: cx(styles.button, cycle.started ? "sc-animate-pulse" : ""), onClick: () => togglePlay() - }, /* @__PURE__ */ React$1.createElement(Icon, { + }, /* @__PURE__ */ React.createElement(Icon, { type: cycle.started ? "pause" : "play" - })), /* @__PURE__ */ React$1.createElement("button", { + })), /* @__PURE__ */ React.createElement("button", { className: cx(dirty ? styles.button : styles.buttonDisabled), onClick: () => activateCode() - }, /* @__PURE__ */ React$1.createElement(Icon, { + }, /* @__PURE__ */ React.createElement(Icon, { type: "refresh" - }))), error && /* @__PURE__ */ React$1.createElement("div", { + }))), error && /* @__PURE__ */ React.createElement("div", { className: styles.error - }, error.message)), /* @__PURE__ */ React$1.createElement("div", { + }, error.message)), /* @__PURE__ */ React.createElement("div", { className: styles.body - }, show && /* @__PURE__ */ React$1.createElement(CodeMirror, { + }, show && /* @__PURE__ */ React.createElement(CodeMirror, { value: code, onChange: setCode, onViewChanged: setView }))); } -export { CodeMirror6 as CodeMirror, MiniRepl }; +/* +useWebMidi.js - +Copyright (C) 2022 Strudel contributors - see +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . +*/ + +function useWebMidi(props) { + const { ready, connected, disconnected } = props; + const [loading, setLoading] = useState(true); + const [outputs, setOutputs] = useState(WebMidi?.outputs || []); + useEffect(() => { + enableWebMidi() + .then(() => { + // Reacting when a new device becomes available + WebMidi.addListener('connected', (e) => { + setOutputs([...WebMidi.outputs]); + connected?.(WebMidi, e); + }); + // Reacting when a device becomes unavailable + WebMidi.addListener('disconnected', (e) => { + setOutputs([...WebMidi.outputs]); + disconnected?.(WebMidi, e); + }); + ready?.(WebMidi); + setLoading(false); + }) + .catch((err) => { + if (err) { + console.error(err); + //throw new Error("Web Midi could not be enabled..."); + console.warn('Web Midi could not be enabled..'); + return; + } + }); + }, [ready, connected, disconnected, outputs]); + const outputByName = (name) => WebMidi.getOutputByName(name); + return { loading, outputs, outputByName }; +} + +export { CodeMirror, MiniRepl, cx, useCycle, useHighlighting, usePostMessage, useRepl, useWebMidi }; diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index e29513fe..1da0a4a1 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -1,6 +1,5 @@ import React, { useState, useMemo, useRef } from 'react'; import { useInView } from 'react-hook-inview'; - import useRepl from '../hooks/useRepl.mjs'; import cx from '../cx'; import useHighlighting from '../hooks/useHighlighting.mjs'; @@ -9,27 +8,7 @@ import 'tailwindcss/tailwind.css'; import styles from './MiniRepl.module.css'; import { Icon } from './Icon'; -import { Tone } from '@strudel.cycles/tone'; -import { evalScope } from '@strudel.cycles/eval'; -evalScope( - Tone, - import('@strudel.cycles/core'), - import('@strudel.cycles/tone'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/midi'), - import('@strudel.cycles/xen'), - import('@strudel.cycles/webaudio'), -); - -const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({ - oscillator: { type: 'triangle' }, - envelope: { - release: 0.01, - }, -}); - -export function MiniRepl({ tune }) { +export function MiniRepl({ tune, defaultSynth }) { const { code, setCode, pattern, activateCode, error, cycle, dirty, togglePlay } = useRepl({ tune, defaultSynth, diff --git a/packages/react/src/index.js b/packages/react/src/index.js index 70030c2c..f008dbde 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -1,4 +1,10 @@ // import 'tailwindcss/tailwind.css'; -export * as CodeMirror from './components/CodeMirror6'; +export { default as CodeMirror } from './components/CodeMirror6'; export * from './components/MiniRepl'; +export { default as useCycle } from './hooks/useCycle'; +export { default as useHighlighting } from './hooks/useHighlighting'; +export { default as usePostMessage } from './hooks/usePostMessage'; +export { default as useRepl } from './hooks/useRepl'; +export { default as cx } from './cx'; +export { useWebMidi } from './hooks/useWebMidi'; diff --git a/packages/tone/draw.mjs b/packages/tone/draw.mjs index 1b7aef1a..4fd3ea9e 100644 --- a/packages/tone/draw.mjs +++ b/packages/tone/draw.mjs @@ -47,7 +47,7 @@ Pattern.prototype.draw = function (callback, cycleSpan, lookaheadCycles = 1) { return this; }; -export const cleanup = () => { +export const cleanupDraw = () => { const ctx = getDrawContext(); ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); if (window.strudelAnimation) { diff --git a/packages/tone/ui.mjs b/packages/tone/ui.mjs index b0a253f0..ace29ac1 100644 --- a/packages/tone/ui.mjs +++ b/packages/tone/ui.mjs @@ -47,7 +47,7 @@ export const backgroundImage = function (src, animateOptions = {}) { ); }; -export const cleanup = () => { +export const cleanupUi = () => { const container = document.getElementById('code'); if (container) { container.style = ''; diff --git a/repl/src/App.jsx b/repl/src/App.jsx index c755f13a..354fdce8 100644 --- a/repl/src/App.jsx +++ b/repl/src/App.jsx @@ -4,59 +4,24 @@ Copyright (C) 2022 Strudel contributors - see . */ -import CodeMirror6, { setHighlights } from '@strudel.cycles/react/src/components/CodeMirror6'; -import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; -import cx from '@strudel.cycles/react/src/cx'; -import logo from './logo.svg'; -// import playStatic from './static.mjs'; -import { getDefaultSynth } from '@strudel.cycles/tone'; -import * as tunes from './tunes.mjs'; -import useRepl from '@strudel.cycles/react/src/hooks/useRepl.mjs'; -import { useWebMidi } from '@strudel.cycles/react/src/hooks/useWebMidi.mjs'; -import useHighlighting from '@strudel.cycles/react/src/hooks/useHighlighting'; -import './App.css'; -// eval stuff start -import { evaluate, extend } from '@strudel.cycles/eval'; -import * as strudel from '@strudel.cycles/core'; -import gist from '@strudel.cycles/core/gist.js'; -import { mini } from '@strudel.cycles/mini/mini.mjs'; -import { Tone } from '@strudel.cycles/tone'; -import * as toneHelpers from '@strudel.cycles/tone/tone.mjs'; -import * as voicingHelpers from '@strudel.cycles/tonal/voicings.mjs'; -import * as uiHelpers from '@strudel.cycles/tone/ui.mjs'; -import * as drawHelpers from '@strudel.cycles/tone/draw.mjs'; -import euclid from '@strudel.cycles/core/euclid.mjs'; -import '@strudel.cycles/tone/tone.mjs'; -import '@strudel.cycles/midi/midi.mjs'; -import '@strudel.cycles/tonal/voicings.mjs'; -import '@strudel.cycles/tonal/tonal.mjs'; -import '@strudel.cycles/xen/xen.mjs'; -import '@strudel.cycles/xen/tune.mjs'; -import '@strudel.cycles/core/euclid.mjs'; -import '@strudel.cycles/core/speak.mjs'; -import '@strudel.cycles/tone/pianoroll.mjs'; -import '@strudel.cycles/tone/draw.mjs'; -import '@strudel.cycles/osc/osc.mjs'; -import '@strudel.cycles/webaudio/webaudio.mjs'; -import '@strudel.cycles/serial/serial.mjs'; import controls from '@strudel.cycles/core/controls.mjs'; - -// TODO: refactor to evalScope -extend( +import { evalScope, evaluate } from '@strudel.cycles/eval'; +import { CodeMirror, cx, useHighlighting, useRepl, useWebMidi } from '@strudel.cycles/react'; +import { getDefaultSynth, cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone'; +import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; +import './App.css'; +import logo from './logo.svg'; +import * as tunes from './tunes.mjs'; +evalScope( Tone, - strudel, - strudel.Pattern.prototype.bootstrap(), controls, - toneHelpers, - voicingHelpers, - drawHelpers, - uiHelpers, - { - gist, - euclid, - mini, - Tone, - }, + import('@strudel.cycles/core'), + import('@strudel.cycles/tone'), + import('@strudel.cycles/tonal'), + import('@strudel.cycles/mini'), + import('@strudel.cycles/midi'), + import('@strudel.cycles/xen'), + import('@strudel.cycles/webaudio'), ); const initialUrl = window.location.href; @@ -195,8 +160,8 @@ function App() { const _code = getRandomTune(); console.log('tune', _code); // uncomment this to debug when random code fails setCode(_code); - drawHelpers.cleanup(); - uiHelpers.cleanup(); + cleanupDraw(); + cleanupUi(); const parsed = await evaluate(_code); setPattern(parsed.pattern); setActiveCode(_code); @@ -235,7 +200,7 @@ function App() {
{/* onCursor={markParens} */} - + {!cycle.started ? `press ctrl+enter to play\n` : dirty ? `ctrl+enter to update\n` : 'no changes\n'} diff --git a/tutorial/MiniRepl.jsx b/tutorial/MiniRepl.jsx new file mode 100644 index 00000000..08da7b4d --- /dev/null +++ b/tutorial/MiniRepl.jsx @@ -0,0 +1,25 @@ +import { Tone } from '@strudel.cycles/tone'; +import { evalScope } from '@strudel.cycles/eval'; +import { MiniRepl as _MiniRepl } from '@strudel.cycles/react'; + +export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({ + oscillator: { type: 'triangle' }, + envelope: { + release: 0.01, + }, +}); + +evalScope( + Tone, + import('@strudel.cycles/core'), + import('@strudel.cycles/tone'), + import('@strudel.cycles/tonal'), + import('@strudel.cycles/mini'), + import('@strudel.cycles/midi'), + import('@strudel.cycles/xen'), + import('@strudel.cycles/webaudio'), +); + +export function MiniRepl({ tune }) { + return <_MiniRepl tune={tune} defaultSynth={defaultSynth} />; +} diff --git a/tutorial/Tutorial.jsx b/tutorial/Tutorial.jsx index 738f7808..fdf856eb 100644 --- a/tutorial/Tutorial.jsx +++ b/tutorial/Tutorial.jsx @@ -10,7 +10,6 @@ import Tutorial from './tutorial.mdx'; import './style.css'; import '@strudel.cycles/react/dist/style.css'; - ReactDOM.render(
@@ -32,5 +31,5 @@ ReactDOM.render(
, - document.getElementById('root') + document.getElementById('root'), ); diff --git a/tutorial/tutorial.mdx b/tutorial/tutorial.mdx index 4a5c08c9..5aa4ed47 100644 --- a/tutorial/tutorial.mdx +++ b/tutorial/tutorial.mdx @@ -1,4 +1,4 @@ -import { MiniRepl } from '@strudel.cycles/react'; +import { MiniRepl } from './MiniRepl'; # What is Strudel?