diff --git a/packages/core/pianoroll.mjs b/packages/core/pianoroll.mjs index b8a597f1..8831652d 100644 --- a/packages/core/pianoroll.mjs +++ b/packages/core/pianoroll.mjs @@ -284,12 +284,14 @@ export function pianoroll({ return this; } -Pattern.prototype.noteroll = function (options = { fold: 1 }) { - return this.onPaint((ctx, time, haps, drawTime) => { - let [lookbehind, lookahead] = drawTime; - lookbehind = Math.abs(lookbehind); - const cycles = lookahead + lookbehind; - const playhead = lookbehind / cycles; - pianoroll({ ctx, time, haps, ...options, cycles, playhead }); - }); +function getOptions(drawTime, options) { + let [lookbehind, lookahead] = drawTime; + lookbehind = Math.abs(lookbehind); + const cycles = lookahead + lookbehind; + const playhead = lookbehind / cycles; + return { ...options, cycles, playhead }; +} + +Pattern.prototype.punchcard = function (options = { fold: 1 }) { + return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) })); }; diff --git a/packages/react/dist/index.cjs.js b/packages/react/dist/index.cjs.js index 86132d69..58d94e08 100644 --- a/packages/react/dist/index.cjs.js +++ b/packages/react/dist/index.cjs.js @@ -1 +1 @@ -"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),ie=require("@uiw/react-codemirror"),y=require("@codemirror/view"),P=require("@codemirror/state"),ue=require("@codemirror/lang-javascript"),i=require("@lezer/highlight"),de=require("@uiw/codemirror-themes"),G=require("@strudel.cycles/webaudio"),fe=require("react-hook-inview"),Q=require("@strudel.cycles/core"),ge=require("@strudel.cycles/transpiler"),X=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},u=X(t),me=X(ie),he=de.createTheme({theme:"dark",settings:{background:"#222",foreground:"#75baff",caret:"#ffcc00",selection:"rgba(128, 203, 196, 0.5)",selectionMatch:"#036dd626",lineHighlight:"#00000050",gutterBackground:"transparent",gutterForeground:"#8a919966"},styles:[{tag:i.tags.keyword,color:"#c792ea"},{tag:i.tags.operator,color:"#89ddff"},{tag:i.tags.special(i.tags.variableName),color:"#eeffff"},{tag:i.tags.typeName,color:"#c3e88d"},{tag:i.tags.atom,color:"#f78c6c"},{tag:i.tags.number,color:"#c3e88d"},{tag:i.tags.definition(i.tags.variableName),color:"#82aaff"},{tag:i.tags.string,color:"#c3e88d"},{tag:i.tags.special(i.tags.string),color:"#c3e88d"},{tag:i.tags.comment,color:"#7d8799"},{tag:i.tags.variableName,color:"#c792ea"},{tag:i.tags.tagName,color:"#c3e88d"},{tag:i.tags.bracket,color:"#525154"},{tag:i.tags.meta,color:"#ffcb6b"},{tag:i.tags.attributeName,color:"#c792ea"},{tag:i.tags.propertyName,color:"#c792ea"},{tag:i.tags.className,color:"#decb6b"},{tag:i.tags.invalid,color:"#ffffff"}]});const T=P.StateEffect.define(),pe=P.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(T))if(r.value){const a=y.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=y.Decoration.set([a.range(0,o.newDoc.length)])}else e=y.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>y.EditorView.decorations.from(e)}),Y=e=>{e.dispatch({effects:T.of(!0)}),setTimeout(()=>{e.dispatch({effects:T.of(!1)})},200)},z=P.StateEffect.define(),ve=P.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(z)){const a=r.value.map(d=>(d.context.locations||[]).map(({start:m,end:f})=>{const s=d.context.color||"#FFCA28";let c=o.newDoc.line(m.line).from+m.column,h=o.newDoc.line(f.line).from+f.column;const p=o.newDoc.length;return c>p||h>p?void 0:y.Decoration.mark({attributes:{style:`outline: 1.5px solid ${s};`}}).range(c,h)})).flat().filter(Boolean)||[];e=y.Decoration.set(a,!0)}return e}catch{return y.Decoration.set([])}},provide:e=>y.EditorView.decorations.from(e)}),be=[ue.javascript(),he,ve,pe];function Z({value:e,onChange:o,onViewChanged:r,onSelectionChange:a,options:d,editorDidMount:m}){const f=t.useCallback(h=>{o?.(h)},[o]),s=t.useCallback(h=>{r?.(h)},[r]),c=t.useCallback(h=>{h.selectionSet&&a&&a?.(h.state.selection)},[a]);return u.default.createElement(u.default.Fragment,null,u.default.createElement(me.default,{value:e,onChange:f,onCreateEditor:s,onUpdate:c,extensions:be}))}function U(...e){return e.filter(Boolean).join(" ")}function ee({view:e,pattern:o,active:r,getTime:a}){const d=t.useRef([]),m=t.useRef();t.useEffect(()=>{if(e)if(o&&r){let f=requestAnimationFrame(function s(){try{const c=a(),p=[Math.max(m.current||c,c-1/10,0),c+1/60];m.current=p[1],d.current=d.current.filter(v=>v.whole.end>c);const n=o.queryArc(...p).filter(v=>v.hasOnset());d.current=d.current.concat(n),e.dispatch({effects:z.of(d.current)})}catch{e.dispatch({effects:z.of([])})}f=requestAnimationFrame(s)});return()=>{cancelAnimationFrame(f)}}else d.current=[],e.dispatch({effects:z.of([])})},[o,r,e])}function Ee(e,o=!1){const r=t.useRef(),a=t.useRef(),d=s=>{if(a.current!==void 0){const c=s-a.current;e(s,c)}a.current=s,r.current=requestAnimationFrame(d)},m=()=>{r.current=requestAnimationFrame(d)},f=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(f(),m())},[e]),t.useEffect(()=>(o&&m(),f),[]),{start:m,stop:f}}function ye({pattern:e,started:o,getTime:r,onDraw:a,drawTime:d=[-2,2]}){let[m,f]=d;m=Math.abs(m);let s=t.useRef([]),c=t.useRef(null);t.useEffect(()=>{if(e){const n=r(),v=e.queryArc(n,n+f);s.current=s.current.filter(E=>E.whole.begin{const n=r()+f;if(c.current===null){c.current=n;return}const v=e.queryArc(Math.max(c.current,n-1/10),n);c.current=n,s.current=(s.current||[]).filter(E=>E.whole.end>n-m-f).concat(v.filter(E=>E.hasOnset())),a(e,n-f,s.current,d)},[e]));t.useEffect(()=>{o?h():(s.current=[],p())},[o])}function te(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(o=>window.postMessage(o,"*"),[])}function re({defaultOutput:e,interval:o,getTime:r,evalOnMount:a=!1,initialCode:d="",autolink:m=!1,beforeEval:f,afterEval:s,onEvalError:c,onToggle:h,canvasId:p,drawContext:n,drawTime:v=[-2,2]}){const E=t.useMemo(()=>we(),[]);p=p||`canvas-${E}`;const[q,S]=t.useState(),[F,A]=t.useState(),[w,C]=t.useState(d),[N,V]=t.useState(),[M,L]=t.useState(),[D,H]=t.useState(!1),O=w!==N,{scheduler:l,evaluate:b,start:W,stop:x,pause:oe}=t.useMemo(()=>Q.repl({interval:o,defaultOutput:e,onSchedulerError:S,onEvalError:g=>{A(g),c?.(g)},getTime:r,drawContext:n,transpiler:ge.transpiler,beforeEval:({code:g})=>{C(g),f?.()},afterEval:({pattern:g,code:k})=>{V(k),L(g),A(),S(),m&&(window.location.hash="#"+encodeURIComponent(btoa(k))),s?.()},onToggle:g=>{H(g),h?.(g)}}),[e,o,r]),ne=te(({data:{from:g,type:k}})=>{k==="start"&&g!==E&&x()}),j=t.useCallback(async(g=!0)=>{const k=await b(w,g);return ne({type:"start",from:E}),k},[b,w]),R=t.useCallback((g,k,I,K)=>{const{onPaint:ce}=g.context||{},le=typeof n=="function"?n(p):n;ce?.(le,k,I,K)},[n,p]),B=t.useCallback(g=>{if(n&&R){const[k,I]=v,K=g.queryArc(0,I);R(g,0,K,v)}},[v,R]),$=t.useRef();t.useEffect(()=>{!$.current&&n&&R&&a&&w&&($.current=!0,b(w,!1).then(g=>B(g)))},[j,a,w,B]),t.useEffect(()=>()=>{l.stop()},[l]);const ae=async()=>{D?(l.stop(),B(M)):await j()},se=q||F;return ye({pattern:M,started:n&&D,getTime:()=>l.now(),drawTime:v,onDraw:R}),{id:E,canvasId:p,code:w,setCode:C,error:se,schedulerError:q,scheduler:l,evalError:F,evaluate:b,activateCode:j,activeCode:N,isDirty:O,pattern:M,started:D,start:W,stop:x,pause:oe,togglePlay:ae}}function we(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function J({type:e}){return u.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:u.default.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:u.default.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:u.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"}),stop:u.default.createElement("path",{fillRule:"evenodd",d:"M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z",clipRule:"evenodd"})}[e])}const ke="_container_3i85k_1",_e="_header_3i85k_5",Me="_buttons_3i85k_9",Ce="_button_3i85k_9",Fe="_buttonDisabled_3i85k_17",De="_error_3i85k_21",Re="_body_3i85k_25",_={container:ke,header:_e,buttons:Me,button:Ce,buttonDisabled:Fe,error:De,body:Re},qe=()=>G.getAudioContext().currentTime;function Se({tune:e,hideOutsideView:o=!1,enableKeyboard:r,drawTime:a,canvasHeight:d=200}){const{code:m,setCode:f,evaluate:s,activateCode:c,error:h,isDirty:p,activeCode:n,pattern:v,started:E,scheduler:q,togglePlay:S,stop:F,canvasId:A,id:w}=re({initialCode:e,defaultOutput:G.webaudioOutput,getTime:qe,evalOnMount:!!a,drawContext:a?l=>document.querySelector("#"+l)?.getContext("2d"):null,drawTime:a}),[C,N]=t.useState(),[V,M]=fe.useInView({threshold:.01}),L=t.useRef(),D=t.useMemo(()=>((M||!o)&&(L.current=!0),M||L.current),[M,o]);ee({view:C,pattern:v,active:E&&!n?.includes("strudel disable-highlighting"),getTime:()=>q.now()}),t.useLayoutEffect(()=>{if(r){const l=async b=>{(b.ctrlKey||b.altKey)&&(b.code==="Enter"?(b.preventDefault(),Y(C),await c()):b.code==="Period"&&(F(),b.preventDefault()))};return window.addEventListener("keydown",l,!0),()=>window.removeEventListener("keydown",l,!0)}},[r,v,m,s,F,C]);const[H,O]=t.useState([]);return Ae(t.useCallback(l=>{const{data:b}=l.detail;b?.hap?.context?.id===w&&O(x=>x.concat([l.detail]).slice(-10))},[])),u.default.createElement("div",{className:_.container,ref:V},u.default.createElement("div",{className:_.header},u.default.createElement("div",{className:_.buttons},u.default.createElement("button",{className:U(_.button,E?"sc-animate-pulse":""),onClick:()=>S()},u.default.createElement(J,{type:E?"stop":"play"})),u.default.createElement("button",{className:U(p?_.button:_.buttonDisabled),onClick:()=>c()},u.default.createElement(J,{type:"refresh"}))),h&&u.default.createElement("div",{className:_.error},h.message)),u.default.createElement("div",{className:_.body},D&&u.default.createElement(Z,{value:m,onChange:f,onViewChanged:N})),a&&u.default.createElement("canvas",{id:A,className:"w-full pointer-events-none",height:d,ref:l=>{l&&l.width!==l.clientWidth&&(l.width=l.clientWidth)}}),!!H.length&&u.default.createElement("div",{className:"sc-bg-gray-800 sc-rounded-md sc-p-2"},H.map(({message:l},b)=>u.default.createElement("div",{key:b},l))))}function Ae(e){Ne(Q.logger.key,e)}function Ne(e,o,r=!1){t.useEffect(()=>(document.addEventListener(e,o,r),()=>{document.removeEventListener(e,o,r)}),[o])}const Le=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=Z;exports.MiniRepl=Se;exports.cx=U;exports.flash=Y;exports.useHighlighting=ee;exports.useKeydown=Le;exports.usePostMessage=te;exports.useStrudel=re; +"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),ie=require("@uiw/react-codemirror"),y=require("@codemirror/view"),P=require("@codemirror/state"),ue=require("@codemirror/lang-javascript"),i=require("@lezer/highlight"),de=require("@uiw/codemirror-themes"),G=require("@strudel.cycles/webaudio"),fe=require("react-hook-inview"),Q=require("@strudel.cycles/core"),ge=require("@strudel.cycles/transpiler"),X=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},u=X(t),me=X(ie),he=de.createTheme({theme:"dark",settings:{background:"#222",foreground:"#75baff",caret:"#ffcc00",selection:"rgba(128, 203, 196, 0.5)",selectionMatch:"#036dd626",lineHighlight:"#00000050",gutterBackground:"transparent",gutterForeground:"#8a919966"},styles:[{tag:i.tags.keyword,color:"#c792ea"},{tag:i.tags.operator,color:"#89ddff"},{tag:i.tags.special(i.tags.variableName),color:"#eeffff"},{tag:i.tags.typeName,color:"#c3e88d"},{tag:i.tags.atom,color:"#f78c6c"},{tag:i.tags.number,color:"#c3e88d"},{tag:i.tags.definition(i.tags.variableName),color:"#82aaff"},{tag:i.tags.string,color:"#c3e88d"},{tag:i.tags.special(i.tags.string),color:"#c3e88d"},{tag:i.tags.comment,color:"#7d8799"},{tag:i.tags.variableName,color:"#c792ea"},{tag:i.tags.tagName,color:"#c3e88d"},{tag:i.tags.bracket,color:"#525154"},{tag:i.tags.meta,color:"#ffcb6b"},{tag:i.tags.attributeName,color:"#c792ea"},{tag:i.tags.propertyName,color:"#c792ea"},{tag:i.tags.className,color:"#decb6b"},{tag:i.tags.invalid,color:"#ffffff"}]});const T=P.StateEffect.define(),pe=P.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(T))if(r.value){const a=y.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=y.Decoration.set([a.range(0,o.newDoc.length)])}else e=y.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>y.EditorView.decorations.from(e)}),Y=e=>{e.dispatch({effects:T.of(!0)}),setTimeout(()=>{e.dispatch({effects:T.of(!1)})},200)},z=P.StateEffect.define(),ve=P.StateField.define({create(){return y.Decoration.none},update(e,o){try{for(let r of o.effects)if(r.is(z)){const a=r.value.map(d=>(d.context.locations||[]).map(({start:m,end:f})=>{const s=d.context.color||"#FFCA28";let c=o.newDoc.line(m.line).from+m.column,h=o.newDoc.line(f.line).from+f.column;const p=o.newDoc.length;return c>p||h>p?void 0:y.Decoration.mark({attributes:{style:`outline: 1.5px solid ${s};`}}).range(c,h)})).flat().filter(Boolean)||[];e=y.Decoration.set(a,!0)}return e}catch{return y.Decoration.set([])}},provide:e=>y.EditorView.decorations.from(e)}),be=[ue.javascript(),he,ve,pe];function Z({value:e,onChange:o,onViewChanged:r,onSelectionChange:a,options:d,editorDidMount:m}){const f=t.useCallback(h=>{o?.(h)},[o]),s=t.useCallback(h=>{r?.(h)},[r]),c=t.useCallback(h=>{h.selectionSet&&a&&a?.(h.state.selection)},[a]);return u.default.createElement(u.default.Fragment,null,u.default.createElement(me.default,{value:e,onChange:f,onCreateEditor:s,onUpdate:c,extensions:be}))}function U(...e){return e.filter(Boolean).join(" ")}function ee({view:e,pattern:o,active:r,getTime:a}){const d=t.useRef([]),m=t.useRef();t.useEffect(()=>{if(e)if(o&&r){let f=requestAnimationFrame(function s(){try{const c=a(),p=[Math.max(m.current||c,c-1/10,0),c+1/60];m.current=p[1],d.current=d.current.filter(v=>v.whole.end>c);const n=o.queryArc(...p).filter(v=>v.hasOnset());d.current=d.current.concat(n),e.dispatch({effects:z.of(d.current)})}catch{e.dispatch({effects:z.of([])})}f=requestAnimationFrame(s)});return()=>{cancelAnimationFrame(f)}}else d.current=[],e.dispatch({effects:z.of([])})},[o,r,e])}function Ee(e,o=!1){const r=t.useRef(),a=t.useRef(),d=s=>{if(a.current!==void 0){const c=s-a.current;e(s,c)}a.current=s,r.current=requestAnimationFrame(d)},m=()=>{r.current=requestAnimationFrame(d)},f=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(f(),m())},[e]),t.useEffect(()=>(o&&m(),f),[]),{start:m,stop:f}}function ye({pattern:e,started:o,getTime:r,onDraw:a,drawTime:d=[-2,2]}){let[m,f]=d;m=Math.abs(m);let s=t.useRef([]),c=t.useRef(null);t.useEffect(()=>{if(e){const n=r(),v=e.queryArc(n,n+f);s.current=s.current.filter(E=>E.whole.begin{const n=r()+f;if(c.current===null){c.current=n;return}const v=e.queryArc(Math.max(c.current,n-1/10),n);c.current=n,s.current=(s.current||[]).filter(E=>E.whole.end>=n-m-f).concat(v.filter(E=>E.hasOnset())),a(e,n-f,s.current,d)},[e]));return t.useEffect(()=>{o?h():(s.current=[],p())},[o]),{clear:()=>{s.current=[]}}}function te(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(o=>window.postMessage(o,"*"),[])}function re({defaultOutput:e,interval:o,getTime:r,evalOnMount:a=!1,initialCode:d="",autolink:m=!1,beforeEval:f,afterEval:s,onEvalError:c,onToggle:h,canvasId:p,drawContext:n,drawTime:v=[-2,2]}){const E=t.useMemo(()=>we(),[]);p=p||`canvas-${E}`;const[q,S]=t.useState(),[F,A]=t.useState(),[w,C]=t.useState(d),[N,V]=t.useState(),[M,L]=t.useState(),[D,H]=t.useState(!1),O=w!==N,{scheduler:l,evaluate:b,start:W,stop:x,pause:oe}=t.useMemo(()=>Q.repl({interval:o,defaultOutput:e,onSchedulerError:S,onEvalError:g=>{A(g),c?.(g)},getTime:r,drawContext:n,transpiler:ge.transpiler,beforeEval:({code:g})=>{C(g),f?.()},afterEval:({pattern:g,code:k})=>{V(k),L(g),A(),S(),m&&(window.location.hash="#"+encodeURIComponent(btoa(k))),s?.()},onToggle:g=>{H(g),h?.(g)}}),[e,o,r]),ne=te(({data:{from:g,type:k}})=>{k==="start"&&g!==E&&x()}),j=t.useCallback(async(g=!0)=>{const k=await b(w,g);return ne({type:"start",from:E}),k},[b,w]),R=t.useCallback((g,k,I,K)=>{const{onPaint:ce}=g.context||{},le=typeof n=="function"?n(p):n;ce?.(le,k,I,K)},[n,p]),B=t.useCallback(g=>{if(n&&R){const[k,I]=v,K=g.queryArc(0,I);R(g,0,K,v)}},[v,R]),$=t.useRef();t.useEffect(()=>{!$.current&&n&&R&&a&&w&&($.current=!0,b(w,!1).then(g=>B(g)))},[j,a,w,B]),t.useEffect(()=>()=>{l.stop()},[l]);const ae=async()=>{D?(l.stop(),B(M)):await j()},se=q||F;return ye({pattern:M,started:n&&D,getTime:()=>l.now(),drawTime:v,onDraw:R}),{id:E,canvasId:p,code:w,setCode:C,error:se,schedulerError:q,scheduler:l,evalError:F,evaluate:b,activateCode:j,activeCode:N,isDirty:O,pattern:M,started:D,start:W,stop:x,pause:oe,togglePlay:ae}}function we(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function J({type:e}){return u.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:u.default.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:u.default.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:u.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"}),stop:u.default.createElement("path",{fillRule:"evenodd",d:"M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z",clipRule:"evenodd"})}[e])}const ke="_container_3i85k_1",_e="_header_3i85k_5",Me="_buttons_3i85k_9",Ce="_button_3i85k_9",Fe="_buttonDisabled_3i85k_17",De="_error_3i85k_21",Re="_body_3i85k_25",_={container:ke,header:_e,buttons:Me,button:Ce,buttonDisabled:Fe,error:De,body:Re},qe=()=>G.getAudioContext().currentTime;function Se({tune:e,hideOutsideView:o=!1,enableKeyboard:r,drawTime:a,canvasHeight:d=200}){const{code:m,setCode:f,evaluate:s,activateCode:c,error:h,isDirty:p,activeCode:n,pattern:v,started:E,scheduler:q,togglePlay:S,stop:F,canvasId:A,id:w}=re({initialCode:e,defaultOutput:G.webaudioOutput,getTime:qe,evalOnMount:!!a,drawContext:a?l=>document.querySelector("#"+l)?.getContext("2d"):null,drawTime:a}),[C,N]=t.useState(),[V,M]=fe.useInView({threshold:.01}),L=t.useRef(),D=t.useMemo(()=>((M||!o)&&(L.current=!0),M||L.current),[M,o]);ee({view:C,pattern:v,active:E&&!n?.includes("strudel disable-highlighting"),getTime:()=>q.now()}),t.useLayoutEffect(()=>{if(r){const l=async b=>{(b.ctrlKey||b.altKey)&&(b.code==="Enter"?(b.preventDefault(),Y(C),await c()):b.code==="Period"&&(F(),b.preventDefault()))};return window.addEventListener("keydown",l,!0),()=>window.removeEventListener("keydown",l,!0)}},[r,v,m,s,F,C]);const[H,O]=t.useState([]);return Ae(t.useCallback(l=>{const{data:b}=l.detail;b?.hap?.context?.id===w&&O(x=>x.concat([l.detail]).slice(-10))},[])),u.default.createElement("div",{className:_.container,ref:V},u.default.createElement("div",{className:_.header},u.default.createElement("div",{className:_.buttons},u.default.createElement("button",{className:U(_.button,E?"sc-animate-pulse":""),onClick:()=>S()},u.default.createElement(J,{type:E?"stop":"play"})),u.default.createElement("button",{className:U(p?_.button:_.buttonDisabled),onClick:()=>c()},u.default.createElement(J,{type:"refresh"}))),h&&u.default.createElement("div",{className:_.error},h.message)),u.default.createElement("div",{className:_.body},D&&u.default.createElement(Z,{value:m,onChange:f,onViewChanged:N})),a&&u.default.createElement("canvas",{id:A,className:"w-full pointer-events-none",height:d,ref:l=>{l&&l.width!==l.clientWidth&&(l.width=l.clientWidth)}}),!!H.length&&u.default.createElement("div",{className:"sc-bg-gray-800 sc-rounded-md sc-p-2"},H.map(({message:l},b)=>u.default.createElement("div",{key:b},l))))}function Ae(e){Ne(Q.logger.key,e)}function Ne(e,o,r=!1){t.useEffect(()=>(document.addEventListener(e,o,r),()=>{document.removeEventListener(e,o,r)}),[o])}const Le=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=Z;exports.MiniRepl=Se;exports.cx=U;exports.flash=Y;exports.useHighlighting=ee;exports.useKeydown=Le;exports.usePostMessage=te;exports.useStrudel=re; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index c72163df..eca17af6 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -178,12 +178,16 @@ function Me({ pattern: e, started: r, getTime: t, onDraw: o, drawTime: u = [-2, return; } const p = e.queryArc(Math.max(c.current, n - 1 / 10), n); - c.current = n, a.current = (a.current || []).filter((b) => b.whole.end > n - m - d).concat(p.filter((b) => b.hasOnset())), o(e, n - d, a.current, u); + c.current = n, a.current = (a.current || []).filter((b) => b.whole.end >= n - m - d).concat(p.filter((b) => b.hasOnset())), o(e, n - d, a.current, u); }, [e]) ); - k(() => { + return k(() => { r ? h() : (a.current = [], g()); - }, [r]); + }, [r]), { + clear: () => { + a.current = []; + } + }; } function Ae(e) { return k(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), w((r) => window.postMessage(r, "*"), []); diff --git a/packages/react/src/hooks/usePatternFrame.mjs b/packages/react/src/hooks/usePatternFrame.mjs index e8b6641d..711af7df 100644 --- a/packages/react/src/hooks/usePatternFrame.mjs +++ b/packages/react/src/hooks/usePatternFrame.mjs @@ -25,7 +25,7 @@ function usePatternFrame({ pattern, started, getTime, onDraw, drawTime = [-2, 2] const haps = pattern.queryArc(Math.max(lastFrame.current, phase - 1 / 10), phase); lastFrame.current = phase; visibleHaps.current = (visibleHaps.current || []) - .filter((h) => h.whole.end > phase - lookbehind - lookahead) // in frame + .filter((h) => h.whole.end >= phase - lookbehind - lookahead) // in frame .concat(haps.filter((h) => h.hasOnset())); onDraw(pattern, phase - lookahead, visibleHaps.current, drawTime); }, [pattern]), @@ -38,6 +38,11 @@ function usePatternFrame({ pattern, started, getTime, onDraw, drawTime = [-2, 2] stopFrame(); } }, [started]); + return { + clear: () => { + visibleHaps.current = []; + }, + }; } export default usePatternFrame; diff --git a/website/src/pages/learn/getting-started.mdx b/website/src/pages/learn/getting-started.mdx index d6a53e0a..be858ed5 100644 --- a/website/src/pages/learn/getting-started.mdx +++ b/website/src/pages/learn/getting-started.mdx @@ -28,7 +28,7 @@ Strudel however runs directly in your web browser, does not require any custom s The main place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/) ([what is a REPL?](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)), but in these pages you will also encounter interactive "MiniREPLs" where you can listen to and edit Strudel patterns. Try clicking the play icon below: - + Then edit the text so it reads `s("bd sd cp hh")` and click the refresh icon. Congratulations, you have now live coded your first Strudel pattern! diff --git a/website/src/pages/learn/mini-notation.mdx b/website/src/pages/learn/mini-notation.mdx index f200d3fc..873ac404 100644 --- a/website/src/pages/learn/mini-notation.mdx +++ b/website/src/pages/learn/mini-notation.mdx @@ -51,12 +51,12 @@ If you do just want to get a regular string that is _not_ parsed as mini-notatio We can play more notes by separating them with spaces: - + Here, those four notes are squashed into one cycle, so each note is a quarter second long. Try adding or removing notes and notice how the tempo changes! - + Note that the overall duration of time does not change, and instead each note length descreases. This is a key idea, as it illustrates the 'Cycle' in TidalCycles! diff --git a/website/src/pages/technical-manual/docs.mdx b/website/src/pages/technical-manual/docs.mdx index 017145da..48f8903d 100644 --- a/website/src/pages/technical-manual/docs.mdx +++ b/website/src/pages/technical-manual/docs.mdx @@ -29,10 +29,10 @@ add a mini repl with - `client:idle` is required to tell astro that the repl should be interactive, see [Client Directive](https://docs.astro.build/en/reference/directives-reference/#client-directives) - `tune`: be any valid pattern code -- `drawTime`: time window for drawing. Use together with `.noteroll()`. Example: +- `drawTime`: time window for drawing. Use together with `.punchcard()`. Example: ```jsx - + ``` ## In-Source Documentation diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 9cd4231f..8c94d157 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -54,9 +54,10 @@ evalScope( export let loadedSamples = []; const presets = prebake(); -let drawContext; +let drawContext, clearCanvas; if (typeof window !== 'undefined') { drawContext = getDrawContext(); + clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); } Promise.all([...modules, presets]).then((data) => { @@ -209,6 +210,7 @@ export function Repl({ embedded = false }) { const handleShuffle = async () => { const { code, name } = getRandomTune(); logger(`[repl] ✨ loading random tune "${name}"`); + clearCanvas(); resetLoadedSamples(); await prebake(); // declare default samples await evaluate(code, false);