@nexart/codemode-sdk 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +326 -0
  2. package/CODE_MODE_PROTOCOL.md +471 -0
  3. package/LICENSE.md +62 -0
  4. package/README.md +296 -58
  5. package/builder.manifest.schema.json +62 -0
  6. package/dist/builder-manifest.d.ts +79 -0
  7. package/dist/builder-manifest.d.ts.map +1 -0
  8. package/dist/builder-manifest.js +97 -0
  9. package/dist/core-index.d.ts +21 -0
  10. package/dist/core-index.d.ts.map +1 -0
  11. package/dist/core-index.js +26 -0
  12. package/dist/engine.d.ts +17 -39
  13. package/dist/engine.d.ts.map +1 -1
  14. package/dist/engine.js +52 -253
  15. package/dist/execute.d.ts +46 -0
  16. package/dist/execute.d.ts.map +1 -0
  17. package/dist/execute.js +283 -0
  18. package/dist/execution-sandbox.d.ts +107 -0
  19. package/dist/execution-sandbox.d.ts.map +1 -0
  20. package/dist/execution-sandbox.js +207 -0
  21. package/dist/index.d.ts +24 -17
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +30 -16
  24. package/dist/loop-engine.d.ts +3 -0
  25. package/dist/loop-engine.d.ts.map +1 -1
  26. package/dist/loop-engine.js +17 -7
  27. package/dist/noise-bridge.d.ts +44 -0
  28. package/dist/noise-bridge.d.ts.map +1 -0
  29. package/dist/noise-bridge.js +68 -0
  30. package/dist/noise-engine.d.ts +74 -0
  31. package/dist/noise-engine.d.ts.map +1 -0
  32. package/dist/noise-engine.js +132 -0
  33. package/dist/noise-sketches/fractalNoise.d.ts +11 -0
  34. package/dist/noise-sketches/fractalNoise.d.ts.map +1 -0
  35. package/dist/noise-sketches/fractalNoise.js +121 -0
  36. package/dist/noise-sketches/index.d.ts +21 -0
  37. package/dist/noise-sketches/index.d.ts.map +1 -0
  38. package/dist/noise-sketches/index.js +28 -0
  39. package/dist/p5-runtime.d.ts +3 -1
  40. package/dist/p5-runtime.d.ts.map +1 -1
  41. package/dist/p5-runtime.js +2 -0
  42. package/dist/sound-bridge.d.ts +89 -0
  43. package/dist/sound-bridge.d.ts.map +1 -0
  44. package/dist/sound-bridge.js +128 -0
  45. package/dist/soundart-engine.d.ts +87 -0
  46. package/dist/soundart-engine.d.ts.map +1 -0
  47. package/dist/soundart-engine.js +173 -0
  48. package/dist/soundart-sketches/chladniBloom.d.ts +3 -0
  49. package/dist/soundart-sketches/chladniBloom.d.ts.map +1 -0
  50. package/dist/soundart-sketches/chladniBloom.js +53 -0
  51. package/dist/soundart-sketches/dualVortex.d.ts +3 -0
  52. package/dist/soundart-sketches/dualVortex.d.ts.map +1 -0
  53. package/dist/soundart-sketches/dualVortex.js +67 -0
  54. package/dist/soundart-sketches/geometryIllusion.d.ts +3 -0
  55. package/dist/soundart-sketches/geometryIllusion.d.ts.map +1 -0
  56. package/dist/soundart-sketches/geometryIllusion.js +89 -0
  57. package/dist/soundart-sketches/index.d.ts +39 -0
  58. package/dist/soundart-sketches/index.d.ts.map +1 -0
  59. package/dist/soundart-sketches/index.js +72 -0
  60. package/dist/soundart-sketches/isoflow.d.ts +3 -0
  61. package/dist/soundart-sketches/isoflow.d.ts.map +1 -0
  62. package/dist/soundart-sketches/isoflow.js +60 -0
  63. package/dist/soundart-sketches/loomWeave.d.ts +3 -0
  64. package/dist/soundart-sketches/loomWeave.d.ts.map +1 -0
  65. package/dist/soundart-sketches/loomWeave.js +59 -0
  66. package/dist/soundart-sketches/noiseTerraces.d.ts +3 -0
  67. package/dist/soundart-sketches/noiseTerraces.d.ts.map +1 -0
  68. package/dist/soundart-sketches/noiseTerraces.js +53 -0
  69. package/dist/soundart-sketches/orb.d.ts +3 -0
  70. package/dist/soundart-sketches/orb.d.ts.map +1 -0
  71. package/dist/soundart-sketches/orb.js +50 -0
  72. package/dist/soundart-sketches/pixelGlyphs.d.ts +3 -0
  73. package/dist/soundart-sketches/pixelGlyphs.d.ts.map +1 -0
  74. package/dist/soundart-sketches/pixelGlyphs.js +72 -0
  75. package/dist/soundart-sketches/prismFlowFields.d.ts +3 -0
  76. package/dist/soundart-sketches/prismFlowFields.d.ts.map +1 -0
  77. package/dist/soundart-sketches/prismFlowFields.js +51 -0
  78. package/dist/soundart-sketches/radialBurst.d.ts +3 -0
  79. package/dist/soundart-sketches/radialBurst.d.ts.map +1 -0
  80. package/dist/soundart-sketches/radialBurst.js +60 -0
  81. package/dist/soundart-sketches/resonantSoundBodies.d.ts +3 -0
  82. package/dist/soundart-sketches/resonantSoundBodies.d.ts.map +1 -0
  83. package/dist/soundart-sketches/resonantSoundBodies.js +89 -0
  84. package/dist/soundart-sketches/rings.d.ts +11 -0
  85. package/dist/soundart-sketches/rings.d.ts.map +1 -0
  86. package/dist/soundart-sketches/rings.js +89 -0
  87. package/dist/soundart-sketches/squares.d.ts +3 -0
  88. package/dist/soundart-sketches/squares.d.ts.map +1 -0
  89. package/dist/soundart-sketches/squares.js +52 -0
  90. package/dist/soundart-sketches/waveStripes.d.ts +3 -0
  91. package/dist/soundart-sketches/waveStripes.d.ts.map +1 -0
  92. package/dist/soundart-sketches/waveStripes.js +44 -0
  93. package/dist/static-engine.d.ts +7 -0
  94. package/dist/static-engine.d.ts.map +1 -1
  95. package/dist/static-engine.js +69 -14
  96. package/dist/types.d.ts +67 -5
  97. package/dist/types.d.ts.map +1 -1
  98. package/dist/types.js +1 -1
  99. package/package.json +26 -15
@@ -0,0 +1,3 @@
1
+ export declare const PRISM_FLOW_FIELDS_SKETCH = "\nfunction setup() {\n const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';\n if (bgMode === 'white') background(245, 245, 245);\n else if (bgMode === 'black') background(10, 10, 10);\n else {\n const r = Math.floor((S.brightness / 100) * 255);\n const g = Math.floor((S.rhythmicity / 100) * 255);\n const b = Math.floor((S.harmonicity / 100) * 255);\n background(r, g, b);\n }\n\n const particleCount = Math.floor(map(S.volume, 0, 100, 1500, 8200));\n const maxSteps = Math.floor(map(S.rhythmicity, 0, 100, 200, 680));\n const weightMin = map(S.volume, 0, 100, 0.5, 1.8);\n const weightMax = map(S.treble, 0, 100, 1.2, 3.2);\n const angleMult = map(max(0, S.harmonicity - 5), 0, 95, 2.5, 7.0);\n const stepJitter = map(S.aggression, 0, 100, 0.5, 2.5);\n const skewOffset = map(S.hue, 0, 100, -PI / 6, PI / 6);\n const alphaBase = map(S.brightness, 0, 100, 0.4, 0.7);\n const scaleF = map(S.harmonicity, 0, 100, 0.0001, 0.0024);\n\n noFill();\n\n for (let i = 0; i < particleCount; i++) {\n let x = random() * width;\n let y = random() * height;\n const hueShift = (i / particleCount) * 360 + (S.hue * 3.6);\n const h = hueShift % 360;\n const sat = map(S.aggression, 0, 100, 70, 100);\n const bri = map(S.brightness, 0, 100, 60, 100);\n \n stroke('hsla(' + h + ',' + sat + '%,' + bri + '%,' + alphaBase + ')');\n strokeWeight(map(random(), 0, 1, weightMin, weightMax));\n \n beginShape();\n vertex(x, y);\n for (let j = 0; j < maxSteps; j++) {\n const angle = noise(x * scaleF, y * scaleF) * PI * 4 + skewOffset;\n const dx = cos(angle) * (angleMult + (random() - 0.5) * stepJitter);\n const dy = sin(angle) * (angleMult + (random() - 0.5) * stepJitter);\n x += dx;\n y += dy;\n if (x < 0 || x > width || y < 0 || y > height) break;\n vertex(x, y);\n }\n endShape();\n }\n}\n";
2
+ export default PRISM_FLOW_FIELDS_SKETCH;
3
+ //# sourceMappingURL=prismFlowFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prismFlowFields.d.ts","sourceRoot":"","sources":["../../soundart-sketches/prismFlowFields.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,43DAiDpC,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,51 @@
1
+ export const PRISM_FLOW_FIELDS_SKETCH = `
2
+ function setup() {
3
+ const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';
4
+ if (bgMode === 'white') background(245, 245, 245);
5
+ else if (bgMode === 'black') background(10, 10, 10);
6
+ else {
7
+ const r = Math.floor((S.brightness / 100) * 255);
8
+ const g = Math.floor((S.rhythmicity / 100) * 255);
9
+ const b = Math.floor((S.harmonicity / 100) * 255);
10
+ background(r, g, b);
11
+ }
12
+
13
+ const particleCount = Math.floor(map(S.volume, 0, 100, 1500, 8200));
14
+ const maxSteps = Math.floor(map(S.rhythmicity, 0, 100, 200, 680));
15
+ const weightMin = map(S.volume, 0, 100, 0.5, 1.8);
16
+ const weightMax = map(S.treble, 0, 100, 1.2, 3.2);
17
+ const angleMult = map(max(0, S.harmonicity - 5), 0, 95, 2.5, 7.0);
18
+ const stepJitter = map(S.aggression, 0, 100, 0.5, 2.5);
19
+ const skewOffset = map(S.hue, 0, 100, -PI / 6, PI / 6);
20
+ const alphaBase = map(S.brightness, 0, 100, 0.4, 0.7);
21
+ const scaleF = map(S.harmonicity, 0, 100, 0.0001, 0.0024);
22
+
23
+ noFill();
24
+
25
+ for (let i = 0; i < particleCount; i++) {
26
+ let x = random() * width;
27
+ let y = random() * height;
28
+ const hueShift = (i / particleCount) * 360 + (S.hue * 3.6);
29
+ const h = hueShift % 360;
30
+ const sat = map(S.aggression, 0, 100, 70, 100);
31
+ const bri = map(S.brightness, 0, 100, 60, 100);
32
+
33
+ stroke('hsla(' + h + ',' + sat + '%,' + bri + '%,' + alphaBase + ')');
34
+ strokeWeight(map(random(), 0, 1, weightMin, weightMax));
35
+
36
+ beginShape();
37
+ vertex(x, y);
38
+ for (let j = 0; j < maxSteps; j++) {
39
+ const angle = noise(x * scaleF, y * scaleF) * PI * 4 + skewOffset;
40
+ const dx = cos(angle) * (angleMult + (random() - 0.5) * stepJitter);
41
+ const dy = sin(angle) * (angleMult + (random() - 0.5) * stepJitter);
42
+ x += dx;
43
+ y += dy;
44
+ if (x < 0 || x > width || y < 0 || y > height) break;
45
+ vertex(x, y);
46
+ }
47
+ endShape();
48
+ }
49
+ }
50
+ `;
51
+ export default PRISM_FLOW_FIELDS_SKETCH;
@@ -0,0 +1,3 @@
1
+ export declare const RADIAL_BURST_SKETCH = "\nfunction setup() {\n const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';\n if (bgMode === 'white') background(245, 245, 245);\n else if (bgMode === 'black') background(10, 10, 10);\n else {\n const r = Math.floor((S.brightness / 100) * 255);\n const g = Math.floor((S.rhythmicity / 100) * 255);\n const b = Math.floor((S.harmonicity / 100) * 255);\n background(r, g, b);\n }\n\n const centerX = width / 2;\n const centerY = height / 2;\n const baseN = Math.floor(map(S.rhythmicity, 0, 100, 90, 320));\n const rhythmMask = 0.4 + (S.rhythmicity / 100) * 0.6;\n\n const palette = [];\n const mainHue = (S.brightness * 3.6) % 360;\n for (let i = 0; i < 16; i++) {\n const h = (mainHue + i * 22.5) % 360;\n const sat = map(S.treble, 0, 100, 60, 90);\n const bri = map(S.bass, 0, 100, 70, 95);\n palette.push('hsl(' + h + ',' + sat + '%,' + bri + '%)');\n }\n\n noFill();\n\n for (let i = 0; i < baseN; i++) {\n if ((i % 3) && random() > rhythmMask) continue;\n\n const angle = (i / baseN) * PI * 2;\n const col = palette[i % palette.length];\n\n const lenJit = map(S.dynamicRange, 0, 100, 0, height * 2.45);\n const rayLength = (S.amplitude / 100) * (height * 1.65) + random() * lenJit;\n\n const waveAmp = map(S.harmonicity, 0, 100, 0, 42);\n const waveFreq = map(S.treble, 0, 100, 1, 48);\n const segments = 28;\n\n const thickness = 1.2 + (S.attack / 100) * 7 + (S.aggression / 100) * 5 + sin(i * 0.9) * 1.4;\n\n stroke(col);\n strokeWeight(thickness);\n\n beginShape();\n for (let s = 0; s <= segments; s++) {\n const t = s / segments;\n const radius = t * rayLength;\n const wobble = sin(t * PI * waveFreq + i * 0.2) * waveAmp * (1 - t);\n const wx = centerX + cos(angle + wobble * 0.01) * radius;\n const wy = centerY + sin(angle + wobble * 0.01) * radius;\n vertex(wx, wy);\n }\n endShape();\n }\n}\n";
2
+ export default RADIAL_BURST_SKETCH;
3
+ //# sourceMappingURL=radialBurst.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radialBurst.d.ts","sourceRoot":"","sources":["../../soundart-sketches/radialBurst.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,+5DA0D/B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,60 @@
1
+ export const RADIAL_BURST_SKETCH = `
2
+ function setup() {
3
+ const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';
4
+ if (bgMode === 'white') background(245, 245, 245);
5
+ else if (bgMode === 'black') background(10, 10, 10);
6
+ else {
7
+ const r = Math.floor((S.brightness / 100) * 255);
8
+ const g = Math.floor((S.rhythmicity / 100) * 255);
9
+ const b = Math.floor((S.harmonicity / 100) * 255);
10
+ background(r, g, b);
11
+ }
12
+
13
+ const centerX = width / 2;
14
+ const centerY = height / 2;
15
+ const baseN = Math.floor(map(S.rhythmicity, 0, 100, 90, 320));
16
+ const rhythmMask = 0.4 + (S.rhythmicity / 100) * 0.6;
17
+
18
+ const palette = [];
19
+ const mainHue = (S.brightness * 3.6) % 360;
20
+ for (let i = 0; i < 16; i++) {
21
+ const h = (mainHue + i * 22.5) % 360;
22
+ const sat = map(S.treble, 0, 100, 60, 90);
23
+ const bri = map(S.bass, 0, 100, 70, 95);
24
+ palette.push('hsl(' + h + ',' + sat + '%,' + bri + '%)');
25
+ }
26
+
27
+ noFill();
28
+
29
+ for (let i = 0; i < baseN; i++) {
30
+ if ((i % 3) && random() > rhythmMask) continue;
31
+
32
+ const angle = (i / baseN) * PI * 2;
33
+ const col = palette[i % palette.length];
34
+
35
+ const lenJit = map(S.dynamicRange, 0, 100, 0, height * 2.45);
36
+ const rayLength = (S.amplitude / 100) * (height * 1.65) + random() * lenJit;
37
+
38
+ const waveAmp = map(S.harmonicity, 0, 100, 0, 42);
39
+ const waveFreq = map(S.treble, 0, 100, 1, 48);
40
+ const segments = 28;
41
+
42
+ const thickness = 1.2 + (S.attack / 100) * 7 + (S.aggression / 100) * 5 + sin(i * 0.9) * 1.4;
43
+
44
+ stroke(col);
45
+ strokeWeight(thickness);
46
+
47
+ beginShape();
48
+ for (let s = 0; s <= segments; s++) {
49
+ const t = s / segments;
50
+ const radius = t * rayLength;
51
+ const wobble = sin(t * PI * waveFreq + i * 0.2) * waveAmp * (1 - t);
52
+ const wx = centerX + cos(angle + wobble * 0.01) * radius;
53
+ const wy = centerY + sin(angle + wobble * 0.01) * radius;
54
+ vertex(wx, wy);
55
+ }
56
+ endShape();
57
+ }
58
+ }
59
+ `;
60
+ export default RADIAL_BURST_SKETCH;
@@ -0,0 +1,3 @@
1
+ export declare const RESONANT_SOUND_BODIES_SKETCH = "\nfunction setup() {\n const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';\n if (bgMode === 'white') background(245, 245, 245);\n else if (bgMode === 'black') background(10, 10, 10);\n else {\n const r = Math.floor((S.brightness / 100) * 255);\n const g = Math.floor((S.rhythmicity / 100) * 255);\n const b = Math.floor((S.harmonicity / 100) * 255);\n background(r, g, b);\n }\n\n const cx = width / 2;\n const cy = height / 2;\n const maxR = min(width, height) * 0.45;\n const shapeSeed = S.bass * 17.3 + S.treble * 23.7 + S.rhythmicity * 11.1;\n\n const numBodies = Math.floor(8 + (S.volume / 100) * 12 + (S.bass / 100) * 8);\n \n noFill();\n\n for (let i = 0; i < numBodies; i++) {\n const t = i / numBodies;\n const radius = maxR * (0.15 + t * 0.85);\n \n const hue = (((S.hue / 100) + t * 0.3 + (S.harmonicity / 100) * 0.2) % 1) * 360;\n const sat = lerp(50, 80, S.brightness / 100);\n const lum = lerp(30, 60, S.brightness / 100);\n \n const alpha = lerp(0.4, 0.15, t) * (0.5 + (S.volume / 100) * 0.5);\n stroke('hsla(' + hue + ',' + sat + '%,' + lum + '%,' + alpha + ')');\n strokeWeight(lerp(3, 1, t) * (1 + (S.aggression / 100) * 2));\n \n beginShape();\n const points = Math.floor(64 + (S.aggression / 100) * 64);\n \n for (let j = 0; j <= points; j++) {\n const angle = (j / points) * PI * 2;\n \n const wobble = noise(\n cos(angle) * 3 + i * 0.5 + shapeSeed * 0.001,\n sin(angle) * 3 + i * 0.5\n );\n \n const bassWobble = sin(angle * (2 + Math.floor((S.bass / 100) * 6))) * (S.bass / 100) * 0.15;\n const trebleWobble = sin(angle * (8 + Math.floor((S.treble / 100) * 12))) * (S.treble / 100) * 0.08;\n \n const r = radius * (1 + (wobble - 0.5) * 0.3 * (S.aggression / 100) + bassWobble + trebleWobble);\n \n const x = cx + cos(angle) * r;\n const y = cy + sin(angle) * r;\n \n vertex(x, y);\n }\n endShape(CLOSE);\n \n if (i % 3 === 0) {\n const fillAlpha = alpha * 0.1;\n fill('hsla(' + hue + ',' + sat + '%,' + lum + '%,' + fillAlpha + ')');\n beginShape();\n for (let j = 0; j <= points; j++) {\n const angle = (j / points) * PI * 2;\n const wobble = noise(cos(angle) * 3 + i * 0.5 + shapeSeed * 0.001, sin(angle) * 3 + i * 0.5);\n const bodyR = radius * (1 + (wobble - 0.5) * 0.3 * (S.aggression / 100));\n vertex(cx + cos(angle) * bodyR, cy + sin(angle) * bodyR);\n }\n endShape(CLOSE);\n noFill();\n }\n }\n\n const numNodes = Math.floor(12 + (S.rhythmicity / 100) * 20);\n for (let i = 0; i < numNodes; i++) {\n const angle = (i / numNodes) * PI * 2 + shapeSeed * 0.001;\n const dist = maxR * (0.3 + noise(i * 0.1, shapeSeed * 0.001) * 0.6);\n \n const x = cx + cos(angle) * dist;\n const y = cy + sin(angle) * dist;\n \n const nodeSize = lerp(2, 8, S.volume / 100) * (1 + (S.bass / 100) * 2);\n const hue = ((((S.hue / 100) + i * 0.05 + (S.treble / 100) * 0.3) % 1)) * 360;\n \n fill('hsla(' + hue + ',70%,' + lerp(40, 70, S.brightness / 100) + '%,' + lerp(0.3, 0.7, S.harmonicity / 100) + ')');\n noStroke();\n ellipse(x, y, nodeSize * 2, nodeSize * 2);\n }\n}\n";
2
+ export default RESONANT_SOUND_BODIES_SKETCH;
3
+ //# sourceMappingURL=resonantSoundBodies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resonantSoundBodies.d.ts","sourceRoot":"","sources":["../../soundart-sketches/resonantSoundBodies.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,wtGAuFxC,CAAC;AAEF,eAAe,4BAA4B,CAAC"}
@@ -0,0 +1,89 @@
1
+ export const RESONANT_SOUND_BODIES_SKETCH = `
2
+ function setup() {
3
+ const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';
4
+ if (bgMode === 'white') background(245, 245, 245);
5
+ else if (bgMode === 'black') background(10, 10, 10);
6
+ else {
7
+ const r = Math.floor((S.brightness / 100) * 255);
8
+ const g = Math.floor((S.rhythmicity / 100) * 255);
9
+ const b = Math.floor((S.harmonicity / 100) * 255);
10
+ background(r, g, b);
11
+ }
12
+
13
+ const cx = width / 2;
14
+ const cy = height / 2;
15
+ const maxR = min(width, height) * 0.45;
16
+ const shapeSeed = S.bass * 17.3 + S.treble * 23.7 + S.rhythmicity * 11.1;
17
+
18
+ const numBodies = Math.floor(8 + (S.volume / 100) * 12 + (S.bass / 100) * 8);
19
+
20
+ noFill();
21
+
22
+ for (let i = 0; i < numBodies; i++) {
23
+ const t = i / numBodies;
24
+ const radius = maxR * (0.15 + t * 0.85);
25
+
26
+ const hue = (((S.hue / 100) + t * 0.3 + (S.harmonicity / 100) * 0.2) % 1) * 360;
27
+ const sat = lerp(50, 80, S.brightness / 100);
28
+ const lum = lerp(30, 60, S.brightness / 100);
29
+
30
+ const alpha = lerp(0.4, 0.15, t) * (0.5 + (S.volume / 100) * 0.5);
31
+ stroke('hsla(' + hue + ',' + sat + '%,' + lum + '%,' + alpha + ')');
32
+ strokeWeight(lerp(3, 1, t) * (1 + (S.aggression / 100) * 2));
33
+
34
+ beginShape();
35
+ const points = Math.floor(64 + (S.aggression / 100) * 64);
36
+
37
+ for (let j = 0; j <= points; j++) {
38
+ const angle = (j / points) * PI * 2;
39
+
40
+ const wobble = noise(
41
+ cos(angle) * 3 + i * 0.5 + shapeSeed * 0.001,
42
+ sin(angle) * 3 + i * 0.5
43
+ );
44
+
45
+ const bassWobble = sin(angle * (2 + Math.floor((S.bass / 100) * 6))) * (S.bass / 100) * 0.15;
46
+ const trebleWobble = sin(angle * (8 + Math.floor((S.treble / 100) * 12))) * (S.treble / 100) * 0.08;
47
+
48
+ const r = radius * (1 + (wobble - 0.5) * 0.3 * (S.aggression / 100) + bassWobble + trebleWobble);
49
+
50
+ const x = cx + cos(angle) * r;
51
+ const y = cy + sin(angle) * r;
52
+
53
+ vertex(x, y);
54
+ }
55
+ endShape(CLOSE);
56
+
57
+ if (i % 3 === 0) {
58
+ const fillAlpha = alpha * 0.1;
59
+ fill('hsla(' + hue + ',' + sat + '%,' + lum + '%,' + fillAlpha + ')');
60
+ beginShape();
61
+ for (let j = 0; j <= points; j++) {
62
+ const angle = (j / points) * PI * 2;
63
+ const wobble = noise(cos(angle) * 3 + i * 0.5 + shapeSeed * 0.001, sin(angle) * 3 + i * 0.5);
64
+ const bodyR = radius * (1 + (wobble - 0.5) * 0.3 * (S.aggression / 100));
65
+ vertex(cx + cos(angle) * bodyR, cy + sin(angle) * bodyR);
66
+ }
67
+ endShape(CLOSE);
68
+ noFill();
69
+ }
70
+ }
71
+
72
+ const numNodes = Math.floor(12 + (S.rhythmicity / 100) * 20);
73
+ for (let i = 0; i < numNodes; i++) {
74
+ const angle = (i / numNodes) * PI * 2 + shapeSeed * 0.001;
75
+ const dist = maxR * (0.3 + noise(i * 0.1, shapeSeed * 0.001) * 0.6);
76
+
77
+ const x = cx + cos(angle) * dist;
78
+ const y = cy + sin(angle) * dist;
79
+
80
+ const nodeSize = lerp(2, 8, S.volume / 100) * (1 + (S.bass / 100) * 2);
81
+ const hue = ((((S.hue / 100) + i * 0.05 + (S.treble / 100) * 0.3) % 1)) * 360;
82
+
83
+ fill('hsla(' + hue + ',70%,' + lerp(40, 70, S.brightness / 100) + '%,' + lerp(0.3, 0.7, S.harmonicity / 100) + ')');
84
+ noStroke();
85
+ ellipse(x, y, nodeSize * 2, nodeSize * 2);
86
+ }
87
+ }
88
+ `;
89
+ export default RESONANT_SOUND_BODIES_SKETCH;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Flow Grid Rings - SoundArt Style as Code Mode Sketch
3
+ *
4
+ * This is the first SoundArt style converted to a Code Mode sketch.
5
+ * It uses S.* globals (sound snapshot) instead of direct sound data.
6
+ *
7
+ * Original: client/src/pages/soundart/engine/styles_impl.ts - drawFlowGridRings
8
+ */
9
+ export declare const RINGS_SKETCH = "\n// Flow Grid Rings - SoundArt Style\n// Uses S.* sound globals and standard p5-like functions\n\nfunction setup() {\n // Set background based on mode (rgb derived from sound, black, or white)\n const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';\n \n if (bgMode === 'white') {\n background(245, 245, 245);\n } else if (bgMode === 'black') {\n background(10, 10, 10);\n } else {\n // Deterministic RGB background derived from sound snapshot\n // Previously used clock time, now uses sound parameters for reproducibility\n const r = Math.floor((S.brightness / 100) * 255);\n const g = Math.floor((S.rhythmicity / 100) * 255);\n const b = Math.floor((S.harmonicity / 100) * 255);\n background(r, g, b);\n }\n \n // Grid dimensions based on sound volume\n const cols = Math.floor(map(S.volume, 0, 100, 2, 32));\n const rows = Math.floor(cols * 1.5);\n const margin = map(S.length, 0, 100, width * 0.05, width * 0.1);\n const innerW = width - margin * 2;\n const innerH = height - margin * 2;\n const cellW = innerW / cols;\n const cellH = innerH / rows;\n const gap = Math.min(cellW, cellH) * 0.25;\n const maxRadius = (Math.min(cellW, cellH) - gap) / 2;\n \n // Palette Harmony based on sound\n const mainHue = (S.brightness * 3.6) % 360;\n const hueShift = map(S.harmonicity, 0, 100, 20, 60);\n const sat = map(S.treble, 0, 100, 60, 90);\n const bri = map(S.bass, 0, 100, 70, 95);\n \n // Generate palette\n const palette = [\n hslColor(mainHue, sat, bri),\n hslColor((mainHue + hueShift) % 360, sat - 10, bri + 5),\n hslColor((mainHue - hueShift + 360) % 360, sat - 10, bri + 5),\n hslColor((mainHue + 180) % 360, sat - 20, bri - 10),\n hslColor((mainHue + 120) % 360, sat, bri - 5),\n hslColor((mainHue + 240) % 360, sat, bri - 5),\n ];\n \n // Flow field parameters\n const noiseScale = 0.15;\n const noiseStrength = map(S.aggression, 0, 100, 0.5, 4.0);\n const jitter = map(S.rhythmicity, 0, 100, 0, 0.15);\n const ringBias = map(S.attack, 0, 100, 0.85, 1.15);\n \n noStroke();\n \n // Render Grid\n for (let gx = 0; gx < cols; gx++) {\n for (let gy = 0; gy < rows; gy++) {\n const cx = margin + gx * cellW + cellW / 2 + (random() - 0.5) * jitter * cellW;\n const cy = margin + gy * cellH + cellH / 2 + (random() - 0.5) * jitter * cellH;\n \n const n = noise(gx * noiseScale, gy * noiseScale);\n const angle = n * PI * 2 * noiseStrength;\n const rings = Math.max(1, Math.floor(map(sin(angle), -1, 1, 1, 6)));\n const step = (maxRadius / rings) * ringBias * 0.9;\n \n for (let i = rings; i > 0; i--) {\n fill(palette[(rings - i) % palette.length]);\n ellipse(cx, cy, i * step * 2, i * step * 2);\n }\n }\n }\n}\n\n// Helper function for HSL colors\nfunction hslColor(h, s, l) {\n return 'hsl(' + h + ',' + s + '%,' + l + '%)';\n}\n";
10
+ export default RINGS_SKETCH;
11
+ //# sourceMappingURL=rings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rings.d.ts","sourceRoot":"","sources":["../../soundart-sketches/rings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,YAAY,u2FA+ExB,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Flow Grid Rings - SoundArt Style as Code Mode Sketch
3
+ *
4
+ * This is the first SoundArt style converted to a Code Mode sketch.
5
+ * It uses S.* globals (sound snapshot) instead of direct sound data.
6
+ *
7
+ * Original: client/src/pages/soundart/engine/styles_impl.ts - drawFlowGridRings
8
+ */
9
+ export const RINGS_SKETCH = `
10
+ // Flow Grid Rings - SoundArt Style
11
+ // Uses S.* sound globals and standard p5-like functions
12
+
13
+ function setup() {
14
+ // Set background based on mode (rgb derived from sound, black, or white)
15
+ const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';
16
+
17
+ if (bgMode === 'white') {
18
+ background(245, 245, 245);
19
+ } else if (bgMode === 'black') {
20
+ background(10, 10, 10);
21
+ } else {
22
+ // Deterministic RGB background derived from sound snapshot
23
+ // Previously used clock time, now uses sound parameters for reproducibility
24
+ const r = Math.floor((S.brightness / 100) * 255);
25
+ const g = Math.floor((S.rhythmicity / 100) * 255);
26
+ const b = Math.floor((S.harmonicity / 100) * 255);
27
+ background(r, g, b);
28
+ }
29
+
30
+ // Grid dimensions based on sound volume
31
+ const cols = Math.floor(map(S.volume, 0, 100, 2, 32));
32
+ const rows = Math.floor(cols * 1.5);
33
+ const margin = map(S.length, 0, 100, width * 0.05, width * 0.1);
34
+ const innerW = width - margin * 2;
35
+ const innerH = height - margin * 2;
36
+ const cellW = innerW / cols;
37
+ const cellH = innerH / rows;
38
+ const gap = Math.min(cellW, cellH) * 0.25;
39
+ const maxRadius = (Math.min(cellW, cellH) - gap) / 2;
40
+
41
+ // Palette Harmony based on sound
42
+ const mainHue = (S.brightness * 3.6) % 360;
43
+ const hueShift = map(S.harmonicity, 0, 100, 20, 60);
44
+ const sat = map(S.treble, 0, 100, 60, 90);
45
+ const bri = map(S.bass, 0, 100, 70, 95);
46
+
47
+ // Generate palette
48
+ const palette = [
49
+ hslColor(mainHue, sat, bri),
50
+ hslColor((mainHue + hueShift) % 360, sat - 10, bri + 5),
51
+ hslColor((mainHue - hueShift + 360) % 360, sat - 10, bri + 5),
52
+ hslColor((mainHue + 180) % 360, sat - 20, bri - 10),
53
+ hslColor((mainHue + 120) % 360, sat, bri - 5),
54
+ hslColor((mainHue + 240) % 360, sat, bri - 5),
55
+ ];
56
+
57
+ // Flow field parameters
58
+ const noiseScale = 0.15;
59
+ const noiseStrength = map(S.aggression, 0, 100, 0.5, 4.0);
60
+ const jitter = map(S.rhythmicity, 0, 100, 0, 0.15);
61
+ const ringBias = map(S.attack, 0, 100, 0.85, 1.15);
62
+
63
+ noStroke();
64
+
65
+ // Render Grid
66
+ for (let gx = 0; gx < cols; gx++) {
67
+ for (let gy = 0; gy < rows; gy++) {
68
+ const cx = margin + gx * cellW + cellW / 2 + (random() - 0.5) * jitter * cellW;
69
+ const cy = margin + gy * cellH + cellH / 2 + (random() - 0.5) * jitter * cellH;
70
+
71
+ const n = noise(gx * noiseScale, gy * noiseScale);
72
+ const angle = n * PI * 2 * noiseStrength;
73
+ const rings = Math.max(1, Math.floor(map(sin(angle), -1, 1, 1, 6)));
74
+ const step = (maxRadius / rings) * ringBias * 0.9;
75
+
76
+ for (let i = rings; i > 0; i--) {
77
+ fill(palette[(rings - i) % palette.length]);
78
+ ellipse(cx, cy, i * step * 2, i * step * 2);
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ // Helper function for HSL colors
85
+ function hslColor(h, s, l) {
86
+ return 'hsl(' + h + ',' + s + '%,' + l + '%)';
87
+ }
88
+ `;
89
+ export default RINGS_SKETCH;
@@ -0,0 +1,3 @@
1
+ export declare const SQUARES_SKETCH = "\nfunction setup() {\n const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';\n if (bgMode === 'white') background(245, 245, 245);\n else if (bgMode === 'black') background(10, 10, 10);\n else {\n const r = Math.floor((S.brightness / 100) * 255);\n const g = Math.floor((S.rhythmicity / 100) * 255);\n const b = Math.floor((S.harmonicity / 100) * 255);\n background(r, g, b);\n }\n\n const numSquares = Math.floor(map(S.length, 0, 100, 40, 300));\n let size = Math.min(width, height) * 0.9;\n const shrinkFactor = map(S.dynamicRange, 0, 100, 0.92, 1.08);\n const rotationStep = map(S.aggression, 0, 100, 0.01, 0.07);\n const strokeMin = map(S.volume, 0, 100, 1.0, 6.0);\n const strokeMax = map(S.bass, 0, 100, 2.0, 8.0);\n\n const colors = [];\n for (let i = 0; i < 5; i++) {\n const baseHue = (S.hue + i * 60 + random() * 20) % 360;\n const sat = constrain(map(S.treble, 0, 100, 60, 95), 50, 100);\n const bri = constrain(map(S.brightness, 0, 100, 40, 85), 30, 90);\n colors.push(hslColor(baseHue, sat, bri));\n }\n\n noFill();\n translate(width / 2, height / 2);\n\n let angle = 0;\n for (let i = 0; i < numSquares; i++) {\n const strokeW = strokeMin + (i / numSquares) * (strokeMax - strokeMin);\n strokeWeight(strokeW);\n stroke(colors[i % colors.length]);\n\n push();\n rotate(angle);\n rect(-size / 2, -size / 2, size, size);\n pop();\n\n angle += rotationStep;\n size *= shrinkFactor;\n if (size < 4) break;\n }\n}\n\nfunction hslColor(h, s, l) {\n return 'hsl(' + h + ',' + s + '%,' + l + '%)';\n}\n";
2
+ export default SQUARES_SKETCH;
3
+ //# sourceMappingURL=squares.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"squares.d.ts","sourceRoot":"","sources":["../../soundart-sketches/squares.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,0kDAkD1B,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -0,0 +1,52 @@
1
+ export const SQUARES_SKETCH = `
2
+ function setup() {
3
+ const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';
4
+ if (bgMode === 'white') background(245, 245, 245);
5
+ else if (bgMode === 'black') background(10, 10, 10);
6
+ else {
7
+ const r = Math.floor((S.brightness / 100) * 255);
8
+ const g = Math.floor((S.rhythmicity / 100) * 255);
9
+ const b = Math.floor((S.harmonicity / 100) * 255);
10
+ background(r, g, b);
11
+ }
12
+
13
+ const numSquares = Math.floor(map(S.length, 0, 100, 40, 300));
14
+ let size = Math.min(width, height) * 0.9;
15
+ const shrinkFactor = map(S.dynamicRange, 0, 100, 0.92, 1.08);
16
+ const rotationStep = map(S.aggression, 0, 100, 0.01, 0.07);
17
+ const strokeMin = map(S.volume, 0, 100, 1.0, 6.0);
18
+ const strokeMax = map(S.bass, 0, 100, 2.0, 8.0);
19
+
20
+ const colors = [];
21
+ for (let i = 0; i < 5; i++) {
22
+ const baseHue = (S.hue + i * 60 + random() * 20) % 360;
23
+ const sat = constrain(map(S.treble, 0, 100, 60, 95), 50, 100);
24
+ const bri = constrain(map(S.brightness, 0, 100, 40, 85), 30, 90);
25
+ colors.push(hslColor(baseHue, sat, bri));
26
+ }
27
+
28
+ noFill();
29
+ translate(width / 2, height / 2);
30
+
31
+ let angle = 0;
32
+ for (let i = 0; i < numSquares; i++) {
33
+ const strokeW = strokeMin + (i / numSquares) * (strokeMax - strokeMin);
34
+ strokeWeight(strokeW);
35
+ stroke(colors[i % colors.length]);
36
+
37
+ push();
38
+ rotate(angle);
39
+ rect(-size / 2, -size / 2, size, size);
40
+ pop();
41
+
42
+ angle += rotationStep;
43
+ size *= shrinkFactor;
44
+ if (size < 4) break;
45
+ }
46
+ }
47
+
48
+ function hslColor(h, s, l) {
49
+ return 'hsl(' + h + ',' + s + '%,' + l + '%)';
50
+ }
51
+ `;
52
+ export default SQUARES_SKETCH;
@@ -0,0 +1,3 @@
1
+ export declare const WAVE_STRIPES_SKETCH = "\nfunction setup() {\n const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';\n if (bgMode === 'white') background(245, 245, 245);\n else if (bgMode === 'black') background(10, 10, 10);\n else {\n const r = Math.floor((S.brightness / 100) * 255);\n const g = Math.floor((S.rhythmicity / 100) * 255);\n const b = Math.floor((S.harmonicity / 100) * 255);\n background(r, g, b);\n }\n\n const numLines = Math.floor(map(S.length, 0, 100, 5, 100));\n const amp = map(S.volume, 0, 100, 0.012, 0.09);\n const lineNoise = map(S.harmonicity, 0, 100, 0.1, 0.8);\n const freqNoise = map(S.aggression, 0, 100, 0.004, 0.02);\n const waveFreq = map(S.rhythmicity, 0, 100, 0.0003, 0.003);\n const strokeW = map(S.bass, 0, 100, 2, 6.0);\n const vividBoost = constrain(map(S.brightness, 0, 100, 0.6, 1.2), 0.6, 1.4);\n\n noFill();\n strokeWeight(strokeW);\n\n for (let j = 0; j < numLines; j++) {\n const yBase = map(j, 0, numLines - 1, 0.05 * height, 0.95 * height);\n const lineTreble = constrain(S.treble + noise(j, 99) * 20 - 10, 0, 100);\n const hue = ((map(lineTreble, 60, 100, 0, 360) + map(S.attack, 0, 100, 0, 120) + noise(j, 100) * 20 - 10) % 360 + 360) % 360;\n \n stroke('hsl(' + hue + ',85%,' + constrain(vividBoost * 55, 40, 70) + '%)');\n \n beginShape();\n const pointSpacing = constrain(map(S.harmonicity, 0, 100, 20, 4), 4, 20);\n for (let i = 0; i <= width; i += pointSpacing) {\n const shimmer = sin(i * 0.05 + j * 0.1) * 0.05;\n const offsetY = sin(i * waveFreq + j * 0.2 + shimmer) * amp * height;\n const localNoiseVal = (noise(i * freqNoise, j * lineNoise) - 0.5) * amp * height;\n const y = yBase + offsetY + localNoiseVal;\n vertex(i, y);\n }\n endShape();\n }\n}\n";
2
+ export default WAVE_STRIPES_SKETCH;
3
+ //# sourceMappingURL=waveStripes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waveStripes.d.ts","sourceRoot":"","sources":["../../soundart-sketches/waveStripes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,6vDA0C/B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,44 @@
1
+ export const WAVE_STRIPES_SKETCH = `
2
+ function setup() {
3
+ const bgMode = typeof backgroundMode !== 'undefined' ? backgroundMode : 'rgb';
4
+ if (bgMode === 'white') background(245, 245, 245);
5
+ else if (bgMode === 'black') background(10, 10, 10);
6
+ else {
7
+ const r = Math.floor((S.brightness / 100) * 255);
8
+ const g = Math.floor((S.rhythmicity / 100) * 255);
9
+ const b = Math.floor((S.harmonicity / 100) * 255);
10
+ background(r, g, b);
11
+ }
12
+
13
+ const numLines = Math.floor(map(S.length, 0, 100, 5, 100));
14
+ const amp = map(S.volume, 0, 100, 0.012, 0.09);
15
+ const lineNoise = map(S.harmonicity, 0, 100, 0.1, 0.8);
16
+ const freqNoise = map(S.aggression, 0, 100, 0.004, 0.02);
17
+ const waveFreq = map(S.rhythmicity, 0, 100, 0.0003, 0.003);
18
+ const strokeW = map(S.bass, 0, 100, 2, 6.0);
19
+ const vividBoost = constrain(map(S.brightness, 0, 100, 0.6, 1.2), 0.6, 1.4);
20
+
21
+ noFill();
22
+ strokeWeight(strokeW);
23
+
24
+ for (let j = 0; j < numLines; j++) {
25
+ const yBase = map(j, 0, numLines - 1, 0.05 * height, 0.95 * height);
26
+ const lineTreble = constrain(S.treble + noise(j, 99) * 20 - 10, 0, 100);
27
+ const hue = ((map(lineTreble, 60, 100, 0, 360) + map(S.attack, 0, 100, 0, 120) + noise(j, 100) * 20 - 10) % 360 + 360) % 360;
28
+
29
+ stroke('hsl(' + hue + ',85%,' + constrain(vividBoost * 55, 40, 70) + '%)');
30
+
31
+ beginShape();
32
+ const pointSpacing = constrain(map(S.harmonicity, 0, 100, 20, 4), 4, 20);
33
+ for (let i = 0; i <= width; i += pointSpacing) {
34
+ const shimmer = sin(i * 0.05 + j * 0.1) * 0.05;
35
+ const offsetY = sin(i * waveFreq + j * 0.2 + shimmer) * amp * height;
36
+ const localNoiseVal = (noise(i * freqNoise, j * lineNoise) - 0.5) * amp * height;
37
+ const y = yBase + offsetY + localNoiseVal;
38
+ vertex(i, y);
39
+ }
40
+ endShape();
41
+ }
42
+ }
43
+ `;
44
+ export default WAVE_STRIPES_SKETCH;
@@ -7,6 +7,13 @@
7
7
  *
8
8
  * Determinism Guarantee:
9
9
  * Same code + same seed + same VARs = identical PNG output
10
+ *
11
+ * Security:
12
+ * All external entropy sources are blocked at runtime via execution sandbox.
13
+ *
14
+ * Oracle Support:
15
+ * When returnImageData is true, returns raw ImageData for determinism hashing.
16
+ * Works in both browser (HTMLCanvasElement) and Node (canvas package) environments.
10
17
  */
11
18
  import type { EngineConfig, RunOptions } from './types';
12
19
  export declare function runStaticMode(config: EngineConfig, options: RunOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"static-engine.d.ts","sourceRoot":"","sources":["../static-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAgB,MAAM,SAAS,CAAC;AAItE,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CAmGf"}
1
+ {"version":3,"file":"static-engine.d.ts","sourceRoot":"","sources":["../static-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAgB,MAAM,SAAS,CAAC;AAoCtE,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CA6Hf"}
@@ -7,11 +7,44 @@
7
7
  *
8
8
  * Determinism Guarantee:
9
9
  * Same code + same seed + same VARs = identical PNG output
10
+ *
11
+ * Security:
12
+ * All external entropy sources are blocked at runtime via execution sandbox.
13
+ *
14
+ * Oracle Support:
15
+ * When returnImageData is true, returns raw ImageData for determinism hashing.
16
+ * Works in both browser (HTMLCanvasElement) and Node (canvas package) environments.
10
17
  */
18
+ import { createRequire } from 'module';
11
19
  import { DEFAULT_CONFIG } from './types';
12
20
  import { createP5Runtime, injectTimeVariables, injectProtocolVariables } from './p5-runtime';
21
+ import { FORBIDDEN_APIS, createSafeMath } from './execution-sandbox';
22
+ const require = createRequire(import.meta.url);
23
+ /**
24
+ * Create a runtime canvas that works in both browser and Node environments.
25
+ * Browser: uses HTMLCanvasElement
26
+ * Node/Headless: uses `canvas` npm package
27
+ */
28
+ function createRuntimeCanvas(width, height) {
29
+ // Browser environment
30
+ if (typeof document !== 'undefined' && typeof document.createElement === 'function') {
31
+ const canvas = document.createElement('canvas');
32
+ canvas.width = width;
33
+ canvas.height = height;
34
+ return canvas;
35
+ }
36
+ // Node / headless environment (oracle, CI)
37
+ try {
38
+ const { createCanvas } = require('canvas');
39
+ return createCanvas(width, height);
40
+ }
41
+ catch (err) {
42
+ throw new Error('[Code Mode Protocol Error] Headless canvas unavailable. ' +
43
+ 'Install `canvas` for oracle execution.');
44
+ }
45
+ }
13
46
  export async function runStaticMode(config, options) {
14
- const { code, seed, vars, onPreview, onProgress, onComplete, onError } = options;
47
+ const { code, seed, vars, onPreview, onProgress, onComplete, onError, returnImageData } = options;
15
48
  const width = config.width ?? DEFAULT_CONFIG.width;
16
49
  const height = config.height ?? DEFAULT_CONFIG.height;
17
50
  try {
@@ -20,10 +53,8 @@ export async function runStaticMode(config, options) {
20
53
  percent: 0,
21
54
  message: 'Initializing canvas...',
22
55
  });
23
- // Create offscreen canvas
24
- const canvas = document.createElement('canvas');
25
- canvas.width = width;
26
- canvas.height = height;
56
+ // Create runtime canvas (browser or Node)
57
+ const canvas = createRuntimeCanvas(width, height);
27
58
  // Create p5 runtime with optional seed for determinism
28
59
  const p = createP5Runtime(canvas, width, height, { seed });
29
60
  // Inject time variables (static = frame 0, t = 0, totalFrames = 1)
@@ -56,18 +87,43 @@ export async function runStaticMode(config, options) {
56
87
  percent: 30,
57
88
  message: 'Executing setup()...',
58
89
  });
59
- // Create wrapped setup function with p5 context and VAR
60
- const wrappedSetup = new Function('p', 'frameCount', 't', 'time', 'tGlobal', 'VAR', `with(p) { ${setupCode} }`);
61
- // Execute setup() only
62
- wrappedSetup(p, 0, 0, 0, 0, p.VAR);
90
+ // Create sandboxed execution context
91
+ // All forbidden APIs are injected as parameters to override globals
92
+ const safeMath = createSafeMath();
93
+ const forbiddenKeys = Object.keys(FORBIDDEN_APIS);
94
+ // Create wrapped setup function with p5 context, VAR, and blocked globals
95
+ const wrappedSetup = new Function('p', 'frameCount', 't', 'time', 'tGlobal', 'VAR', 'Math', ...forbiddenKeys, `with(p) { ${setupCode} }`);
96
+ // Execute setup() only with sandboxed context
97
+ const forbiddenValues = forbiddenKeys.map(k => FORBIDDEN_APIS[k]);
98
+ wrappedSetup(p, 0, 0, 0, 0, p.VAR, safeMath, ...forbiddenValues);
63
99
  // Provide preview callback
64
100
  onPreview?.(canvas);
65
101
  onProgress?.({
66
102
  phase: 'encoding',
67
103
  percent: 70,
68
- message: 'Capturing PNG...',
104
+ message: returnImageData ? 'Capturing ImageData...' : 'Capturing PNG...',
69
105
  });
70
- // Capture as PNG
106
+ // Get 2D context for pixel access
107
+ const ctx = canvas.getContext('2d');
108
+ if (!ctx) {
109
+ throw new Error('Failed to acquire 2D context');
110
+ }
111
+ // Always capture pixel data first
112
+ const imageData = ctx.getImageData(0, 0, width, height);
113
+ // ORACLE / NODE PATH — MUST NOT TOUCH toBlob
114
+ if (returnImageData) {
115
+ onProgress?.({
116
+ phase: 'complete',
117
+ percent: 100,
118
+ message: 'Complete',
119
+ });
120
+ onComplete({
121
+ type: 'image',
122
+ imageData,
123
+ });
124
+ return; // Early return - never touch toBlob in oracle mode
125
+ }
126
+ // BROWSER / UI PATH ONLY
71
127
  const blob = await new Promise((resolve, reject) => {
72
128
  canvas.toBlob((b) => b ? resolve(b) : reject(new Error('Failed to capture PNG')), 'image/png');
73
129
  });
@@ -76,11 +132,10 @@ export async function runStaticMode(config, options) {
76
132
  percent: 100,
77
133
  message: 'Complete',
78
134
  });
79
- const result = {
135
+ onComplete({
80
136
  type: 'image',
81
137
  blob,
82
- };
83
- onComplete(result);
138
+ });
84
139
  }
85
140
  catch (error) {
86
141
  const err = error instanceof Error ? error : new Error(String(error));