@genart-api/core
Version:
Platform-independent extensible API for browser-based generative art
1,170 lines (1,144 loc) • 34.1 kB
JavaScript
;
(() => {
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/math.ts
var math_exports = {};
__export(math_exports, {
clamp: () => clamp,
clamp01: () => clamp01,
div: () => div,
easeInOut5: () => easeInOut5,
fit: () => fit,
mix: () => mix,
norm: () => norm,
parseNum: () => parseNum,
round: () => round,
smoothstep: () => smoothstep,
smoothstep01: () => smoothstep01
});
var parseNum = (x, fallback = 0) => {
const y = x ? parseFloat(x) : Number.NaN;
return isNaN(y) ? fallback : y;
};
var mix = (a, b, t) => a + (b - a) * t;
var fit = (x, a, b, c, d) => c + (d - c) * norm(x, a, b);
var clamp = (x, min, max) => x < min ? min : x > max ? max : x;
var clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x;
var round = (x, y) => Math.round(div(x, y)) * y;
var norm = (x, a, b) => div(x - a, b - a);
var div = (x, y) => y != 0 ? x / y : 0;
var smoothstep = (edge0, edge1, x) => smoothstep01(clamp01(div(x - edge0, edge1 - edge0)));
var smoothstep01 = (x) => x * x * (3 - 2 * x);
var __easeInOut = (k) => {
const k2 = 2 ** (k - 1);
return (t) => t < 0.5 ? k2 * t ** k : 1 - (-2 * t + 2) ** k / 2;
};
var easeInOut5 = __easeInOut(5);
// src/params/factories.ts
var factories_exports = {};
__export(factories_exports, {
PARAM_DEFAULTS: () => PARAM_DEFAULTS,
bigint: () => bigint,
binary: () => binary,
choice: () => choice,
color: () => color,
date: () => date2,
datetime: () => datetime2,
image: () => image,
numlist: () => numlist,
ramp: () => ramp,
range: () => range,
strlist: () => strlist,
text: () => text,
time: () => time2,
toggle: () => toggle,
vector: () => vector2,
weighted: () => weighted,
xy: () => xy
});
// src/utils.ts
var utils_exports = {};
__export(utils_exports, {
ensure: () => ensure,
equiv: () => equiv,
equivArrayLike: () => equivArrayLike,
equivObject: () => equivObject,
formatValuePrec: () => formatValuePrec,
hashBytes: () => hashBytes,
hashString: () => hashString,
isBigInt: () => isBigInt,
isFunction: () => isFunction,
isInRange: () => isInRange,
isNumber: () => isNumber,
isNumericArray: () => isNumericArray,
isPrim: () => isPrim,
isString: () => isString,
isStringArray: () => isStringArray,
isTypedArray: () => isTypedArray,
parseBigInt: () => parseBigInt,
parseBigInt128: () => parseBigInt128,
parseUUID: () => parseUUID,
stringifyBigInt: () => stringifyBigInt,
stringifyJSON: () => stringifyJSON,
u16: () => u16,
u24: () => u24,
u32: () => u32,
u8: () => u8,
valuePrec: () => valuePrec
});
var M = 0xfffffffffn;
var imul = Math.imul;
var OBJP = Object.getPrototypeOf({});
var ensure = (x, msg) => {
if (!x) throw new Error(msg);
return x;
};
var isBigInt = (x) => typeof x === "bigint";
var isNumber = (x) => typeof x === "number" && !isNaN(x);
var isString = (x) => typeof x === "string";
var isPrim = (x) => {
const type = typeof x;
return type === "bigint" || type === "boolean" || type === "number" || type === "string" || type === "symbol";
};
var isFunction = (x) => typeof x === "function";
var isNumericArray = (x) => isTypedArray(x) || Array.isArray(x) && x.every(isNumber);
var isStringArray = (x) => Array.isArray(x) && x.every(isString);
var isTypedArray = (x) => !!x && (x instanceof Float32Array || x instanceof Float64Array || x instanceof Uint32Array || x instanceof Int32Array || x instanceof Uint8Array || x instanceof Int8Array || x instanceof Uint16Array || x instanceof Int16Array || x instanceof Uint8ClampedArray);
var isInRange = (x, min, max) => x >= min && x <= max;
var u8 = (x) => (x &= 255, (x < 16 ? "0" : "") + x.toString(16));
var u16 = (x) => u8(x >>> 8) + u8(x);
var u24 = (x) => u16(x >>> 8) + u8(x & 255);
var u32 = (x) => u16(x >>> 16) + u16(x);
var stringifyBigInt = (x, radix = 10) => {
const prefix = { 10: "", 2: "0b", 8: "0o", 16: "0x" }[radix];
return x < 0n ? "-" + prefix + (-x).toString(radix) : prefix + x.toString(radix);
};
var parseBigInt = (x) => /^-0[box]/.test(x) ? -BigInt(x.substring(1)) : BigInt(x);
var parseBigInt128 = (x) => new Uint32Array([
Number(x >> 96n & M),
Number(x >> 64n & M),
Number(x >> 32n & M),
Number(x & M)
]);
var stringifyJSON = (value) => JSON.stringify(
value,
(_, x) => isBigInt(x) ? x.toString() : isTypedArray(x) ? [...x] : x,
4
);
var valuePrec = (step) => {
const str = step.toString();
const i = str.indexOf(".");
return i > 0 ? str.length - i - 1 : 0;
};
var formatValuePrec = (step) => {
const prec = valuePrec(step);
return (x) => x.toFixed(prec);
};
var equiv = (a, b) => {
let proto;
if (a === b) return true;
if (a == null) return b == null;
if (b == null) return a == null;
if (isPrim(a) || isPrim(b) || isFunction(a) || isFunction(b))
return a === b || a !== a && b !== b;
if (a.length != null && b.length != null) {
return equivArrayLike(a, b);
}
if ((proto = Object.getPrototypeOf(a), proto == null || proto === OBJP) && (proto = Object.getPrototypeOf(b), proto == null || proto === OBJP)) {
return equivObject(a, b);
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
if (a instanceof RegExp && b instanceof RegExp) {
return a.toString() === b.toString();
}
return a === b;
};
var equivObject = (a, b) => {
if (Object.keys(a).length !== Object.keys(b).length) {
return false;
}
for (let k in a) {
if (!(b.hasOwnProperty(k) && equiv(a[k], b[k]))) return false;
}
return true;
};
var equivArrayLike = (a, b) => {
if (a.length !== b.length) return false;
let i = a.length;
while (i-- > 0 && equiv(a[i], b[i])) ;
return i < 0;
};
var parseUUID = (uuid) => parseBigInt128(BigInt("0x" + uuid.replace(/-/g, "").substring(0, 32)));
var hashBytes = (buf, seed = 0) => {
const u322 = (i2) => buf[i2 + 3] << 24 | buf[i2 + 2] << 16 | buf[i2 + 1] << 8 | buf[i2];
const rotate = (x, r) => x << r | x >>> 32 - r;
const update = (i2, p) => {
const q = p + 1 & 3;
H[p] = imul(rotate(H[p] ^ imul(rotate(imul(u322(i2), P[p]), 15 + p), P[q]), 19 - (p << 1)) + H[q], 5) + K[p];
};
const sum = (h) => {
const h0 = h[0] += h[1] + h[2] + h[3];
h[1] += h0;
h[2] += h0;
h[3] += h0;
return h;
};
const fmix = (h) => {
h ^= h >>> 16;
h = imul(h, 2246822507);
h ^= h >>> 13;
h = imul(h, 3266489909);
return h ^= h >>> 16;
};
const N = buf.length;
const K = new Uint32Array([1444728091, 197830471, 2530024501, 850148119]);
const P = new Uint32Array([597399067, 2869860233, 951274213, 2716044179]);
const H = P.map((x) => x ^ seed);
let i = 0;
for (const blockLimit = N & -16; i < blockLimit; i += 16) {
update(i, 0);
update(i + 4, 1);
update(i + 8, 2);
update(i + 12, 3);
}
K.fill(0);
for (let j = N & 15; j > 0; j--) {
const j1 = j - 1;
if ((j & 3) === 1) {
const bin = j >> 2;
K[bin] = rotate(imul(K[bin] ^ buf[i + j1], P[bin]), 15 + bin);
H[bin] ^= imul(K[bin], P[bin + 1 & 3]);
} else {
K[j1 >> 2] ^= buf[i + j1] << (j1 << 3);
}
}
return sum(sum(H.map((x) => x ^ N)).map(fmix));
};
var hashString = (x, seed) => hashBytes(new TextEncoder().encode(x), seed);
// src/params/date.ts
var RE_DATE = /^\d{4}-\d{2}-\d{2}$/;
var date = {
validate: (_, value) => value instanceof Date || isNumber(value) || isString(value) && RE_DATE.test(value),
coerce: (_, value) => isNumber(value) ? new Date(value) : isString(value) ? new Date(Date.parse(value)) : value
};
// src/params/datetime.ts
var RE_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[-+]\d{2}:\d{2})$/;
var datetime = {
validate: (_, value) => value instanceof Date || isNumber(value) || isString(value) && RE_DATETIME.test(value),
coerce: (_, value) => isNumber(value) ? new Date(value) : isString(value) ? new Date(Date.parse(value)) : value
};
// src/params/time.ts
var time = {
validate: (_, value) => isNumericArray(value) && value.length === 3 && isInRange(value[0], 0, 23) && isInRange(value[1], 0, 59) && isInRange(value[2], 0, 59) || isString(value) && /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/.test(value),
coerce: (_, value) => isString(value) ? value.split(":").map(parseNum) : value,
randomize: (_, rnd) => [
rnd() * 24 | 0,
rnd() * 60 | 0,
rnd() * 60 | 0
]
};
// src/params/vector.ts
var vector = {
validate: (spec, value) => {
const { min, max, size } = spec;
return isNumericArray(value) && value.length === size && value.every((x, i) => isInRange(x, min[i], max[i]));
},
coerce: (spec, value) => {
const { min, max, step } = spec;
return value.map(
(x, i) => clamp(round(x, step[i]), min[i], max[i])
);
},
randomize: (spec, rnd) => {
const { min, max, size, step } = spec;
return new Array(size).fill(0).map(
(_, i) => clamp(
round(mix(min[i], max[i], rnd()), step[i]),
min[i],
max[i]
)
);
}
};
// src/params/factories.ts
var PARAM_DEFAULTS = {
desc: "TODO description",
edit: "protected",
group: "main",
order: 0,
randomize: true,
state: "void",
update: "event",
widget: "default"
};
var $ = (type, spec, randomize = true) => {
ensure(spec.name, "missing param `name`");
return {
...PARAM_DEFAULTS,
type,
randomize,
...spec
};
};
var $default = (impl, value) => value != null ? ensure(
impl.validate(null, value),
`invalid default value: ${value}`
) && impl.coerce(null, value) : value;
var minMaxLength = (spec, maxDefault = 10) => {
let min = 0, max = spec.maxLength || maxDefault;
if (spec.minLength) {
min = spec.minLength;
if (!spec.maxLength) max = Math.max(min, maxDefault);
}
ensure(min <= max, `invalid list length constraint`);
return [min, max];
};
var bigint = (spec) => $("bigint", {
min: 0n,
max: 0xffffffffffffffffn,
...spec
});
var binary = (spec) => $(
"binary",
{
minLength: 0,
maxLength: 1024,
...spec
},
false
);
var choice = (spec) => $("choice", spec);
var color = (spec) => $("color", spec);
var date2 = (spec) => $(
"date",
{
...spec,
default: $default(date, spec.default)
},
false
);
var datetime2 = (spec) => $(
"datetime",
{
...spec,
default: $default(datetime, spec.default)
},
false
);
var image = (spec) => $(
"image",
{
default: spec.default || new (spec.format === "gray" ? Uint8Array : Uint32Array)(
spec.width * spec.height
),
...spec
},
false
);
var numlist = (spec) => {
const [minLength, maxLength] = minMaxLength(spec, 10);
return $(
"numlist",
{
default: spec.default || new Array(minLength).fill(0),
minLength,
maxLength,
...spec
},
false
);
};
var ramp = (spec) => $(
"ramp",
{
...spec,
stops: spec.stops ? spec.stops.flat() : [0, 0, 1, 1],
mode: spec.mode || "linear",
default: 0
},
false
);
var range = (spec) => $("range", {
min: 0,
max: 100,
step: 1,
...spec
});
var strlist = (spec) => {
const [minLength, maxLength] = minMaxLength(spec, 10);
return $(
"strlist",
{
default: spec.default || new Array(minLength).fill(""),
minLength,
maxLength,
...spec
},
false
);
};
var text = (spec) => $(
"text",
{ minLength: 0, maxLength: 32, multiline: false, ...spec },
false
);
var time2 = (spec) => {
return $("time", {
...spec,
default: spec.default != null ? ensure(time.validate(null, spec.default), ``) && time.coerce(null, spec.default) : spec.default
});
};
var toggle = (spec) => $("toggle", spec);
var vector2 = (spec) => {
if (spec.labels) {
ensure(spec.labels.length >= spec.size, `expected ${spec.size} labels`);
} else {
ensure(spec.size <= 4, "missing vector labels");
}
const $vec = (n, value, defaultValue = 0) => Array.isArray(value) ? (ensure(value.length === n, "wrong vector size"), value) : new Array(n).fill(isNumber(value) ? value : defaultValue);
const limits = {
min: $vec(spec.size, spec.min, 0),
max: $vec(spec.size, spec.max, 1),
step: $vec(spec.size, spec.step, 0.01)
};
return $("vector", {
...spec,
...limits,
default: spec.default ? ensure(
spec.default.length == spec.size,
`wrong vector size, expected ${spec.size} values`
) && vector.coerce(limits, spec.default) : spec.default,
labels: spec.labels || ["X", "Y", "Z", "W"].slice(0, spec.size)
});
};
var weighted = (spec) => $("weighted", {
...spec,
options: spec.options.sort((a, b) => b[0] - a[0]),
total: spec.options.reduce((acc, x) => acc + x[0], 0)
});
var xy = (spec) => $("xy", spec);
// src/prng.ts
var prng_exports = {};
__export(prng_exports, {
SFC32: () => SFC32,
randomBigInt: () => randomBigInt
});
var MAX = 4294967296;
var SFC32 = class _SFC32 {
constructor(seed) {
this.seed = seed;
this.buf = new Uint32Array(4);
this.buf.set(seed);
this.#rnd = () => {
const buf = this.buf;
const t = (buf[0] + buf[1] >>> 0) + buf[3] >>> 0;
buf[3] = buf[3] + 1 >>> 0;
buf[0] = buf[1] ^ buf[1] >>> 9;
buf[1] = buf[2] + (buf[2] << 3) >>> 0;
buf[2] = (buf[2] << 21 | buf[2] >>> 11) + t >>> 0;
return t / MAX;
};
}
buf;
#rnd;
// allow rnd() to be used as standalone function
get rnd() {
return this.#rnd;
}
reset() {
this.buf.set(this.seed);
}
copy() {
return new _SFC32(this.buf.slice());
}
};
var randomBigInt = (max, rnd = Math.random) => {
if (!isFunction(rnd)) rnd = rnd.rnd.bind(rnd);
let value = 0n;
for (let i = Math.log2(Number(max)) + 31 >> 5; i-- > 0; )
value = value << 32n | BigInt(rnd() * MAX >>> 0);
return value % max;
};
// src/params/bigint.ts
var bigint2 = {
validate: (spec, value) => {
const { min, max } = spec;
if (isString(value)) {
if (!/^-?([0-9]+|0x[0-9a-f]+|0b[01]+|0o[0-7]+)$/.test(value)) {
return false;
}
value = parseBigInt(value);
} else if (isNumber(value) || isBigInt(value)) {
value = BigInt(value);
} else {
return false;
}
return value >= min && value <= max;
},
coerce: (_, value) => isString(value) ? parseBigInt(value) : BigInt(value),
randomize: (spec, rnd) => {
const { min, max } = spec;
return min + randomBigInt(max - min, rnd);
}
};
// src/params/binary.ts
var binary2 = {
validate: (spec, value) => {
const { minLength, maxLength } = spec;
return value instanceof Uint8Array && value.length >= minLength && value.length <= maxLength;
}
};
// src/params/choice.ts
var choice2 = {
validate: (spec, value) => !!spec.options.find(
(x) => (isString(x) ? x : x[0]) === value
),
randomize: (spec, rnd) => {
const opts = spec.options;
const value = opts[rnd() * opts.length | 0];
return isString(value) ? value : value[0];
}
};
// src/params/color.ts
var color2 = {
validate: (_, value) => isString(value) && /^#?[0-9a-f]{6,8}$/i.test(value),
coerce: (_, value) => (value[0] !== "#" ? "#" + value : value).substring(0, 7),
randomize: (_, rnd) => "#" + u24(rnd() * 16777216 | 0)
};
// src/params/image.ts
var image2 = {
validate: (spec, value) => {
const { width, height, format } = spec;
return isTypedArray(value) && value.length == width * height && (format === "gray" ? value instanceof Uint8Array || value instanceof Uint8ClampedArray : value instanceof Uint32Array);
}
};
// src/params/numlist.ts
var numlist2 = {
validate: (spec, value) => {
const { minLength, maxLength } = spec;
return isNumericArray(value) && isInRange(value.length, minLength, maxLength);
}
};
// src/params/ramp.ts
var ramp2 = {
validate: () => false,
read: (spec, t) => {
const { stops, mode } = spec;
let n = stops.length;
let i = n;
for (; (i -= 2) >= 0; ) {
if (t >= stops[i]) break;
}
n -= 2;
const at = stops[i];
const av = stops[i + 1];
const bt = stops[i + 2];
const bv = stops[i + 3];
return i < 0 ? stops[1] : i >= n ? stops[n + 1] : {
exp: () => mix(av, bv, easeInOut5(norm(t, at, bt))),
linear: () => fit(t, at, bt, av, bv),
smooth: () => mix(av, bv, smoothstep01(norm(t, at, bt)))
}[mode || "linear"]();
},
params: {
stops: numlist({
name: "Ramp stops",
desc: "Control points",
minLength: 4,
maxLength: Infinity,
default: []
}),
mode: choice({
name: "Ramp mode",
desc: "Interpolation method",
options: ["linear", "smooth", "exp"]
})
}
};
// src/params/range.ts
var range2 = {
validate: (spec, value) => {
const { min, max } = spec;
return isNumber(value) && isInRange(value, min, max);
},
coerce: (spec, value) => {
const $spec = spec;
return clamp(
round(value ?? $spec.default, $spec.step || 1),
$spec.min,
$spec.max
);
},
randomize: (spec, rnd) => {
const { min, max, step } = spec;
return clamp(round(mix(min, max, rnd()), step || 1), min, max);
}
};
// src/params/strlist.ts
var strlist2 = {
validate: (spec, value) => {
const { minLength, maxLength, match } = spec;
if (!(isStringArray(value) && isInRange(value.length, minLength, maxLength)))
return false;
if (match) {
const regExp = isString(match) ? new RegExp(match) : match;
return value.every((x) => regExp.test(x));
}
return true;
}
};
// src/params/text.ts
var text2 = {
validate: (spec, value) => {
if (!isString(value)) return false;
const { minLength, maxLength, match } = spec;
if (match) {
const regexp = isString(match) ? new RegExp(match) : match;
if (!regexp.test(value)) return false;
}
return isInRange(value.length, minLength, maxLength);
}
};
// src/params/toggle.ts
var toggle2 = {
validate: (_, value) => isString(value) ? /^(true|false|0|1)$/.test(value) : value === 1 || value === 0 || typeof value === "boolean",
coerce: (_, value) => value === "true" || value === "1" ? true : value === "false" || value === "0" ? false : !!value,
randomize: (_, rnd) => rnd() < 0.5
};
// src/params/weighted.ts
var weighted2 = {
validate: (spec, value) => !!spec.options.find((x) => x[1] === value),
randomize: (spec, rnd) => {
let {
options,
total,
default: fallback
} = spec;
const r = rnd() * total;
for (let i = 0, n = options.length; i < n; i++) {
total -= options[i][0];
if (total <= r) return options[i][1];
}
return fallback;
}
};
// src/params/xy.ts
var xy2 = {
validate: (_, value) => isNumericArray(value) && value.length == 2,
coerce: (_, value) => [clamp01(value[0]), clamp01(value[1])],
randomize: (_, rnd) => [rnd(), rnd()]
};
// src/time/offline.ts
var timeProviderOffline = (frameDelay = 250, referenceFPS = 60, frameOffset = 0) => {
let frame = frameOffset;
const frameTime = 1e3 / referenceFPS;
let isActive = false;
return {
start() {
frame = frameOffset - 1;
isActive = true;
},
stop() {
isActive = false;
},
next(fn) {
setTimeout(() => {
frame++;
isActive && fn(frame * frameTime, frame);
}, frameDelay);
},
now: () => [frame * frameTime, frame]
};
};
// src/time/raf.ts
var timeProviderRAF = (timeOffset = 0, frameOffset = 0) => {
let t0 = performance.now();
let frame = frameOffset;
let now = timeOffset;
let isStart = true;
let isActive = false;
return {
start() {
isActive = isStart = true;
},
stop() {
isActive = false;
},
next(fn) {
requestAnimationFrame((t) => {
if (!isActive) return;
if (isStart) {
t0 = t;
frame = frameOffset;
isStart = false;
} else {
frame++;
}
now = timeOffset + t - t0;
fn(now, frame);
});
},
now: () => [now, frame]
};
};
// src/genart.ts
var { ensure: ensure2, isFunction: isFunction2 } = utils_exports;
var hasWindow = typeof window !== "undefined";
var API = class {
_opts = {
// auto-generated instance ID
id: Math.floor(Math.random() * 1e12).toString(36),
allowExternalConfig: false,
notifyFrameUpdate: false
};
_adapter;
_time = timeProviderRAF();
_prng;
_update;
_state = "init";
_traits;
_params;
_paramTypes = {
bigint: bigint2,
binary: binary2,
choice: choice2,
color: color2,
date,
datetime,
image: image2,
numlist: numlist2,
ramp: ramp2,
range: range2,
strlist: strlist2,
text: text2,
time,
toggle: toggle2,
vector,
weighted: weighted2,
xy: xy2
};
math = math_exports;
params = factories_exports;
prng = prng_exports;
utils = utils_exports;
time = {
offline: timeProviderOffline,
raf: timeProviderRAF
};
constructor() {
hasWindow && window.addEventListener("message", (e) => {
const data = e.data;
if (!this.isRecipient(e) || data?.__self) return;
switch (data.type) {
case "genart:get-info":
this.notifyInfo();
break;
case "genart:randomize-param":
this.randomizeParamValue(data.paramID, data.key);
break;
case "genart:resume":
this.start(true);
break;
case "genart:configure": {
if (!this._opts.allowExternalConfig) return;
const opts = data.opts;
delete opts.id;
delete opts.allowExternalConfig;
this.configure(opts);
break;
}
case "genart:set-param-value":
this.setParamValue(data.paramID, data.value, data.key);
break;
case "genart:start":
this.start();
break;
case "genart:stop":
this.stop();
break;
}
});
}
get version() {
return "0.33.0";
}
get id() {
return this._opts.id;
}
get mode() {
return this._adapter?.mode || "play";
}
get collector() {
return this._adapter?.collector;
}
get iteration() {
return this._adapter?.iteration;
}
get screen() {
return this._adapter?.screen || (hasWindow ? {
width: window.innerWidth,
height: window.innerHeight,
dpr: window.devicePixelRatio || 1
} : { width: 640, height: 640, dpr: 1 });
}
get random() {
if (this._prng) return this._prng;
return this._prng = ensureAdapter(this._adapter).prng;
}
get seed() {
return ensureAdapter(this._adapter).seed;
}
get state() {
return this._state;
}
get paramSpecs() {
return this._params;
}
get adapter() {
return this._adapter;
}
get timeProvider() {
return this._time;
}
registerParamType(type, impl) {
ensureValidType(type);
if (this._paramTypes[type]) {
console.warn("overriding impl for param type:", type);
}
this._paramTypes[type] = impl;
}
paramType(type) {
ensureValidType(type);
return this._paramTypes[type];
}
async setParams(params) {
try {
if (this._adapter?.augmentParams) {
params = this._adapter.augmentParams(params);
}
this._params = {};
for (let id in params) {
ensureValidID(id);
const param = {
...params.PARAM_DEFAULTS,
...params[id]
};
const impl = this.ensureParamImpl(param.type);
if (param.default == null) {
if (impl.randomize) {
param.default = impl.randomize(param, this.random.rnd);
param.state = "random";
} else if (impl.read) {
param.state = "dynamic";
} else {
throw new Error(
`missing default value for param: ${id}`
);
}
} else {
if (!(impl.read || impl.validate(param, param.default))) {
throw new Error(
`invalid default value for param: ${id} (${param.default})`
);
}
param.state = "default";
}
this._params[id] = param;
}
if (this._adapter) {
if (this._adapter.initParams) {
await this._adapter.initParams(this._params);
}
await this.updateParams();
}
this.notifySetParams();
return this.getParamValue.bind(this);
} catch (e) {
this.setState("error", e.message);
throw e;
}
}
setTraits(traits) {
this._traits = traits;
this.emit({ type: "genart:traits", traits });
}
setAdapter(adapter) {
this._adapter = adapter;
this.notifyReady();
}
waitForAdapter() {
return this.waitFor("_adapter");
}
setTimeProvider(time3) {
this._time.stop();
this._time = time3;
if (["play", "stop"].includes(this._state)) {
time3.start();
} else {
this.notifyReady();
}
}
waitForTimeProvider() {
return this.waitFor("_time");
}
setUpdate(fn) {
this._update = fn;
this.notifyReady();
}
async updateParams(notify = "none") {
if (!this._adapter) return;
for (let id in this._params) {
const spec = this._params[id];
const result = await this._adapter.updateParam(id, spec);
if (!result) continue;
const { value, update } = result;
if (update) {
for (let key in update) {
this.setParamValue(id, update[key], key, "none");
}
}
this.setParamValue(
id,
value,
void 0,
value != null || update ? notify : "none"
);
}
}
setParamValue(id, value, key, notify = "all") {
let { spec, impl } = this.ensureParam(id);
if (value != null) {
let updateSpec = spec;
if (key) {
const { spec: nested, impl: nestedImpl } = this.ensureNestedParam(spec, key);
updateSpec = nested;
impl = nestedImpl;
}
if (!impl.validate(updateSpec, value)) {
this.paramError(id);
return;
}
spec[key || "value"] = impl.coerce ? impl.coerce(updateSpec, value) : value;
if (!key) spec.state = "custom";
}
this.emit(
{
type: "genart:param-change",
__self: true,
param: this.asNestedParam(spec),
paramID: id,
key
},
notify
);
}
randomizeParamValue(id, key, rnd = Math.random, notify = "all") {
const {
spec,
impl: { randomize }
} = this.ensureParam(id);
const canRandomizeValue = randomize && spec.randomize !== false;
if (key) {
const { spec: nested, impl } = this.ensureNestedParam(spec, key);
const canRandomizeKey = impl.randomize && nested.randomize !== false;
if (canRandomizeKey) {
this.setParamValue(
id,
impl.randomize(nested, rnd),
key,
canRandomizeKey || !canRandomizeValue ? notify : "none"
);
}
}
if (canRandomizeValue) {
this.setParamValue(id, randomize(spec, rnd), void 0, notify);
}
}
getParamValue(id, opt) {
return this.paramValueGetter(id)(opt);
}
paramValueGetter(id) {
const {
spec,
impl: { randomize, read }
} = this.ensureParam(id);
return (t = 0) => {
if (isFunction2(t)) {
if (randomize) return randomize(spec, t);
t = 0;
}
return read ? read(spec, t) : spec.value ?? spec.default;
};
}
paramError(paramID) {
this.emit({ type: "genart:param-error", paramID });
}
configure(opts) {
Object.assign(this._opts, opts);
this.notifyInfo();
}
on(type, listener) {
ensure2(hasWindow, "current env has no messaging support");
window.addEventListener("message", (e) => {
if (this.isRecipient(e) && e.data?.type === type) listener(e.data);
});
}
emit(e, notify = "all") {
if (!hasWindow || notify === "none") return;
e.apiID = this.id;
const isAll = notify === "all";
if (isAll || notify === "self") window.postMessage(e, "*");
if (isAll && parent !== window || notify === "parent")
parent.postMessage(e, "*");
}
start(resume = false) {
const state = this._state;
if (state == "play") return;
if (state !== "ready" && state !== "stop") {
throw new Error(`can't start in state: ${state}`);
}
this.setState("play");
const msg = {
type: "genart:frame",
__self: true,
apiID: this.id,
time: 0,
frame: 0
};
const update = (time3, frame) => {
if (this._state != "play") return;
if (this._update.call(null, time3, frame)) {
this._time.next(update);
} else {
this.stop();
}
if (this._opts.notifyFrameUpdate) {
msg.time = time3;
msg.frame = frame;
this.emit(msg);
}
};
if (!resume) this._time.start();
this._time.next(update);
this.emit({
type: `genart:${resume ? "resume" : "start"}`,
__self: true
});
}
stop() {
if (this._state !== "play") return;
this.setState("stop");
this.emit({ type: `genart:stop`, __self: true });
}
capture(el) {
this._adapter?.capture(el);
this.emit(
{ type: `genart:capture`, __self: true },
"parent"
);
}
setState(state, info) {
this._state = state;
this.emit({
type: "genart:state-change",
__self: true,
state,
info
});
}
ensureParam(id) {
ensureValidID(id);
const spec = ensure2(
ensure2(this._params, "no params defined")[id],
`unknown param: ${id}`
);
return { spec, impl: this.ensureParamImpl(spec.type) };
}
ensureParamImpl(type) {
ensureValidType(type);
return ensure2(this._paramTypes[type], `unknown param type: ${type}`);
}
ensureNestedParam(param, key) {
const spec = ensure2(
this.ensureParamImpl(param.type).params?.[key],
`param type '${param.type}' has no nested: ${key}`
);
return { spec, impl: this.ensureParamImpl(spec.type) };
}
async waitFor(type) {
while (!this[type]) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
return this[type];
}
/**
* Emits {@link ParamsMessage} message (only iff the params specs aren't
* empty).
*/
notifySetParams() {
if (this._params && Object.keys(this._params).length) {
this.emit({
type: "genart:params",
__self: true,
params: this.asNestedParams({}, this._params)
});
}
}
notifyReady() {
if (this._state === "init" && this._adapter && this._time && this._update)
this.setState("ready");
}
notifyInfo() {
const [time3, frame] = this._time.now();
const { id, collector, iteration } = this._adapter ?? {};
this.emit({
type: "genart:info",
opts: this._opts,
state: this._state,
version: this.version,
seed: this.seed,
adapter: id,
collector,
iteration,
time: time3,
frame
});
}
/**
* Returns true if this API instance is the likely recipient for a received
* IPC message.
*
* @param event
*/
isRecipient({ data }) {
return data != null && typeof data === "object" && (data.apiID === this.id || data.apiID === "*");
}
asNestedParams(dest, src) {
for (let id in src) {
dest[id] = this.asNestedParam(src[id]);
}
return dest;
}
asNestedParam(param) {
const dest = { ...param };
const impl = this._paramTypes[param.type];
if (impl.params) {
dest.__params = this.asNestedParams({}, impl.params);
}
return dest;
}
};
var ensureValidID = (id, kind = "ID") => ensure2(
!(id === "__proto__" || id === "prototype" || id === "constructor"),
`illegal param ${kind}: ${id}`
);
var ensureValidType = (type) => ensureValidID(type, "type");
var ensureAdapter = (adapter) => ensure2(adapter, "missing platform adapter");
globalThis.$genart = new API();
})();