Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
07e931b46This file is pinned to the selected release. Download the original to open it in your design tool.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Patternflow sandbox</title>
<style>
html, body { margin: 0; height: 100%; background: transparent; overflow: hidden; }
canvas {
display: block;
width: 100%;
height: 100%;
image-rendering: pixelated;
object-fit: contain;
}
</style>
</head>
<body>
<canvas id="screen" width="128" height="64"></canvas>
<script>
// Built by scripts/build-sandbox.ts from src/sandbox/sandbox.ts and the lab runtime in
// src/lib/pattern/. Do not edit: run `npm run build:sandbox` in web/.
"use strict";(()=>{function clampByte(value){return Number.isFinite(value)?Math.max(0,Math.min(255,Math.round(value))):0}var RAMP_MODES=["linear","smooth","step","hsvShort","hsvLong","oklab","oklchShort","oklchLong"];function rgbToHsv(r,g,b){let rf=r/255,gf=g/255,bf=b/255,max=Math.max(rf,gf,bf),min=Math.min(rf,gf,bf),delta=max-min,h=0;delta>0&&(max===rf?h=(gf-bf)/delta%6:max===gf?h=(bf-rf)/delta+2:h=(rf-gf)/delta+4,h/=6,h<0&&(h+=1));let s=max===0?0:delta/max;return[h,s,max]}function hsvToRgbTuple(h,s,v){h=h-Math.floor(h);let i=Math.floor(h*6),f=h*6-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s),r,g,b;switch(i%6){case 0:r=v,g=t,b=p;break;case 1:r=q,g=v,b=p;break;case 2:r=p,g=v,b=t;break;case 3:r=p,g=q,b=v;break;case 4:r=t,g=p,b=v;break;default:r=v,g=p,b=q;break}return[Math.round(r*255),Math.round(g*255),Math.round(b*255)]}function srgbChannelToLinear(c){return c/=255,c<=.04045?c/12.92:Math.pow((c+.055)/1.055,2.4)}function linearChannelToSrgb(c){let v=c<=.0031308?c*12.92:1.055*Math.pow(c,.4166666666666667)-.055;return Math.max(0,Math.min(1,v))*255}function srgbToOklab(r,g,b){let lr=srgbChannelToLinear(r),lg=srgbChannelToLinear(g),lb=srgbChannelToLinear(b),l=Math.cbrt(.4122214708*lr+.5363325363*lg+.0514459929*lb),m=Math.cbrt(.2119034982*lr+.6806995451*lg+.1073969566*lb),s=Math.cbrt(.0883024619*lr+.2817188376*lg+.6299787005*lb);return[.2104542553*l+.793617785*m-.0040720468*s,1.9779984951*l-2.428592205*m+.4505937099*s,.0259040371*l+.7827717662*m-.808675766*s]}function oklabToLinearRgb(L,A,B){let l_=L+.3963377774*A+.2158037573*B,m_=L-.1055613458*A-.0638541728*B,s_=L-.0894841775*A-1.291485548*B,l=l_*l_*l_,m=m_*m_*m_,s=s_*s_*s_;return[4.0767416621*l-3.3077115913*m+.2309699292*s,-1.2684380046*l+2.6097574011*m-.3413193965*s,-.0041960863*l-.7034186147*m+1.707614701*s]}var GAMUT_EPS=1e-4;function inLinearGamut(rgb){return rgb[0]>=-GAMUT_EPS&&rgb[0]<=1+GAMUT_EPS&&rgb[1]>=-GAMUT_EPS&&rgb[1]<=1+GAMUT_EPS&&rgb[2]>=-GAMUT_EPS&&rgb[2]<=1+GAMUT_EPS}function oklabToSrgb(L,A,B){let rgb=oklabToLinearRgb(L,A,B);if(!inLinearGamut(rgb)){let lo=0,hi=1;for(let i=0;i<16;i++){let mid=(lo+hi)/2;inLinearGamut(oklabToLinearRgb(L,A*mid,B*mid))?lo=mid:hi=mid}rgb=oklabToLinearRgb(L,A*lo,B*lo)}return[linearChannelToSrgb(rgb[0]),linearChannelToSrgb(rgb[1]),linearChannelToSrgb(rgb[2])]}function mixRampColors(a,b,t,mode){if(mode==="smooth"&&(t=t*t*(3-2*t)),mode==="oklab"||mode==="oklchShort"||mode==="oklchLong"){let[La,Aa,Ba]=srgbToOklab(a[0],a[1],a[2]),[Lb,Ab,Bb]=srgbToOklab(b[0],b[1],b[2]),L=La+(Lb-La)*t;if(mode==="oklab")return oklabToSrgb(L,Aa+(Ab-Aa)*t,Ba+(Bb-Ba)*t);let CHROMA_EPS=1e-4,TWO_PI=Math.PI*2,Ca=Math.sqrt(Aa*Aa+Ba*Ba),Cb=Math.sqrt(Ab*Ab+Bb*Bb),hueA=Math.atan2(Ba,Aa)/TWO_PI;hueA<0&&(hueA+=1);let hueB=Math.atan2(Bb,Ab)/TWO_PI;hueB<0&&(hueB+=1);let h1=Ca<CHROMA_EPS?Cb<CHROMA_EPS?0:hueB:hueA,dh=(Cb<CHROMA_EPS?h1:hueB)-h1;mode==="oklchShort"?(dh>.5&&(dh-=1),dh<-.5&&(dh+=1)):dh>0&&dh<.5?dh-=1:dh<=0&&dh>-.5&&(dh+=1);let C=Ca+(Cb-Ca)*t,H=(h1+dh*t)*TWO_PI;return oklabToSrgb(L,Math.cos(H)*C,Math.sin(H)*C)}if(mode==="hsvShort"||mode==="hsvLong"){let ha=rgbToHsv(a[0],a[1],a[2]),hb=rgbToHsv(b[0],b[1],b[2]),h1=ha[1]===0?hb[0]:ha[0],dh=(hb[1]===0?h1:hb[0])-h1;return mode==="hsvShort"?(dh>.5&&(dh-=1),dh<-.5&&(dh+=1)):dh>0&&dh<.5?dh-=1:dh<=0&&dh>-.5&&(dh+=1),hsvToRgbTuple(h1+dh*t,ha[1]+(hb[1]-ha[1])*t,ha[2]+(hb[2]-ha[2])*t)}return[a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t]}function stopAlpha(stop){let a=stop.alpha;return a===void 0||!Number.isFinite(a)?1:Math.max(0,Math.min(1,a))}function mixStopAlpha(a,b,t,mode){mode==="smooth"&&(t=t*t*(3-2*t));let aa=stopAlpha(a);return aa+(stopAlpha(b)-aa)*t}function sampleSortedRampRGBA(stops,mode,wrap,t){if(stops.length===0)return[255,255,255,1];if(stops.length===1)return[...stops[0].color,stopAlpha(stops[0])];t=Math.max(0,Math.min(1,t));let first=stops[0],last=stops[stops.length-1];if(t<=first.position||t>=last.position){if(!wrap){let edge=t<=first.position?first:last;return[...edge.color,stopAlpha(edge)]}let span=1-last.position+first.position,local=span<=0?0:(t>=last.position?t-last.position:t+1-last.position)/span;return mode==="step"?[...last.color,stopAlpha(last)]:[...mixRampColors(last.color,first.color,local,mode),mixStopAlpha(last,first,local,mode)]}for(let i=0;i<stops.length-1;i++){let a=stops[i],b=stops[i+1];if(t>b.position)continue;if(mode==="step")return[...a.color,stopAlpha(a)];let span=b.position-a.position,local=span<=0?0:(t-a.position)/span;return[...mixRampColors(a.color,b.color,local,mode),mixStopAlpha(a,b,local,mode)]}return[...last.color,stopAlpha(last)]}function buildRampLUT(ramp){let sorted=[...ramp.stops].sort((a,b)=>a.position-b.position),lut=new Uint8Array(256*3);for(let i=0;i<256;i++){let[r,g,b]=sampleSortedRampRGBA(sorted,ramp.mode,ramp.wrap,i/255);lut[i*3]=clampByte(r),lut[i*3+1]=clampByte(g),lut[i*3+2]=clampByte(b)}return lut}function buildRampLUTRGBA(ramp){let sorted=[...ramp.stops].sort((a,b)=>a.position-b.position),lut=new Uint8Array(256*4);for(let i=0;i<256;i++){let[r,g,b,a]=sampleSortedRampRGBA(sorted,ramp.mode,ramp.wrap,i/255);lut[i*4]=clampByte(r),lut[i*4+1]=clampByte(g),lut[i*4+2]=clampByte(b),lut[i*4+3]=clampByte(a*255)}return lut}function normalizePatternCode(code){return code.replace(/^\s*import\s+.*?;?\s*$/gm,"").replace(/\bexport\s+function\b/g,"function")}var WRAPPER_LINES_BEFORE_CODE=2,functionPreambleLines=null;function stackLocation(error){let stack=error instanceof Error?error.stack:null;if(!stack)return null;let match=stack.match(/(?:<anonymous>|Function|eval):(\d+):(\d+)/);return match?{line:Number(match[1]),column:Number(match[2])}:null}function getFunctionPreambleLines(){if(functionPreambleLines!==null)return functionPreambleLines;functionPreambleLines=2;try{new Function("throw new Error('probe');")()}catch(error){let location=stackLocation(error);location&&(functionPreambleLines=location.line-1)}return functionPreambleLines}function describePatternError(error,code){let message=error instanceof Error?error.message:String(error);if(!code)return message;let location=stackLocation(error);if(!location)return message;let line=location.line-getFunctionPreambleLines()-WRAPPER_LINES_BEFORE_CODE,sourceLines=code.split(`
`);if(line<1||line>sourceLines.length)return message;let source=sourceLines[line-1].trim(),quoted=source.length>80?`${source.slice(0,79)}\u2026`:source;return`${message}
line ${line}: ${quoted}`}function compilePatternCode(code){let wrapper=`
"use strict";
${normalizePatternCode(code)}
return {
setup: typeof setup === "function" ? setup : undefined,
update: typeof update === "function" ? update : undefined,
draw: typeof draw === "function" ? draw : undefined
};
`,moduleObject=new Function(wrapper)();if(typeof moduleObject.draw!="function")throw new Error("Pattern must export draw(display, params, time).");return moduleObject}var PatternRuntime=class{constructor(width=128,height=64){this.ramp=null;this.recolor=!1;this.rampLUT=null;this.rampLUTA=null;this.module=null;this.params={};this.source=null;this.loadError=null;this.width=width,this.height=height,this.data=new Uint8ClampedArray(width*height*4),this.display={width,height,setPixel:(x,y,r,g,b)=>{var _a,_b,_c;let xi=Math.floor(x),yi=Math.floor(y);if(xi<0||xi>=this.width||yi<0||yi>=this.height)return;let cr,cg,cb;Array.isArray(r)?(cr=(_a=r[0])!=null?_a:0,cg=(_b=r[1])!=null?_b:0,cb=(_c=r[2])!=null?_c:0):(cr=r,cg=g!=null?g:0,cb=b!=null?b:0);let index=(yi*this.width+xi)*4;this.data[index]=clampByte(cr),this.data[index+1]=clampByte(cg),this.data[index+2]=clampByte(cb),this.data[index+3]=255},setValue:(x,y,v)=>{let xi=Math.floor(x),yi=Math.floor(y);if(xi<0||xi>=this.width||yi<0||yi>=this.height)return;let value=Number.isFinite(v)?Math.max(0,Math.min(1,v)):0,index=(yi*this.width+xi)*4,lut=this.rampLUTA;if(lut){let li=Math.round(value*255)*4;this.data[index]=lut[li],this.data[index+1]=lut[li+1],this.data[index+2]=lut[li+2],this.data[index+3]=lut[li+3]}else{let byte=Math.round(value*255);this.data[index]=byte,this.data[index+1]=byte,this.data[index+2]=byte,this.data[index+3]=255}}}}setRamp(ramp){this.ramp=ramp,this.rampLUT=ramp&&ramp.stops.length>0?buildRampLUT(ramp):null,this.rampLUTA=ramp&&ramp.stops.length>0?buildRampLUTRGBA(ramp):null}loadCode(code){var _a,_b;this.source=code;try{return this.module=compilePatternCode(code),this.params={},this.data.fill(0),(_b=(_a=this.module).setup)==null||_b.call(_a,this.params),this.loadError=null,{ok:!0}}catch(error){return this.module=null,this.loadError=describePatternError(error,code),{ok:!1,error:this.loadError}}}renderFrame(dt,time,input){var _a,_b,_c;if(!this.module)return{ok:!1,error:(_a=this.loadError)!=null?_a:"No pattern loaded."};try{if(this.params.knobValues=input.knobValues,this.params.knobNormalized=input.knobNormalized,this.params.knobDeltas=input.knobDeltas,this.params.knobRanges=input.knobRanges,this.params.btnPressed=input.btnPressed,this.params.btnHeld=input.btnHeld,(_c=(_b=this.module).update)==null||_c.call(_b,dt,input,this.params),this.data.fill(0),this.module.draw(this.display,this.params,time),this.recolor&&this.rampLUTA){let lut=this.rampLUTA,data=this.data;for(let index=0;index<data.length;index+=4){if(data[index+3]===0)continue;let li=Math.round(.2126*data[index]+.7152*data[index+1]+.0722*data[index+2])*4;data[index]=lut[li],data[index+1]=lut[li+1],data[index+2]=lut[li+2],data[index+3]=lut[li+3]}}return{ok:!0}}catch(error){return{ok:!1,error:describePatternError(error,this.source)}}}getParamsSnapshot(){return JSON.parse(JSON.stringify(this.params))}};function knobTargetToDelta(previous,next,wrap=!1,unitsPerTurn=1){let delta=next-previous;return wrap&&(delta>.5&&(delta-=1),delta<-.5&&(delta+=1)),delta*(24/unitsPerTurn)}function hexToRgb(hex){let value=parseInt(hex.slice(1),16);return[value>>16&255,value>>8&255,value&255]}var DEFAULT_MATRIX={width:128,height:64};var MATRIX_MIN=8,MATRIX_MAX=512,MATRIX_LINE_RE=/^[ \t]*\/\/[ \t]*@matrix[ \t]+(\d{1,4})[ \t]*[x×*][ \t]*(\d{1,4})[ \t]*$/m;function clampDimension(value){if(!Number.isFinite(value))return null;let rounded=Math.round(value);return rounded<MATRIX_MIN||rounded>MATRIX_MAX?null:rounded}var MATRIX_HEAVY_PIXELS=8192*4;function parseMatrixAnnotation(code){let match=code.match(MATRIX_LINE_RE);if(!match)return null;let width=clampDimension(Number(match[1])),height=clampDimension(Number(match[2]));return width===null||height===null?null:{width,height}}function matrixFromCode(code){var _a;return(_a=parseMatrixAnnotation(code))!=null?_a:DEFAULT_MATRIX}var MAX_ANNOTATION_STOPS=64,RAMP_LINE_RE=/^[ \t]*\/\/[ \t]*@ramp[ \t]+(.+)$/m,STOP_RE=/^(\d*\.?\d+):(#[0-9a-fA-F]{6})$/;function parseRampAnnotation(code){let match=code.match(RAMP_LINE_RE);if(!match)return null;let tokens=match[1].trim().split(/\s+/);if(tokens.length===0)return null;let mode=RAMP_MODES.includes(tokens[0])?tokens[0]:null;if(!mode)return null;let wrap=!1,recolor=!1,stops=[];for(let token of tokens.slice(1)){if(token==="wrap"){wrap=!0;continue}if(token==="recolor"){recolor=!0;continue}let stop=token.match(STOP_RE);if(!stop||stops.length>=MAX_ANNOTATION_STOPS)continue;let position=Number(stop[1]);Number.isFinite(position)&&stops.push({position:Math.max(0,Math.min(1,position)),color:stop[2].toLowerCase()})}return stops.length===0?null:{stops,mode,wrap,recolor}}var DEFAULT_RAMP={stops:[{position:0,color:[8,24,64]},{position:.55,color:[255,77,0]},{position:1,color:[255,232,154]}],mode:"linear",wrap:!1};function load(code,frame){let runtime=new PatternRuntime(frame.width,frame.height),annotation=parseRampAnnotation(code),ramp=annotation?{stops:annotation.stops.map(stop=>({position:stop.position,color:hexToRgb(stop.color)})),mode:annotation.mode,wrap:annotation.wrap}:DEFAULT_RAMP;runtime.setRamp(ramp),runtime.recolor=!!(annotation!=null&&annotation.recolor);let result=runtime.loadCode(code);return{loaded:{runtime},ok:result.ok,error:result.error}}function render(loaded,dt,time,input){return loaded.runtime.renderFrame(dt,time,input)}function makeInput(deltas,values,normalized,ranges){let flags=[!1,!1,!1,!1];return{knobDeltas:deltas!=null?deltas:[0,0,0,0],knobValues:values,knobNormalized:normalized,knobRanges:ranges,btnPressed:flags,btnHeld:flags.slice()}}function normalize(values,ranges){return values.map((v,i)=>{var _a;let range=(_a=ranges[i])!=null?_a:[0,1],span=Math.max(.001,range[1]-range[0]);return(v-range[0])/span})}var canvas=document.getElementById("screen"),context=canvas.getContext("2d"),imageData=context.createImageData(canvas.width,canvas.height);function resizeLive(frame){canvas.width===frame.width&&canvas.height===frame.height||(canvas.width=frame.width,canvas.height=frame.height,imageData=context.createImageData(frame.width,frame.height))}var MAX_STEP=.05,MAX_CATCHUP=.25,live={loaded:null,running:!0,simTime:0,lastNow:0,knobValues:[.5,.5,.5,.5],knobRanges:[[0,1],[0,1],[0,1],[0,1]],knobWrap:[!1,!1,!1,!1],knobUnitsPerTurn:[1,1,1,1],prevKnobs:null,failed:!1,painted:!1,frame:0,get runtime(){var _a,_b;return(_b=(_a=this.loaded)==null?void 0:_a.runtime)!=null?_b:null}};function paint(loaded){imageData.data.set(loaded.runtime.data),context.putImageData(imageData,0,0)}function post(message){parent.postMessage(message,"*")}function step(dt,withKnobDeltas){var _a;if(!live.loaded)return!1;live.simTime+=dt;let deltas=null;if(withKnobDeltas){let prev=(_a=live.prevKnobs)!=null?_a:live.knobValues;deltas=live.knobValues.map((v,i)=>knobTargetToDelta(prev[i],v,live.knobWrap[i],live.knobUnitsPerTurn[i]||1)),live.prevKnobs=live.knobValues.slice()}let input=makeInput(deltas,live.knobValues,normalize(live.knobValues,live.knobRanges),live.knobRanges),result=render(live.loaded,dt,live.simTime,input);return result.ok?!0:(live.failed=!0,post({type:"pf-status",ok:!1,error:result.error}),!1)}function wake(){live.frame!==0||live.failed||!live.loaded||(live.lastNow=performance.now(),live.frame=requestAnimationFrame(tick))}function tick(now){live.frame=0;let loaded=live.loaded;if(!loaded||live.failed)return;let elapsed=Math.min(Math.max(0,(now-live.lastNow)/1e3),MAX_CATCHUP);if(live.lastNow=now,!live.running){!live.painted&&step(0,!0)&&(paint(loaded),live.painted=!0);return}let remaining=elapsed,first=!0;do{let slice=remaining>MAX_STEP?MAX_STEP:remaining;if(!step(slice,first))return;remaining-=slice,first=!1}while(remaining>1e-5);paint(loaded),live.painted=!0,live.frame=requestAnimationFrame(tick)}function renderStill(request){var _a,_b,_c;let code=String((_a=request.code)!=null?_a:""),frame=matrixFromCode(code),{loaded,ok,error}=load(code,frame);if(!ok)return{ok:!1,error};let fps=request.fps||15,seconds=typeof request.seconds=="number"?request.seconds:.9,frames=Math.max(1,Math.floor(seconds*fps)),values=(_b=request.knobValues)!=null?_b:[.5,.5,.5,.5],ranges=(_c=request.knobRanges)!=null?_c:[[0,1],[0,1],[0,1],[0,1]],normalizedValues=normalize(values,ranges);for(let f=0;f<frames;f++){let result=render(loaded,1/fps,f/fps,makeInput(null,values,normalizedValues,ranges));if(!result.ok)return{ok:!1,error:result.error}}let off=document.createElement("canvas");off.width=frame.width,off.height=frame.height;let offContext=off.getContext("2d"),offImage=offContext.createImageData(frame.width,frame.height);return offImage.data.set(loaded.runtime.data),offContext.putImageData(offImage,0,0),{ok:!0,dataUrl:off.toDataURL("image/png")}}window.addEventListener("message",event=>{var _a;let msg=event.data;if(!(!msg||typeof msg.type!="string")){if(msg.type==="pf-load"){let code=String((_a=msg.code)!=null?_a:""),frame=matrixFromCode(code);resizeLive(frame),live.simTime=0,live.lastNow=performance.now(),live.failed=!1,live.prevKnobs=null,live.painted=!1,Array.isArray(msg.knobValues)&&(live.knobValues=msg.knobValues.slice(0,4)),Array.isArray(msg.knobRanges)&&(live.knobRanges=msg.knobRanges),Array.isArray(msg.knobWrap)&&(live.knobWrap=msg.knobWrap),Array.isArray(msg.knobUnitsPerTurn)&&(live.knobUnitsPerTurn=msg.knobUnitsPerTurn),typeof msg.running=="boolean"&&(live.running=msg.running);let{loaded,ok,error}=load(code,frame);live.loaded=loaded,ok?(paint(loaded),post({type:"pf-status",ok:!0}),wake()):(live.failed=!0,post({type:"pf-status",ok:!1,error}))}else if(msg.type==="pf-knobs")Array.isArray(msg.values)&&(live.knobValues=msg.values.slice(0,4)),Array.isArray(msg.ranges)&&(live.knobRanges=msg.ranges),live.painted=!1,wake();else if(msg.type==="pf-run")live.running=!!msg.running,wake();else if(msg.type==="pf-still"){let result;try{result=renderStill(msg)}catch(error){result={ok:!1,error:error instanceof Error?error.message:String(error)}}post({...result,type:"pf-still-result",id:msg.id})}}});globalThis.__pfLive=live;post({type:"pf-ready"});})();
</script>
</body>
</html>