@kernel.chat/kbot 2.10.1 → 2.12.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.
- package/README.md +1 -1
- package/dist/agent-protocol.js +1 -1
- package/dist/agent-protocol.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +11 -0
- package/dist/agent.js.map +1 -1
- package/dist/agents/agents.test.d.ts +2 -0
- package/dist/agents/agents.test.d.ts.map +1 -0
- package/dist/agents/agents.test.js +127 -0
- package/dist/agents/agents.test.js.map +1 -0
- package/dist/agents/creative.d.ts +27 -0
- package/dist/agents/creative.d.ts.map +1 -0
- package/dist/agents/creative.js +77 -0
- package/dist/agents/creative.js.map +1 -0
- package/dist/agents/developer.d.ts +27 -0
- package/dist/agents/developer.d.ts.map +1 -0
- package/dist/agents/developer.js +107 -0
- package/dist/agents/developer.js.map +1 -0
- package/dist/cli.js +70 -3
- package/dist/cli.js.map +1 -1
- package/dist/evolution.d.ts +112 -0
- package/dist/evolution.d.ts.map +1 -0
- package/dist/evolution.js +642 -0
- package/dist/evolution.js.map +1 -0
- package/dist/evolution.test.d.ts +2 -0
- package/dist/evolution.test.d.ts.map +1 -0
- package/dist/evolution.test.js +160 -0
- package/dist/evolution.test.js.map +1 -0
- package/dist/ide/acp-server.js +2 -2
- package/dist/ide/bridge.d.ts.map +1 -1
- package/dist/ide/bridge.js +2 -0
- package/dist/ide/bridge.js.map +1 -1
- package/dist/learned-router.d.ts.map +1 -1
- package/dist/learned-router.js +6 -0
- package/dist/learned-router.js.map +1 -1
- package/dist/matrix.d.ts.map +1 -1
- package/dist/matrix.js +22 -0
- package/dist/matrix.js.map +1 -1
- package/dist/quality-diversity.d.ts +106 -0
- package/dist/quality-diversity.d.ts.map +1 -0
- package/dist/quality-diversity.js +296 -0
- package/dist/quality-diversity.js.map +1 -0
- package/dist/tools/comfyui-plugin.d.ts +2 -0
- package/dist/tools/comfyui-plugin.d.ts.map +1 -0
- package/dist/tools/comfyui-plugin.js +523 -0
- package/dist/tools/comfyui-plugin.js.map +1 -0
- package/dist/tools/creative.d.ts +2 -0
- package/dist/tools/creative.d.ts.map +1 -0
- package/dist/tools/creative.js +803 -0
- package/dist/tools/creative.js.map +1 -0
- package/dist/tools/creative.test.d.ts +2 -0
- package/dist/tools/creative.test.d.ts.map +1 -0
- package/dist/tools/creative.test.js +281 -0
- package/dist/tools/creative.test.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/magenta-plugin.d.ts +2 -0
- package/dist/tools/magenta-plugin.d.ts.map +1 -0
- package/dist/tools/magenta-plugin.js +405 -0
- package/dist/tools/magenta-plugin.js.map +1 -0
- package/dist/tools/subagent.js +1 -1
- package/dist/tools/subagent.js.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +4 -2
- package/dist/ui.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
// K:BOT Creative Intelligence Tools
|
|
2
|
+
// Generate art, shaders, music patterns, SVGs, and evolve designs.
|
|
3
|
+
// All outputs are self-contained files — no external dependencies at generation time.
|
|
4
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { dirname, basename, extname, join } from 'node:path';
|
|
6
|
+
import { registerTool } from './index.js';
|
|
7
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
8
|
+
/** Seed a simple deterministic PRNG from a string */
|
|
9
|
+
function hashSeed(s) {
|
|
10
|
+
let h = 0;
|
|
11
|
+
for (let i = 0; i < s.length; i++) {
|
|
12
|
+
h = ((h << 5) - h + s.charCodeAt(i)) | 0;
|
|
13
|
+
}
|
|
14
|
+
return Math.abs(h);
|
|
15
|
+
}
|
|
16
|
+
/** Pick a seeded-random float in [min, max) */
|
|
17
|
+
function seededRandom(seed, index) {
|
|
18
|
+
const x = Math.sin(seed + index * 9301 + 49297) * 233280;
|
|
19
|
+
return x - Math.floor(x);
|
|
20
|
+
}
|
|
21
|
+
/** Pick a random item from an array using seed */
|
|
22
|
+
function pick(arr, seed, index) {
|
|
23
|
+
return arr[Math.floor(seededRandom(seed, index) * arr.length)];
|
|
24
|
+
}
|
|
25
|
+
/** Generate a CSS hex color from seed */
|
|
26
|
+
function seedColor(seed, idx) {
|
|
27
|
+
const h = Math.floor(seededRandom(seed, idx) * 360);
|
|
28
|
+
const s = 50 + Math.floor(seededRandom(seed, idx + 1) * 40);
|
|
29
|
+
const l = 30 + Math.floor(seededRandom(seed, idx + 2) * 40);
|
|
30
|
+
return `hsl(${h}, ${s}%, ${l}%)`;
|
|
31
|
+
}
|
|
32
|
+
function ensureDir(path) {
|
|
33
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
// ─── p5.js Art Generator ────────────────────────────────────────────
|
|
36
|
+
function generateP5Sketch(description, style) {
|
|
37
|
+
const seed = hashSeed(description);
|
|
38
|
+
const palette = Array.from({ length: 5 }, (_, i) => seedColor(seed, i * 3));
|
|
39
|
+
const bgColor = seedColor(seed, 100);
|
|
40
|
+
const sketchBodies = {
|
|
41
|
+
abstract: `
|
|
42
|
+
// Abstract expressionist composition
|
|
43
|
+
const shapes = ${3 + Math.floor(seededRandom(seed, 10) * 12)};
|
|
44
|
+
for (let i = 0; i < shapes; i++) {
|
|
45
|
+
const x = random(width);
|
|
46
|
+
const y = random(height);
|
|
47
|
+
const sz = random(20, 200);
|
|
48
|
+
fill(palette[i % palette.length]);
|
|
49
|
+
noStroke();
|
|
50
|
+
if (random() > 0.5) {
|
|
51
|
+
ellipse(x, y, sz, sz * random(0.5, 1.5));
|
|
52
|
+
} else {
|
|
53
|
+
rect(x - sz/2, y - sz/2, sz, sz * random(0.5, 1.5));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Gestural lines
|
|
57
|
+
for (let i = 0; i < ${2 + Math.floor(seededRandom(seed, 11) * 6)}; i++) {
|
|
58
|
+
stroke(palette[i % palette.length]);
|
|
59
|
+
strokeWeight(random(1, 8));
|
|
60
|
+
noFill();
|
|
61
|
+
beginShape();
|
|
62
|
+
for (let j = 0; j < 8; j++) {
|
|
63
|
+
curveVertex(random(width), random(height));
|
|
64
|
+
}
|
|
65
|
+
endShape();
|
|
66
|
+
}`,
|
|
67
|
+
geometric: `
|
|
68
|
+
// Geometric pattern
|
|
69
|
+
const cols = ${3 + Math.floor(seededRandom(seed, 20) * 8)};
|
|
70
|
+
const rows = ${3 + Math.floor(seededRandom(seed, 21) * 8)};
|
|
71
|
+
const cellW = width / cols;
|
|
72
|
+
const cellH = height / rows;
|
|
73
|
+
noStroke();
|
|
74
|
+
for (let r = 0; r < rows; r++) {
|
|
75
|
+
for (let c = 0; c < cols; c++) {
|
|
76
|
+
const x = c * cellW;
|
|
77
|
+
const y = r * cellH;
|
|
78
|
+
fill(palette[(r + c) % palette.length]);
|
|
79
|
+
const shape = (r * cols + c + ${seed}) % 4;
|
|
80
|
+
push();
|
|
81
|
+
translate(x + cellW/2, y + cellH/2);
|
|
82
|
+
rotate(((r + c) * PI) / ${2 + Math.floor(seededRandom(seed, 22) * 4)});
|
|
83
|
+
if (shape === 0) rect(-cellW/2, -cellH/2, cellW, cellH);
|
|
84
|
+
else if (shape === 1) ellipse(0, 0, cellW, cellH);
|
|
85
|
+
else if (shape === 2) triangle(-cellW/2, cellH/2, cellW/2, cellH/2, 0, -cellH/2);
|
|
86
|
+
else { beginShape(); for (let i = 0; i < 6; i++) { const a = (TWO_PI / 6) * i; vertex(cos(a) * cellW/2, sin(a) * cellH/2); } endShape(CLOSE); }
|
|
87
|
+
pop();
|
|
88
|
+
}
|
|
89
|
+
}`,
|
|
90
|
+
organic: `
|
|
91
|
+
// Organic flow field
|
|
92
|
+
const scale = ${10 + Math.floor(seededRandom(seed, 30) * 20)};
|
|
93
|
+
const particles = ${200 + Math.floor(seededRandom(seed, 31) * 800)};
|
|
94
|
+
noStroke();
|
|
95
|
+
for (let i = 0; i < particles; i++) {
|
|
96
|
+
let x = random(width);
|
|
97
|
+
let y = random(height);
|
|
98
|
+
fill(palette[i % palette.length] + '40');
|
|
99
|
+
for (let step = 0; step < 50; step++) {
|
|
100
|
+
const angle = noise(x / scale, y / scale, ${seededRandom(seed, 32).toFixed(2)}) * TWO_PI * 2;
|
|
101
|
+
x += cos(angle) * 2;
|
|
102
|
+
y += sin(angle) * 2;
|
|
103
|
+
ellipse(x, y, 3, 3);
|
|
104
|
+
if (x < 0 || x > width || y < 0 || y > height) break;
|
|
105
|
+
}
|
|
106
|
+
}`,
|
|
107
|
+
fractal: `
|
|
108
|
+
// Recursive fractal tree
|
|
109
|
+
const maxDepth = ${4 + Math.floor(seededRandom(seed, 40) * 5)};
|
|
110
|
+
const branchAngle = ${15 + Math.floor(seededRandom(seed, 41) * 30)};
|
|
111
|
+
const lengthRatio = ${(0.6 + seededRandom(seed, 42) * 0.2).toFixed(2)};
|
|
112
|
+
|
|
113
|
+
const branch = (x, y, len, angle, depth) => {
|
|
114
|
+
if (depth >= maxDepth || len < 2) return;
|
|
115
|
+
const x2 = x + cos(radians(angle)) * len;
|
|
116
|
+
const y2 = y - sin(radians(angle)) * len;
|
|
117
|
+
stroke(palette[depth % palette.length]);
|
|
118
|
+
strokeWeight(map(depth, 0, maxDepth, 6, 1));
|
|
119
|
+
line(x, y, x2, y2);
|
|
120
|
+
branch(x2, y2, len * lengthRatio, angle + branchAngle, depth + 1);
|
|
121
|
+
branch(x2, y2, len * lengthRatio, angle - branchAngle, depth + 1);
|
|
122
|
+
if (random() > 0.5) branch(x2, y2, len * lengthRatio * 0.8, angle + branchAngle * 0.5, depth + 1);
|
|
123
|
+
};
|
|
124
|
+
branch(width/2, height, height * 0.3, 90, 0);`,
|
|
125
|
+
noise: `
|
|
126
|
+
// Perlin noise landscape
|
|
127
|
+
const resolution = ${2 + Math.floor(seededRandom(seed, 50) * 4)};
|
|
128
|
+
const zOff = ${seededRandom(seed, 51).toFixed(3)};
|
|
129
|
+
loadPixels();
|
|
130
|
+
for (let x = 0; x < width; x += resolution) {
|
|
131
|
+
for (let y = 0; y < height; y += resolution) {
|
|
132
|
+
const n = noise(x * 0.005, y * 0.005, zOff);
|
|
133
|
+
const ci = floor(n * palette.length);
|
|
134
|
+
const c = color(palette[ci % palette.length]);
|
|
135
|
+
for (let dx = 0; dx < resolution; dx++) {
|
|
136
|
+
for (let dy = 0; dy < resolution; dy++) {
|
|
137
|
+
const idx = 4 * ((y + dy) * width + (x + dx));
|
|
138
|
+
pixels[idx] = red(c);
|
|
139
|
+
pixels[idx+1] = green(c);
|
|
140
|
+
pixels[idx+2] = blue(c);
|
|
141
|
+
pixels[idx+3] = 255;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
updatePixels();
|
|
147
|
+
// Overlay contour lines
|
|
148
|
+
noFill();
|
|
149
|
+
stroke(255, 80);
|
|
150
|
+
strokeWeight(0.5);
|
|
151
|
+
for (let threshold = 0.1; threshold < 1; threshold += 0.1) {
|
|
152
|
+
beginShape();
|
|
153
|
+
for (let x = 0; x < width; x += 4) {
|
|
154
|
+
const y = height * noise(x * 0.005, threshold * 5, zOff + 1);
|
|
155
|
+
vertex(x, y);
|
|
156
|
+
}
|
|
157
|
+
endShape();
|
|
158
|
+
}`,
|
|
159
|
+
};
|
|
160
|
+
const body = sketchBodies[style] || sketchBodies.abstract;
|
|
161
|
+
return `<!DOCTYPE html>
|
|
162
|
+
<html lang="en">
|
|
163
|
+
<head>
|
|
164
|
+
<meta charset="UTF-8">
|
|
165
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
166
|
+
<title>K:BOT Art — ${description}</title>
|
|
167
|
+
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"><\/script>
|
|
168
|
+
<style>
|
|
169
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
170
|
+
body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #111; }
|
|
171
|
+
canvas { border-radius: 4px; box-shadow: 0 4px 24px rgba(0,0,0,0.5); }
|
|
172
|
+
</style>
|
|
173
|
+
</head>
|
|
174
|
+
<body>
|
|
175
|
+
<script>
|
|
176
|
+
// Generated by K:BOT creative tools
|
|
177
|
+
// Description: ${description}
|
|
178
|
+
// Style: ${style}
|
|
179
|
+
// Seed: ${seed}
|
|
180
|
+
|
|
181
|
+
const palette = ${JSON.stringify(palette)};
|
|
182
|
+
|
|
183
|
+
function setup() {
|
|
184
|
+
createCanvas(800, 800);
|
|
185
|
+
background('${bgColor}');
|
|
186
|
+
noiseSeed(${seed});
|
|
187
|
+
randomSeed(${seed});
|
|
188
|
+
draw_art();
|
|
189
|
+
noLoop();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function draw_art() {
|
|
193
|
+
${body}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mousePressed() {
|
|
197
|
+
// Click to regenerate with new random seed
|
|
198
|
+
noiseSeed(millis());
|
|
199
|
+
randomSeed(millis());
|
|
200
|
+
background('${bgColor}');
|
|
201
|
+
draw_art();
|
|
202
|
+
}
|
|
203
|
+
<\/script>
|
|
204
|
+
</body>
|
|
205
|
+
</html>`;
|
|
206
|
+
}
|
|
207
|
+
// ─── GLSL Shader Generator ─────────────────────────────────────────
|
|
208
|
+
function generateGLSL(description) {
|
|
209
|
+
const seed = hashSeed(description);
|
|
210
|
+
const r1 = seededRandom(seed, 0);
|
|
211
|
+
const r2 = seededRandom(seed, 1);
|
|
212
|
+
const r3 = seededRandom(seed, 2);
|
|
213
|
+
const r4 = seededRandom(seed, 3);
|
|
214
|
+
const r5 = seededRandom(seed, 4);
|
|
215
|
+
const octaves = 3 + Math.floor(r1 * 5);
|
|
216
|
+
const speed = (0.2 + r2 * 1.5).toFixed(2);
|
|
217
|
+
const zoom = (1.0 + r3 * 4.0).toFixed(2);
|
|
218
|
+
const colorShiftA = (r4 * 6.28).toFixed(3);
|
|
219
|
+
const colorShiftB = (r5 * 6.28).toFixed(3);
|
|
220
|
+
const technique = Math.floor(r1 * 4);
|
|
221
|
+
let core = '';
|
|
222
|
+
if (technique === 0) {
|
|
223
|
+
// Fractal noise with domain warping
|
|
224
|
+
core = `
|
|
225
|
+
// Domain-warped fractal noise
|
|
226
|
+
vec2 q = vec2(fbm(uv + vec2(0.0, 0.0)),
|
|
227
|
+
fbm(uv + vec2(5.2, 1.3)));
|
|
228
|
+
vec2 r2 = vec2(fbm(uv + 4.0 * q + vec2(1.7, 9.2) + ${speed} * iTime),
|
|
229
|
+
fbm(uv + 4.0 * q + vec2(8.3, 2.8) + ${speed} * 0.7 * iTime));
|
|
230
|
+
float f = fbm(uv + 4.0 * r2);
|
|
231
|
+
vec3 color = mix(vec3(0.1, 0.05, 0.2), vec3(0.9, 0.4, 0.2), clamp(f * f * 2.0, 0.0, 1.0));
|
|
232
|
+
color = mix(color, vec3(0.0, 0.2, 0.6), clamp(length(q), 0.0, 1.0));
|
|
233
|
+
color = mix(color, vec3(0.9, 0.8, 0.5), clamp(length(r2.x), 0.0, 1.0));`;
|
|
234
|
+
}
|
|
235
|
+
else if (technique === 1) {
|
|
236
|
+
// Raymarched SDF
|
|
237
|
+
core = `
|
|
238
|
+
// Raymarched distance field
|
|
239
|
+
vec3 ro = vec3(0.0, 0.0, -3.0);
|
|
240
|
+
vec3 rd = normalize(vec3(uv, 1.5));
|
|
241
|
+
float t = 0.0;
|
|
242
|
+
vec3 color = vec3(0.02);
|
|
243
|
+
for (int i = 0; i < 80; i++) {
|
|
244
|
+
vec3 p = ro + rd * t;
|
|
245
|
+
float d = sceneSDF(p, iTime * ${speed});
|
|
246
|
+
if (d < 0.001) {
|
|
247
|
+
vec3 n = calcNormal(p, iTime * ${speed});
|
|
248
|
+
vec3 light = normalize(vec3(1.0, 1.0, -1.0));
|
|
249
|
+
float diff = max(dot(n, light), 0.0);
|
|
250
|
+
float spec = pow(max(dot(reflect(-light, n), -rd), 0.0), 32.0);
|
|
251
|
+
color = vec3(0.2, 0.4, 0.8) * diff + vec3(1.0) * spec * 0.5;
|
|
252
|
+
color += vec3(0.05, 0.02, 0.1);
|
|
253
|
+
float ao = 1.0 - float(i) / 80.0;
|
|
254
|
+
color *= ao;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
t += d;
|
|
258
|
+
if (t > 20.0) break;
|
|
259
|
+
}
|
|
260
|
+
color += vec3(0.02, 0.01, 0.04) * (1.0 - length(uv));`;
|
|
261
|
+
}
|
|
262
|
+
else if (technique === 2) {
|
|
263
|
+
// Kaleidoscopic pattern
|
|
264
|
+
core = `
|
|
265
|
+
// Kaleidoscopic symmetry
|
|
266
|
+
vec2 p = uv * ${zoom};
|
|
267
|
+
float a = atan(p.y, p.x);
|
|
268
|
+
float r = length(p);
|
|
269
|
+
float segments = ${3 + Math.floor(r2 * 10)}.0;
|
|
270
|
+
a = mod(a, 6.28318 / segments) - 3.14159 / segments;
|
|
271
|
+
p = vec2(cos(a), sin(a)) * r;
|
|
272
|
+
float pattern = sin(p.x * 10.0 + iTime * ${speed}) * cos(p.y * 10.0 - iTime * ${speed} * 0.7);
|
|
273
|
+
pattern += sin(r * 8.0 - iTime * ${speed} * 1.5) * 0.5;
|
|
274
|
+
pattern = sin(pattern * 3.14159 * 2.0);
|
|
275
|
+
vec3 color = 0.5 + 0.5 * cos(vec3(${colorShiftA}, ${colorShiftB}, 4.0) + pattern * 2.0 + iTime * 0.3);
|
|
276
|
+
color *= smoothstep(0.0, 0.02, abs(pattern));
|
|
277
|
+
color *= 1.0 - 0.3 * r;`;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Voronoi cells
|
|
281
|
+
core = `
|
|
282
|
+
// Animated Voronoi cells
|
|
283
|
+
vec2 p = uv * ${zoom};
|
|
284
|
+
float minDist = 1.0;
|
|
285
|
+
float secondDist = 1.0;
|
|
286
|
+
vec2 minPoint = vec2(0.0);
|
|
287
|
+
for (int y = -1; y <= 1; y++) {
|
|
288
|
+
for (int x = -1; x <= 1; x++) {
|
|
289
|
+
vec2 cell = floor(p) + vec2(float(x), float(y));
|
|
290
|
+
vec2 point = cell + hash2(cell);
|
|
291
|
+
point += 0.5 * sin(iTime * ${speed} + 6.28 * hash2(cell));
|
|
292
|
+
float d = length(p - point);
|
|
293
|
+
if (d < minDist) {
|
|
294
|
+
secondDist = minDist;
|
|
295
|
+
minDist = d;
|
|
296
|
+
minPoint = point;
|
|
297
|
+
} else if (d < secondDist) {
|
|
298
|
+
secondDist = d;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
float edge = secondDist - minDist;
|
|
303
|
+
vec3 color = 0.5 + 0.5 * cos(vec3(${colorShiftA}, ${colorShiftB}, 4.0) + minPoint.x * 3.0 + minPoint.y * 2.0);
|
|
304
|
+
color *= smoothstep(0.0, 0.05, edge);
|
|
305
|
+
color += 0.1 * (1.0 - minDist);`;
|
|
306
|
+
}
|
|
307
|
+
return `// Generated by K:BOT Creative Tools
|
|
308
|
+
// Description: ${description}
|
|
309
|
+
// Shadertoy-compatible GLSL fragment shader
|
|
310
|
+
// Paste into https://www.shadertoy.com/new
|
|
311
|
+
|
|
312
|
+
// --- Utility functions ---
|
|
313
|
+
|
|
314
|
+
vec2 hash2(vec2 p) {
|
|
315
|
+
p = vec2(dot(p, vec2(127.1, 311.7)),
|
|
316
|
+
dot(p, vec2(269.5, 183.3)));
|
|
317
|
+
return fract(sin(p) * 43758.5453);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
float hash(vec2 p) {
|
|
321
|
+
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
float noise(vec2 p) {
|
|
325
|
+
vec2 i = floor(p);
|
|
326
|
+
vec2 f = fract(p);
|
|
327
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
328
|
+
float a = hash(i);
|
|
329
|
+
float b = hash(i + vec2(1.0, 0.0));
|
|
330
|
+
float c = hash(i + vec2(0.0, 1.0));
|
|
331
|
+
float d = hash(i + vec2(1.0, 1.0));
|
|
332
|
+
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
float fbm(vec2 p) {
|
|
336
|
+
float v = 0.0;
|
|
337
|
+
float a = 0.5;
|
|
338
|
+
mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));
|
|
339
|
+
for (int i = 0; i < ${octaves}; i++) {
|
|
340
|
+
v += a * noise(p);
|
|
341
|
+
p = rot * p * 2.0;
|
|
342
|
+
a *= 0.5;
|
|
343
|
+
}
|
|
344
|
+
return v;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// SDF primitives (used by technique 1)
|
|
348
|
+
float sdSphere(vec3 p, float r) { return length(p) - r; }
|
|
349
|
+
float sdBox(vec3 p, vec3 b) { vec3 d = abs(p) - b; return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0)); }
|
|
350
|
+
|
|
351
|
+
float sceneSDF(vec3 p, float t) {
|
|
352
|
+
float d = sdSphere(p, 1.0 + 0.2 * sin(t * 2.0));
|
|
353
|
+
p.xz *= mat2(cos(t * 0.3), sin(t * 0.3), -sin(t * 0.3), cos(t * 0.3));
|
|
354
|
+
d = min(d, sdBox(p - vec3(0.0, sin(t), 0.0), vec3(0.5 + 0.2 * sin(t * 1.3))));
|
|
355
|
+
// Repetition
|
|
356
|
+
vec3 rp = mod(p + 2.0, 4.0) - 2.0;
|
|
357
|
+
d = min(d, sdSphere(rp, 0.3));
|
|
358
|
+
return d;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
vec3 calcNormal(vec3 p, float t) {
|
|
362
|
+
vec2 e = vec2(0.001, 0.0);
|
|
363
|
+
return normalize(vec3(
|
|
364
|
+
sceneSDF(p + e.xyy, t) - sceneSDF(p - e.xyy, t),
|
|
365
|
+
sceneSDF(p + e.yxy, t) - sceneSDF(p - e.yxy, t),
|
|
366
|
+
sceneSDF(p + e.yyx, t) - sceneSDF(p - e.yyx, t)
|
|
367
|
+
));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// --- Main ---
|
|
371
|
+
|
|
372
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
373
|
+
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
374
|
+
${core}
|
|
375
|
+
|
|
376
|
+
// Vignette
|
|
377
|
+
float vig = 1.0 - 0.4 * dot(uv, uv);
|
|
378
|
+
color *= vig;
|
|
379
|
+
|
|
380
|
+
// Gamma correction
|
|
381
|
+
color = pow(color, vec3(0.4545));
|
|
382
|
+
|
|
383
|
+
fragColor = vec4(color, 1.0);
|
|
384
|
+
}
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
// ─── Music Pattern Generator ────────────────────────────────────────
|
|
388
|
+
function generateMusicPattern(description, genre, format) {
|
|
389
|
+
const seed = hashSeed(description + genre);
|
|
390
|
+
// Scale definitions
|
|
391
|
+
const scales = {
|
|
392
|
+
major: [0, 2, 4, 5, 7, 9, 11],
|
|
393
|
+
minor: [0, 2, 3, 5, 7, 8, 10],
|
|
394
|
+
pentatonic: [0, 2, 4, 7, 9],
|
|
395
|
+
blues: [0, 3, 5, 6, 7, 10],
|
|
396
|
+
dorian: [0, 2, 3, 5, 7, 9, 10],
|
|
397
|
+
mixolydian: [0, 2, 4, 5, 7, 9, 10],
|
|
398
|
+
};
|
|
399
|
+
// Genre → scale + tempo mapping
|
|
400
|
+
const genreConfig = {
|
|
401
|
+
ambient: { scale: 'pentatonic', tempoMin: 60, tempoMax: 80, noteNames: [':C4', ':E4', ':G4', ':A4', ':C5', ':E5'] },
|
|
402
|
+
electronic: { scale: 'minor', tempoMin: 120, tempoMax: 140, noteNames: [':C3', ':Eb3', ':G3', ':Bb3', ':C4', ':Eb4', ':G4'] },
|
|
403
|
+
jazz: { scale: 'dorian', tempoMin: 90, tempoMax: 130, noteNames: [':C3', ':D3', ':Eb3', ':G3', ':A3', ':C4', ':D4'] },
|
|
404
|
+
classical: { scale: 'major', tempoMin: 80, tempoMax: 120, noteNames: [':C4', ':D4', ':E4', ':F4', ':G4', ':A4', ':B4', ':C5'] },
|
|
405
|
+
hiphop: { scale: 'blues', tempoMin: 80, tempoMax: 100, noteNames: [':C2', ':Eb2', ':F2', ':Gb2', ':G2', ':Bb2', ':C3'] },
|
|
406
|
+
default: { scale: 'major', tempoMin: 100, tempoMax: 130, noteNames: [':C4', ':D4', ':E4', ':F4', ':G4', ':A4', ':B4'] },
|
|
407
|
+
};
|
|
408
|
+
const config = genreConfig[genre.toLowerCase()] || genreConfig.default;
|
|
409
|
+
const tempo = config.tempoMin + Math.floor(seededRandom(seed, 0) * (config.tempoMax - config.tempoMin));
|
|
410
|
+
const scaleNotes = scales[config.scale] || scales.major;
|
|
411
|
+
const noteCount = 8 + Math.floor(seededRandom(seed, 1) * 16);
|
|
412
|
+
const baseOctave = 60; // MIDI middle C
|
|
413
|
+
if (format === 'sonic-pi') {
|
|
414
|
+
// Generate Sonic Pi code
|
|
415
|
+
const notes = Array.from({ length: noteCount }, (_, i) => {
|
|
416
|
+
return pick(config.noteNames, seed, i + 10);
|
|
417
|
+
});
|
|
418
|
+
const durations = Array.from({ length: noteCount }, (_, i) => {
|
|
419
|
+
return pick([0.25, 0.5, 0.5, 1, 1, 2], seed, i + 100);
|
|
420
|
+
});
|
|
421
|
+
const synthChoices = ['prophet', 'saw', 'tb303', 'blade', 'pluck', 'piano'];
|
|
422
|
+
const fxChoices = ['reverb', 'echo', 'flanger'];
|
|
423
|
+
const synth = pick(synthChoices, seed, 200);
|
|
424
|
+
const fx = pick(fxChoices, seed, 201);
|
|
425
|
+
let code = `# Generated by K:BOT Creative Tools
|
|
426
|
+
# Description: ${description}
|
|
427
|
+
# Genre: ${genre}
|
|
428
|
+
# Tempo: ${tempo} BPM
|
|
429
|
+
|
|
430
|
+
use_bpm ${tempo}
|
|
431
|
+
use_synth :${synth}
|
|
432
|
+
|
|
433
|
+
`;
|
|
434
|
+
// Melody line
|
|
435
|
+
code += `# Melody\nlive_loop :melody do\n with_fx :${fx}, mix: 0.4 do\n`;
|
|
436
|
+
for (let i = 0; i < notes.length; i++) {
|
|
437
|
+
const amp = (0.4 + seededRandom(seed, i + 300) * 0.5).toFixed(2);
|
|
438
|
+
code += ` play ${notes[i]}, amp: ${amp}, release: ${durations[i]}\n`;
|
|
439
|
+
code += ` sleep ${durations[i]}\n`;
|
|
440
|
+
}
|
|
441
|
+
code += ` end\nend\n\n`;
|
|
442
|
+
// Bass line
|
|
443
|
+
const bassNotes = Array.from({ length: 4 }, (_, i) => {
|
|
444
|
+
return pick([':C2', ':F2', ':G2', ':Bb2'], seed, i + 400);
|
|
445
|
+
});
|
|
446
|
+
code += `# Bass\nlive_loop :bass do\n use_synth :fm\n`;
|
|
447
|
+
for (const note of bassNotes) {
|
|
448
|
+
code += ` play ${note}, amp: 0.6, release: 0.8\n sleep 1\n`;
|
|
449
|
+
}
|
|
450
|
+
code += `end\n\n`;
|
|
451
|
+
// Drum pattern
|
|
452
|
+
code += `# Drums\nlive_loop :drums do\n sample :bd_haus, amp: 0.8\n sleep 0.5\n`;
|
|
453
|
+
code += ` sample :sn_dub, amp: 0.5 if one_in(2)\n sleep 0.25\n`;
|
|
454
|
+
code += ` sample :drum_cymbal_closed, amp: 0.3\n sleep 0.25\n`;
|
|
455
|
+
code += `end\n`;
|
|
456
|
+
return code;
|
|
457
|
+
}
|
|
458
|
+
// JSON format — MIDI-like pattern
|
|
459
|
+
const pattern = {
|
|
460
|
+
meta: { description, genre, tempo, scale: config.scale, seed },
|
|
461
|
+
tracks: [],
|
|
462
|
+
};
|
|
463
|
+
// Note names for MIDI
|
|
464
|
+
const noteNameFromMidi = (midi) => {
|
|
465
|
+
const names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
|
466
|
+
return `${names[midi % 12]}${Math.floor(midi / 12) - 1}`;
|
|
467
|
+
};
|
|
468
|
+
// Melody track
|
|
469
|
+
const melodyNotes = [];
|
|
470
|
+
let currentTime = 0;
|
|
471
|
+
for (let i = 0; i < noteCount; i++) {
|
|
472
|
+
const scaleIdx = Math.floor(seededRandom(seed, i + 10) * scaleNotes.length);
|
|
473
|
+
const octaveShift = Math.floor(seededRandom(seed, i + 50) * 2) * 12;
|
|
474
|
+
const pitch = baseOctave + scaleNotes[scaleIdx] + octaveShift;
|
|
475
|
+
const velocity = 60 + Math.floor(seededRandom(seed, i + 70) * 60);
|
|
476
|
+
const duration = pick([0.25, 0.5, 0.5, 1, 1, 1.5, 2], seed, i + 90);
|
|
477
|
+
melodyNotes.push({ pitch, velocity, start: parseFloat(currentTime.toFixed(3)), duration, name: noteNameFromMidi(pitch) });
|
|
478
|
+
currentTime += duration;
|
|
479
|
+
}
|
|
480
|
+
pattern.tracks.push({ name: 'melody', instrument: 'piano', notes: melodyNotes });
|
|
481
|
+
// Bass track
|
|
482
|
+
const bassTrackNotes = [];
|
|
483
|
+
currentTime = 0;
|
|
484
|
+
for (let i = 0; i < Math.floor(noteCount / 2); i++) {
|
|
485
|
+
const scaleIdx = Math.floor(seededRandom(seed, i + 500) * scaleNotes.length);
|
|
486
|
+
const pitch = 36 + scaleNotes[scaleIdx]; // bass octave
|
|
487
|
+
const duration = pick([1, 1, 2, 2, 4], seed, i + 520);
|
|
488
|
+
bassTrackNotes.push({ pitch, velocity: 80, start: parseFloat(currentTime.toFixed(3)), duration, name: noteNameFromMidi(pitch) });
|
|
489
|
+
currentTime += duration;
|
|
490
|
+
}
|
|
491
|
+
pattern.tracks.push({ name: 'bass', instrument: 'bass', notes: bassTrackNotes });
|
|
492
|
+
// Drum track
|
|
493
|
+
const drumNotes = [];
|
|
494
|
+
const totalBeats = Math.ceil(currentTime);
|
|
495
|
+
for (let beat = 0; beat < totalBeats; beat++) {
|
|
496
|
+
// Kick on 1 and 3
|
|
497
|
+
if (beat % 2 === 0)
|
|
498
|
+
drumNotes.push({ pitch: 36, velocity: 100, start: beat, duration: 0.25, name: 'kick' });
|
|
499
|
+
// Snare on 2 and 4
|
|
500
|
+
if (beat % 2 === 1)
|
|
501
|
+
drumNotes.push({ pitch: 38, velocity: 90, start: beat, duration: 0.25, name: 'snare' });
|
|
502
|
+
// Hi-hat on every beat
|
|
503
|
+
drumNotes.push({ pitch: 42, velocity: 60, start: beat, duration: 0.125, name: 'hihat' });
|
|
504
|
+
// Extra hi-hat on off-beats sometimes
|
|
505
|
+
if (seededRandom(seed, beat + 600) > 0.4) {
|
|
506
|
+
drumNotes.push({ pitch: 42, velocity: 40, start: beat + 0.5, duration: 0.125, name: 'hihat' });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
pattern.tracks.push({ name: 'drums', instrument: 'drums', notes: drumNotes });
|
|
510
|
+
return JSON.stringify(pattern, null, 2);
|
|
511
|
+
}
|
|
512
|
+
// ─── SVG Art Generator ──────────────────────────────────────────────
|
|
513
|
+
function generateSVG(description, width, height) {
|
|
514
|
+
const seed = hashSeed(description);
|
|
515
|
+
const palette = Array.from({ length: 6 }, (_, i) => seedColor(seed, i * 3));
|
|
516
|
+
const bgColor = seedColor(seed, 100);
|
|
517
|
+
const technique = Math.floor(seededRandom(seed, 0) * 5);
|
|
518
|
+
let elements = '';
|
|
519
|
+
if (technique === 0) {
|
|
520
|
+
// Concentric circles with rotation
|
|
521
|
+
const numCircles = 8 + Math.floor(seededRandom(seed, 10) * 12);
|
|
522
|
+
const cx = width / 2;
|
|
523
|
+
const cy = height / 2;
|
|
524
|
+
for (let i = 0; i < numCircles; i++) {
|
|
525
|
+
const r = ((i + 1) / numCircles) * Math.min(width, height) * 0.45;
|
|
526
|
+
const strokeW = 1 + seededRandom(seed, i + 20) * 4;
|
|
527
|
+
const dashLen = 5 + Math.floor(seededRandom(seed, i + 30) * 20);
|
|
528
|
+
const dashGap = 3 + Math.floor(seededRandom(seed, i + 40) * 15);
|
|
529
|
+
const color = palette[i % palette.length];
|
|
530
|
+
const rotation = Math.floor(seededRandom(seed, i + 50) * 360);
|
|
531
|
+
elements += ` <circle cx="${cx}" cy="${cy}" r="${r.toFixed(1)}" fill="none" stroke="${color}" stroke-width="${strokeW.toFixed(1)}" stroke-dasharray="${dashLen} ${dashGap}" transform="rotate(${rotation} ${cx} ${cy})" opacity="0.8"/>\n`;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else if (technique === 1) {
|
|
535
|
+
// Voronoi-like polygons
|
|
536
|
+
const numPoints = 10 + Math.floor(seededRandom(seed, 10) * 20);
|
|
537
|
+
const points = Array.from({ length: numPoints }, (_, i) => ({
|
|
538
|
+
x: seededRandom(seed, i * 2 + 100) * width,
|
|
539
|
+
y: seededRandom(seed, i * 2 + 101) * height,
|
|
540
|
+
}));
|
|
541
|
+
// Draw Delaunay-ish triangulation as lines
|
|
542
|
+
for (let i = 0; i < points.length; i++) {
|
|
543
|
+
// Connect to 2-3 nearest other points
|
|
544
|
+
const dists = points
|
|
545
|
+
.map((p, j) => ({ j, d: Math.hypot(p.x - points[i].x, p.y - points[i].y) }))
|
|
546
|
+
.filter((x) => x.j !== i)
|
|
547
|
+
.sort((a, b) => a.d - b.d);
|
|
548
|
+
const conns = 2 + Math.floor(seededRandom(seed, i + 200) * 2);
|
|
549
|
+
for (let k = 0; k < Math.min(conns, dists.length); k++) {
|
|
550
|
+
const target = points[dists[k].j];
|
|
551
|
+
elements += ` <line x1="${points[i].x.toFixed(1)}" y1="${points[i].y.toFixed(1)}" x2="${target.x.toFixed(1)}" y2="${target.y.toFixed(1)}" stroke="${palette[k % palette.length]}" stroke-width="1" opacity="0.5"/>\n`;
|
|
552
|
+
}
|
|
553
|
+
// Draw a circle at each point
|
|
554
|
+
const r = 3 + seededRandom(seed, i + 300) * 8;
|
|
555
|
+
elements += ` <circle cx="${points[i].x.toFixed(1)}" cy="${points[i].y.toFixed(1)}" r="${r.toFixed(1)}" fill="${palette[i % palette.length]}" opacity="0.7"/>\n`;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
else if (technique === 2) {
|
|
559
|
+
const rects = [];
|
|
560
|
+
function subdivide(x, y, w, h, depth) {
|
|
561
|
+
if (depth > 4 || w < 30 || h < 30) {
|
|
562
|
+
rects.push({ x, y, w, h, depth });
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (seededRandom(seed, depth * 100 + rects.length) > 0.4) {
|
|
566
|
+
// Split horizontally
|
|
567
|
+
const split = 0.3 + seededRandom(seed, depth * 100 + rects.length + 1) * 0.4;
|
|
568
|
+
subdivide(x, y, w * split, h, depth + 1);
|
|
569
|
+
subdivide(x + w * split, y, w * (1 - split), h, depth + 1);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
// Split vertically
|
|
573
|
+
const split = 0.3 + seededRandom(seed, depth * 100 + rects.length + 2) * 0.4;
|
|
574
|
+
subdivide(x, y, w, h * split, depth + 1);
|
|
575
|
+
subdivide(x, y + h * split, w, h * (1 - split), depth + 1);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
subdivide(0, 0, width, height, 0);
|
|
579
|
+
for (let i = 0; i < rects.length; i++) {
|
|
580
|
+
const r = rects[i];
|
|
581
|
+
const fill = seededRandom(seed, i + 400) > 0.6 ? palette[i % palette.length] : bgColor;
|
|
582
|
+
elements += ` <rect x="${r.x.toFixed(1)}" y="${r.y.toFixed(1)}" width="${r.w.toFixed(1)}" height="${r.h.toFixed(1)}" fill="${fill}" stroke="#222" stroke-width="3"/>\n`;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
else if (technique === 3) {
|
|
586
|
+
// Spirograph-like curves
|
|
587
|
+
const numCurves = 3 + Math.floor(seededRandom(seed, 10) * 5);
|
|
588
|
+
for (let c = 0; c < numCurves; c++) {
|
|
589
|
+
const R = 100 + seededRandom(seed, c * 10 + 11) * 150;
|
|
590
|
+
const r = 20 + seededRandom(seed, c * 10 + 12) * 80;
|
|
591
|
+
const d = 30 + seededRandom(seed, c * 10 + 13) * 100;
|
|
592
|
+
const steps = 500;
|
|
593
|
+
let pathData = '';
|
|
594
|
+
for (let i = 0; i <= steps; i++) {
|
|
595
|
+
const t = (i / steps) * Math.PI * 20;
|
|
596
|
+
const x = width / 2 + (R - r) * Math.cos(t) + d * Math.cos(((R - r) / r) * t);
|
|
597
|
+
const y = height / 2 + (R - r) * Math.sin(t) - d * Math.sin(((R - r) / r) * t);
|
|
598
|
+
pathData += i === 0 ? `M${x.toFixed(1)},${y.toFixed(1)}` : ` L${x.toFixed(1)},${y.toFixed(1)}`;
|
|
599
|
+
}
|
|
600
|
+
elements += ` <path d="${pathData}" fill="none" stroke="${palette[c % palette.length]}" stroke-width="0.8" opacity="0.7"/>\n`;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
// Layered wave lines
|
|
605
|
+
const numWaves = 8 + Math.floor(seededRandom(seed, 10) * 12);
|
|
606
|
+
for (let w = 0; w < numWaves; w++) {
|
|
607
|
+
const yBase = (height / (numWaves + 1)) * (w + 1);
|
|
608
|
+
const amp = 10 + seededRandom(seed, w + 20) * 50;
|
|
609
|
+
const freq = 0.01 + seededRandom(seed, w + 30) * 0.03;
|
|
610
|
+
const phase = seededRandom(seed, w + 40) * Math.PI * 2;
|
|
611
|
+
let pathData = '';
|
|
612
|
+
for (let x = 0; x <= width; x += 2) {
|
|
613
|
+
const y = yBase + Math.sin(x * freq + phase) * amp + Math.sin(x * freq * 2.3 + phase * 0.7) * amp * 0.3;
|
|
614
|
+
pathData += x === 0 ? `M${x},${y.toFixed(1)}` : ` L${x},${y.toFixed(1)}`;
|
|
615
|
+
}
|
|
616
|
+
elements += ` <path d="${pathData}" fill="none" stroke="${palette[w % palette.length]}" stroke-width="${(1 + seededRandom(seed, w + 50) * 3).toFixed(1)}" opacity="0.7" stroke-linecap="round"/>\n`;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
620
|
+
<!-- Generated by K:BOT Creative Tools -->
|
|
621
|
+
<!-- Description: ${description} -->
|
|
622
|
+
<!-- Seed: ${seed} -->
|
|
623
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
|
|
624
|
+
<rect width="100%" height="100%" fill="${bgColor}"/>
|
|
625
|
+
${elements}</svg>
|
|
626
|
+
`;
|
|
627
|
+
}
|
|
628
|
+
// ─── Design Evolution ───────────────────────────────────────────────
|
|
629
|
+
function evolveDesign(source, mutationIndex) {
|
|
630
|
+
const lines = source.split('\n');
|
|
631
|
+
const seed = hashSeed(source + String(mutationIndex));
|
|
632
|
+
// Strategies for mutation
|
|
633
|
+
const mutated = lines.map((line, lineIdx) => {
|
|
634
|
+
let result = line;
|
|
635
|
+
// Mutate numeric values with some probability
|
|
636
|
+
result = result.replace(/(\d+\.?\d*)/g, (match, num) => {
|
|
637
|
+
const val = parseFloat(num);
|
|
638
|
+
if (isNaN(val) || seededRandom(seed, lineIdx * 100 + mutationIndex) > 0.3)
|
|
639
|
+
return match;
|
|
640
|
+
const factor = 0.7 + seededRandom(seed, lineIdx * 100 + mutationIndex + 1) * 0.6; // 0.7 to 1.3
|
|
641
|
+
const mutated = val * factor;
|
|
642
|
+
// Preserve integer vs float
|
|
643
|
+
return match.includes('.') ? mutated.toFixed(match.split('.')[1]?.length || 2) : String(Math.round(mutated));
|
|
644
|
+
});
|
|
645
|
+
// Mutate hex colors
|
|
646
|
+
result = result.replace(/#([0-9a-fA-F]{6})/g, (match, hex) => {
|
|
647
|
+
if (seededRandom(seed, lineIdx * 200 + mutationIndex) > 0.4)
|
|
648
|
+
return match;
|
|
649
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
650
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
651
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
652
|
+
const shift = Math.floor((seededRandom(seed, lineIdx * 200 + mutationIndex + 1) - 0.5) * 60);
|
|
653
|
+
const clamp = (v) => Math.max(0, Math.min(255, v + shift));
|
|
654
|
+
return `#${clamp(r).toString(16).padStart(2, '0')}${clamp(g).toString(16).padStart(2, '0')}${clamp(b).toString(16).padStart(2, '0')}`;
|
|
655
|
+
});
|
|
656
|
+
// Mutate hsl colors
|
|
657
|
+
result = result.replace(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/g, (match, h, s, l) => {
|
|
658
|
+
if (seededRandom(seed, lineIdx * 300 + mutationIndex) > 0.4)
|
|
659
|
+
return match;
|
|
660
|
+
const hShift = Math.floor((seededRandom(seed, lineIdx * 300 + mutationIndex + 1) - 0.5) * 60);
|
|
661
|
+
const newH = (parseInt(h) + hShift + 360) % 360;
|
|
662
|
+
const newS = Math.max(0, Math.min(100, parseInt(s) + Math.floor((seededRandom(seed, lineIdx * 300 + mutationIndex + 2) - 0.5) * 20)));
|
|
663
|
+
const newL = Math.max(0, Math.min(100, parseInt(l) + Math.floor((seededRandom(seed, lineIdx * 300 + mutationIndex + 3) - 0.5) * 20)));
|
|
664
|
+
return `hsl(${newH}, ${newS}%, ${newL}%)`;
|
|
665
|
+
});
|
|
666
|
+
return result;
|
|
667
|
+
});
|
|
668
|
+
// Occasionally duplicate or remove a line (structural mutation)
|
|
669
|
+
const finalLines = [...mutated];
|
|
670
|
+
if (seededRandom(seed, 999) > 0.7 && finalLines.length > 5) {
|
|
671
|
+
// Duplicate a random line
|
|
672
|
+
const idx = Math.floor(seededRandom(seed, 998) * finalLines.length);
|
|
673
|
+
finalLines.splice(idx, 0, finalLines[idx]);
|
|
674
|
+
}
|
|
675
|
+
if (seededRandom(seed, 997) > 0.8 && finalLines.length > 10) {
|
|
676
|
+
// Remove a random non-critical line
|
|
677
|
+
const idx = 2 + Math.floor(seededRandom(seed, 996) * (finalLines.length - 4));
|
|
678
|
+
finalLines.splice(idx, 1);
|
|
679
|
+
}
|
|
680
|
+
return finalLines.join('\n');
|
|
681
|
+
}
|
|
682
|
+
// ─── Tool Registration ──────────────────────────────────────────────
|
|
683
|
+
export function registerCreativeTools() {
|
|
684
|
+
registerTool({
|
|
685
|
+
name: 'generate_art',
|
|
686
|
+
description: 'Generate a self-contained p5.js sketch as an HTML file from a text description. Creates generative art that can be opened directly in a browser.',
|
|
687
|
+
parameters: {
|
|
688
|
+
description: { type: 'string', description: 'Text description of the desired artwork (e.g., "flowing ocean waves at sunset")', required: true },
|
|
689
|
+
style: { type: 'string', description: 'Art style: "abstract", "geometric", "organic", "fractal", or "noise". Defaults to "abstract".' },
|
|
690
|
+
output_path: { type: 'string', description: 'File path to write the HTML file', required: true },
|
|
691
|
+
},
|
|
692
|
+
tier: 'free',
|
|
693
|
+
async execute(args) {
|
|
694
|
+
const description = String(args.description);
|
|
695
|
+
const style = String(args.style || 'abstract');
|
|
696
|
+
const validStyles = ['abstract', 'geometric', 'organic', 'fractal', 'noise'];
|
|
697
|
+
if (!validStyles.includes(style)) {
|
|
698
|
+
return `Error: Invalid style "${style}". Choose from: ${validStyles.join(', ')}`;
|
|
699
|
+
}
|
|
700
|
+
const outputPath = String(args.output_path);
|
|
701
|
+
const html = generateP5Sketch(description, style);
|
|
702
|
+
ensureDir(outputPath);
|
|
703
|
+
writeFileSync(outputPath, html);
|
|
704
|
+
return `Generated p5.js art (${style} style) at ${outputPath} (${html.length} bytes)\nOpen in a browser to view. Click the canvas to regenerate.`;
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
registerTool({
|
|
708
|
+
name: 'generate_shader',
|
|
709
|
+
description: 'Generate a Shadertoy-compatible GLSL fragment shader from a text description. Creates animated procedural graphics using noise, raymarching, voronoi, or kaleidoscopic techniques.',
|
|
710
|
+
parameters: {
|
|
711
|
+
description: { type: 'string', description: 'Text description of the desired shader effect (e.g., "molten lava flowing through cracks")', required: true },
|
|
712
|
+
output_path: { type: 'string', description: 'File path to write the .glsl file', required: true },
|
|
713
|
+
},
|
|
714
|
+
tier: 'free',
|
|
715
|
+
async execute(args) {
|
|
716
|
+
const description = String(args.description);
|
|
717
|
+
const outputPath = String(args.output_path);
|
|
718
|
+
const glsl = generateGLSL(description);
|
|
719
|
+
ensureDir(outputPath);
|
|
720
|
+
writeFileSync(outputPath, glsl);
|
|
721
|
+
return `Generated GLSL shader at ${outputPath} (${glsl.length} bytes)\nPaste into https://www.shadertoy.com/new to preview.`;
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
registerTool({
|
|
725
|
+
name: 'generate_music_pattern',
|
|
726
|
+
description: 'Generate a music pattern as Sonic Pi code or a MIDI-like JSON structure. Creates melodies, bass lines, and drum patterns based on genre and description.',
|
|
727
|
+
parameters: {
|
|
728
|
+
description: { type: 'string', description: 'Text description of the desired music (e.g., "upbeat jazz with walking bass")', required: true },
|
|
729
|
+
genre: { type: 'string', description: 'Music genre: "ambient", "electronic", "jazz", "classical", "hiphop". Defaults to "classical".' },
|
|
730
|
+
format: { type: 'string', description: 'Output format: "sonic-pi" or "json". Defaults to "sonic-pi".', required: true },
|
|
731
|
+
output_path: { type: 'string', description: 'File path to write the output file', required: true },
|
|
732
|
+
},
|
|
733
|
+
tier: 'free',
|
|
734
|
+
async execute(args) {
|
|
735
|
+
const description = String(args.description);
|
|
736
|
+
const genre = String(args.genre || 'classical');
|
|
737
|
+
const format = String(args.format || 'sonic-pi');
|
|
738
|
+
if (format !== 'sonic-pi' && format !== 'json') {
|
|
739
|
+
return `Error: Invalid format "${format}". Choose "sonic-pi" or "json".`;
|
|
740
|
+
}
|
|
741
|
+
const outputPath = String(args.output_path);
|
|
742
|
+
const content = generateMusicPattern(description, genre, format);
|
|
743
|
+
ensureDir(outputPath);
|
|
744
|
+
writeFileSync(outputPath, content);
|
|
745
|
+
const ext = format === 'json' ? 'JSON' : 'Sonic Pi';
|
|
746
|
+
return `Generated ${ext} music pattern at ${outputPath} (${content.length} bytes)\n${format === 'sonic-pi' ? 'Open in Sonic Pi (https://sonic-pi.net/) to play.' : 'Load the JSON into a DAW or MIDI player.'}`;
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
registerTool({
|
|
750
|
+
name: 'generate_svg',
|
|
751
|
+
description: 'Generate algorithmic SVG art from a text description. Creates generative patterns including concentric circles, mesh networks, Mondrian grids, spirographs, or wave patterns.',
|
|
752
|
+
parameters: {
|
|
753
|
+
description: { type: 'string', description: 'Text description of the desired SVG artwork', required: true },
|
|
754
|
+
width: { type: 'number', description: 'SVG width in pixels. Defaults to 800.' },
|
|
755
|
+
height: { type: 'number', description: 'SVG height in pixels. Defaults to 800.' },
|
|
756
|
+
output_path: { type: 'string', description: 'File path to write the .svg file', required: true },
|
|
757
|
+
},
|
|
758
|
+
tier: 'free',
|
|
759
|
+
async execute(args) {
|
|
760
|
+
const description = String(args.description);
|
|
761
|
+
const width = typeof args.width === 'number' ? args.width : 800;
|
|
762
|
+
const height = typeof args.height === 'number' ? args.height : 800;
|
|
763
|
+
if (width < 10 || width > 10000 || height < 10 || height > 10000) {
|
|
764
|
+
return 'Error: Width and height must be between 10 and 10000.';
|
|
765
|
+
}
|
|
766
|
+
const outputPath = String(args.output_path);
|
|
767
|
+
const svg = generateSVG(description, width, height);
|
|
768
|
+
ensureDir(outputPath);
|
|
769
|
+
writeFileSync(outputPath, svg);
|
|
770
|
+
return `Generated SVG art at ${outputPath} (${svg.length} bytes, ${width}x${height})\nOpen in a browser or SVG editor to view.`;
|
|
771
|
+
},
|
|
772
|
+
});
|
|
773
|
+
registerTool({
|
|
774
|
+
name: 'evolve_design',
|
|
775
|
+
description: 'Take an existing design file (HTML, SVG, GLSL, CSS, etc.) and generate N mutations by tweaking numeric values, colors, and structure. Outputs each variant as a separate file. Useful for exploring design spaces.',
|
|
776
|
+
parameters: {
|
|
777
|
+
source_path: { type: 'string', description: 'Path to the source design file to mutate', required: true },
|
|
778
|
+
mutations: { type: 'number', description: 'Number of mutations to generate. Defaults to 5.', default: 5 },
|
|
779
|
+
output_dir: { type: 'string', description: 'Directory to write mutated variants', required: true },
|
|
780
|
+
},
|
|
781
|
+
tier: 'free',
|
|
782
|
+
async execute(args) {
|
|
783
|
+
const sourcePath = String(args.source_path);
|
|
784
|
+
if (!existsSync(sourcePath))
|
|
785
|
+
return `Error: Source file not found: ${sourcePath}`;
|
|
786
|
+
const source = readFileSync(sourcePath, 'utf-8');
|
|
787
|
+
const mutations = typeof args.mutations === 'number' ? Math.max(1, Math.min(args.mutations, 50)) : 5;
|
|
788
|
+
const outputDir = String(args.output_dir);
|
|
789
|
+
mkdirSync(outputDir, { recursive: true });
|
|
790
|
+
const ext = extname(sourcePath);
|
|
791
|
+
const base = basename(sourcePath, ext);
|
|
792
|
+
const results = [];
|
|
793
|
+
for (let i = 0; i < mutations; i++) {
|
|
794
|
+
const mutated = evolveDesign(source, i);
|
|
795
|
+
const outPath = join(outputDir, `${base}_variant_${i + 1}${ext}`);
|
|
796
|
+
writeFileSync(outPath, mutated);
|
|
797
|
+
results.push(` ${outPath} (${mutated.length} bytes)`);
|
|
798
|
+
}
|
|
799
|
+
return `Evolved ${mutations} variants from ${sourcePath}:\n${results.join('\n')}\n\nEach variant has randomized tweaks to numeric values, colors, and occasionally structure.`;
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
//# sourceMappingURL=creative.js.map
|