@nexart/codemode-sdk 1.0.1 → 1.1.1

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 (88) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/CODE_MODE_PROTOCOL.md +312 -0
  3. package/README.md +308 -56
  4. package/dist/core-index.d.ts +21 -0
  5. package/dist/core-index.d.ts.map +1 -0
  6. package/dist/core-index.js +26 -0
  7. package/dist/execute.d.ts +46 -0
  8. package/dist/execute.d.ts.map +1 -0
  9. package/dist/execute.js +268 -0
  10. package/dist/index.d.ts +36 -17
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +43 -17
  13. package/dist/loop-engine.d.ts +4 -1
  14. package/dist/loop-engine.d.ts.map +1 -1
  15. package/dist/loop-engine.js +17 -12
  16. package/dist/noise-bridge.d.ts +44 -0
  17. package/dist/noise-bridge.d.ts.map +1 -0
  18. package/dist/noise-bridge.js +68 -0
  19. package/dist/noise-engine.d.ts +74 -0
  20. package/dist/noise-engine.d.ts.map +1 -0
  21. package/dist/noise-engine.js +132 -0
  22. package/dist/noise-sketches/fractalNoise.d.ts +11 -0
  23. package/dist/noise-sketches/fractalNoise.d.ts.map +1 -0
  24. package/dist/noise-sketches/fractalNoise.js +121 -0
  25. package/dist/noise-sketches/index.d.ts +21 -0
  26. package/dist/noise-sketches/index.d.ts.map +1 -0
  27. package/dist/noise-sketches/index.js +28 -0
  28. package/dist/p5-runtime.d.ts +56 -4
  29. package/dist/p5-runtime.d.ts.map +1 -1
  30. package/dist/p5-runtime.js +348 -22
  31. package/dist/sound-bridge.d.ts +89 -0
  32. package/dist/sound-bridge.d.ts.map +1 -0
  33. package/dist/sound-bridge.js +128 -0
  34. package/dist/soundart-engine.d.ts +87 -0
  35. package/dist/soundart-engine.d.ts.map +1 -0
  36. package/dist/soundart-engine.js +173 -0
  37. package/dist/soundart-sketches/chladniBloom.d.ts +3 -0
  38. package/dist/soundart-sketches/chladniBloom.d.ts.map +1 -0
  39. package/dist/soundart-sketches/chladniBloom.js +53 -0
  40. package/dist/soundart-sketches/dualVortex.d.ts +3 -0
  41. package/dist/soundart-sketches/dualVortex.d.ts.map +1 -0
  42. package/dist/soundart-sketches/dualVortex.js +67 -0
  43. package/dist/soundart-sketches/geometryIllusion.d.ts +3 -0
  44. package/dist/soundart-sketches/geometryIllusion.d.ts.map +1 -0
  45. package/dist/soundart-sketches/geometryIllusion.js +89 -0
  46. package/dist/soundart-sketches/index.d.ts +39 -0
  47. package/dist/soundart-sketches/index.d.ts.map +1 -0
  48. package/dist/soundart-sketches/index.js +72 -0
  49. package/dist/soundart-sketches/isoflow.d.ts +3 -0
  50. package/dist/soundart-sketches/isoflow.d.ts.map +1 -0
  51. package/dist/soundart-sketches/isoflow.js +60 -0
  52. package/dist/soundart-sketches/loomWeave.d.ts +3 -0
  53. package/dist/soundart-sketches/loomWeave.d.ts.map +1 -0
  54. package/dist/soundart-sketches/loomWeave.js +59 -0
  55. package/dist/soundart-sketches/noiseTerraces.d.ts +3 -0
  56. package/dist/soundart-sketches/noiseTerraces.d.ts.map +1 -0
  57. package/dist/soundart-sketches/noiseTerraces.js +53 -0
  58. package/dist/soundart-sketches/orb.d.ts +3 -0
  59. package/dist/soundart-sketches/orb.d.ts.map +1 -0
  60. package/dist/soundart-sketches/orb.js +50 -0
  61. package/dist/soundart-sketches/pixelGlyphs.d.ts +3 -0
  62. package/dist/soundart-sketches/pixelGlyphs.d.ts.map +1 -0
  63. package/dist/soundart-sketches/pixelGlyphs.js +72 -0
  64. package/dist/soundart-sketches/prismFlowFields.d.ts +3 -0
  65. package/dist/soundart-sketches/prismFlowFields.d.ts.map +1 -0
  66. package/dist/soundart-sketches/prismFlowFields.js +51 -0
  67. package/dist/soundart-sketches/radialBurst.d.ts +3 -0
  68. package/dist/soundart-sketches/radialBurst.d.ts.map +1 -0
  69. package/dist/soundart-sketches/radialBurst.js +60 -0
  70. package/dist/soundart-sketches/resonantSoundBodies.d.ts +3 -0
  71. package/dist/soundart-sketches/resonantSoundBodies.d.ts.map +1 -0
  72. package/dist/soundart-sketches/resonantSoundBodies.js +89 -0
  73. package/dist/soundart-sketches/rings.d.ts +11 -0
  74. package/dist/soundart-sketches/rings.d.ts.map +1 -0
  75. package/dist/soundart-sketches/rings.js +89 -0
  76. package/dist/soundart-sketches/squares.d.ts +3 -0
  77. package/dist/soundart-sketches/squares.d.ts.map +1 -0
  78. package/dist/soundart-sketches/squares.js +52 -0
  79. package/dist/soundart-sketches/waveStripes.d.ts +3 -0
  80. package/dist/soundart-sketches/waveStripes.d.ts.map +1 -0
  81. package/dist/soundart-sketches/waveStripes.js +44 -0
  82. package/dist/static-engine.d.ts +4 -1
  83. package/dist/static-engine.d.ts.map +1 -1
  84. package/dist/static-engine.js +13 -8
  85. package/dist/types.d.ts +75 -1
  86. package/dist/types.d.ts.map +1 -1
  87. package/dist/types.js +19 -1
  88. package/package.json +23 -17
@@ -1,11 +1,97 @@
1
1
  /**
2
2
  * NexArt Code Mode Runtime SDK - p5-like Runtime
3
- * Version: 0.1.1
4
3
  *
5
- * Minimal p5.js-like runtime for deterministic generative art execution.
6
- * This is a headless runtime - no UI dependencies.
4
+ * ╔══════════════════════════════════════════════════════════════════════════╗
5
+ * ║ CODE MODE PROTOCOL v1.0.0 (Phase 1) LOCKED ║
6
+ * ║ ║
7
+ * ║ Status: HARD PROTOCOL ENFORCEMENT ║
8
+ * ║ This is the stable, canonical execution surface. ║
9
+ * ║ SDKs, ByX, and external builders can depend on this API. ║
10
+ * ║ ║
11
+ * ║ Phase 1 Surface: ║
12
+ * ║ - VAR[0..9]: 10 read-only protocol variables (0-100 range) ║
13
+ * ║ - Drawing: line, rect, ellipse, circle, triangle, quad, arc, etc. ║
14
+ * ║ - Style: fill, stroke, colorMode, strokeWeight ║
15
+ * ║ - Transform: push, pop, translate, rotate, scale ║
16
+ * ║ - Random: random(), randomSeed(), randomGaussian() (seeded) ║
17
+ * ║ - Noise: noise(), noiseSeed(), noiseDetail() (seeded) ║
18
+ * ║ - Math: map, constrain, lerp, lerpColor, dist, mag, norm ║
19
+ * ║ - Color: Full CSS format support, color extraction functions ║
20
+ * ║ - Time: frameCount, t, time, tGlobal ║
21
+ * ║ ║
22
+ * ║ Determinism Guarantees: ║
23
+ * ║ - Same code + same seed + same VARs = identical output ║
24
+ * ║ - No external state, no browser entropy, no time-based drift ║
25
+ * ║ - Randomness ONLY from: random(), noise() (both seeded) ║
26
+ * ║ ║
27
+ * ║ ⚠️ Future changes require Phase 2+ ║
28
+ * ╚══════════════════════════════════════════════════════════════════════════╝
7
29
  */
8
- export function createP5Runtime(canvas, width, height) {
30
+ /**
31
+ * Code Mode Protocol Version
32
+ * This constant defines the locked protocol version.
33
+ * Changes to the execution surface require a version bump.
34
+ */
35
+ export const CODE_MODE_PROTOCOL_VERSION = '1.0.0';
36
+ export const CODE_MODE_PROTOCOL_PHASE = 1;
37
+ export const CODE_MODE_ENFORCEMENT = 'HARD';
38
+ /**
39
+ * Create a seeded random number generator (Mulberry32)
40
+ * Same algorithm used in SoundArt for consistency
41
+ */
42
+ function createSeededRNG(seed = 123456) {
43
+ let a = seed >>> 0;
44
+ return () => {
45
+ a += 0x6D2B79F5;
46
+ let t = Math.imul(a ^ (a >>> 15), a | 1);
47
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
48
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
49
+ };
50
+ }
51
+ /**
52
+ * Create improved Perlin-like noise with seeding support
53
+ */
54
+ function createSeededNoise(seed = 0) {
55
+ const permutation = [];
56
+ const rng = createSeededRNG(seed);
57
+ for (let i = 0; i < 256; i++) {
58
+ permutation[i] = i;
59
+ }
60
+ for (let i = 255; i > 0; i--) {
61
+ const j = Math.floor(rng() * (i + 1));
62
+ [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
63
+ }
64
+ for (let i = 0; i < 256; i++) {
65
+ permutation[256 + i] = permutation[i];
66
+ }
67
+ const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
68
+ const lerp = (a, b, t) => a + t * (b - a);
69
+ const grad = (hash, x, y, z) => {
70
+ const h = hash & 15;
71
+ const u = h < 8 ? x : y;
72
+ const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
73
+ return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
74
+ };
75
+ return (x, y = 0, z = 0) => {
76
+ const X = Math.floor(x) & 255;
77
+ const Y = Math.floor(y) & 255;
78
+ const Z = Math.floor(z) & 255;
79
+ x -= Math.floor(x);
80
+ y -= Math.floor(y);
81
+ z -= Math.floor(z);
82
+ const u = fade(x);
83
+ const v = fade(y);
84
+ const w = fade(z);
85
+ const A = permutation[X] + Y;
86
+ const AA = permutation[A] + Z;
87
+ const AB = permutation[A + 1] + Z;
88
+ const B = permutation[X + 1] + Y;
89
+ const BA = permutation[B] + Z;
90
+ const BB = permutation[B + 1] + Z;
91
+ return (lerp(lerp(lerp(grad(permutation[AA], x, y, z), grad(permutation[BA], x - 1, y, z), u), lerp(grad(permutation[AB], x, y - 1, z), grad(permutation[BB], x - 1, y - 1, z), u), v), lerp(lerp(grad(permutation[AA + 1], x, y, z - 1), grad(permutation[BA + 1], x - 1, y, z - 1), u), lerp(grad(permutation[AB + 1], x, y - 1, z - 1), grad(permutation[BB + 1], x - 1, y - 1, z - 1), u), v), w) + 1) / 2;
92
+ };
93
+ }
94
+ export function createP5Runtime(canvas, width, height, config) {
9
95
  const ctx = canvas.getContext('2d', { willReadFrequently: true });
10
96
  let currentFill = 'rgba(255, 255, 255, 1)';
11
97
  let currentStroke = 'rgba(0, 0, 0, 1)';
@@ -14,14 +100,119 @@ export function createP5Runtime(canvas, width, height) {
14
100
  let currentStrokeWeight = 1;
15
101
  let colorModeSettings = { mode: 'RGB', maxR: 255, maxG: 255, maxB: 255, maxA: 255 };
16
102
  let shapeStarted = false;
103
+ // Seeded random state
104
+ let randomSeedValue = config?.seed ?? Math.floor(Math.random() * 2147483647);
105
+ let rng = createSeededRNG(randomSeedValue);
106
+ // Seeded noise state
107
+ let noiseSeedValue = config?.seed ?? 0;
108
+ let noiseFunc = createSeededNoise(noiseSeedValue);
109
+ let noiseOctaves = 4;
110
+ let noiseFalloff = 0.5;
111
+ /**
112
+ * Parse CSS color string to normalized RGBA values
113
+ * Supports: hex (#RGB, #RRGGBB, #RRGGBBAA), rgb(), rgba(), hsl(), hsla()
114
+ */
115
+ const parseCssColor = (str) => {
116
+ const s = str.trim();
117
+ // Hex format: #RGB, #RRGGBB, #RRGGBBAA
118
+ if (s.startsWith('#')) {
119
+ const hex = s.slice(1);
120
+ if (hex.length === 3) {
121
+ const r = parseInt(hex[0] + hex[0], 16);
122
+ const g = parseInt(hex[1] + hex[1], 16);
123
+ const b = parseInt(hex[2] + hex[2], 16);
124
+ return { r, g, b, a: 1 };
125
+ }
126
+ else if (hex.length === 6) {
127
+ const r = parseInt(hex.slice(0, 2), 16);
128
+ const g = parseInt(hex.slice(2, 4), 16);
129
+ const b = parseInt(hex.slice(4, 6), 16);
130
+ return { r, g, b, a: 1 };
131
+ }
132
+ else if (hex.length === 8) {
133
+ const r = parseInt(hex.slice(0, 2), 16);
134
+ const g = parseInt(hex.slice(2, 4), 16);
135
+ const b = parseInt(hex.slice(4, 6), 16);
136
+ const a = parseInt(hex.slice(6, 8), 16) / 255;
137
+ return { r, g, b, a };
138
+ }
139
+ }
140
+ // rgb(r, g, b) or rgb(r g b)
141
+ const rgbMatch = s.match(/^rgb\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*\)$/i);
142
+ if (rgbMatch) {
143
+ return {
144
+ r: parseInt(rgbMatch[1]),
145
+ g: parseInt(rgbMatch[2]),
146
+ b: parseInt(rgbMatch[3]),
147
+ a: 1
148
+ };
149
+ }
150
+ // rgba(r, g, b, a) or rgba(r g b / a)
151
+ const rgbaMatch = s.match(/^rgba\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\/\s]\s*([\d.]+)\s*\)$/i);
152
+ if (rgbaMatch) {
153
+ return {
154
+ r: parseInt(rgbaMatch[1]),
155
+ g: parseInt(rgbaMatch[2]),
156
+ b: parseInt(rgbaMatch[3]),
157
+ a: parseFloat(rgbaMatch[4])
158
+ };
159
+ }
160
+ // hsl(h, s%, l%) or hsla(h, s%, l%, a)
161
+ const hslMatch = s.match(/^hsla?\s*\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)%?\s*[,\s]\s*([\d.]+)%?\s*(?:[,\/\s]\s*([\d.]+))?\s*\)$/i);
162
+ if (hslMatch) {
163
+ const h = parseFloat(hslMatch[1]) / 360;
164
+ const sat = parseFloat(hslMatch[2]) / 100;
165
+ const l = parseFloat(hslMatch[3]) / 100;
166
+ const a = hslMatch[4] ? parseFloat(hslMatch[4]) : 1;
167
+ // HSL to RGB conversion
168
+ let r, g, b;
169
+ if (sat === 0) {
170
+ r = g = b = l;
171
+ }
172
+ else {
173
+ const hue2rgb = (p, q, t) => {
174
+ if (t < 0)
175
+ t += 1;
176
+ if (t > 1)
177
+ t -= 1;
178
+ if (t < 1 / 6)
179
+ return p + (q - p) * 6 * t;
180
+ if (t < 1 / 2)
181
+ return q;
182
+ if (t < 2 / 3)
183
+ return p + (q - p) * (2 / 3 - t) * 6;
184
+ return p;
185
+ };
186
+ const q = l < 0.5 ? l * (1 + sat) : l + sat - l * sat;
187
+ const p = 2 * l - q;
188
+ r = hue2rgb(p, q, h + 1 / 3);
189
+ g = hue2rgb(p, q, h);
190
+ b = hue2rgb(p, q, h - 1 / 3);
191
+ }
192
+ return {
193
+ r: Math.round(r * 255),
194
+ g: Math.round(g * 255),
195
+ b: Math.round(b * 255),
196
+ a
197
+ };
198
+ }
199
+ return null;
200
+ };
17
201
  const parseColor = (...args) => {
18
202
  if (args.length === 0)
19
203
  return 'rgba(0, 0, 0, 1)';
20
204
  const { mode, maxR, maxG, maxB, maxA } = colorModeSettings;
21
205
  if (args.length === 1) {
22
206
  const val = args[0];
23
- if (typeof val === 'string')
207
+ if (typeof val === 'string') {
208
+ // Try to parse CSS color formats
209
+ const parsed = parseCssColor(val);
210
+ if (parsed) {
211
+ return `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${parsed.a})`;
212
+ }
213
+ // Return as-is for named colors (canvas handles them)
24
214
  return val;
215
+ }
25
216
  if (mode === 'HSB') {
26
217
  return `hsla(${val}, 100%, 50%, 1)`;
27
218
  }
@@ -112,7 +303,71 @@ export function createP5Runtime(canvas, width, height) {
112
303
  },
113
304
  color: (...args) => parseColor(...args),
114
305
  lerpColor: (c1, c2, amt) => {
115
- return c1; // Simplified - full implementation would blend colors
306
+ // Parse both colors
307
+ const color1 = parseCssColor(c1) || { r: 0, g: 0, b: 0, a: 1 };
308
+ const color2 = parseCssColor(c2) || { r: 255, g: 255, b: 255, a: 1 };
309
+ // Linearly interpolate each channel
310
+ const r = Math.round(color1.r + (color2.r - color1.r) * amt);
311
+ const g = Math.round(color1.g + (color2.g - color1.g) * amt);
312
+ const b = Math.round(color1.b + (color2.b - color1.b) * amt);
313
+ const a = color1.a + (color2.a - color1.a) * amt;
314
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
315
+ },
316
+ red: (color) => {
317
+ const parsed = parseCssColor(color);
318
+ return parsed ? parsed.r : 0;
319
+ },
320
+ green: (color) => {
321
+ const parsed = parseCssColor(color);
322
+ return parsed ? parsed.g : 0;
323
+ },
324
+ blue: (color) => {
325
+ const parsed = parseCssColor(color);
326
+ return parsed ? parsed.b : 0;
327
+ },
328
+ alpha: (color) => {
329
+ const parsed = parseCssColor(color);
330
+ return parsed ? parsed.a * 255 : 255;
331
+ },
332
+ brightness: (color) => {
333
+ const parsed = parseCssColor(color);
334
+ if (!parsed)
335
+ return 0;
336
+ return Math.max(parsed.r, parsed.g, parsed.b) / 255 * 100;
337
+ },
338
+ saturation: (color) => {
339
+ const parsed = parseCssColor(color);
340
+ if (!parsed)
341
+ return 0;
342
+ const max = Math.max(parsed.r, parsed.g, parsed.b);
343
+ const min = Math.min(parsed.r, parsed.g, parsed.b);
344
+ if (max === 0)
345
+ return 0;
346
+ return ((max - min) / max) * 100;
347
+ },
348
+ hue: (color) => {
349
+ const parsed = parseCssColor(color);
350
+ if (!parsed)
351
+ return 0;
352
+ const { r, g, b } = parsed;
353
+ const max = Math.max(r, g, b);
354
+ const min = Math.min(r, g, b);
355
+ if (max === min)
356
+ return 0;
357
+ let h = 0;
358
+ const d = max - min;
359
+ switch (max) {
360
+ case r:
361
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
362
+ break;
363
+ case g:
364
+ h = ((b - r) / d + 2) / 6;
365
+ break;
366
+ case b:
367
+ h = ((r - g) / d + 4) / 6;
368
+ break;
369
+ }
370
+ return h * 360;
116
371
  },
117
372
  // Shape functions
118
373
  ellipse: (x, y, w, h) => {
@@ -242,30 +497,53 @@ export function createP5Runtime(canvas, width, height) {
242
497
  resetMatrix: () => {
243
498
  ctx.setTransform(1, 0, 0, 1, 0, 0);
244
499
  },
245
- // Math functions
500
+ // Math functions - SEEDED for determinism
246
501
  random: (min, max) => {
502
+ // Support random() with arrays
503
+ if (Array.isArray(min)) {
504
+ return min[Math.floor(rng() * min.length)];
505
+ }
247
506
  if (min === undefined)
248
- return Math.random();
507
+ return rng();
249
508
  if (max === undefined)
250
- return Math.random() * min;
251
- return min + Math.random() * (max - min);
509
+ return rng() * min;
510
+ return min + rng() * (max - min);
252
511
  },
253
512
  randomSeed: (seed) => {
254
- // Simplified - would need proper seeded random
513
+ randomSeedValue = seed;
514
+ rng = createSeededRNG(seed);
515
+ },
516
+ randomGaussian: (mean = 0, sd = 1) => {
517
+ // Box-Muller transform for Gaussian distribution
518
+ const u1 = rng();
519
+ const u2 = rng();
520
+ const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
521
+ return z0 * sd + mean;
255
522
  },
256
523
  noise: (x, y, z) => {
257
- // Simplified Perlin-like noise
258
- const hash = (n) => {
259
- const s = Math.sin(n) * 43758.5453123;
260
- return s - Math.floor(s);
261
- };
262
- const xVal = x ?? 0;
263
- const yVal = y ?? 0;
264
- const zVal = z ?? 0;
265
- return hash(xVal * 12.9898 + yVal * 78.233 + zVal * 37.719);
524
+ // Use seeded Perlin noise with octaves
525
+ let total = 0;
526
+ let frequency = 1;
527
+ let amplitude = 1;
528
+ let maxValue = 0;
529
+ for (let i = 0; i < noiseOctaves; i++) {
530
+ total += noiseFunc(x * frequency, (y ?? 0) * frequency, (z ?? 0) * frequency) * amplitude;
531
+ maxValue += amplitude;
532
+ amplitude *= noiseFalloff;
533
+ frequency *= 2;
534
+ }
535
+ return total / maxValue;
536
+ },
537
+ noiseSeed: (seed) => {
538
+ noiseSeedValue = seed;
539
+ noiseFunc = createSeededNoise(seed);
540
+ },
541
+ noiseDetail: (lod, falloff) => {
542
+ noiseOctaves = Math.max(1, Math.min(8, lod));
543
+ if (falloff !== undefined) {
544
+ noiseFalloff = Math.max(0, Math.min(1, falloff));
545
+ }
266
546
  },
267
- noiseSeed: (seed) => { },
268
- noiseDetail: (lod, falloff) => { },
269
547
  map: (value, start1, stop1, start2, stop2) => {
270
548
  return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
271
549
  },
@@ -316,3 +594,51 @@ export function createP5Runtime(canvas, width, height) {
316
594
  export function injectTimeVariables(p, time) {
317
595
  p.frameCount = time.frameCount;
318
596
  }
597
+ /**
598
+ * VAR Protocol Constants (Phase 1 — Protocol v1.0.0)
599
+ * SDK v1.0.2: VAR input is optional (0-10 elements), but runtime always has 10
600
+ */
601
+ export const VAR_COUNT = 10; // Exactly 10 protocol variables: VAR[0..9]
602
+ export const VAR_MIN = 0; // Minimum value
603
+ export const VAR_MAX = 100; // Maximum value (normalized range)
604
+ /**
605
+ * Create a protected, read-only VAR array for protocol execution.
606
+ *
607
+ * SDK v1.0.2 Rules (Protocol v1.0.0):
608
+ * - Input accepts 0-10 elements
609
+ * - Runtime VAR is ALWAYS 10 elements (padded with zeros)
610
+ * - Values are numeric, must be in 0-100 range (validated upstream)
611
+ * - Read-only: writes throw descriptive errors
612
+ * - Available in both setup() and draw()
613
+ */
614
+ export function createProtocolVAR(vars) {
615
+ // Create frozen 10-element array (upstream normalizeVars ensures this)
616
+ const normalizedVars = [];
617
+ for (let i = 0; i < VAR_COUNT; i++) {
618
+ normalizedVars[i] = vars?.[i] ?? 0;
619
+ }
620
+ // Freeze the array to prevent modifications
621
+ const frozenVars = Object.freeze(normalizedVars);
622
+ // Wrap in Proxy for descriptive error messages on write attempts
623
+ return new Proxy(frozenVars, {
624
+ set(_target, prop, _value) {
625
+ const propName = typeof prop === 'symbol' ? prop.toString() : prop;
626
+ throw new Error(`[Code Mode Protocol Error] VAR is read-only. ` +
627
+ `Cannot write to VAR[${propName}]. ` +
628
+ `VAR[0..9] are protocol inputs, not sketch state.`);
629
+ },
630
+ deleteProperty(_target, prop) {
631
+ const propName = typeof prop === 'symbol' ? prop.toString() : prop;
632
+ throw new Error(`[Code Mode Protocol Error] VAR is read-only. ` +
633
+ `Cannot delete VAR[${propName}].`);
634
+ },
635
+ defineProperty(_target, prop) {
636
+ const propName = typeof prop === 'symbol' ? prop.toString() : prop;
637
+ throw new Error(`[Code Mode Protocol Error] Cannot define new VAR properties. ` +
638
+ `VAR is fixed at 10 elements (VAR[0..9]). Attempted: ${propName}`);
639
+ },
640
+ });
641
+ }
642
+ export function injectProtocolVariables(p, vars) {
643
+ p.VAR = createProtocolVAR(vars);
644
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * SoundArt → Code Mode Bridge
3
+ *
4
+ * This module injects sound-derived parameters into the Code Mode runtime
5
+ * as read-only `S.*` globals. It is the integration layer between sound
6
+ * analysis and generative art rendering.
7
+ *
8
+ * Usage in Code Mode sketches:
9
+ * S.volume // 0-100: Overall loudness
10
+ * S.bass // 0-100: Bass frequency energy
11
+ * S.treble // 0-100: Treble frequency energy
12
+ * S.aggression // 0-100: Intensity/harshness
13
+ * S.t // 0-1: Normalized time position
14
+ * etc.
15
+ */
16
+ import type { SoundSnapshot } from '../../shared/soundSnapshot';
17
+ /**
18
+ * The injected sound globals interface
19
+ * This is what sketches see as `S`
20
+ */
21
+ export interface SoundGlobals extends Readonly<SoundSnapshot> {
22
+ }
23
+ /**
24
+ * Create a frozen, read-only sound globals object from a snapshot
25
+ */
26
+ export declare function createSoundGlobals(snapshot: SoundSnapshot): SoundGlobals;
27
+ /**
28
+ * Create empty/default sound globals for testing or fallback
29
+ */
30
+ export declare function createEmptySoundGlobals(): SoundGlobals;
31
+ /**
32
+ * Inject sound globals into the p5 runtime context
33
+ *
34
+ * This adds the `S` global object to the runtime so sketches can access
35
+ * sound-derived parameters like S.volume, S.bass, etc.
36
+ *
37
+ * @param runtime - The p5 runtime object
38
+ * @param snapshot - The sound snapshot to inject
39
+ * @returns The runtime with S globals added
40
+ */
41
+ export declare function injectSoundGlobals<T extends Record<string, any>>(runtime: T, snapshot: SoundSnapshot): T & {
42
+ S: SoundGlobals;
43
+ };
44
+ /**
45
+ * Helper function to map S.* values to useful ranges
46
+ *
47
+ * Usage in sketches:
48
+ * const density = mapSound(S.volume, 100, 2000) // Map 0-100 to 100-2000
49
+ */
50
+ export declare function createSoundHelpers(): {
51
+ /**
52
+ * Map a sound value (0-100) to a custom range
53
+ */
54
+ mapSound: (value: number, outMin: number, outMax: number) => number;
55
+ /**
56
+ * Map a sound value (0-100) with easing
57
+ */
58
+ mapSoundEased: (value: number, outMin: number, outMax: number, easing?: number) => number;
59
+ /**
60
+ * Get a value that oscillates based on sound
61
+ */
62
+ soundOscillate: (base: number, amplitude: number, soundValue: number) => number;
63
+ };
64
+ /**
65
+ * Convert SoundSnapshot hue (0-100) to degrees (0-360)
66
+ */
67
+ export declare function hueToDegrees(hue: number): number;
68
+ /**
69
+ * Generate a color palette from sound parameters
70
+ *
71
+ * @param snapshot - The sound snapshot
72
+ * @param paletteSize - Number of colors to generate
73
+ * @returns Array of HSL color strings
74
+ */
75
+ export declare function generateSoundPalette(snapshot: SoundSnapshot, paletteSize?: number): string[];
76
+ /**
77
+ * Infer a genre profile from sound parameters
78
+ * This helps sketches adapt their visual style to the audio content
79
+ */
80
+ export type EnergyLevel = 'low' | 'mid' | 'high';
81
+ export type StructureType = 'chaotic' | 'organic' | 'geometric';
82
+ export type ClarityLevel = 'muddy' | 'clear' | 'sharp';
83
+ export interface GenreProfile {
84
+ energy: EnergyLevel;
85
+ structure: StructureType;
86
+ clarity: ClarityLevel;
87
+ }
88
+ export declare function inferGenreProfile(snapshot: SoundSnapshot): GenreProfile;
89
+ //# sourceMappingURL=sound-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sound-bridge.d.ts","sourceRoot":"","sources":["../sound-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGhE;;;GAGG;AACH,MAAM,WAAW,YAAa,SAAQ,QAAQ,CAAC,aAAa,CAAC;CAE5D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,YAAY,CAExE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,YAAY,CAEtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9D,OAAO,EAAE,CAAC,EACV,QAAQ,EAAE,aAAa,GACtB,CAAC,GAAG;IAAE,CAAC,EAAE,YAAY,CAAA;CAAE,CAuBzB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB;IAE9B;;OAEG;sBACe,MAAM,UAAU,MAAM,UAAU,MAAM,KAAG,MAAM;IAIjE;;OAEG;2BACoB,MAAM,UAAU,MAAM,UAAU,MAAM,WAAU,MAAM,KAAO,MAAM;IAK1F;;OAEG;2BACoB,MAAM,aAAa,MAAM,cAAc,MAAM,KAAG,MAAM;EAIhF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,aAAa,EACvB,WAAW,GAAE,MAAU,GACtB,MAAM,EAAE,CAeV;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AACjD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,GAAG,YAAY,CAiBvE"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * SoundArt → Code Mode Bridge
3
+ *
4
+ * This module injects sound-derived parameters into the Code Mode runtime
5
+ * as read-only `S.*` globals. It is the integration layer between sound
6
+ * analysis and generative art rendering.
7
+ *
8
+ * Usage in Code Mode sketches:
9
+ * S.volume // 0-100: Overall loudness
10
+ * S.bass // 0-100: Bass frequency energy
11
+ * S.treble // 0-100: Treble frequency energy
12
+ * S.aggression // 0-100: Intensity/harshness
13
+ * S.t // 0-1: Normalized time position
14
+ * etc.
15
+ */
16
+ import { createEmptySoundSnapshot, freezeSoundSnapshot } from '../../shared/soundSnapshot';
17
+ /**
18
+ * Create a frozen, read-only sound globals object from a snapshot
19
+ */
20
+ export function createSoundGlobals(snapshot) {
21
+ return freezeSoundSnapshot(snapshot);
22
+ }
23
+ /**
24
+ * Create empty/default sound globals for testing or fallback
25
+ */
26
+ export function createEmptySoundGlobals() {
27
+ return freezeSoundSnapshot(createEmptySoundSnapshot());
28
+ }
29
+ /**
30
+ * Inject sound globals into the p5 runtime context
31
+ *
32
+ * This adds the `S` global object to the runtime so sketches can access
33
+ * sound-derived parameters like S.volume, S.bass, etc.
34
+ *
35
+ * @param runtime - The p5 runtime object
36
+ * @param snapshot - The sound snapshot to inject
37
+ * @returns The runtime with S globals added
38
+ */
39
+ export function injectSoundGlobals(runtime, snapshot) {
40
+ const globals = createSoundGlobals(snapshot);
41
+ // Create a proxy to make S immutable and prevent modification
42
+ const immutableS = new Proxy(globals, {
43
+ set() {
44
+ console.warn('[SoundBridge] S.* globals are read-only');
45
+ return false;
46
+ },
47
+ deleteProperty() {
48
+ console.warn('[SoundBridge] Cannot delete S.* properties');
49
+ return false;
50
+ },
51
+ defineProperty() {
52
+ console.warn('[SoundBridge] Cannot define new S.* properties');
53
+ return false;
54
+ }
55
+ });
56
+ return {
57
+ ...runtime,
58
+ S: immutableS
59
+ };
60
+ }
61
+ /**
62
+ * Helper function to map S.* values to useful ranges
63
+ *
64
+ * Usage in sketches:
65
+ * const density = mapSound(S.volume, 100, 2000) // Map 0-100 to 100-2000
66
+ */
67
+ export function createSoundHelpers() {
68
+ return {
69
+ /**
70
+ * Map a sound value (0-100) to a custom range
71
+ */
72
+ mapSound: (value, outMin, outMax) => {
73
+ return outMin + (value / 100) * (outMax - outMin);
74
+ },
75
+ /**
76
+ * Map a sound value (0-100) with easing
77
+ */
78
+ mapSoundEased: (value, outMin, outMax, easing = 2) => {
79
+ const t = Math.pow(value / 100, easing);
80
+ return outMin + t * (outMax - outMin);
81
+ },
82
+ /**
83
+ * Get a value that oscillates based on sound
84
+ */
85
+ soundOscillate: (base, amplitude, soundValue) => {
86
+ return base + Math.sin(soundValue * Math.PI / 50) * amplitude;
87
+ }
88
+ };
89
+ }
90
+ /**
91
+ * Convert SoundSnapshot hue (0-100) to degrees (0-360)
92
+ */
93
+ export function hueToDegrees(hue) {
94
+ return (hue / 100) * 360;
95
+ }
96
+ /**
97
+ * Generate a color palette from sound parameters
98
+ *
99
+ * @param snapshot - The sound snapshot
100
+ * @param paletteSize - Number of colors to generate
101
+ * @returns Array of HSL color strings
102
+ */
103
+ export function generateSoundPalette(snapshot, paletteSize = 6) {
104
+ const mainHue = hueToDegrees(snapshot.hue);
105
+ const hueShift = 20 + (snapshot.harmonicity / 100) * 40; // 20-60 degrees
106
+ const sat = 60 + (snapshot.treble / 100) * 30; // 60-90%
107
+ const light = 70 + (snapshot.bass / 100) * 25; // 70-95%
108
+ const palette = [];
109
+ for (let i = 0; i < paletteSize; i++) {
110
+ const hue = (mainHue + i * hueShift) % 360;
111
+ const satVar = sat - (i % 2) * 10;
112
+ const lightVar = light - (i % 3) * 5;
113
+ palette.push(`hsl(${hue}, ${satVar}%, ${lightVar}%)`);
114
+ }
115
+ return palette;
116
+ }
117
+ export function inferGenreProfile(snapshot) {
118
+ // Energy based on volume and aggression
119
+ const energy = (snapshot.volume > 60 || snapshot.aggression > 60) ? 'high' :
120
+ (snapshot.volume > 30) ? 'mid' : 'low';
121
+ // Structure based on rhythmicity and aggression
122
+ const structure = (snapshot.rhythmicity > 50 && snapshot.dynamicRange < 25) ? 'geometric' :
123
+ (snapshot.aggression > 60 && snapshot.dynamicRange > 50) ? 'chaotic' : 'organic';
124
+ // Clarity based on brightness
125
+ const clarity = (snapshot.brightness > 60) ? 'sharp' :
126
+ (snapshot.brightness > 40) ? 'clear' : 'muddy';
127
+ return { energy, structure, clarity };
128
+ }