Metabolism: song as substrate, ecology grown from the track

A new top-level section building the metabolism concept: the track
supplies the food field, event impulses and per-section laws; the seed
picks the species (a rule set, not a parameter vector). Same song + seed
=> bit-identical frames; different songs => different worlds.

- analysis: single FFT pass -> per-frame feature table; score (onsets
  folded into world geometry via voice ring + pitch-class sector), wells
  (food placed by the harmony), graph (band correlations -> interaction
  matrix), laws (per-section regimes)
- sim: typed-array world, 3 castes (builder/orbit, forager/commute,
  spark/orbit-wide) with causality rings, persistence trails,
  consequence field; fully deterministic
- lens: software framebuffer renderer (PNG in node, ImageData in
  browser) + event-reactive camera (cuts on activity change, alternating
  shot sizes, drift-and-push)
- metrics: coupling x residual fitness (not per-frame descriptors) plus
  a grown-structure fingerprint; report + strip tools; 15 tests green
- DESIGN.md captures the concept, invariants and build order

3 reference test signals (drone/runner/dusk) grow measurably distinct
worlds while seeds of one song stay in the same biome.
This commit is contained in:
Dejvino
2026-10-24 08:23:33 +02:00
parent 6a2af2e45f
commit 13b0939221
26 changed files with 4469 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
// Node-only: WAV -> analysis -> song. The browser does the same with a
// decoded AudioBuffer; this keeps the tools honest about the same pipeline.
import { readFileSync } from 'node:fs';
import { decodeWav, mono } from '../src/audio/wav.js';
import { analyse } from '../src/audio/analyse.js';
import { buildSong } from '../src/song/song.js';
export function loadSong(path, { fps = 30, seed = 12345 } = {}) {
const wav = decodeWav(readFileSync(path));
if (!wav) throw new Error(`not a WAV: ${path}`);
const pcm = mono(wav.channels);
const analysis = analyse(pcm, wav.sampleRate, { fps });
const song = buildSong(analysis, seed);
return { pcm, sampleRate: wav.sampleRate, analysis, song, name: path.split('/').pop().replace(/\.wav$/i, '') };
}