diff --git a/packages/core/pianoroll.mjs b/packages/core/pianoroll.mjs index 378480ba..16a2dc89 100644 --- a/packages/core/pianoroll.mjs +++ b/packages/core/pianoroll.mjs @@ -153,3 +153,131 @@ Pattern.prototype.pianoroll = function ({ ); return this; }; + +// this function allows drawing a pianoroll without ties to Pattern.prototype +// it will probably replace the above in the future +export function pianoroll({ + time, + haps, + cycles = 4, + playhead = 0.5, + flipTime = 0, + flipValues = 0, + hideNegative = false, + // inactive = '#C9E597', + // inactive = '#FFCA28', + inactive = '#7491D2', + active = '#FFCA28', + // background = '#2A3236', + background = 'transparent', + smear = 0, + playheadColor = 'white', + minMidi = 10, + maxMidi = 90, + autorange = 0, + timeframe: timeframeProp, + fold = 0, + vertical = 0, + ctx, +} = {}) { + const w = ctx.canvas.width; + const h = ctx.canvas.height; + let from = -cycles * playhead; + let to = cycles * (1 - playhead); + + if (timeframeProp) { + console.warn('timeframe is deprecated! use from/to instead'); + from = 0; + to = timeframeProp; + } + if (!autorange && fold) { + console.warn('disabling autorange has no effect when fold is enabled'); + } + const timeAxis = vertical ? h : w; + const valueAxis = vertical ? w : h; + let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time + const timeExtent = to - from; // number of seconds that fit inside the canvas frame + const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values + let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true + let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true + let foldValues = []; + flipTime && timeRange.reverse(); + flipValues && valueRange.reverse(); + + // onQuery + const { min, max, values } = haps.reduce( + ({ min, max, values }, e) => { + const v = getValue(e); + return { + min: v < min ? v : min, + max: v > max ? v : max, + values: values.includes(v) ? values : [...values, v], + }; + }, + { min: Infinity, max: -Infinity, values: [] }, + ); + if (autorange) { + minMidi = min; + maxMidi = max; + valueExtent = maxMidi - minMidi + 1; + } + foldValues = values.sort((a, b) => a - b); + barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent; + + ctx.fillStyle = background; + ctx.globalAlpha = 1; // reset! + if (!smear) { + ctx.clearRect(0, 0, w, h); + ctx.fillRect(0, 0, w, h); + } + /* const inFrame = (event) => + (!hideNegative || event.whole.begin >= 0) && event.whole.begin <= time + to && event.whole.end >= time + from; */ + haps + // .filter(inFrame) + .forEach((event) => { + const isActive = event.whole.begin <= time && event.whole.end > time; + ctx.fillStyle = event.context?.color || inactive; + ctx.strokeStyle = event.context?.color || active; + ctx.globalAlpha = event.context.velocity ?? 1; + const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange); + let durationPx = scale(event.duration / timeExtent, 0, timeAxis); + const value = getValue(event); + const valuePx = scale( + fold ? foldValues.indexOf(value) / foldValues.length : (Number(value) - minMidi) / valueExtent, + ...valueRange, + ); + let margin = 0; + const offset = scale(time / timeExtent, ...timeRange); + let coords; + if (vertical) { + coords = [ + valuePx + 1 - (flipValues ? barThickness : 0), // x + timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y + barThickness - 2, // width + durationPx - 2, // height + ]; + } else { + coords = [ + timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x + valuePx + 1 - (flipValues ? 0 : barThickness), // y + durationPx - 2, // widith + barThickness - 2, // height + ]; + } + isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords); + }); + ctx.globalAlpha = 1; // reset! + const playheadPosition = scale(-from / timeExtent, ...timeRange); + // draw playhead + ctx.strokeStyle = playheadColor; + ctx.beginPath(); + if (vertical) { + ctx.moveTo(0, playheadPosition); + ctx.lineTo(valueAxis, playheadPosition); + } else { + ctx.moveTo(playheadPosition, 0); + ctx.lineTo(playheadPosition, valueAxis); + } + ctx.stroke(); + return this; +} diff --git a/packages/react/dist/index.cjs.js b/packages/react/dist/index.cjs.js index 771d8be0..1d12bd87 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"),X=require("@uiw/react-codemirror"),p=require("@codemirror/view"),A=require("@codemirror/state"),Y=require("@codemirror/lang-javascript"),o=require("@lezer/highlight"),Z=require("@uiw/codemirror-themes"),ee=require("react-hook-inview"),B=require("@strudel.cycles/webaudio"),te=require("@strudel.cycles/core"),re=require("@strudel.cycles/transpiler"),I=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},n=I(t),oe=I(X),ae=Z.createTheme({theme:"dark",settings:{background:"#222",foreground:"#75baff",caret:"#ffcc00",selection:"rgba(128, 203, 196, 0.5)",selectionMatch:"#036dd626",lineHighlight:"#00000050",gutterBackground:"transparent",gutterForeground:"#8a919966"},styles:[{tag:o.tags.keyword,color:"#c792ea"},{tag:o.tags.operator,color:"#89ddff"},{tag:o.tags.special(o.tags.variableName),color:"#eeffff"},{tag:o.tags.typeName,color:"#c3e88d"},{tag:o.tags.atom,color:"#f78c6c"},{tag:o.tags.number,color:"#c3e88d"},{tag:o.tags.definition(o.tags.variableName),color:"#82aaff"},{tag:o.tags.string,color:"#c3e88d"},{tag:o.tags.special(o.tags.string),color:"#c3e88d"},{tag:o.tags.comment,color:"#7d8799"},{tag:o.tags.variableName,color:"#c792ea"},{tag:o.tags.tagName,color:"#c3e88d"},{tag:o.tags.bracket,color:"#525154"},{tag:o.tags.meta,color:"#ffcb6b"},{tag:o.tags.attributeName,color:"#c792ea"},{tag:o.tags.propertyName,color:"#c792ea"},{tag:o.tags.className,color:"#decb6b"},{tag:o.tags.invalid,color:"#ffffff"}]});const L=A.StateEffect.define(),ne=A.StateField.define({create(){return p.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(L))if(a.value){const s=p.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=p.Decoration.set([s.range(0,r.newDoc.length)])}else e=p.Decoration.set([]);return e}catch(a){return console.warn("flash error",a),e}},provide:e=>p.EditorView.decorations.from(e)}),K=e=>{e.dispatch({effects:L.of(!0)}),setTimeout(()=>{e.dispatch({effects:L.of(!1)})},200)},x=A.StateEffect.define(),se=A.StateField.define({create(){return p.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(x)){const s=a.value.map(c=>(c.context.locations||[]).map(({start:f,end:d})=>{const g=c.context.color||"#FFCA28";let i=r.newDoc.line(f.line).from+f.column,l=r.newDoc.line(d.line).from+d.column;const m=r.newDoc.length;return i>m||l>m?void 0:p.Decoration.mark({attributes:{style:`outline: 1.5px solid ${g};`}}).range(i,l)})).flat().filter(Boolean)||[];e=p.Decoration.set(s,!0)}return e}catch{return p.Decoration.set([])}},provide:e=>p.EditorView.decorations.from(e)}),ce=[Y.javascript(),ae,se,ne];function O({value:e,onChange:r,onViewChanged:a,onSelectionChange:s,options:c,editorDidMount:f}){const d=t.useCallback(l=>{r?.(l)},[r]),g=t.useCallback(l=>{a?.(l)},[a]),i=t.useCallback(l=>{l.selectionSet&&s&&s?.(l.state.selection)},[s]);return n.default.createElement(n.default.Fragment,null,n.default.createElement(oe.default,{value:e,onChange:d,onCreateEditor:g,onUpdate:i,extensions:ce}))}function T(...e){return e.filter(Boolean).join(" ")}function U({view:e,pattern:r,active:a,getTime:s}){const c=t.useRef([]),f=t.useRef();t.useEffect(()=>{if(e)if(r&&a){let d=requestAnimationFrame(function g(){try{const i=s(),m=[Math.max(f.current||i,i-1/10,0),i+1/60];f.current=m[1],c.current=c.current.filter(h=>h.whole.end>i);const v=r.queryArc(...m).filter(h=>h.hasOnset());c.current=c.current.concat(v),e.dispatch({effects:x.of(c.current)})}catch{e.dispatch({effects:x.of([])})}d=requestAnimationFrame(g)});return()=>{cancelAnimationFrame(d)}}else c.current=[],e.dispatch({effects:x.of([])})},[r,a,e])}const ie="_container_3i85k_1",le="_header_3i85k_5",ue="_buttons_3i85k_9",de="_button_3i85k_9",fe="_buttonDisabled_3i85k_17",ge="_error_3i85k_21",me="_body_3i85k_25",E={container:ie,header:le,buttons:ue,button:de,buttonDisabled:fe,error:ge,body:me};function j({type:e}){return n.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:n.default.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:n.default.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:n.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"})}[e])}function J(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(r=>window.postMessage(r,"*"),[])}function $({defaultOutput:e,interval:r,getTime:a,evalOnMount:s=!1,initialCode:c="",autolink:f=!1,beforeEval:d,afterEval:g,onEvalError:i,onToggle:l}){const m=t.useMemo(()=>he(),[]),[v,h]=t.useState(),[_,C]=t.useState(),[b,y]=t.useState(c),[M,P]=t.useState(),[k,D]=t.useState(),[S,N]=t.useState(!1),w=b!==M,{scheduler:R,evaluate:H,start:G,stop:z,pause:Q}=t.useMemo(()=>te.repl({interval:r,defaultOutput:e,onSchedulerError:h,onEvalError:u=>{C(u),i?.(u)},getTime:a,transpiler:re.transpiler,beforeEval:({code:u})=>{y(u),d?.()},afterEval:({pattern:u,code:F})=>{P(F),D(u),C(),h(),f&&(window.location.hash="#"+encodeURIComponent(btoa(F))),g?.()},onToggle:u=>{N(u),l?.(u)}}),[e,r,a]),W=J(({data:{from:u,type:F}})=>{F==="start"&&u!==m&&z()}),q=t.useCallback(async(u=!0)=>{await H(b,u),W({type:"start",from:m})},[H,b]),V=t.useRef();return t.useEffect(()=>{!V.current&&s&&b&&(V.current=!0,q())},[q,s,b]),t.useEffect(()=>()=>{R.stop()},[R]),{code:b,setCode:y,error:v||_,schedulerError:v,scheduler:R,evalError:_,evaluate:H,activateCode:q,activeCode:M,isDirty:w,pattern:k,started:S,start:G,stop:z,pause:Q,togglePlay:async()=>{S?R.pause():await q()}}}function he(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}const pe=()=>B.getAudioContext().currentTime;function be({tune:e,hideOutsideView:r=!1,init:a,enableKeyboard:s}){const{code:c,setCode:f,evaluate:d,activateCode:g,error:i,isDirty:l,activeCode:m,pattern:v,started:h,scheduler:_,togglePlay:C,stop:b}=$({initialCode:e,defaultOutput:B.webaudioOutput,getTime:pe}),[y,M]=t.useState(),[P,k]=ee.useInView({threshold:.01}),D=t.useRef(),S=t.useMemo(()=>((k||!r)&&(D.current=!0),k||D.current),[k,r]);return U({view:y,pattern:v,active:h&&!m?.includes("strudel disable-highlighting"),getTime:()=>_.getPhase()}),t.useLayoutEffect(()=>{if(s){const N=async w=>{(w.ctrlKey||w.altKey)&&(w.code==="Enter"?(w.preventDefault(),K(y),await g()):w.code==="Period"&&(b(),w.preventDefault()))};return window.addEventListener("keydown",N,!0),()=>window.removeEventListener("keydown",N,!0)}},[s,v,c,d,b,y]),n.default.createElement("div",{className:E.container,ref:P},n.default.createElement("div",{className:E.header},n.default.createElement("div",{className:E.buttons},n.default.createElement("button",{className:T(E.button,h?"sc-animate-pulse":""),onClick:()=>C()},n.default.createElement(j,{type:h?"pause":"play"})),n.default.createElement("button",{className:T(l?E.button:E.buttonDisabled),onClick:()=>g()},n.default.createElement(j,{type:"refresh"}))),i&&n.default.createElement("div",{className:E.error},i.message)),n.default.createElement("div",{className:E.body},S&&n.default.createElement(O,{value:c,onChange:f,onViewChanged:M})))}const ve=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=O;exports.MiniRepl=be;exports.cx=T;exports.flash=K;exports.useHighlighting=U;exports.useKeydown=ve;exports.usePostMessage=J;exports.useStrudel=$; +"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),Z=require("@uiw/react-codemirror"),b=require("@codemirror/view"),x=require("@codemirror/state"),ee=require("@codemirror/lang-javascript"),c=require("@lezer/highlight"),te=require("@uiw/codemirror-themes"),K=require("@strudel.cycles/core"),I=require("@strudel.cycles/webaudio"),re=require("react-hook-inview"),ae=require("@strudel.cycles/transpiler"),U=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},u=U(t),oe=U(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 z=x.StateEffect.define(),se=x.StateField.define({create(){return b.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(z))if(a.value){const s=b.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=b.Decoration.set([s.range(0,r.newDoc.length)])}else e=b.Decoration.set([]);return e}catch(a){return console.warn("flash error",a),e}},provide:e=>b.EditorView.decorations.from(e)}),W=e=>{e.dispatch({effects:z.of(!0)}),setTimeout(()=>{e.dispatch({effects:z.of(!1)})},200)},A=x.StateEffect.define(),ce=x.StateField.define({create(){return b.Decoration.none},update(e,r){try{for(let a of r.effects)if(a.is(A)){const s=a.value.map(n=>(n.context.locations||[]).map(({start:i,end:l})=>{const f=n.context.color||"#FFCA28";let o=r.newDoc.line(i.line).from+i.column,d=r.newDoc.line(l.line).from+l.column;const m=r.newDoc.length;return o>m||d>m?void 0:b.Decoration.mark({attributes:{style:`outline: 1.5px solid ${f};`}}).range(o,d)})).flat().filter(Boolean)||[];e=b.Decoration.set(s,!0)}return e}catch{return b.Decoration.set([])}},provide:e=>b.EditorView.decorations.from(e)}),ie=[ee.javascript(),ne,ce,se];function $({value:e,onChange:r,onViewChanged:a,onSelectionChange:s,options:n,editorDidMount:i}){const l=t.useCallback(d=>{r?.(d)},[r]),f=t.useCallback(d=>{a?.(d)},[a]),o=t.useCallback(d=>{d.selectionSet&&s&&s?.(d.state.selection)},[s]);return u.default.createElement(u.default.Fragment,null,u.default.createElement(oe.default,{value:e,onChange:l,onCreateEditor:f,onUpdate:o,extensions:ie}))}function V(...e){return e.filter(Boolean).join(" ")}function J({view:e,pattern:r,active:a,getTime:s}){const n=t.useRef([]),i=t.useRef();t.useEffect(()=>{if(e)if(r&&a){let l=requestAnimationFrame(function f(){try{const o=s(),m=[Math.max(i.current||o,o-1/10,0),o+1/60];i.current=m[1],n.current=n.current.filter(w=>w.whole.end>o);const h=r.queryArc(...m).filter(w=>w.hasOnset());n.current=n.current.concat(h),e.dispatch({effects:A.of(n.current)})}catch{e.dispatch({effects:A.of([])})}l=requestAnimationFrame(f)});return()=>{cancelAnimationFrame(l)}}else n.current=[],e.dispatch({effects:A.of([])})},[r,a,e])}function le(e,r=!1){const a=t.useRef(),s=t.useRef(),n=f=>{if(s.current!==void 0){const o=f-s.current;e(f,o)}s.current=f,a.current=requestAnimationFrame(n)},i=()=>{a.current=requestAnimationFrame(n)},l=()=>{a.current&&cancelAnimationFrame(a.current),delete a.current};return t.useEffect(()=>{a.current&&(l(),i())},[e]),t.useEffect(()=>(r&&i(),l),[]),{start:i,stop:l}}function ue({pattern:e,started:r,getTime:a,onDraw:s}){let n=t.useRef([]),i=t.useRef(null);const{start:l,stop:f}=le(t.useCallback(()=>{const o=a();if(i.current===null){i.current=o;return}const d=e.queryArc(Math.max(i.current,o-1/10),o),m=4;i.current=o,n.current=(n.current||[]).filter(h=>h.whole.end>o-m).concat(d.filter(h=>h.hasOnset())),s(o,n.current)},[e]));t.useEffect(()=>{r?l():(n.current=[],f())},[r])}function G(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(r=>window.postMessage(r,"*"),[])}function Q({defaultOutput:e,interval:r,getTime:a,evalOnMount:s=!1,initialCode:n="",autolink:i=!1,beforeEval:l,afterEval:f,onEvalError:o,onToggle:d,canvasId:m}){const h=t.useMemo(()=>de(),[]);m=m||`canvas-${h}`;const[w,_]=t.useState(),[k,M]=t.useState(),[E,D]=t.useState(n),[F,R]=t.useState(),[T,P]=t.useState(),[C,q]=t.useState(!1),H=E!==F,{scheduler:g,evaluate:v,start:L,stop:j,pause:X}=t.useMemo(()=>K.repl({interval:r,defaultOutput:e,onSchedulerError:_,onEvalError:p=>{M(p),o?.(p)},getTime:a,transpiler:ae.transpiler,beforeEval:({code:p})=>{D(p),l?.()},afterEval:({pattern:p,code:N})=>{R(N),P(p),M(),_(),i&&(window.location.hash="#"+encodeURIComponent(btoa(N))),f?.()},onToggle:p=>{q(p),d?.(p)}}),[e,r,a]),Y=G(({data:{from:p,type:N}})=>{N==="start"&&p!==h&&j()}),S=t.useCallback(async(p=!0)=>{await v(E,p),Y({type:"start",from:h})},[v,E]),O=t.useRef();return t.useEffect(()=>{!O.current&&s&&E&&(O.current=!0,S())},[S,s,E]),t.useEffect(()=>()=>{g.stop()},[g]),{id:h,canvasId:m,code:E,setCode:D,error:w||k,schedulerError:w,scheduler:g,evalError:k,evaluate:v,activateCode:S,activeCode:F,isDirty:H,pattern:T,started:C,start:L,stop:j,pause:X,togglePlay:async()=>{C?g.pause():await S()}}}function de(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function B({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 fe="_container_3i85k_1",ge="_header_3i85k_5",me="_buttons_3i85k_9",he="_button_3i85k_9",pe="_buttonDisabled_3i85k_17",ve="_error_3i85k_21",be="_body_3i85k_25",y={container:fe,header:ge,buttons:me,button:he,buttonDisabled:pe,error:ve,body:be},we=()=>I.getAudioContext().currentTime;function Ee({tune:e,hideOutsideView:r=!1,init:a,enableKeyboard:s,withCanvas:n=!1,canvasHeight:i=200}){const{code:l,setCode:f,evaluate:o,activateCode:d,error:m,isDirty:h,activeCode:w,pattern:_,started:k,scheduler:M,togglePlay:E,stop:D,canvasId:F}=Q({initialCode:e,defaultOutput:I.webaudioOutput,getTime:we});ue({pattern:_,started:k,getTime:()=>M.now(),onDraw:(g,v)=>{const L=document.querySelector("#"+F).getContext("2d");K.pianoroll({ctx:L,time:g,haps:v,autorange:1,fold:1,playhead:1})}});const[R,T]=t.useState(),[P,C]=re.useInView({threshold:.01}),q=t.useRef(),H=t.useMemo(()=>((C||!r)&&(q.current=!0),C||q.current),[C,r]);return J({view:R,pattern:_,active:k&&!w?.includes("strudel disable-highlighting"),getTime:()=>M.getPhase()}),t.useLayoutEffect(()=>{if(s){const g=async v=>{(v.ctrlKey||v.altKey)&&(v.code==="Enter"?(v.preventDefault(),W(R),await d()):v.code==="Period"&&(D(),v.preventDefault()))};return window.addEventListener("keydown",g,!0),()=>window.removeEventListener("keydown",g,!0)}},[s,_,l,o,D,R]),u.default.createElement("div",{className:y.container,ref:P},u.default.createElement("div",{className:y.header},u.default.createElement("div",{className:y.buttons},u.default.createElement("button",{className:V(y.button,k?"sc-animate-pulse":""),onClick:()=>E()},u.default.createElement(B,{type:k?"pause":"play"})),u.default.createElement("button",{className:V(h?y.button:y.buttonDisabled),onClick:()=>d()},u.default.createElement(B,{type:"refresh"}))),m&&u.default.createElement("div",{className:y.error},m.message)),u.default.createElement("div",{className:y.body},H&&u.default.createElement($,{value:l,onChange:f,onViewChanged:T})),n&&u.default.createElement("canvas",{id:F,className:"w-full pointer-events-none",height:i,ref:g=>{g&&g.width!==g.clientWidth&&(g.width=g.clientWidth)}}))}const ye=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=$;exports.MiniRepl=Ee;exports.cx=V;exports.flash=W;exports.useHighlighting=J;exports.useKeydown=ye;exports.usePostMessage=G;exports.useStrudel=Q; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index 5a00ac91..eb313a3f 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -1,15 +1,15 @@ -import n, { useCallback as _, useRef as H, useEffect as L, useMemo as V, useState as w, useLayoutEffect as j } from "react"; -import X from "@uiw/react-codemirror"; -import { Decoration as E, EditorView as U } from "@codemirror/view"; -import { StateEffect as $, StateField as G } from "@codemirror/state"; -import { javascript as Y } from "@codemirror/lang-javascript"; -import { tags as r } from "@lezer/highlight"; -import { createTheme as Z } from "@uiw/codemirror-themes"; -import { useInView as ee } from "react-hook-inview"; -import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio"; -import { repl as oe } from "@strudel.cycles/core"; -import { transpiler as ne } from "@strudel.cycles/transpiler"; -const ae = Z({ +import l, { useCallback as M, useRef as E, useEffect as C, useMemo as B, useState as _, useLayoutEffect as W } from "react"; +import Y from "@uiw/react-codemirror"; +import { Decoration as y, EditorView as $ } from "@codemirror/view"; +import { StateEffect as G, StateField as J } from "@codemirror/state"; +import { javascript as Z } 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({ theme: "dark", settings: { background: "#222", @@ -22,299 +22,364 @@ const ae = Z({ gutterForeground: "#8a919966" }, styles: [ - { tag: r.keyword, color: "#c792ea" }, - { tag: r.operator, color: "#89ddff" }, - { tag: r.special(r.variableName), color: "#eeffff" }, - { tag: r.typeName, color: "#c3e88d" }, - { tag: r.atom, color: "#f78c6c" }, - { tag: r.number, color: "#c3e88d" }, - { tag: r.definition(r.variableName), color: "#82aaff" }, - { tag: r.string, color: "#c3e88d" }, - { tag: r.special(r.string), color: "#c3e88d" }, - { tag: r.comment, color: "#7d8799" }, - { tag: r.variableName, color: "#c792ea" }, - { tag: r.tagName, color: "#c3e88d" }, - { tag: r.bracket, color: "#525154" }, - { tag: r.meta, color: "#ffcb6b" }, - { tag: r.attributeName, color: "#c792ea" }, - { tag: r.propertyName, color: "#c792ea" }, - { tag: r.className, color: "#decb6b" }, - { tag: r.invalid, color: "#ffffff" } + { 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" } ] }); -const B = $.define(), se = G.define({ +const O = G.define(), ie = J.define({ create() { - return E.none; + return y.none; }, update(e, t) { try { - for (let o of t.effects) - if (o.is(B)) - if (o.value) { - const a = E.mark({ attributes: { style: "background-color: #FFCA2880" } }); - e = E.set([a.range(0, t.newDoc.length)]); + for (let r of t.effects) + if (r.is(O)) + if (r.value) { + const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } }); + e = y.set([a.range(0, t.newDoc.length)]); } else - e = E.set([]); + e = y.set([]); return e; - } catch (o) { - return console.warn("flash error", o), e; + } catch (r) { + return console.warn("flash error", r), e; } }, - provide: (e) => U.decorations.from(e) -}), ce = (e) => { - e.dispatch({ effects: B.of(!0) }), setTimeout(() => { - e.dispatch({ effects: B.of(!1) }); + provide: (e) => $.decorations.from(e) +}), le = (e) => { + e.dispatch({ effects: O.of(!0) }), setTimeout(() => { + e.dispatch({ effects: O.of(!1) }); }, 200); -}, z = $.define(), ie = G.define({ +}, H = G.define(), ue = J.define({ create() { - return E.none; + return y.none; }, update(e, t) { try { - for (let o of t.effects) - if (o.is(z)) { - const a = o.value.map( - (s) => (s.context.locations || []).map(({ start: u, end: d }) => { - const f = s.context.color || "#FFCA28"; - let c = t.newDoc.line(u.line).from + u.column, i = t.newDoc.line(d.line).from + d.column; + for (let r of t.effects) + if (r.is(H)) { + const a = r.value.map( + (n) => (n.context.locations || []).map(({ start: c, end: i }) => { + const d = n.context.color || "#FFCA28"; + let o = t.newDoc.line(c.line).from + c.column, u = t.newDoc.line(i.line).from + i.column; const m = t.newDoc.length; - return c > m || i > m ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${f};` } }).range(c, i); + return o > m || u > m ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(o, u); }) ).flat().filter(Boolean) || []; - e = E.set(a, !0); + e = y.set(a, !0); } return e; } catch { - return E.set([]); + return y.set([]); } }, - provide: (e) => U.decorations.from(e) -}), le = [Y(), ae, ie, se]; -function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: u }) { - const d = _( - (i) => { - t?.(i); + provide: (e) => $.decorations.from(e) +}), de = [Z(), ce, ue, ie]; +function fe({ value: e, onChange: t, onViewChanged: r, onSelectionChange: a, options: n, editorDidMount: c }) { + const i = M( + (u) => { + t?.(u); }, [t] - ), f = _( - (i) => { - o?.(i); + ), d = M( + (u) => { + r?.(u); }, - [o] - ), c = _( - (i) => { - i.selectionSet && a && a?.(i.state.selection); + [r] + ), o = M( + (u) => { + u.selectionSet && a && a?.(u.state.selection); }, [a] ); - return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(X, { + return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(Y, { value: e, - onChange: d, - onCreateEditor: f, - onUpdate: c, - extensions: le + onChange: i, + onCreateEditor: d, + onUpdate: o, + extensions: de })); } -function K(...e) { +function I(...e) { return e.filter(Boolean).join(" "); } -function ue({ view: e, pattern: t, active: o, getTime: a }) { - const s = H([]), u = H(); - L(() => { +function me({ view: e, pattern: t, active: r, getTime: a }) { + const n = E([]), c = E(); + C(() => { if (e) - if (t && o) { - let d = requestAnimationFrame(function f() { + if (t && r) { + let i = requestAnimationFrame(function d() { try { - const c = a(), m = [Math.max(u.current || c, c - 1 / 10, 0), c + 1 / 60]; - u.current = m[1], s.current = s.current.filter((g) => g.whole.end > c); - const h = t.queryArc(...m).filter((g) => g.hasOnset()); - s.current = s.current.concat(h), e.dispatch({ effects: z.of(s.current) }); + const o = a(), m = [Math.max(c.current || o, o - 1 / 10, 0), o + 1 / 60]; + c.current = m[1], n.current = n.current.filter((v) => v.whole.end > o); + const g = t.queryArc(...m).filter((v) => v.hasOnset()); + n.current = n.current.concat(g), e.dispatch({ effects: H.of(n.current) }); } catch { - e.dispatch({ effects: z.of([]) }); + e.dispatch({ effects: H.of([]) }); } - d = requestAnimationFrame(f); + i = requestAnimationFrame(d); }); return () => { - cancelAnimationFrame(d); + cancelAnimationFrame(i); }; } else - s.current = [], e.dispatch({ effects: z.of([]) }); - }, [t, o, e]); + n.current = [], e.dispatch({ effects: H.of([]) }); + }, [t, r, e]); } -const fe = "_container_3i85k_1", me = "_header_3i85k_5", ge = "_buttons_3i85k_9", pe = "_button_3i85k_9", he = "_buttonDisabled_3i85k_17", be = "_error_3i85k_21", ve = "_body_3i85k_25", v = { - container: fe, - header: me, - buttons: ge, - button: pe, - buttonDisabled: he, - error: be, - body: ve -}; -function O({ type: e }) { - return /* @__PURE__ */ n.createElement("svg", { +function ge(e, t = !1) { + const r = E(), a = E(), n = (d) => { + if (a.current !== void 0) { + const o = d - a.current; + e(d, o); + } + a.current = d, r.current = requestAnimationFrame(n); + }, c = () => { + r.current = requestAnimationFrame(n); + }, i = () => { + r.current && cancelAnimationFrame(r.current), delete r.current; + }; + return C(() => { + r.current && (i(), c()); + }, [e]), C(() => (t && c(), i), []), { + start: c, + stop: i + }; +} +function pe({ pattern: e, started: t, getTime: r, onDraw: a }) { + let n = E([]), c = E(null); + const { start: i, stop: d } = ge( + M(() => { + const o = r(); + if (c.current === null) { + c.current = o; + return; + } + const u = e.queryArc(Math.max(c.current, o - 1 / 10), o), m = 4; + c.current = o, n.current = (n.current || []).filter((g) => g.whole.end > o - m).concat(u.filter((g) => g.hasOnset())), a(o, n.current); + }, [e]) + ); + C(() => { + t ? i() : (n.current = [], d()); + }, [t]); +} +function he(e) { + return C(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), M((t) => window.postMessage(t, "*"), []); +} +function ve({ + defaultOutput: e, + interval: t, + getTime: r, + evalOnMount: a = !1, + initialCode: n = "", + autolink: c = !1, + beforeEval: i, + afterEval: d, + onEvalError: o, + onToggle: u, + canvasId: m +}) { + const g = B(() => be(), []); + m = m || `canvas-${g}`; + const [v, F] = _(), [k, A] = _(), [b, D] = _(n), [x, R] = _(), [S, z] = _(), [N, T] = _(!1), L = b !== x, { scheduler: f, evaluate: h, start: V, stop: K, pause: Q } = B( + () => te({ + interval: t, + defaultOutput: e, + onSchedulerError: F, + onEvalError: (p) => { + A(p), o?.(p); + }, + getTime: r, + transpiler: se, + beforeEval: ({ code: p }) => { + D(p), i?.(); + }, + afterEval: ({ pattern: p, code: q }) => { + R(q), z(p), A(), F(), c && (window.location.hash = "#" + encodeURIComponent(btoa(q))), d?.(); + }, + onToggle: (p) => { + T(p), u?.(p); + } + }), + [e, t, r] + ), X = he(({ data: { from: p, type: q } }) => { + q === "start" && p !== g && K(); + }), P = M( + async (p = !0) => { + await h(b, p), X({ type: "start", from: g }); + }, + [h, b] + ), j = E(); + return C(() => { + !j.current && a && b && (j.current = !0, P()); + }, [P, a, b]), C(() => () => { + f.stop(); + }, [f]), { + id: g, + canvasId: m, + code: b, + setCode: D, + error: v || k, + schedulerError: v, + scheduler: f, + evalError: k, + evaluate: h, + activateCode: P, + activeCode: x, + isDirty: L, + pattern: S, + started: N, + start: V, + stop: K, + pause: Q, + togglePlay: async () => { + N ? f.pause() : await P(); + } + }; +} +function be() { + return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); +} +function U({ 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__ */ n.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__ */ n.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__ */ n.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]); } -function Ee(e) { - return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []); -} -function we({ - defaultOutput: e, - interval: t, - getTime: o, - evalOnMount: a = !1, - initialCode: s = "", - autolink: u = !1, - beforeEval: d, - afterEval: f, - onEvalError: c, - onToggle: i +const we = "_container_3i85k_1", ye = "_header_3i85k_5", Ee = "_buttons_3i85k_9", ke = "_button_3i85k_9", _e = "_buttonDisabled_3i85k_17", Ce = "_error_3i85k_21", Fe = "_body_3i85k_25", w = { + container: we, + header: ye, + buttons: Ee, + button: ke, + buttonDisabled: _e, + error: Ce, + body: Fe +}, Ne = () => ne().currentTime; +function Be({ + tune: e, + hideOutsideView: t = !1, + init: r, + enableKeyboard: a, + withCanvas: n = !1, + canvasHeight: c = 200 }) { - const m = V(() => ye(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V( - () => oe({ - interval: t, - defaultOutput: e, - onSchedulerError: g, - onEvalError: (l) => { - N(l), c?.(l); - }, - getTime: o, - transpiler: ne, - beforeEval: ({ code: l }) => { - y(l), d?.(); - }, - afterEval: ({ pattern: l, code: P }) => { - S(P), D(l), N(), g(), u && (window.location.hash = "#" + encodeURIComponent(btoa(P))), f?.(); - }, - onToggle: (l) => { - x(l), i?.(l); - } - }), - [e, t, o] - ), W = Ee(({ data: { from: l, type: P } }) => { - P === "start" && l !== m && q(); - }), R = _( - async (l = !0) => { - await T(p, l), W({ type: "start", from: m }); - }, - [T, p] - ), I = H(); - return L(() => { - !I.current && a && p && (I.current = !0, R()); - }, [R, a, p]), L(() => () => { - A.stop(); - }, [A]), { - code: p, - setCode: y, - error: h || C, - schedulerError: h, - scheduler: A, - evalError: C, - evaluate: T, - activateCode: R, - activeCode: M, - isDirty: b, - pattern: k, - started: F, - start: J, - stop: q, - pause: Q, - togglePlay: async () => { - F ? A.pause() : await R(); - } - }; -} -function ye() { - return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); -} -const ke = () => re().currentTime; -function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) { const { - code: s, - setCode: u, - evaluate: d, - activateCode: f, - error: c, - isDirty: i, - activeCode: m, - pattern: h, - started: g, - scheduler: C, - togglePlay: N, - stop: p - } = we({ + code: i, + setCode: d, + evaluate: o, + activateCode: u, + error: m, + isDirty: g, + activeCode: v, + pattern: F, + started: k, + scheduler: A, + togglePlay: b, + stop: D, + canvasId: x + } = ve({ initialCode: e, - defaultOutput: te, - getTime: ke - }), [y, M] = w(), [S, k] = ee({ - threshold: 0.01 - }), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]); - return ue({ - view: y, - pattern: h, - active: g && !m?.includes("strudel disable-highlighting"), - getTime: () => C.getPhase() - }), j(() => { - if (a) { - const x = async (b) => { - (b.ctrlKey || b.altKey) && (b.code === "Enter" ? (b.preventDefault(), ce(y), await f()) : b.code === "Period" && (p(), b.preventDefault())); - }; - return window.addEventListener("keydown", x, !0), () => window.removeEventListener("keydown", x, !0); + defaultOutput: oe, + getTime: Ne + }); + pe({ + pattern: F, + started: k, + getTime: () => A.now(), + onDraw: (f, h) => { + const V = document.querySelector("#" + x).getContext("2d"); + re({ ctx: V, time: f, haps: h, autorange: 1, fold: 1, playhead: 1 }); } - }, [a, h, s, d, p, y]), /* @__PURE__ */ n.createElement("div", { - className: v.container, - ref: S - }, /* @__PURE__ */ n.createElement("div", { - className: v.header - }, /* @__PURE__ */ n.createElement("div", { - className: v.buttons - }, /* @__PURE__ */ n.createElement("button", { - className: K(v.button, g ? "sc-animate-pulse" : ""), - onClick: () => N() - }, /* @__PURE__ */ n.createElement(O, { - type: g ? "pause" : "play" - })), /* @__PURE__ */ n.createElement("button", { - className: K(i ? v.button : v.buttonDisabled), - onClick: () => f() - }, /* @__PURE__ */ n.createElement(O, { + }); + const [R, S] = _(), [z, N] = ae({ + threshold: 0.01 + }), T = E(), L = B(() => ((N || !t) && (T.current = !0), N || T.current), [N, t]); + return me({ + view: R, + pattern: F, + active: k && !v?.includes("strudel disable-highlighting"), + getTime: () => A.getPhase() + }), W(() => { + if (a) { + const f = async (h) => { + (h.ctrlKey || h.altKey) && (h.code === "Enter" ? (h.preventDefault(), le(R), await u()) : h.code === "Period" && (D(), h.preventDefault())); + }; + return window.addEventListener("keydown", f, !0), () => window.removeEventListener("keydown", f, !0); + } + }, [a, F, i, o, D, R]), /* @__PURE__ */ l.createElement("div", { + className: w.container, + ref: z + }, /* @__PURE__ */ l.createElement("div", { + className: w.header + }, /* @__PURE__ */ l.createElement("div", { + className: w.buttons + }, /* @__PURE__ */ l.createElement("button", { + className: I(w.button, k ? "sc-animate-pulse" : ""), + onClick: () => b() + }, /* @__PURE__ */ l.createElement(U, { + type: k ? "pause" : "play" + })), /* @__PURE__ */ l.createElement("button", { + className: I(g ? w.button : w.buttonDisabled), + onClick: () => u() + }, /* @__PURE__ */ l.createElement(U, { type: "refresh" - }))), c && /* @__PURE__ */ n.createElement("div", { - className: v.error - }, c.message)), /* @__PURE__ */ n.createElement("div", { - className: v.body - }, F && /* @__PURE__ */ n.createElement(de, { - value: s, - onChange: u, - onViewChanged: M - }))); + }))), m && /* @__PURE__ */ l.createElement("div", { + className: w.error + }, m.message)), /* @__PURE__ */ l.createElement("div", { + className: w.body + }, L && /* @__PURE__ */ l.createElement(fe, { + value: i, + onChange: d, + onViewChanged: S + })), n && /* @__PURE__ */ l.createElement("canvas", { + id: x, + className: "w-full pointer-events-none", + height: c, + ref: (f) => { + f && f.width !== f.clientWidth && (f.width = f.clientWidth); + } + })); } -const Te = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); +const Oe = (e) => W(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); export { - de as CodeMirror, - Se as MiniRepl, - K as cx, - ce as flash, - ue as useHighlighting, - Te as useKeydown, - Ee as usePostMessage, - we as useStrudel + fe as CodeMirror, + Be as MiniRepl, + I as cx, + le as flash, + me as useHighlighting, + Oe as useKeydown, + he as usePostMessage, + ve as useStrudel }; diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index ab035b6a..15c75e5c 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -1,18 +1,20 @@ -import React, { useState, useMemo, useRef, useEffect, useLayoutEffect } from 'react'; +import { pianoroll } from '@strudel.cycles/core'; +import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import React, { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useInView } from 'react-hook-inview'; +import 'tailwindcss/tailwind.css'; import cx from '../cx'; import useHighlighting from '../hooks/useHighlighting.mjs'; -import CodeMirror6, { flash } from './CodeMirror6'; -import 'tailwindcss/tailwind.css'; -import './style.css'; -import styles from './MiniRepl.module.css'; -import { Icon } from './Icon'; -import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import usePatternFrame from '../hooks/usePatternFrame.mjs'; import useStrudel from '../hooks/useStrudel.mjs'; +import CodeMirror6, { flash } from './CodeMirror6'; +import { Icon } from './Icon'; +import styles from './MiniRepl.module.css'; +import './style.css'; const getTime = () => getAudioContext().currentTime; -export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }) { +export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCanvas = false, canvasHeight = 200 }) { const { code, setCode, @@ -26,11 +28,23 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard } scheduler, togglePlay, stop, + canvasId, } = useStrudel({ initialCode: tune, defaultOutput: webaudioOutput, getTime, }); + + usePatternFrame({ + pattern, + 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]); */ @@ -88,6 +102,18 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }
{tune};
+ return Repl ? {tune};
}
diff --git a/website/src/pages/learn/notes.mdx b/website/src/pages/learn/notes.mdx
index 6bfd3b3d..f4410e90 100644
--- a/website/src/pages/learn/notes.mdx
+++ b/website/src/pages/learn/notes.mdx
@@ -15,7 +15,7 @@ Here's the same pattern written in three different ways:
- `note`: letter notation, good for those who are familiar with western music theory:
-