Strata Blueprint
Varve · blueprint v2.0 · 12 sep 2026

Strata

Een procedureel muzieksysteem dat elke start ergens anders begint, nooit terugkeert naar dezelfde staat, en volledig bedienbaar is met vier vlakken op een iPhone-scherm. Elke generatie zet een laag af; de laag is leesbaar, reproduceerbaar en herspeelbaar.

kernbestanden
40
fases klaar
F1 – F8
bedieningsvlakken
4
log / uur
~4 kB

1 · Wat het is, in één alinea

Een seed van 32 bits bepaalt een genoom: toonsoort, modus, tempo, luslengtes, ritmecel, CA-regel, FM-timbres, en welke parameters op welk vlak liggen. Vanuit dat genoom draait een klok die generatoren aanstuurt; elke acht maten muteert precies één gen. De gebruiker heeft vier vlakken die per generatie een andere parameter kunnen sturen — behalve de vlakken die hij heeft vastgezet. Alles is deterministisch: seed plus actielog reconstrueert de sessie noot voor noot.

De naam

Een varve is een jaarlaagje sediment: één paar lagen per jaar, afleesbaar als archief. Strata is het meervoud van de lagen die het systeem afzet. Het project heet zoals het werkt.

Samengevoegd

Het AV-synth-traject liep parallel met hetzelfde doel. Besluit sept 2026: één systeem. Strata levert de motor en de modulestructuur, de AV-synth levert de beeldlagen, de koppelingsmatrix en de Bank. Beeld wordt laag L8.

De harde eis

Determinisme is geen luxe maar de architectuurbeslissing waar alles aan hangt: archief, herspeelbaarheid, deelbaarheid en debugbaarheid komen er gratis uit.


2 · Architectuur als kolom

Van bedrock naar oppervlak. Elke laag kent alleen de laag eronder. Geen laag mag omhoog reiken — dat is de regel die het systeem testbaar houdt.

L0
Seed & entropie
Eén 32-bits getal. Afgeleide streams per subsysteem, zodat een wijziging in ritme de melodie niet verschuift.
core/rng.js · mulberry32(seed) · sub(seed,'melody') = mulberry32(seed ^ fnv1a(salt))
L1
Genoom
Alle vaste eigenschappen van deze run als plat object. Muteerbaar door evolutie, serialiseerbaar, diffbaar.
core/genome.js · {root, mode, bpm, euclid{k,n,rot}, ca{rule,width}, loops[], fm[], bindings[]}
L2
Generatoren
Pure functies: staat in, gebeurtenissen uit. Geen audio, geen DOM, geen tijd. Volledig te unit-testen.
gen/euclid · gen/markov · gen/ca · gen/phase · gen/walk → Event{t, voice, pitch, dur, vel, pan}
L3
Harmonie
Filtert en herstemt de ruwe graden van de generatoren naar wat op dit moment mag klinken.
harmony/scale · harmony/tonnetz (P/L/R) · harmony/krumhansl (spanningsgewicht)
L4
Klok
Web Worker tikt elke 25 ms, hoofdthread plant 120 ms vooruit op de AudioContext-tijdlijn. Overleeft tab-throttling.
core/clock.js · lookahead 0.12 s · onTick(from,to) → generatoren → voices
L5
Stemmen & ruimte
Vaste audiograaf, per-noot wegwerpstemmen. Één reverb, één delay, één bus-compressor. Nooit meer dan 24 stemmen.
synth/fm · synth/ks · synth/grain (worklet) · synth/space · voice pool met steal-by-oldest
L6
Modulatie & evolutie
Twee kloksnelheden: continue Perlin-drift op parameters, en discrete mutatie van één gen per acht maten.
mod/perlin.js · mod/evolve.js → mutation{gen, trait, from, to} → strata-log
L7
Bediening
Gebaren worden intenties; intenties worden handles; handles schrijven naar het genoom. Het scherm kent geen parameters.
ctl/gestures → Intent · ctl/handles → Handle · ctl/slots → Slot[4] · ui/app · ui/viz
L8
Beeld uit av-synth
Negen stapelbare lagen; elke laageigenschap bindbaar aan elke van 21 bronnen via 8 krommen, met eigen sterkte. Leest alles eronder, schrijft nergens naartoe — behalve de twee terugkoppelingen naar het geluid.
ui/layers.js · ui/viz.js (compositor) · mod/matrix.js · mod/field.js (vaste 1/60 s op de audioklok) · synth/analysis.js

3 · Het besturingsmodel

Dit is de kern van het ontwerp en het punt waarop Strata verschilt van elke andere generatieve app: het vlak is het instrument, niet de parameter. Er zijn altijd vier slots. Een slot houdt een handle vast. Bij elke generatie mag het systeem een slot herbinden aan een andere handle uit de pool. Wat de gebruiker vasthoudt, blijft.

De drie objecten

Handle = {
  id, label, sub,          // 'dichtheid', 'noten / stap'
  get(g), set(g, v),       // leest/schrijft genoom, altijd 0..1
  curve: 'lin'|'exp'|'step', steps?: n,
  affinity: ['continu','bipolair'],   // welke gebaren passen
  weight: 1.0              // fitness, groeit door vasthouden
}

Slot = { index: 0..3, handle, value, latched, range, touchedAt }

Intent = { slot, kind, amount?, axis? }
  kind ∈ tap | doubletap | hold | drag | flick | pinch | tilt | shake

Gebaar → intentie → effect

gebaarintentieeffect op sloteffect op systeem
tiktap+1 stap (curve-afhankelijk)—
dubbeltikdoubletapterug naar systeemwaarde—
lang drukken 500 msholdlatch aan/uitfitness +1 op deze handle; slot wordt niet herbonden
sleep ↕drag yabsolute waarde, 1 vlakhoogte = volle range × bereik—
sleep ↔drag xsecundaire as van de handle—
flick > 1,2 px/msflicksprong naar willekeurige waardefitness −1 op deze handle
knijpenpinchbereik ×0,25 – ×2,0 (fijnregeling)—
kantelentiltadditieve modulatie, niet-destructiefalleen op niet-gelatchte slots
schuddenshake—forceer generatie nu
twee vingers houden + sleepdrag fijn×0,1 gevoeligheid—

Herbinden bij generatie

rebind(slots, pool, rng):
  kandidaten = slots.filter(s => !s.latched
                             && now - s.touchedAt > 45_000)   // niet net aangeraakt
  if kandidaten.length === 0 or rng() > 0.35: return       // 35% kans per generatie
  slot = pick(kandidaten)
  vrij = pool.filter(h => !slots.some(s => s.handle.id === h.id))
  nieuw = weightedPick(vrij, h => h.weight)               // fitness stuurt de keuze
  // crossfade: oude handle houdt 2 maten zijn waarde vast,
  // nieuwe handle start op zijn huidige genoomwaarde => geen sprong
  slot.handle = nieuw; slot.value = nieuw.get(genome)
  log('rebind', slot.index, oud.id, nieuw.id)
Risico. Herbinden is desoriënterend als het onaangekondigd gebeurt. Oplossing: het vlak toont 2 maten lang de nieuwe naam in accentkleur en pulseert één keer op de maatstreep. Bij méér dan één herbinding per generatie voelt het willekeurig — vandaar de harde limiet van één.

Handle-pool bij oplevering F2

handleschrijft naarcurvesecundaire as ↔
dichtheidkans per stap in markov + CA-drempellineuclid-rotatie
helderheidfilter-cutoff + FM-indexexpreverb-send
spanningKrumhansl-gewicht (chromatiek)linTonnetz-afstand tot huis
tijdtempo ×0,75–1,25lineuclid n ∈ {8,12,16}
pulseuclid kstep 1..n−1swing 50–66%
textuurCA-regel-indexstep 6hi-hat toonhoogte
dieptereverb-tijd + delay-feedbackexpstereo-breedte
driftPerlin-stapgrootte (hoe wild het systeem zelf beweegt)expgeneratie-interval 4/8/16 maten
registeroctaafspreiding melodiestep 4bas aan/uit
korrel F4granulaire positie in archiefsamplelinkorrelgrootte

Tien handles, vier slots: op elk moment is 60% van het systeem autonoom. Dat is het punt — je stuurt bij, je bestuurt niet.


4 · Evolutie

Twee snelheden. De trage snelheid is hoorbaar als beweging, de snelle als leven.

Drift · continu

Perlin-ruis (1D, 3 octaven) op elke niet-gelatchte parameter. Periode 40–90 s per parameter, uit de seed. Amplitude = de drift-handle. Gecorreleerd, dus het ademt in plaats van te trillen.

Mutatie · discreet

Elke 8 maten precies één gen. Gewogen keuze; de gewichten verschuiven met fitness. Elke mutatie is een record in het strata-log.

Mutatietabel

mutatiekanshoorbaarheidbeperking
euclid-rotatie ±119%laag — het ritme verschuift—
euclid k ±114%midden1 ≤ k ≤ n−1
markov-rij hertrekken14%laag — melodie krijgt andere neiging1 rij per keer
grondtoon over de kwintencirkel F3 → Tonnetz14%hoog — de muziek wendtver van huis alleen nog kwintstappen, geen tertssprong
CA-regel wisselen9%midden — textuur verandertuit {30,90,110,150,182,54}
fase-lus herzaaien9%laaglengtes blijven copriem
modus wisselen7%hoogalleen op maat 8 van een 32-maats blok
FM-ratio herzaaien6%hoog — timbre verandertuit harmonische set, nooit willekeurig
tempo ±4,5%7%laag — het zakt of trekt aan40–120 bpm; het anker trekt terug
Risico. Zonder rem stapelen kleine mutaties tot een systeem dat na 40 minuten niets meer met zijn beginpunt te maken heeft — dat klinkt als grijze soep, niet als evolutie. Rem: een thuis-anker. Tempo veert 6% per generatie terug naar de seedwaarde. De grondtoon wordt pas gecorrigeerd voorbij 2 kwinten van huis, met een kans die met de afstand meeschaalt (0,18 × afstand, tot 92%) — dichtbij mag hij vrij dwalen, ver weg wordt hij hard teruggehaald. Modus en timbre krijgen geen anker: die mogen wél weglopen.

Fitness

hold  op slot s  →  s.handle.weight *= 1.35   // dit wil ik houden
flick op slot s  →  s.handle.weight *= 0.75   // weg ermee
elke generatie   →  weight = 1 + (weight-1)*0.98   // vergeet langzaam

weight bepaalt: (a) kans dat de handle wordt gekozen bij rebind,
                (b) kans dat het gen eronder muteert (hoog gewicht = met rust laten)

Geen expliciete "vind je dit mooi"-knop. De bediening zelf ís het fitnesssignaal — vasthouden is goedkeuren, weggooien is afkeuren.


5 · Het strata-log

Elke generatie schrijft één regel. Dat log is tegelijk archief, debugger en reproductie-instructie. Bij 74 bpm duurt een generatie 26 seconden, dus een uur levert ongeveer 140 regels op: circa 4 kB, plus de gebruikersacties.

{ s: "e5212af7",        // seed, hex
  t0: 1757462400,        // starttijd unix
  g: [                   // per generatie: [maat, mutatiecode, waarde]
    [8,  'er', +1], [16, 'mk', 3], [24, 'tn', 'L'], [32, 'ek', -1], ...
  ],
  u: [                   // gebruikersacties: [maat.tick, slot, intent, waarde]
    [12.3, 0, 'd', 0.62], [19.0, 2, 'h', 1], [27.5, 1, 'f', 0.11], ...
  ] }

Reproductie

Zelfde seed + zelfde log = identieke audio, sample voor sample. Voorwaarde: elke Math.random() is verboden in de hele codebase. Eén ESLint-regel bewaakt dat.

Renderen

OfflineAudioContext speelt het log op 40× snelheid af en levert een WAV. Een uur ambient rendert in ~90 s op de iPhone zelf — geen server nodig.

Archief

Logs gaan naar de artifact-database. Een sessie die goed was, is een permalink: ?s=e5212af7&l=…. Dat is de brug naar de albumradio en de lange mixen.


6 · Audiograaf

                                    ┌─ reverb (procedurele IR, 2.2 s) ─┐
voice ──▶ filter ──▶ gain ──▶ pan ──┼─ delay (3/16, fb 0.35) ─────────┼──▶ bus ──▶ comp ──▶ out
  (wegwerp, max 24)                 └─ dry ───────────────────────────┘        │
                                                                        analyser ──▶ viz
stembouwrolbudget
bastriangle + lowpass, 1 osceuclid-puls, fundament1 tegelijk
leadFM 2-operator, ratio uit genoommarkov-melodie4 tegelijk
pad3× saw, licht ontstemd, traag envfase-lussen (7/11/13)3 tegelijk
textuurruisburst + highpassCA-accenten8 tegelijk
snaar F4Karplus-Strong via delay+lowpass-fbgepluk, tegenmelodie4 tegelijk
korrel F4AudioWorklet, 40 korrels maxarchieftextuur1 worklet
Risico. Wegwerpstemmen (nieuwe OscillatorNode per noot) is correct Web Audio maar de GC-druk loopt op bij hoge dichtheid. Mitigatie: harde cap op 24 gelijktijdige stemmen met steal-by-oldest, en een stemtelling in de viz zodat het meetbaar is voordat het hoorbaar wordt.

7 · Bestandsstructuur

strata/
├─ index.html            // shell, manifest-link, één script-entry
├─ sw.js                 // service worker: offline cache
├─ manifest.webmanifest
├─ core/
│  ├─ rng.js             mulberry32, sub(seed,salt), fnv1a
│  ├─ genome.js          fromSeed(s), mutate(g,rng), serialize/parse
│  ├─ clock.js           Worker-tik, lookahead, onTick(from,to)
│  └─ worker-clock.js
├─ gen/
│  ├─ euclid.js          bjorklund(k,n), rotate
│  ├─ markov.js          matrix(n,rng), next(m,i,rng), reroll(m,i)
│  ├─ ca.js              step(cells,rule)
│  ├─ phase.js           coprime loops, advance
│  └─ walk.js            perlin1d(seed), octaves
├─ harmony/
│  ├─ scale.js           MODES, degToMidi(g,deg,oct)
│  ├─ tonnetz.js         P, L, R, distanceFromHome
│  └─ krumhansl.js       PROFILES, weight(pc,key,tension)
├─ synth/
│  ├─ graph.js           buildGraph(ac) → {bus,verb,delay,analyser}
│  ├─ voices.js          pool, alloc, steal
│  ├─ fm.js  ks.js  noise.js  grain.js
│  └─ space.js           procedurele IR
├─ mod/
│  ├─ drift.js           apply(genome, t)
│  └─ evolve.js          tick(genome, rng, fitness) → mutation
├─ ctl/
│  ├─ gestures.js        pointer → Intent (geen kennis van muziek)
│  ├─ handles.js         POOL: Handle[]
│  ├─ slots.js           Slot[4], apply(intent), rebind()
│  └─ fitness.js
├─ io/
│  ├─ persist.js         localStorage: seed, gen, slots, log
│  ├─ strata.js          log schrijven/lezen, permalink
│  ├─ render.js          OfflineAudioContext → WAV
│  └─ bridge.js          WebSocket → NEXUS (OSC-vorm)
└─ ui/
   ├─ app.js  pads.js  viz.js  hud.js

Geen framework, geen bundler in F1–F3. ES-modules native; Safari doet dat prima. Een bundler komt pas als het worklet-bestand erbij komt (F4).


8 · iOS-schil

onderwerpaanpakfase
Audio ontgrendelenStartscherm met één knop: resume() + stille buffer. Geen automatische start, ooit.F1
Belschakelaar demptStille loopende <audio> bij unlock — dwingt de "media"-audiosessie af in plaats van "ambient".F6
Achtergrond doorspelenDatzelfde element + MediaSession-metadata. Vergrendeld scherm toont titel = huidige toonsoort en generatie.F6
Timer-throttlingWorker-klok (F1 al). Bij visibilitychange → resume() en klok herijken.F1
Scherm aanWake Lock; opnieuw aanvragen na visibilitychange (iOS geeft hem vrij).F6
Zoom / scroll / rand-swipetouch-action:none op vlakken, overscroll-behavior:none, vlakken ≥ 20 px van de schermrand, in standalone helemaal geen randgebaar.F1
SensorenDeviceMotionEvent.requestPermission() in de tik van de startknop meenemen — één toestemmingsmoment, niet twee.F6
Geen haptiek, geen MIDIAccepteren. Feedback visueel + auditief. Externe hardware loopt via de WebSocket-brug.—
GeheugenIR procedureel genereren, archiefsamples ≤ 10 s mono 32 kHz, lazy laden.F4

9 · Fasering

F0

Harnas klaar

Perpetuum Harnas: device-probe, kennisbank, gebarenlog, werkend proefsysteem. Blijft staan als meetinstrument.

F1

Kernel klaar

rng · genome · clock · euclid · markov · scale · graph · fm · noise · gestures · slots · pads · viz.

Klaar als: app start op de iPhone, speelt oneindig door, vier vlakken sturen vier vaste parameters, dezelfde seed geeft twee keer exact dezelfde eerste 60 seconden.

F2

Evolutie & herbinding klaar

evolve · drift · fitness · rebind · strata-log · thuis-anker.

Gemeten over 200 generaties (≈ 85 minuten muziek):

metinguitkomstoordeel
Zelfde seed, zelfde mutatiereeksidentiekgoed
Gemiddelde afstand tot de thuistoonsoort1,24 kwintgoed
Tijd ver van huis (> 3 kwinten)1,5% · max 2 generatiesuitstapje, geen wegdwalen
Tempo-uitslag rond de seedwaarde9,4 bpmleeft en keert terug
Herbindingen37% van de generatiesop het ontwerp
Vastgezet vlak ooit herbondennooitgoed
F2.5

Beeld klaar

Negen lagen — nasleep, achtergrond, strata, contour, deeltjesveld, spectrum, golfvorm, gebeurtenissen, patch-graaf — met 21 bronnen en 8 krommen. Elke laageigenschap hangt via bron → kromme → sterkte aan wat er ook maar meet.

Beeld praat terug naar geluid. Twee wegen: de spreiding van het deeltjesveld stuurt de reverb-send, de rimpelenergie stuurt de delay-terugkoppeling en de demping. Het veld draait daarom op de audioklok met een vaste stap van 1/60 s en niet op requestAnimationFrame — anders zou beeldsnelheid de muziek veranderen en was de sessie niet meer reproduceerbaar.

metinguitkomstoordeel
Deeltjesveld reproduceerbaar bij zelfde seedidentiekdeterminisme blijft heel
Beeldsnelheid met 7 lagen aan60 fpsgoed
Reverb-send door het veld, over 15 minuten0,31 – 0,41ademt, schokt niet
Sediment zet een band af per generatiebevestigdgoed

Bank gemigreerd: 75 entries in 8 hoofdstukken (één bron, krommen, bestemmingen, beeldlagen, gebaren, combinaties, lussen, stukken), elk met hint, "probeer", instellen, beoordeling en notitie. Beoordelingen gaan naar de database.

F3

Harmonie klaar

tonnetz (P/L/R, BFS-afstand, stap begrensd op 3 van huis) · krumhansl (K-K-profielen; spanning keert de weging om) · stemvoering: geen twee kwinten achter elkaar tussen bas en pad.

Gemeten: 300 generaties → 30 akkoordstappen over 12 verschillende drieklanken, nooit verder dan 3 stappen van huis. Elke fase-lus draagt nu één akkoordtoon; bij spanning > 0,6 wordt de terts een septiem.

F4

Timbre & archief klaar

ks (Karplus-Strong met native delay-feedback, seed per aanslag) · grain (AudioWorklet als Blob in de bundel, 40 korrels, eigen LCG) · sample-loader (mono, ≤ 10 s) · korrel-handle (↕ mix, ↔ positie) · ingebouwd testsample zodat de laag zonder archief al klinkt.

Gemeten: worklet laadt over http(s); onder file:// niet — op de iPhone dus wél. Render naar wav slaat de korrellaag over.

F5

Schil klaar

manifest + service worker + icoon (alleen in de echte deployment; de artifact is één bestand) · wake lock, opnieuw na visibilitychange · MediaSession met toonsoort en generatie op het vergrendelscherm · stille <audio>-loop bij ontgrendelen zodat de belschakelaar en de achtergrond geen audio meer stoppen · sensoren in dezelfde tik als de audio.

F6

Render & archief klaar

replay (acties op stapindex, niet op wandkloktijd — daarvoor zijn de afkoeltijden van slots en drift omgezet naar stappen) · permalink ?s=seed&l=log · OfflineAudioContext → wav via downloads-capability of anchor · sessies en Bank-beoordelingen naar de artifact-database · galerij van de laatste 12.

Gemeten: replay van 5 acties over 40 maten → byte-identieke motorstaat; 6 s render in 0,56 s (≈ 11× realtime headless), bestandsgrootte exact zoals berekend.

F7

Brug klaar

WebSocket-client met OSC-vormige JSON: per maat één bundel (/strata/bar, /gen, /key, /chord, /bpm, /p, /voices, /field, /euclid) en per gebaar /strata/action. Herverbinden met backoff. Adres in de app instelbaar.

Niet getest tegen NEXUS zelf — alleen het berichtformaat. Dat is de enige stap in deze reeks die op jouw laptop moet.

F8

Genetisch geheugen klaar

Gewichten overleven de sessie (localStorage) en kleuren elk nieuw genoom: een handle die je structureel vasthield start tot +0,3 hoger, weggeflickt tot −0,3 lager. Nooit een noot opgeslagen.

Gemeten: gewicht 1,6 op spanning → start 0,35 → 0,65; gewicht 0,6 op helderheid → 0,50 → 0,26.


10 · Waar dit misgaat

  1. Grijze soep. Eindeloze evolutie zonder anker convergeert naar textuur zonder karakter. Het thuis-anker en de mutatie-beperkingen zijn de tegenmaatregel — als het na F2 alsnog vervlakt, is de volgende stap terugkerende secties (A/B met een 64-maats vorm), niet meer mutatiesoorten.
  2. Bediening die niets doet. Vier knoppen op tien parameters betekent dat je zes parameters nooit aanraakt. Het gebarenlog uit het harnas moet dat aantonen vóór F2; blijkt het dat je maar twee gebaren gebruikt, dan snoeien we de pool in plaats van hem uit te breiden.
  3. Determinisme lekt. Eén vergeten Math.random() of één floating-point-verschil tussen toestellen breekt reproductie. De ESLint-regel dekt het eerste; het tweede is in de praktijk geen probleem zolang alle DSP-parameters via de graaf lopen en niet via handmatige sample-berekening.
  4. AirPods. 150–250 ms latentie maakt tikken-op-de-beat onbruikbaar. Strata is geen instrument dat je bespeelt op de tel — het is een systeem dat je bijstuurt. Dat is een ontwerpbesluit, geen tekortkoming: geen enkele handle is tijdkritisch.
  5. Scope. F4 (granulair archief) en F7 (NEXUS-brug) zijn de twee fases die het meest verleiden en het minst opleveren zolang F2 niet af is. Ze staan bewust achteraan.

11 · Wat alleen jij nog kunt

Alles wat mechaniek is, is gebouwd en gemeten. Wat overblijft, vraagt oren, een toestel of een laptop.

  1. Luisteren. Klinkt het als iets? Elke klankbeslissing hierboven is zonder oren genomen.
  2. Probe op iPhone en AirPods in het Perpetuum Harnas — latentie.
  3. 20 minuten spelen, Log en Sessie opslaan. Dan kan ik de handle-pool snoeien op wat je echt gebruikt in plaats van te gokken.
  4. Eigen opnames laden via "Sample laden" — de korrellaag op jouw archief.
  5. NEXUS aanzetten en de brug op het echte adres testen.
  6. Bank doorlopen — hoofdstuk H eerst, dat zijn de stukken.

12 · Strata als module

Drie artifacts en een repo zijn geen onderdeel; een contract wel. Dit is wat Strata naar buiten toe is, zodat het als één blok in de rest van het systeem past.

contractinhoud
identiteitstrata · seed van 32 bits · elke sessie = seed + log
ingangseed (of niets), gebaren, eigen samples, gewichten uit vorige sessies, dagdatum (radio)
uitgangaudio (live / wav), beeld (canvas), OSC-bundel per maat, strata-log (~4 kB/uur), sedimentbeeld
geheugenartifact-database: sessies, Bank-beoordelingen, fitness
documentatieHarnas (waarom) · Blueprint (hoe) · README (draaien) — één navigatie, één huisstijl
bronstrata/: src, docs (PWA), test

Waar het inplugt

modulekoppelingstatus
NEXUS / TouchDesigner/strata/* OSC-bundel over WebSocket :8080gebouwd wacht op NEXUS
Varve Radiodagselectie: seed = hash(datum), 60 min wav — deterministisch per dag, zoals de radio zelfgebouwd
Oude Data Archief"Sample laden" in de korrellaag (Heritage Rule)gebouwd
Logic ProMIDI-clock via de laptopkant van de brugniet gebouwd
varve.nlprojectkaart met seed-permalinkjij
Second brain / masterdocumentstrata-logs zijn databaserecordsleesbaar
WerkwereldStrata als instrument in de 3D-werkplaatsidee

Wat het nu nog niet één onderdeel maakt: drie losse artifact-adressen en een PWA die nergens gehost is. De repo lost dat op.