@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.
- package/CHANGELOG.md +109 -0
- package/CODE_MODE_PROTOCOL.md +312 -0
- package/README.md +308 -56
- package/dist/core-index.d.ts +21 -0
- package/dist/core-index.d.ts.map +1 -0
- package/dist/core-index.js +26 -0
- package/dist/execute.d.ts +46 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +268 -0
- package/dist/index.d.ts +36 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -17
- package/dist/loop-engine.d.ts +4 -1
- package/dist/loop-engine.d.ts.map +1 -1
- package/dist/loop-engine.js +17 -12
- package/dist/noise-bridge.d.ts +44 -0
- package/dist/noise-bridge.d.ts.map +1 -0
- package/dist/noise-bridge.js +68 -0
- package/dist/noise-engine.d.ts +74 -0
- package/dist/noise-engine.d.ts.map +1 -0
- package/dist/noise-engine.js +132 -0
- package/dist/noise-sketches/fractalNoise.d.ts +11 -0
- package/dist/noise-sketches/fractalNoise.d.ts.map +1 -0
- package/dist/noise-sketches/fractalNoise.js +121 -0
- package/dist/noise-sketches/index.d.ts +21 -0
- package/dist/noise-sketches/index.d.ts.map +1 -0
- package/dist/noise-sketches/index.js +28 -0
- package/dist/p5-runtime.d.ts +56 -4
- package/dist/p5-runtime.d.ts.map +1 -1
- package/dist/p5-runtime.js +348 -22
- package/dist/sound-bridge.d.ts +89 -0
- package/dist/sound-bridge.d.ts.map +1 -0
- package/dist/sound-bridge.js +128 -0
- package/dist/soundart-engine.d.ts +87 -0
- package/dist/soundart-engine.d.ts.map +1 -0
- package/dist/soundart-engine.js +173 -0
- package/dist/soundart-sketches/chladniBloom.d.ts +3 -0
- package/dist/soundart-sketches/chladniBloom.d.ts.map +1 -0
- package/dist/soundart-sketches/chladniBloom.js +53 -0
- package/dist/soundart-sketches/dualVortex.d.ts +3 -0
- package/dist/soundart-sketches/dualVortex.d.ts.map +1 -0
- package/dist/soundart-sketches/dualVortex.js +67 -0
- package/dist/soundart-sketches/geometryIllusion.d.ts +3 -0
- package/dist/soundart-sketches/geometryIllusion.d.ts.map +1 -0
- package/dist/soundart-sketches/geometryIllusion.js +89 -0
- package/dist/soundart-sketches/index.d.ts +39 -0
- package/dist/soundart-sketches/index.d.ts.map +1 -0
- package/dist/soundart-sketches/index.js +72 -0
- package/dist/soundart-sketches/isoflow.d.ts +3 -0
- package/dist/soundart-sketches/isoflow.d.ts.map +1 -0
- package/dist/soundart-sketches/isoflow.js +60 -0
- package/dist/soundart-sketches/loomWeave.d.ts +3 -0
- package/dist/soundart-sketches/loomWeave.d.ts.map +1 -0
- package/dist/soundart-sketches/loomWeave.js +59 -0
- package/dist/soundart-sketches/noiseTerraces.d.ts +3 -0
- package/dist/soundart-sketches/noiseTerraces.d.ts.map +1 -0
- package/dist/soundart-sketches/noiseTerraces.js +53 -0
- package/dist/soundart-sketches/orb.d.ts +3 -0
- package/dist/soundart-sketches/orb.d.ts.map +1 -0
- package/dist/soundart-sketches/orb.js +50 -0
- package/dist/soundart-sketches/pixelGlyphs.d.ts +3 -0
- package/dist/soundart-sketches/pixelGlyphs.d.ts.map +1 -0
- package/dist/soundart-sketches/pixelGlyphs.js +72 -0
- package/dist/soundart-sketches/prismFlowFields.d.ts +3 -0
- package/dist/soundart-sketches/prismFlowFields.d.ts.map +1 -0
- package/dist/soundart-sketches/prismFlowFields.js +51 -0
- package/dist/soundart-sketches/radialBurst.d.ts +3 -0
- package/dist/soundart-sketches/radialBurst.d.ts.map +1 -0
- package/dist/soundart-sketches/radialBurst.js +60 -0
- package/dist/soundart-sketches/resonantSoundBodies.d.ts +3 -0
- package/dist/soundart-sketches/resonantSoundBodies.d.ts.map +1 -0
- package/dist/soundart-sketches/resonantSoundBodies.js +89 -0
- package/dist/soundart-sketches/rings.d.ts +11 -0
- package/dist/soundart-sketches/rings.d.ts.map +1 -0
- package/dist/soundart-sketches/rings.js +89 -0
- package/dist/soundart-sketches/squares.d.ts +3 -0
- package/dist/soundart-sketches/squares.d.ts.map +1 -0
- package/dist/soundart-sketches/squares.js +52 -0
- package/dist/soundart-sketches/waveStripes.d.ts +3 -0
- package/dist/soundart-sketches/waveStripes.d.ts.map +1 -0
- package/dist/soundart-sketches/waveStripes.js +44 -0
- package/dist/static-engine.d.ts +4 -1
- package/dist/static-engine.d.ts.map +1 -1
- package/dist/static-engine.js +13 -8
- package/dist/types.d.ts +75 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +19 -1
- package/package.json +23 -17
package/dist/p5-runtime.js
CHANGED
|
@@ -1,11 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NexArt Code Mode Runtime SDK - p5-like Runtime
|
|
3
|
-
* Version: 0.1.1
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
507
|
+
return rng();
|
|
249
508
|
if (max === undefined)
|
|
250
|
-
return
|
|
251
|
-
return min +
|
|
509
|
+
return rng() * min;
|
|
510
|
+
return min + rng() * (max - min);
|
|
252
511
|
},
|
|
253
512
|
randomSeed: (seed) => {
|
|
254
|
-
|
|
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
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
+
}
|