Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion web/public/pattern-sandbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
<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";(()=>{var PANEL_GEOMETRIES={"128x64_SINGLE":{id:1,physW:128,physH:64,chain:1,resW:128,resH:64},"64x64_SINGLE":{id:2,physW:64,physH:64,chain:1,resW:64,resH:64},"64x64_CHAIN2":{id:3,physW:64,physH:64,chain:2,resW:128,resH:64}},PANEL_GEOM_DEFAULT="128x64_SINGLE",KNOB_COUNT=4,DETENTS_PER_TURN=24;function wrapKnobDelta(delta){return delta>.5?delta-1:delta<-.5?delta+1:delta}var PATTERN_MATRIX_WIDTH=PANEL_GEOMETRIES[PANEL_GEOM_DEFAULT].resW,PATTERN_MATRIX_HEIGHT=PANEL_GEOMETRIES[PANEL_GEOM_DEFAULT].resH,PATTERN_KNOB_COUNT=KNOB_COUNT,PATTERN_DETENTS_PER_TURN=DETENTS_PER_TURN;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 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(`
"use strict";(()=>{var PANEL_GEOMETRIES={"128x64_SINGLE":{id:1,physW:128,physH:64,chain:1,resW:128,resH:64},"64x64_SINGLE":{id:2,physW:64,physH:64,chain:1,resW:64,resH:64},"64x64_CHAIN2":{id:3,physW:64,physH:64,chain:2,resW:128,resH:64}},PANEL_GEOM_DEFAULT="128x64_SINGLE",KNOB_COUNT=4,DETENTS_PER_TURN=24;function wrapKnobDelta(delta){return delta>.5?delta-1:delta<-.5?delta+1:delta}var PATTERN_MATRIX_WIDTH=PANEL_GEOMETRIES[PANEL_GEOM_DEFAULT].resW,PATTERN_MATRIX_HEIGHT=PANEL_GEOMETRIES[PANEL_GEOM_DEFAULT].resH,PATTERN_KNOB_COUNT=KNOB_COUNT,PATTERN_DETENTS_PER_TURN=DETENTS_PER_TURN;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 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}var OPENER_OF={")":"(","]":"[","}":"{"},REGEX_AFTER_WORDS=new Set(["return","typeof","instanceof","in","of","new","delete","void","throw","case","do","else","yield","await"]);function regexCanStart(prev,word){return word?REGEX_AFTER_WORDS.has(word):prev===""||"(,=:[!&|?{};+-*%<>~^".includes(prev)}function openAtEnd(code){let stack=[],innermostFirst=inside=>inside?[inside,...stack.slice().reverse()]:stack.slice().reverse(),prev="",word="",i=0;for(;i<code.length;){let c=code[i];if(stack[stack.length-1]==="`"){c==="\\"?i+=2:c==="`"?(stack.pop(),prev=c,word="",i++):c==="$"&&code[i+1]==="{"?(stack.push("${"),prev="{",word="",i+=2):i++;continue}if(/\s/.test(c)){i++;continue}if(/[\w$]/.test(c)){let j=i+1;for(;j<code.length&&/[\w$]/.test(code[j]);)j++;word=code.slice(i,j),prev=code[j-1],i=j;continue}let next=code[i+1];if(c==="/"&&next==="/"){let end=code.indexOf(`
`,i);if(end<0)break;i=end;continue}if(c==="/"&&next==="*"){let end=code.indexOf("*/",i+2);if(end<0)return innermostFirst("/*");i=end+2;continue}if(c==='"'||c==="'"){let j=i+1;for(;j<code.length&&code[j]!==c;){if(code[j]===`
`)return null;j+=code[j]==="\\"?2:1}if(j>=code.length)return innermostFirst(c);prev=c,word="",i=j+1;continue}if(c==="/"&&regexCanStart(prev,word)){let j=i+1,inClass=!1;for(;j<code.length&&(inClass||code[j]!=="/");){if(code[j]===`
`)return null;code[j]==="["?inClass=!0:code[j]==="]"&&(inClass=!1),j+=code[j]==="\\"?2:1}if(j>=code.length)return null;prev=c,word="",i=j+1;continue}if(c==="`"||c==="("||c==="["||c==="{")stack.push(c);else if(c==="}"&&stack[stack.length-1]==="${")stack.pop();else if(c in OPENER_OF&&stack.pop()!==OPENER_OF[c])return null;prev=c,word="",i++}return innermostFirst()}function describeCutOff(code){var _a;let open=openAtEnd(code);if(!open||open.length===0)return null;let counts=new Map;for(let token of open)counts.set(token,((_a=counts.get(token))!=null?_a:0)+1);let parts=[...counts].map(([token,count])=>`${count} ${token}`),list=parts.length>1?`${parts.slice(0,-1).join(", ")} and ${parts[parts.length-1]}`:parts[0],verb=open.length===1?"never closes":"never close",lines=code.split(`
`),last=lines.length;for(;last>1&&!lines[last-1].trim();)last--;let source=lines[last-1].trim(),quoted=source.length>80?`\u2026${source.slice(-79)}`:source;return`The code stops before it ends: ${list} ${verb}. Was the paste cut off?
line ${last} (the last): ${quoted}`}function describePatternError(error,code){let message=error instanceof Error?error.message:String(error);if(!code)return message;if(error instanceof Error&&error.name==="SyntaxError"){let cutOff=describeCutOff(code);if(cutOff)return cutOff}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";
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/3d/LedMatrixTexture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('LedMatrixTexture errors', () => {
frames(texture, 1);

expect(onError).toHaveBeenCalledTimes(1);
expect(onError.mock.calls[0][0]).toMatch(/^Unexpected/);
expect(onError.mock.calls[0][0]).toMatch(/^The code stops before it ends: 1 \( and 1 \{ never close\./);
expect(topLeft(texture)).toEqual([255, 0, 0]);
});

Expand Down
Loading
Loading