Commit 975f9150 by PLN (Algolia)

pipeline driver + generated catalog + ground-truth drawer

tide.py: one command regenerates every downstream artifact in dependency order
(track_recording_map → catalog_view → catalog → ts_types) + `tide.py test`.

Demote catalog.yaml (hand-state, no valid-as-of) → generated artifact:
- catalog.authored.yaml: the ONLY hand facts (license/collab/inspiration/status/
  notes), each with provenance; keyed by .tidal path
- build_catalog.py: catalog_view ⊕ overlay → catalog.generated.json (73 tracks,
  7 authored), validated against models.Catalog on emit
- catalog.yaml marked DEPRECATED

Triangle drawer = ground-truth validator (per corner):
- A: scrollable, syntax-highlighted .tidal source (fetched live) + parsed orbits
- B: audio player when a take proxy exists
- C: per-gig bpm/style/dur + external gig link + raw ingredient list (site's claim)
- search now spans ingredients/samples too
- enrich catalog_view rows with raw ingredients (+ Ingredient/Catalog models, TS)

Serve from the REPO ROOT so source/audio resolve:
  python3 armada/serve.py --dir . --port 8731
  → /armada/tide-table/triangle.html

30 pytest green.
parent 2704f92c
#!/usr/bin/env python3
"""build_catalog — the GENERATED catalog (replaces hand-maintained catalog.yaml).
catalog.yaml had no knowable "valid as of". The catalog is now a downstream
artifact: derived facts from `catalog_view.json` (sounds, gigs, takes, recording
status, A↔C level) ⊕ the authored overlay `catalog.authored.yaml` (license,
inspiration, collab, curated status, notes — each with provenance). Validated
against models.Catalog on emit. Run via `tide.py build catalog` (after catalog_view).
python3 build_catalog.py
"""
from __future__ import annotations
import json
from pathlib import Path
import yaml
HERE = Path(__file__).parent
CATALOG_VIEW = HERE / "catalog_view.json"
AUTHORED = HERE / "catalog.authored.yaml"
OUT = HERE / "catalog.generated.json"
def build():
view = json.loads(CATALOG_VIEW.read_text())
authored = (yaml.safe_load(AUTHORED.read_text()) or {}).get("tracks", {}) or {}
tracks = []
for r in view["tracks"]:
a = authored.get(r["track"], {})
tracks.append({
"id": r["track"],
"title": a.get("title") or r["name"],
"aka": sorted(set(r["names"]) | set(a.get("aka", []))),
# derived — do not hand-edit
"sounds": r["score_sounds"],
"orbits": r["n_orbits"],
"gigs": r["gigs"],
"takes": [t["take"] for t in r["takes"]],
"recorded": r["recorded"],
"eda_grounded": r["n_takes_eda"] > 0,
"ac_level": r["ac"]["level"],
# authored overlay
"license": a.get("license"),
"inspiration": a.get("inspiration", []),
"collab": a.get("collab", []),
"status": a.get("status"),
"notes": a.get("notes"),
"authored_prov": a.get("prov"),
})
return {
"schema": "catalog v1 (GENERATED — catalog_view ⊕ catalog.authored.yaml; "
"do not hand-edit; edit the overlay or the source)",
"as_of": view["as_of"],
"n_tracks": len(tracks),
"n_authored": len(authored),
"tracks": tracks,
}
def main():
out = build()
from models import Catalog # DRY contract: validate before writing
Catalog.model_validate(out)
OUT.write_text(json.dumps(out, ensure_ascii=False, indent=1))
print(f"✓ {OUT} ({out['n_tracks']} tracks · {out['n_authored']} authored overlays)")
if __name__ == "__main__":
main()
...@@ -215,13 +215,16 @@ def build(): ...@@ -215,13 +215,16 @@ def build():
score = ts.orbit_sounds(path, vocab, kind) if a_present else {} score = ts.orbit_sounds(path, vocab, kind) if a_present else {}
score_sounds = [d["sound"] for d in score.values()] score_sounds = [d["sound"] for d in score.values()]
# corner C: union claimed sounds + representative metadata # corner C: union claimed sounds + representative metadata + raw ingredients
claimed, metas, gig_slugs = [], [], [] claimed, metas, gig_slugs, ingredients = [], [], [], []
for slug, gdate, tr in rec["appearances"]: for slug, gdate, tr in rec["appearances"]:
gig_slugs.append(slug) gig_slugs.append(slug)
claimed += ingredient_sounds(tr, vocab) claimed += ingredient_sounds(tr, vocab)
metas.append({"gig": slug, "date": gdate, "bpm": tr.get("bpm"), metas.append({"gig": slug, "date": gdate, "bpm": tr.get("bpm"),
"style": tr.get("style"), "dur": tr.get("duration")}) "style": tr.get("style"), "dur": tr.get("duration")})
for ing in tr.get("ingredients", []): # ground-truth for the drawer
ingredients.append({"type": ing.get("type", ""), "code": ing.get("code", ""),
"description": ing.get("description", ""), "gig": slug})
claimed = list(dict.fromkeys(claimed)) # de-dup, keep order claimed = list(dict.fromkeys(claimed)) # de-dup, keep order
# corner B: candidate takes via date-join across this track's gigs # corner B: candidate takes via date-join across this track's gigs
...@@ -253,6 +256,7 @@ def build(): ...@@ -253,6 +256,7 @@ def build():
"score_sounds": sorted(set(score_sounds)), "score_sounds": sorted(set(score_sounds)),
# corner C # corner C
"claimed_sounds": claimed, "claimed_sounds": claimed,
"ingredients": ingredients, # raw code+description, for ground-truth view
# A↔C # A↔C
"ac": ac, "ac": ac,
# corner B # corner B
......
# tide-table · AUTHORED overlay — the ONLY hand-maintained catalog facts.
#
# Everything DERIVABLE (sounds, gigs, takes, bpm, style, recording/EDA status,
# A↔C agreement) is GENERATED into catalog.generated.json by `tide.py build` from
# the parsers — never duplicate it here. This file holds ONLY what no parser can
# know: licensing, inspiration, human collab credits, curated lifecycle, prose
# notes. Each entry carries provenance (feedback_metadata_provenance).
#
# Keyed by canonical track id = the .tidal path (merges FR⇄EN aliases — the same
# file is "L'Or Bleu" and "Blue Gold"). Fields (all optional):
# title canonical display name (overrides the gig name)
# aka[] extra aliases beyond those derived from gig naming
# license e.g. "CC BY-SA"
# inspiration[] artists/works that inspired it
# collab[] human collaborators (ontology keys: rhadamanthe, raph, …)
# status idea | demo | performed | recorded | mastered | released
# notes prose a parser can't infer (sample-pack provenance, story)
# prov {source: user|ear|file|web|derived, locator, as_of}
#
# This started as a SEED migrated from catalog.yaml (now deprecated). Grow it as
# facts are verified — and prefer correcting at the source (.tidal / site) when
# the truth is actually derivable.
tracks:
live/collab/mousquetaires/blue_gold.tidal:
title: "L'Or Bleu"
aka: ["Blue Gold", "Blue Gold 🌇"]
license: "CC BY-SA"
inspiration: ["Leifur James"]
status: released
notes: "suns_keys/suns_guitar/suns_voice originals. FR release title EN stage name."
prov: {source: user, locator: "catalog.yaml seed", as_of: "2026-06-06"}
live/midi/nova/lounge/suns_of_gold.tidal:
title: "L'Or Bleu suns of gold (ébauche)"
aka: ["L'Or Bleu", "Blue Gold"]
inspiration: ["Leifur James"]
status: demo
notes: "Earlier ébauche of the same L'Or Bleu remix idea (per PLN, 2026-06-06)."
prov: {source: user, locator: "PLN 2026-06-06", as_of: "2026-06-06"}
live/collab/baba/sept1.tidal:
title: "Premier Septembre"
aka: ["Sept1", "Sept 1"]
collab: ["rhadamanthe"]
status: released
notes: "rhadamanthe_melo/vocal + cpluck."
prov: {source: user, locator: "catalog.yaml seed", as_of: "2026-06-06"}
live/midi/nova/breaks/lady_perplexity.tidal:
title: "Lady Perplexity"
collab: ["rhadamanthe"]
status: released
prov: {source: user, locator: "catalog.yaml seed", as_of: "2026-06-06"}
live/collab/raph/jeudrill.tidal:
title: "Jeudi Drill"
aka: ["JeuDrill"]
collab: ["raph"]
status: released
notes: "bogdan_grime 'I'm from Cardiff!' vocals; marimba1 melody."
prov: {source: user, locator: "catalog.yaml seed", as_of: "2026-06-06"}
live/midi/nova/techno/ete_a_mauerpark.tidal:
title: "L'été à Mauerpark"
aka: ["Mauerpark"]
status: released
notes: "moogBass Berlin warmth, Leslie/chorus."
prov: {source: user, locator: "catalog.yaml seed", as_of: "2026-06-06"}
live/midi/nova/dnb/venons_ensemble.tidal:
title: "Venons Ensemble"
status: released
notes: "come_bass/come_eguitar/come_voice 3-part band pack."
prov: {source: user, locator: "catalog.yaml seed", as_of: "2026-06-06"}
{
"schema": "catalog v1 (GENERATED — catalog_view ⊕ catalog.authored.yaml; do not hand-edit; edit the overlay or the source)",
"as_of": "2026-06-06",
"n_tracks": 73,
"n_authored": 7,
"tracks": [
{
"id": "copycat/because_its_there.tidal",
"title": "Because It's There",
"aka": [
"Because It's There"
],
"sounds": [
"90s_synatm",
"FMRhodes1",
"bassWarsaw",
"giorgio_syn",
"ho",
"jazz",
"jungle_breaks",
"risers",
"sn"
],
"orbits": 10,
"gigs": [
"2025/bunker",
"2025/val-thorens"
],
"takes": [
"Take80"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/boeuf/algorythm/fdlm2022/toxic.tidal",
"title": "Toxic",
"aka": [
"Toxic"
],
"sounds": [
"FMRhodes1",
"bassWarsaw",
"drum",
"hh",
"ifdrums"
],
"orbits": 5,
"gigs": [
"2024/velociteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/chip/ataright.tidal",
"title": "Atari-ght",
"aka": [
"Atari-ght"
],
"sounds": [
"808bd",
"FMRhodes1",
"FMRhodes2",
"drums_atari",
"fbreak120",
"h2ogmhh"
],
"orbits": 6,
"gigs": [
"2022/bazurto",
"2024/divin-live"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "conflict",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/baba/sept1.tidal",
"title": "Premier Septembre",
"aka": [
"Premier Septembre",
"Sept 1",
"Sept1",
"Septembre 1er"
],
"sounds": [
"breaks165",
"cpluck",
"h2ogmcy",
"h2ogmhh",
"jazz",
"jungle_breaks",
"nujazz_keys120",
"rhadamanthe_melo",
"risers"
],
"orbits": 10,
"gigs": [
"2025/air-elementeuf",
"2025/bunker",
"2025/cosmicfest",
"2025/raise",
"2025/val-thorens",
"2026/montreuil-algorave"
],
"takes": [
"Take70",
"Take80",
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [
"rhadamanthe"
],
"status": "released",
"notes": "rhadamanthe_melo/vocal + cpluck.",
"authored_prov": {
"source": "user",
"locator": "catalog.yaml seed",
"as_of": "2026-06-06"
}
},
{
"id": "live/collab/ccc/ccc0.tidal",
"title": "Blue Gold",
"aka": [
"Blue Gold"
],
"sounds": [
"909",
"ccc",
"cp",
"cpluck",
"h2ogmhh",
"jungle_breaks",
"moog",
"risers",
"vec2_synth_acid"
],
"orbits": 10,
"gigs": [
"2024/ccc-live"
],
"takes": [
"Take20",
"Take21"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "divergent",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/ccc/ghosts_in_the_toilets.tidal",
"title": "Ghosts in the T01l3ts",
"aka": [
"Ghosts in the T01l3ts",
"Ghosts in the Toilets"
],
"sounds": [
"armora",
"bassWarsaw",
"breaks165",
"cp",
"ghost",
"jazz",
"moog",
"movie_wolf",
"snare"
],
"orbits": 9,
"gigs": [
"2024/38c3-toilet",
"2025/cosmicfest"
],
"takes": [
"Take36",
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/jane/drifting_soul.tidal",
"title": "Drifting Soul",
"aka": [
"Drifting Soul"
],
"sounds": [
"cpluck",
"h2ogmhh",
"jane_wang",
"jungle_breaks",
"moogBass",
"movie_vitality",
"rampleM6",
"scratch_long",
"sn",
"techno"
],
"orbits": 10,
"gigs": [
"2025/bunker"
],
"takes": [
"Take80"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/josh/oct_18_rainy_day.tidal",
"title": "Rainy Day",
"aka": [
"Rainy Day"
],
"sounds": [
"909",
"bassWarsaw",
"break",
"cp",
"jazz",
"snare",
"trance_amb"
],
"orbits": 7,
"gigs": [
"2024/velociteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/mousquetaires/blue_gold.tidal",
"title": "L'Or Bleu",
"aka": [
"Blue Gold",
"Blue Gold 🌇",
"L'or Bleu"
],
"sounds": [
"bassWarsaw",
"jazz",
"jungle_breaks",
"rampleD2",
"risers",
"snare",
"suns_guitar",
"suns_keys",
"suns_voice"
],
"orbits": 11,
"gigs": [
"2024/38c3-toilet",
"2024/algolia-last-all-hands",
"2024/cookie-collective-compilation",
"2025/raise",
"2025/val-thorens",
"2026/montreuil-algorave"
],
"takes": [
"Take36",
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": "CC BY-SA",
"inspiration": [
"Leifur James"
],
"collab": [],
"status": "released",
"notes": "suns_keys/suns_guitar/suns_voice originals. FR release title ⇄ EN stage name.",
"authored_prov": {
"source": "user",
"locator": "catalog.yaml seed",
"as_of": "2026-06-06"
}
},
{
"id": "live/collab/nass/love_first.tidal",
"title": "Love First",
"aka": [
"Love First"
],
"sounds": [
"hh27",
"jazz",
"jungle_bass",
"jungle_breaks",
"moogBass",
"movie_cat",
"nujazz_guitar120",
"nujazz_keys120",
"risers",
"vec1_snare"
],
"orbits": 11,
"gigs": [
"2024/algolia-last-all-hands",
"2025/algolia-rko"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "divergent",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/nass/sogood.tidal",
"title": "So Good",
"aka": [
"So Good"
],
"sounds": [
"armora",
"bassWarsaw",
"cs80leadMH",
"gtkick",
"h2ogmhh",
"jungle_breaks",
"moogBass",
"risers",
"sogood"
],
"orbits": 11,
"gigs": [
"2025/val-thorens"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/acidule.tidal",
"title": "Acidule",
"aka": [
"ACIDULE",
"Acidule",
"Acidulé",
"L'ACID d'abord",
"L'ACID d'abord <3"
],
"sounds": [
"808cy",
"FMRhodes1",
"cp",
"jungle_breaks",
"kick",
"moog",
"rampleP3",
"rhadamanthe_vocal",
"vec2_synth_acid"
],
"orbits": 11,
"gigs": [
"2024/38c3-toilet",
"2024/ccc-live",
"2024/cookie-collective-compilation",
"2025/bunker",
"2025/val-thorens"
],
"takes": [
"Take20",
"Take21",
"Take36",
"Take80"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/aria_sans_serif.tidal",
"title": "Aria Sans Serif",
"aka": [
"Aria Sans Serif"
],
"sounds": [
"acidOto3091",
"clubkick",
"h2ogmhh",
"jazz",
"jungle_breaks",
"moog",
"oil"
],
"orbits": 7,
"gigs": [
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/biscuit_acide.tidal",
"title": "Biscuit Acide",
"aka": [
"Biscuit Acide"
],
"sounds": [
"ho",
"kick",
"moog",
"vec1_acid",
"vec1_snare"
],
"orbits": 5,
"gigs": [
"2025/fairyteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/des_efforts.tidal",
"title": "Des Efforts",
"aka": [
"Des Efforts"
],
"sounds": [
"bassWarsaw",
"breaks165",
"cp",
"h2ogmhh",
"jazz",
"law_adri"
],
"orbits": 7,
"gigs": [
"2025/fairyteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/desire.tidal",
"title": "Desire",
"aka": [
"Desire"
],
"sounds": [
"desire",
"h2ogmcp",
"h2ogmhh",
"jazz"
],
"orbits": 7,
"gigs": [
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/esperluette.tidal",
"title": "Esperluette",
"aka": [
"Esperluette"
],
"sounds": [
"bassWarsaw",
"h2ogmhh",
"jungle_breaks",
"kick",
"moog",
"praise",
"vec1_claps"
],
"orbits": 7,
"gigs": [
"2025/fairyteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/jeudrill.tidal",
"title": "Jeudi Drill",
"aka": [
"JeuDrill",
"Jeudi Drill"
],
"sounds": [
"90s_synatm",
"bogdan",
"clubkick",
"giorgio_syn",
"jungle_breaks",
"marimba1",
"meth_bass",
"snare"
],
"orbits": 8,
"gigs": [
"2024/algolia-fdlm",
"2025/cosmicfest",
"2025/fairyteuf",
"2025/raise",
"2026/montreuil-algorave"
],
"takes": [
"Take70",
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [
"raph"
],
"status": "released",
"notes": "bogdan_grime 'I'm from Cardiff!' vocals; marimba1 melody.",
"authored_prov": {
"source": "user",
"locator": "catalog.yaml seed",
"as_of": "2026-06-06"
}
},
{
"id": "live/collab/raph/long_way.tidal",
"title": "Long Way",
"aka": [
"Long Way"
],
"sounds": [
"h2ogmhh",
"jungle_breaks",
"kick",
"praise",
"risers",
"vec1_snare"
],
"orbits": 10,
"gigs": [
"2025/fairyteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/nouveau_punk.tidal",
"title": "Nouveau Punk",
"aka": [
"Nouveau Punk"
],
"sounds": [
"90s_synatm",
"cbow",
"cpluck",
"h2ogmhh",
"jazz",
"jungle_breaks",
"meth_bass",
"moogBass",
"punk",
"snare"
],
"orbits": 10,
"gigs": [
"2024/38c3-toilet",
"2025/val-thorens"
],
"takes": [
"Take36"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/permanence.tidal",
"title": "Permanence",
"aka": [
"PERMANENCE",
"Permanence"
],
"sounds": [
"909",
"bass_nes",
"fguitar",
"fsynth",
"hh",
"jungle_breaks",
"moogBass",
"snare",
"superfork"
],
"orbits": 9,
"gigs": [
"2023/toplap-solstice",
"2024/algolia-fdlm",
"2024/algolia-last-all-hands",
"2024/la-french-stack",
"2024/toplap-20-years",
"2025/algolia-rko",
"2025/la-french-stack"
],
"takes": [
"Take18",
"Take19",
"Take63",
"Take64",
"Take65",
"Take66"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/piment_bresilien.tidal",
"title": "Piment Bresilien",
"aka": [
"Piment Bresilien",
"Piment brésilien"
],
"sounds": [
"909",
"90s_synatm",
"FMRhodes2",
"bassWarsaw",
"gfunk_lead",
"h2ogmhh",
"jungle_breaks",
"vec1_claps"
],
"orbits": 8,
"gigs": [
"2025/fairyteuf",
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/collab/raph/punkachien.tidal",
"title": "PunkAChien",
"aka": [
"Pitbul Punk",
"PunkAChien"
],
"sounds": [
"cpluck",
"dr",
"jungle_breaks",
"kick",
"meth_bass",
"moog",
"snare",
"vec1_acid"
],
"orbits": 8,
"gigs": [
"2024/38c3-toilet",
"2025/raise",
"2025/val-thorens",
"2026/montreuil-algorave"
],
"takes": [
"Take36",
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/dnb/nass_revient.tidal",
"title": "Nass Revient de Mars!",
"aka": [
"Nass Revient de Mars",
"Nass Revient de Mars!"
],
"sounds": [
"FMRhodes1",
"cosmicg",
"cp",
"drum",
"forgan",
"giorgio_syn",
"jungle_breaks",
"movie_vitality",
"supersaw"
],
"orbits": 10,
"gigs": [
"2024/ccc-live",
"2024/cookie-collective-compilation"
],
"takes": [
"Take20",
"Take21"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/hip/darkside/clameur.tidal",
"title": "Clameur",
"aka": [
"Clameur"
],
"sounds": [
"bass1",
"break",
"cp",
"dr",
"jazz",
"pad",
"voices_persian"
],
"orbits": 7,
"gigs": [
"2024/toi-toi-mon-toit"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "divergent",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/hip/lofi/slow_mo.tidal",
"title": "SlowMo",
"aka": [
"SlowMo"
],
"sounds": [
"90s_megafx",
"FMRhodes1",
"bassWarsaw",
"cp",
"fx_commodore",
"hh27",
"jungle_vocals",
"tech"
],
"orbits": 9,
"gigs": [
"2023/devcon23"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/ambient/contre_visite.tidal",
"title": "Contre visite",
"aka": [
"Contre Visite",
"Contre visite"
],
"sounds": [
"90s_synatm",
"bassWarsaw",
"break",
"cp",
"h2ogmhh",
"jazz",
"jungle_breaks"
],
"orbits": 9,
"gigs": [
"2022/bazurto",
"2023/cmny-2",
"2024/algolia-fdlm",
"2024/algolia-last-all-hands",
"2024/divin-live",
"2024/la-french-stack",
"2024/velociteuf",
"2025/algolia-rko",
"2025/la-french-stack"
],
"takes": [
"Take18",
"Take19",
"Take63",
"Take64",
"Take65",
"Take66"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "partial",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/ambient/empreinte_du_numerique.tidal",
"title": "Empreinte du numerique",
"aka": [
"Empreinte du numerique"
],
"sounds": [
"808bd",
"FMRhodes1",
"break",
"db",
"electro1",
"fbass",
"supermandolin"
],
"orbits": 7,
"gigs": [
"2023/cmny-2"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/ambient/prestance.tidal",
"title": "Prestance",
"aka": [
"Prestance"
],
"sounds": [
"808bd",
"FMRhodes1",
"bassWarsaw",
"break",
"jungle_pads",
"meth_bass",
"moogBass",
"prophet5pwmStrings",
"snare"
],
"orbits": 9,
"gigs": [
"2024/velociteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/ambient/quand_on_decolle.tidal",
"title": "Quand on Décolle",
"aka": [
"Quand on Décolle",
"Quand on décolle"
],
"sounds": [
"airports",
"bassWarsaw",
"hh",
"jazz",
"jungle_breaks",
"moogBass",
"risers",
"snare"
],
"orbits": 10,
"gigs": [
"2025/cosmicfest",
"2026/montreuil-algorave"
],
"takes": [
"Take70",
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/ambient/raise.tidal",
"title": "RAISE",
"aka": [
"RAISE"
],
"sounds": [
"ai_welcome",
"airports",
"bassWarsaw",
"hh",
"jazz",
"jungle_breaks",
"moogBass",
"risers",
"snare"
],
"orbits": 11,
"gigs": [
"2025/raise"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/beatober/oct_16_haunted_house_insouciance.tidal",
"title": "La fin de l'insouciance",
"aka": [
"Haunted House",
"Insouciance",
"La Fin de l'Insouciance",
"La fin de l'insouciance"
],
"sounds": [
"808hc",
"FMRhodes1",
"armora",
"bassWarsaw",
"cp",
"jazz",
"jungle_breaks",
"risers",
"snare",
"trance_pads",
"weird_dialogs"
],
"orbits": 11,
"gigs": [
"2023/toplap-solstice",
"2025/cosmicfest",
"2025/raise",
"2025/val-thorens"
],
"takes": [
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/beatober/oct_glitchs_sauvages.tidal",
"title": "Oct4 Glitch Sauvages",
"aka": [
"Oct4 Glitch Sauvages"
],
"sounds": [
"90s_matrix",
"cp",
"glitch",
"jazz",
"jungle_breaks",
"rampleS0",
"superfork"
],
"orbits": 8,
"gigs": [
"2024/velociteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/bain_electrique.tidal",
"title": "Bain électrique",
"aka": [
"Bain Electrique",
"Bain Électrique",
"Bain électrique"
],
"sounds": [
"fguitar",
"giorgio_syn",
"h2ogmhh",
"jungbass",
"jungle_breaks",
"jungle_pads",
"rampleA2",
"snare",
"weird_dialogs"
],
"orbits": 10,
"gigs": [
"2024/la-french-stack",
"2025/cosmicfest",
"2025/la-french-stack",
"2025/raise"
],
"takes": [
"Take18",
"Take19",
"Take63",
"Take64",
"Take65",
"Take66",
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/break_dynasty.tidal",
"title": "VelociTeuf",
"aka": [
"Break Dynasty",
"VelociTeuf"
],
"sounds": [
"FMRhodes2",
"bassWarsaw",
"jungle_breaks",
"jungle_vocals",
"kick",
"shiloh",
"snare"
],
"orbits": 8,
"gigs": [
"2023/devcon23",
"2024/divin-live",
"2024/toi-toi-mon-toit"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/break_the_rentree.tidal",
"title": "Sessions Break",
"aka": [
"Sessions Break"
],
"sounds": [
"dr2",
"dr_few",
"fbass",
"fbreak80",
"fepiano",
"jazz"
],
"orbits": 7,
"gigs": [
"2023/devcon23"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/green_land.tidal",
"title": "Green Land",
"aka": [
"Green Land"
],
"sounds": [
"808cy",
"bssnare",
"celtic_guitar80",
"hh",
"jazz",
"moog",
"voices_celtic"
],
"orbits": 8,
"gigs": [
"2024/divin-live"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/its_about_time.tidal",
"title": "It's About Time",
"aka": [
"About Time",
"It's About Time"
],
"sounds": [
"808bd",
"90s_synatm",
"cp",
"drums_atari",
"fbreak100",
"fx_gameboy",
"jungle_breaks",
"synth_commodore"
],
"orbits": 9,
"gigs": [
"2022/bazurto",
"2024/divin-live",
"2024/toi-toi-mon-toit",
"2024/velociteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "partial",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/lady_perplexity.tidal",
"title": "Lady Perplexity",
"aka": [
"Lady Perplexity"
],
"sounds": [
"909",
"FMRhodes1",
"FMRhodes2",
"bassWarsaw",
"jungle_breaks",
"rhadamanthe_vocal"
],
"orbits": 6,
"gigs": [
"2025/cosmicfest",
"2025/raise"
],
"takes": [
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [
"rhadamanthe"
],
"status": "released",
"notes": null,
"authored_prov": {
"source": "user",
"locator": "catalog.yaml seed",
"as_of": "2026-06-06"
}
},
{
"id": "live/midi/nova/breaks/lunar.tidal",
"title": "Lunar",
"aka": [
"Lunar"
],
"sounds": [
"FMRhodes1",
"bassWarsaw",
"break",
"cp",
"hh"
],
"orbits": 5,
"gigs": [
"2022/bazurto",
"2023/cmny-2"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/madeleine_de_paris.tidal",
"title": "Paris",
"aka": [
"Paris"
],
"sounds": [
"h2ogmsn",
"jazz",
"jungle_breaks",
"moog",
"temptation",
"vec1_claps",
"vec2_synth_acid"
],
"orbits": 7,
"gigs": [
"2025/cosmicfest"
],
"takes": [
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/nuit_agitee.tidal",
"title": "Nuit Agitée",
"aka": [
"Nuit Agitee",
"Nuit Agitée"
],
"sounds": [
"amencutup",
"bass_commodore",
"cp",
"cpluck",
"harpsichord2",
"jazz",
"jungbass",
"moogBass",
"sn"
],
"orbits": 10,
"gigs": [
"2024/algolia-fdlm",
"2024/algolia-last-all-hands",
"2024/ccc-live",
"2024/cookie-collective-compilation",
"2024/toplap-20-years",
"2025/algolia-rko",
"2025/raise",
"2025/val-thorens"
],
"takes": [
"Take20",
"Take21"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/solar.tidal",
"title": "Solar",
"aka": [
"Solar"
],
"sounds": [
"FMRhodes1",
"bassWarsaw",
"break",
"cbow",
"clap",
"fbass",
"forgan",
"jazz"
],
"orbits": 9,
"gigs": [
"2022/bazurto",
"2023/cmny-2"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/breaks/ton_numero.tidal",
"title": "Ton Numero",
"aka": [
"Ton Numero"
],
"sounds": [
"808bd",
"bassWarsaw",
"h2ogmhh",
"jungle_breaks",
"private_number",
"rampleS13",
"vec1_snare",
"vocalOoh1",
"vocalScatAr",
"vocalScatJ"
],
"orbits": 11,
"gigs": [
"2025/raise"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/alerte_verte.tidal",
"title": "Alerte Verte",
"aka": [
"Alerte Verte"
],
"sounds": [
"808mc",
"cp",
"fbass",
"fguitar",
"h2ogmhh",
"jungle_breaks",
"movie_giant",
"movie_wolf",
"rhadamanthe_vocal",
"snare",
"supersiren"
],
"orbits": 11,
"gigs": [
"2022/bazurto",
"2023/cmny-2",
"2024/ccc-live",
"2024/cookie-collective-compilation",
"2024/divin-live",
"2025/val-thorens"
],
"takes": [
"Take20",
"Take21"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "partial",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/break_the_loop.tidal",
"title": "Break the Loop",
"aka": [
"Break the Loop"
],
"sounds": [
"90s_matrix",
"90s_synatm",
"FMRhodes1",
"break",
"drum",
"reverbkick",
"snare"
],
"orbits": 7,
"gigs": [
"2022/bazurto"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/force_motrice.tidal",
"title": "Force Motrice",
"aka": [
"Atelier de Force Motrice",
"Force Motrice"
],
"sounds": [
"FMRhodes1",
"cbow",
"giorgio_syn",
"jazz",
"jungle_breaks",
"jungle_vocals",
"meth_bass",
"nujazz_bass125",
"rampleM8",
"risers"
],
"orbits": 11,
"gigs": [
"2024/algolia-fdlm",
"2024/algolia-last-all-hands",
"2024/ccc-live",
"2024/cookie-collective-compilation",
"2024/la-french-stack",
"2024/toplap-20-years",
"2025/bunker",
"2025/la-french-stack",
"2025/raise"
],
"takes": [
"Take18",
"Take19",
"Take20",
"Take21",
"Take63",
"Take64",
"Take65",
"Take66",
"Take80"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/liquid/you_my_sunshine.tidal",
"title": "You My Sunshine",
"aka": [
"You My Sunshine"
],
"sounds": [
"jazz",
"no_sunshine",
"org_jungle_breaks",
"rampleS34",
"rampleS37"
],
"orbits": 9,
"gigs": [
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/nouveau_soleil.tidal",
"title": "Nouveau Soleil",
"aka": [
"Nouveau Soleil"
],
"sounds": [
"909",
"bass_gameboy",
"breaks165",
"dr",
"moogBass",
"synth_atari",
"synth_gameboy"
],
"orbits": 7,
"gigs": [
"2023/devcon23",
"2024/divin-live",
"2024/toi-toi-mon-toit"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/plosive.tidal",
"title": "'Plosive",
"aka": [
"'Plosive"
],
"sounds": [
"bassWarsaw",
"breaks165",
"h2ogmsn",
"kicklinn",
"vec1_snare",
"xplosive"
],
"orbits": 8,
"gigs": [
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/something_about_drums.tidal",
"title": "Something about Drums",
"aka": [
"Something About Drums",
"Something about Drums"
],
"sounds": [
"FMRhodes2",
"bassWarsaw",
"drum",
"drumtraks",
"jazz",
"jungle_breaks",
"moogBass",
"movie_paris",
"superpiano"
],
"orbits": 9,
"gigs": [
"2024/algolia-last-all-hands",
"2024/la-french-stack",
"2025/algolia-rko",
"2025/la-french-stack",
"2025/raise"
],
"takes": [
"Take18",
"Take19",
"Take63",
"Take64",
"Take65",
"Take66"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/dnb/venons_ensemble.tidal",
"title": "Venons Ensemble",
"aka": [
"Venons Ensemble"
],
"sounds": [
"909",
"come_bass",
"come_eguitar",
"come_guitar",
"come_voice",
"jazz",
"jungle_breaks",
"risers"
],
"orbits": 8,
"gigs": [
"2024/algolia-fdlm",
"2024/divin-live",
"2024/velociteuf",
"2025/cosmicfest",
"2025/la-french-stack"
],
"takes": [
"Take63",
"Take64",
"Take65",
"Take66",
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": "released",
"notes": "come_bass/come_eguitar/come_voice 3-part band pack.",
"authored_prov": {
"source": "user",
"locator": "catalog.yaml seed",
"as_of": "2026-06-06"
}
},
{
"id": "live/midi/nova/dnb/wap.tidal",
"title": "WAP",
"aka": [
"W.I.P. W.A.P.",
"WAP"
],
"sounds": [
"cp",
"jungle_breaks",
"kick",
"vec1_acid",
"vec1_snare",
"wap"
],
"orbits": 8,
"gigs": [
"2025/fairyteuf",
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/jazz/the_revolution_will_be_sampled.tidal",
"title": "La Révolution Sera Samplée",
"aka": [
"La Révolution Sera Samplée"
],
"sounds": [
"FMRhodes1",
"h2ogmhh",
"jungle_breaks",
"like_sugar",
"stack",
"the_revolution"
],
"orbits": 10,
"gigs": [
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lofi/lendemain_divin.tidal",
"title": "Lendemain Divin",
"aka": [
"Lendemain Divin"
],
"sounds": [
"FMRhodes1",
"bassWarsaw",
"come_guitar",
"jazz",
"jungle_breaks",
"vec1_snare"
],
"orbits": 6,
"gigs": [
"2023/devcon23",
"2024/algolia-fdlm"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lofi/premiere_grillade.tidal",
"title": "Premiere Grillade",
"aka": [
"Premiere Grillade"
],
"sounds": [
"FMRhodes2",
"bassWarsaw",
"jazz",
"jungle_breaks",
"shiloh",
"snare"
],
"orbits": 6,
"gigs": [
"2023/devcon23",
"2024/toi-toi-mon-toit",
"2024/velociteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lofi/reboot.tidal",
"title": "Reboot",
"aka": [
"Reboot"
],
"sounds": [
"break",
"drum",
"fbass",
"fguitar",
"fpiano",
"jazz",
"snare",
"supersiren"
],
"orbits": 8,
"gigs": [
"2024/velociteuf"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lounge/cbow.tidal",
"title": "CBOW",
"aka": [
"CBOW"
],
"sounds": [
"cbow",
"cp",
"cpluck",
"fbreak100",
"hh27",
"jazz",
"sn"
],
"orbits": 8,
"gigs": [
"2023/cmny-2"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lounge/fabuleux.tidal",
"title": "Fabuleux",
"aka": [
"Fabuleux"
],
"sounds": [
"FMRhodes2",
"aoc_fr",
"bassWarsaw",
"break",
"feelfx",
"hh",
"moogBass",
"rampleA0",
"snare"
],
"orbits": 9,
"gigs": [
"2024/cookie-collective-compilation",
"2025/cosmicfest"
],
"takes": [
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "no-claim",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lounge/invoque_ete.tidal",
"title": "Invoque l'ete",
"aka": [
"Invoque l'Ete",
"Invoque l'ete",
"Invoque l'Été"
],
"sounds": [
"90s_synatm",
"breaks125",
"electro1",
"fbass",
"fguitar",
"fpiano",
"hh",
"jazz",
"sn"
],
"orbits": 9,
"gigs": [
"2022/bazurto",
"2023/cmny-2",
"2023/devcon23",
"2024/algolia-fdlm",
"2024/algolia-last-all-hands",
"2024/divin-live",
"2025/algolia-rko"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lounge/michael.tidal",
"title": "Michael",
"aka": [
"Michael"
],
"sounds": [
"cp",
"fbreak120",
"forgan",
"fpiano",
"fsynth",
"hh27",
"jazz"
],
"orbits": 7,
"gigs": [
"2022/bazurto"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lounge/sunny_side_up.tidal",
"title": "Sunny Side Up",
"aka": [
"Sunny",
"Sunny Side Up",
"Super Sunny Side Up"
],
"sounds": [
"acidOto3092",
"h2ogmhh",
"jazz",
"jungle",
"jungle_breaks",
"nujazz_keys120",
"risers",
"sunny_bass",
"sunny_brass"
],
"orbits": 10,
"gigs": [
"2024/algolia-last-all-hands",
"2024/la-french-stack",
"2025/air-elementeuf",
"2025/algolia-rko",
"2025/bunker",
"2025/cosmicfest",
"2025/fairyteuf",
"2025/la-french-stack",
"2025/raise",
"2025/val-thorens",
"2026/montreuil-algorave"
],
"takes": [
"Take18",
"Take19",
"Take63",
"Take64",
"Take65",
"Take66",
"Take70",
"Take80",
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/lounge/suns_of_gold.tidal",
"title": "L'Or Bleu — suns of gold (ébauche)",
"aka": [
"Blue Gold",
"L'Or Bleu"
],
"sounds": [
"dr55",
"giorgio_syn",
"jazz",
"jungle_breaks",
"moog",
"moogBass",
"rampleC5",
"suns_guitar",
"suns_keys",
"suns_voice"
],
"orbits": 10,
"gigs": [
"2024/la-french-stack",
"2025/cosmicfest",
"2025/la-french-stack"
],
"takes": [
"Take18",
"Take19",
"Take63",
"Take64",
"Take65",
"Take66",
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "partial",
"license": null,
"inspiration": [
"Leifur James"
],
"collab": [],
"status": "demo",
"notes": "Earlier ébauche of the same L'Or Bleu remix idea (per PLN, 2026-06-06).",
"authored_prov": {
"source": "user",
"locator": "PLN 2026-06-06",
"as_of": "2026-06-06"
}
},
{
"id": "live/midi/nova/lounge/take_5_drops.tidal",
"title": "Take 5 Drops",
"aka": [
"Take 5 Drops"
],
"sounds": [
"909",
"bassWarsaw",
"h2ogmhh",
"moogBass",
"risers",
"take5"
],
"orbits": 9,
"gigs": [
"2026/montreuil-algorave"
],
"takes": [
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/nujazz/cafe_bouillant.tidal",
"title": "Café Bouillant",
"aka": [
"Bain Bouillant",
"Cafe Bouillant",
"Café Bouillant"
],
"sounds": [
"bassWarsaw",
"cpluck",
"house",
"jungle_breaks",
"nujazz_bass120",
"nujazz_beats120",
"nujazz_keys120",
"rampleM1",
"rampleS57"
],
"orbits": 9,
"gigs": [
"2023/toplap-solstice",
"2024/algolia-fdlm",
"2024/algolia-last-all-hands",
"2025/air-elementeuf",
"2025/algolia-rko",
"2025/val-thorens"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/nujazz/cafe_glace.tidal",
"title": "Café Glacé",
"aka": [
"Cafe Glace",
"Café Glacé"
],
"sounds": [
"h2ogmcy",
"house",
"jungle_breaks",
"meth_bass",
"nujazz_bass120",
"nujazz_keys120",
"rampleM1"
],
"orbits": 8,
"gigs": [
"2024/algolia-fdlm",
"2024/ccc-live",
"2024/cookie-collective-compilation",
"2024/toplap-20-years",
"2025/air-elementeuf",
"2025/raise",
"2025/val-thorens"
],
"takes": [
"Take20",
"Take21"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/nujazz/cafe_tiede.tidal",
"title": "Café Tiède",
"aka": [
"Cafe Tiede",
"Café Tiède"
],
"sounds": [
"FMRhodes1",
"h2ogmcp",
"h2ogmsn",
"jazz",
"jungle_breaks",
"nujazz_bass125",
"nujazz_keys125"
],
"orbits": 10,
"gigs": [
"2024/algolia-fdlm",
"2024/algolia-last-all-hands",
"2024/ccc-live",
"2024/cookie-collective-compilation",
"2024/la-french-stack",
"2024/toplap-20-years",
"2025/air-elementeuf",
"2025/algolia-rko",
"2025/la-french-stack",
"2025/raise"
],
"takes": [
"Take18",
"Take19",
"Take20",
"Take21",
"Take63",
"Take64",
"Take65",
"Take66"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/nujazz/salut_nu.tidal",
"title": "Salut Nu",
"aka": [
"Salut Nu",
"Salut nu"
],
"sounds": [
"bassWarsaw",
"h2ogmsn",
"hh",
"jazz",
"jungle_vocals",
"nujazz_beats120",
"nujazz_keys120",
"nujazz_wahwah125",
"rampleM1"
],
"orbits": 10,
"gigs": [
"2023/toplap-solstice",
"2024/algolia-fdlm",
"2024/ccc-live",
"2024/cookie-collective-compilation",
"2024/la-french-stack",
"2025/air-elementeuf",
"2025/la-french-stack",
"2025/raise"
],
"takes": [
"Take18",
"Take19",
"Take20",
"Take21",
"Take63",
"Take64",
"Take65",
"Take66"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/techno/ere_de_jeu.tidal",
"title": "Ere de Jeu",
"aka": [
"Ere de Jeu"
],
"sounds": [
"FMRhodes1",
"cbow",
"cpluck",
"giorgio_syn",
"h2ogmhh",
"jazz",
"jungle_breaks",
"moogBass",
"risers",
"snare",
"vec1_acid"
],
"orbits": 11,
"gigs": [
"2025/bunker",
"2025/val-thorens"
],
"takes": [
"Take80"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "conflict",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/midi/nova/techno/ete_a_mauerpark.tidal",
"title": "L'été à Mauerpark",
"aka": [
"Ete a MauerPark",
"Ete a Mauerpark",
"L'été à Mauerpark",
"MauerPark",
"Mauerpark"
],
"sounds": [
"909",
"FMRhodes1",
"bassWarsaw",
"cbow",
"jungle_breaks",
"moogBass"
],
"orbits": 6,
"gigs": [
"2023/devcon23",
"2024/algolia-fdlm",
"2024/divin-live",
"2025/cosmicfest"
],
"takes": [
"Take70"
],
"recorded": true,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": "released",
"notes": "moogBass Berlin warmth, Leslie/chorus.",
"authored_prov": {
"source": "user",
"locator": "catalog.yaml seed",
"as_of": "2026-06-06"
}
},
{
"id": "live/midi/nova/techno/techno_orage.tidal",
"title": "Orage",
"aka": [
"Nuit d'orage",
"Orage",
"Techno Orage"
],
"sounds": [
"FMRhodes2",
"bassWarsaw",
"jungle_breaks",
"lead",
"orage",
"risers",
"superpiano",
"vec1_acid"
],
"orbits": 11,
"gigs": [
"2025/bunker",
"2025/cosmicfest",
"2025/raise",
"2026/montreuil-algorave"
],
"takes": [
"Take70",
"Take80",
"Take89"
],
"recorded": true,
"eda_grounded": true,
"ac_level": "partial",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/techno/nightly_repair.tidal",
"title": "Nightly Repair",
"aka": [
"Nightly Repair"
],
"sounds": [
"808bd",
"90s_matrix",
"FMRhodes1",
"gretsch",
"h2ogmcp"
],
"orbits": 6,
"gigs": [
"2022/bazurto",
"2023/cmny-2",
"2024/algolia-fdlm"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
},
{
"id": "live/techno/noir/burn_this_book.tidal",
"title": "Burn this Book",
"aka": [
"Burn this Book"
],
"sounds": [
"90s_megafx",
"90s_synatm",
"cp",
"cpluck",
"drums_nes",
"jazz"
],
"orbits": 7,
"gigs": [
"2022/bazurto"
],
"takes": [],
"recorded": false,
"eda_grounded": false,
"ac_level": "agree",
"license": null,
"inspiration": [],
"collab": [],
"status": null,
"notes": null,
"authored_prov": null
}
]
}
\ No newline at end of file
# ⚠️ DEPRECATED (2026-06-06) — hand-state with no knowable "valid as of".
# The catalog is now GENERATED: `tide.py build catalog` →
# • catalog.generated.json — derived facts (catalog_view) ⊕ authored overlay
# • catalog.authored.yaml — the ONLY hand-maintained facts (license, collab,
# inspiration, status, notes), each with provenance
# Kept for reference only. DO NOT edit as a source of truth — edit the overlay or
# fix the truth at its source (.tidal / site tracks.json), then re-run the build.
#
# tide-table · Table des Vagues — catalog (SEED / worked example) # tide-table · Table des Vagues — catalog (SEED / worked example)
# First real, reconciled data: CosmicFest v0, charted across site + Bandcamp + SoundCloud. # First real, reconciled data: CosmicFest v0, charted across site + Bandcamp + SoundCloud.
# Validates the schema & the performed-vs-released / FR⇄EN aliasing problem. # Validates the schema & the performed-vs-released / FR⇄EN aliasing problem.
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -255,6 +255,14 @@ class TrackMeta(BaseModel): ...@@ -255,6 +255,14 @@ class TrackMeta(BaseModel):
dur: Optional[float] = None dur: Optional[float] = None
class Ingredient(BaseModel):
"""A raw corner-C ingredient (the site's claim) — ground truth for the drawer."""
type: str = ""
code: str = ""
description: str = ""
gig: str = ""
class TakeRef(BaseModel): class TakeRef(BaseModel):
"""Corner B candidate: a take this track plausibly lives in (date-join, L0).""" """Corner B candidate: a take this track plausibly lives in (date-join, L0)."""
take: str take: str
...@@ -279,6 +287,7 @@ class TrackRow(BaseModel): ...@@ -279,6 +287,7 @@ class TrackRow(BaseModel):
score_sounds: list[str] = Field(default_factory=list) score_sounds: list[str] = Field(default_factory=list)
# corner C — metadata # corner C — metadata
claimed_sounds: list[str] = Field(default_factory=list) claimed_sounds: list[str] = Field(default_factory=list)
ingredients: list[Ingredient] = Field(default_factory=list)
# A↔C # A↔C
ac: AgreeResult ac: AgreeResult
# corner B — recording # corner B — recording
...@@ -316,3 +325,37 @@ class CatalogView(BaseModel): ...@@ -316,3 +325,37 @@ class CatalogView(BaseModel):
as_of: str as_of: str
stats: CatalogStats stats: CatalogStats
tracks: list[TrackRow] = Field(default_factory=list) tracks: list[TrackRow] = Field(default_factory=list)
# ── catalog — GENERATED rollup: derived facts ⊕ authored overlay (#51) ────────
# catalog.yaml was hand-state with no "valid as of". The catalog is now generated
# (catalog.generated.json) from catalog_view (derived) ⊕ catalog.authored.yaml
# (the only hand-maintained facts), every entry stamped. parsers-over-copy.
class CatalogEntry(BaseModel):
id: str # canonical = .tidal path
title: str
aka: list[str] = Field(default_factory=list)
# derived (from catalog_view — do not hand-edit)
sounds: list[str] = Field(default_factory=list)
orbits: int = 0
gigs: list[str] = Field(default_factory=list)
takes: list[str] = Field(default_factory=list)
recorded: bool = False
eda_grounded: bool = False
ac_level: AgreeLevel
# authored (from catalog.authored.yaml overlay — may be absent)
license: Optional[str] = None
inspiration: list[str] = Field(default_factory=list)
collab: list[str] = Field(default_factory=list)
status: Optional[str] = None
notes: Optional[str] = None
authored_prov: Optional[Provenance] = None
class Catalog(BaseModel):
model_config = ConfigDict(populate_by_name=True)
schema_: str = Field(alias="schema")
as_of: str
n_tracks: int
n_authored: int
tracks: list[CatalogEntry] = Field(default_factory=list)
"""The GENERATED catalog: derived facts ⊕ authored overlay, validated + merged."""
import build_catalog as bc
def test_catalog_validates_and_merges_overlay():
out = bc.build()
from models import Catalog
cat = Catalog.model_validate(out) # DRY contract
assert cat.n_tracks == len(cat.tracks)
# the authored overlay must actually merge onto the derived rows
by_id = {t.id: t for t in cat.tracks}
sept = by_id.get("live/collab/baba/sept1.tidal")
assert sept and "rhadamanthe" in sept.collab # authored collab credit
assert sept.sounds # derived sounds still present
assert sept.authored_prov is not None # provenance carried through
def test_no_authored_entry_still_yields_a_row():
out = bc.build()
# a track with no overlay must still appear, just without authored fields
rows = [t for t in out["tracks"] if not t["authored_prov"]]
assert rows and all(r["title"] for r in rows)
#!/usr/bin/env python3
"""tide — the tide-table pipeline driver.
ONE reproducible command regenerates every DOWNSTREAM ARTIFACT from its parsers,
in dependency order. The catalog is not hand-maintained state with an unknowable
"valid as of" — it is generated, every artifact stamped (parsers-over-copy). Run
under system python3 (numpy + pydantic).
python3 tide.py build # regenerate all artifacts
python3 tide.py build catalog_view # just one step
python3 tide.py test # run the pytest suite (the mechanical gate)
python3 tide.py list # show the pipeline
Each step is importable and pure-ish (reads the corpus, writes one artifact), so
the same functions back the test suite. Add a step → add one STEPS entry.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent.parent # …/Sound/Tidal
sys.path.insert(0, str(HERE))
def _track_recording_map():
import build_track_recording_map as m
m.main()
return m.OUT
def _catalog_view():
import build_catalog_view as m
m.main() # validates against CatalogView on emit
return m.OUT
def _catalog():
import build_catalog as m
m.main() # validates against Catalog on emit
return m.OUT
def _ts_types():
sys.path.insert(0, str(ROOT / "tools"))
import gen_ts_types as g
g.main()
return g.OUT
# name → (one-line description, fn → artifact Path). Dependency order top-to-bottom.
STEPS = [
("track_recording_map", "L0 metadata map · site tracklist × take_gig_map", _track_recording_map),
("catalog_view", "triangle · A=score ⋈ C=metadata ⋈ B=recording (validated)", _catalog_view),
("catalog", "GENERATED catalog · catalog_view ⊕ authored overlay", _catalog),
("ts_types", "pydantic models → generated TypeScript (DRY)", _ts_types),
]
NAMES = [n for n, _, _ in STEPS]
def cmd_build(which):
todo = which or NAMES
bad = [w for w in todo if w not in NAMES]
if bad:
sys.exit(f"unknown step(s): {', '.join(bad)}\n available: {', '.join(NAMES)}")
print(f"⛵ tide build — {len(todo)} step(s)\n")
for name, desc, fn in STEPS:
if name not in todo:
continue
print(f"▸ {name} ({desc})")
try:
out = fn()
rel = Path(out).relative_to(ROOT) if Path(out).is_absolute() else out
print(f" ✓ {rel}\n")
except Exception as e: # surface, don't swallow — a broken parser must fail loud
print(f" ✗ FAILED: {type(e).__name__}: {e}\n")
raise
print("✓ tide build complete")
def cmd_test():
print("⛵ tide test — pytest\n")
r = subprocess.run([sys.executable, "-m", "pytest", str(HERE / "tests"), "-q"])
sys.exit(r.returncode)
def cmd_list():
print("⛵ tide pipeline (dependency order):\n")
for name, desc, _ in STEPS:
print(f" {name:<22} {desc}")
print("\n test run the pytest suite")
def main():
args = sys.argv[1:]
cmd = args[0] if args else "build"
if cmd == "build":
cmd_build(args[1:])
elif cmd == "test":
cmd_test()
elif cmd in ("list", "ls"):
cmd_list()
else:
sys.exit(f"usage: tide.py [build [step…] | test | list]")
if __name__ == "__main__":
main()
...@@ -72,9 +72,29 @@ tbody tr.sel{background:#ffffff12;outline:1px solid var(--hairline)} ...@@ -72,9 +72,29 @@ tbody tr.sel{background:#ffffff12;outline:1px solid var(--hairline)}
.d-shared{background:#5bc09122;color:var(--agree);border:1px solid #5bc09155} .d-shared{background:#5bc09122;color:var(--agree);border:1px solid #5bc09155}
.d-score{background:#8a93a622;color:var(--atmos);border:1px solid #8a93a655} .d-score{background:#8a93a622;color:var(--atmos);border:1px solid #8a93a655}
.d-meta{background:#ff525222;color:var(--conflict);border:1px solid #ff525255} .d-meta{background:#ff525222;color:var(--conflict);border:1px solid #ff525255}
.tk{display:flex;justify-content:space-between;border:1px solid var(--hairline);border-radius:7px;padding:6px 9px;margin-bottom:5px;font-size:12px} .tk{display:flex;justify-content:space-between;align-items:center;gap:8px;border:1px solid var(--hairline);border-radius:7px;padding:6px 9px;margin-bottom:5px;font-size:12px}
.empty{color:var(--faint);font-style:italic;font-size:12px} .empty{color:var(--faint);font-style:italic;font-size:12px}
a.src{color:var(--melodic);text-decoration:none;font-size:11px}a.src:hover{text-decoration:underline} a.lnk{color:var(--melodic);text-decoration:none}a.lnk:hover{text-decoration:underline}
/* corner badges */
.corner{display:inline-block;width:15px;height:15px;border-radius:4px;text-align:center;
line-height:15px;font-size:10px;font-weight:700;color:#000;margin-right:6px}
.cA{background:var(--melodic)}.cB{background:var(--percs)}.cC{background:var(--tops)}
/* source code view */
.code{background:#000;border:1px solid var(--hairline);border-radius:8px;padding:9px 11px;
max-height:300px;overflow:auto;font-family:"Geist Mono",ui-monospace,monospace;font-size:11px;
line-height:1.5;white-space:pre;color:#cdd6dd;margin-top:6px}
.code .c-com{color:#5a6b73;font-style:italic}
.code .c-str{color:#7fe3b0}
.code .c-dn{color:var(--magenta);font-weight:700}
.toggle{cursor:pointer;color:var(--melodic);font-size:11px;user-select:none}
.toggle:hover{text-decoration:underline}
/* ingredients */
.ing{border:1px solid var(--hairline);border-radius:6px;padding:5px 8px;margin-bottom:4px}
.ing code{color:#7fe3b0;font-size:11px;font-family:"Geist Mono",monospace}
.ing .ds{color:var(--mute);font-size:11px;display:block;margin-top:1px}
.ing .ty{font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:var(--faint)}
audio{width:100%;height:30px;margin-top:5px}
.gigrow{display:flex;justify-content:space-between;border:1px solid var(--hairline);border-radius:6px;padding:5px 8px;margin-bottom:4px;font-size:12px;align-items:center}
</style></head><body> </style></head><body>
<div class="app"> <div class="app">
<header> <header>
...@@ -142,7 +162,8 @@ function boot(){ ...@@ -142,7 +162,8 @@ function boot(){
function match(r){ function match(r){
const q=document.getElementById('q').value.toLowerCase().trim(); const q=document.getElementById('q').value.toLowerCase().trim();
if(q){const hay=(r.name+' '+r.track+' '+r.score_sounds.join(' ')+' '+r.claimed_sounds.join(' ')+' '+r.gigs.join(' ')).toLowerCase(); if(q){const ing=(r.ingredients||[]).map(g=>g.code+' '+g.description).join(' ');
const hay=(r.name+' '+r.track+' '+r.score_sounds.join(' ')+' '+r.claimed_sounds.join(' ')+' '+r.gigs.join(' ')+' '+ing).toLowerCase();
if(!hay.includes(q))return false} if(!hay.includes(q))return false}
if(FILTER==='all')return true; if(FILTER==='all')return true;
if(FILTER==='unrecorded')return !r.recorded; if(FILTER==='unrecorded')return !r.recorded;
...@@ -177,27 +198,67 @@ function render(){ ...@@ -177,27 +198,67 @@ function render(){
document.querySelectorAll('#rows tr').forEach(tr=>tr.onclick=()=>open(+tr.dataset.i)); document.querySelectorAll('#rows tr').forEach(tr=>tr.onclick=()=>open(+tr.dataset.i));
} }
// safe, line-based Tidal highlighter: split comment in RAW, escape, then color
function hl(src){
return src.split('\n').map(line=>{
let raw=line,com='';const k=raw.indexOf('--');
if(k>=0){com='<span class="c-com">'+esc(raw.slice(k))+'</span>';raw=raw.slice(0,k)}
let e=esc(raw).replace(/&quot;([^&]*)&quot;/g,'<span class="c-str">&quot;$1&quot;</span>')
.replace(/^(\s*)(d\d{1,2}|p\d{1,2})\b/,'$1<span class="c-dn">$2</span>');
return e+com;
}).join('\n');
}
function loadSrc(i,track){
fetch('../../'+track).then(x=>x.ok?x.text():null).then(t=>{
const el=document.getElementById('src-'+i);if(!el)return;
el.innerHTML=t!=null?hl(t):'<span class="empty">source not reachable — run serve.py with --dir at the repo root</span>';
}).catch(()=>{const el=document.getElementById('src-'+i);if(el)el.innerHTML='<span class="empty">source fetch failed</span>'});
}
function toggleSrc(i,track){const el=document.getElementById('src-'+i);
if(el.dataset.open==='1'){el.style.display='none';el.dataset.open='0';return}
el.style.display='block';el.dataset.open='1';if(!el.dataset.loaded){el.dataset.loaded='1';loadSrc(i,track)}}
const gigURL=slug=>'https://me.nech.pl/parvagues/live/'+slug.split('/').pop();
function open(i){SEL=i;render();const r=DATA.tracks[i];const lv=r.ac.level,c=css(C[lv]); function open(i){SEL=i;render();const r=DATA.tracks[i];const lv=r.ac.level,c=css(C[lv]);
const orb=Object.entries(r.score).map(([d,snd])=>`<div class="o mono">${d}</div><div class="mono">${esc(snd)}</div>`).join(''); const orb=Object.entries(r.score).map(([d,snd])=>`<div class="o mono">${d}</div><div class="mono">${esc(snd)}</div>`).join('');
const diff=g=>(r.ac[g]||[]).map(s=>`<span class="d-${g==='metadata_only'?'meta':g==='score_only'?'score':'shared'}">${esc(s)}</span>`).join('')||'<span class="empty">none</span>'; const diff=g=>(r.ac[g]||[]).map(s=>`<span class="d-${g==='metadata_only'?'meta':g==='score_only'?'score':'shared'}">${esc(s)}</span>`).join('')||'<span class="empty">none</span>';
// B · recordings — with audio when a take proxy exists
const takes=r.takes.length?r.takes.map(t=>`<div class="tk"><span class="mono">${t.take}</span> const takes=r.takes.length?r.takes.map(t=>`<div class="tk"><span class="mono">${t.take}</span>
<span class="muted">${t.type} · via ${esc(t.via)} · ${t.method}</span> <span class="muted" style="flex:1">${t.type} · via ${esc(t.via)} · ${t.method}</span>
<span class="${t.eda?'eda-yes':'eda-no'}">${t.eda?'◉':'○'}</span></div>`).join(''):'<div class="empty">no candidate take (date-join found nothing)</div>'; <span class="${t.eda?'eda-yes':'eda-no'}" title="EDA-grounded">${t.eda?'◉ EDA':'○'}</span></div>
const gigs=r.gigs.map(g=>`<span class="pill">${esc(g)}</span>`).join(''); <audio controls preload="none" src="punkachien/proxy_${t.take.toLowerCase()}.mp3"
onerror="this.style.display='none'"></audio>`).join('')
:'<div class="empty">no candidate take (date-join found nothing) — unrecorded</div>';
// C · gigs + raw ingredients (the site's claim = ground truth for corner C)
const gigs=r.gigs.map(g=>{const m=(r.metas||[]).find(x=>x.gig===g)||{};
return `<div class="gigrow"><a class="lnk" href="${gigURL(g)}" target="_blank">${esc(g)} ↗</a>
<span class="muted mono">${m.bpm?m.bpm+'bpm ':''}${m.style||''}${m.dur?' · '+Math.round(m.dur)+'s':''}</span></div>`}).join('');
const ings=(r.ingredients||[]).length?r.ingredients.map(g=>`<div class="ing">
<span class="ty">${esc(g.type||'?')}</span> <code>${esc(g.code||'')}</code>
${g.description?`<span class="ds">${esc(g.description)}</span>`:''}</div>`).join('')
:'<div class="empty">no ingredients listed in metadata</div>';
const alias=r.alias_siblings.length?`<div class="sec"><h3>⚠ name shared with other files</h3> const alias=r.alias_siblings.length?`<div class="sec"><h3>⚠ name shared with other files</h3>
${r.alias_siblings.map(s=>`<div class="path mono">${esc(s)}</div>`).join('')} ${r.alias_siblings.map(s=>`<div class="path mono">${esc(s)}</div>`).join('')}
<div class="empty">versions of one track, or a wrong link — ear-verify</div></div>`:''; <div class="empty">versions of one track, or a wrong link — ear-verify</div></div>`:'';
document.getElementById('drawer').innerHTML=`<span class="x" onclick="close_()">✕</span> document.getElementById('drawer').innerHTML=`<span class="x" onclick="close_()">✕</span>
<h2>${esc(r.name)}</h2><div class="path mono">${esc(r.track)}</div> <h2>${esc(r.name)}</h2><div class="path mono">${esc(r.track)}</div>
<div style="margin-top:9px"><span class="tag lvl" style="background:${c}">${lv}${r.ac.precision!=null?' · precision '+Math.round(r.ac.precision*100)+'%':''}</span></div> <div style="margin-top:9px"><span class="tag lvl" style="background:${c}">${lv}${r.ac.precision!=null?' · precision '+Math.round(r.ac.precision*100)+'%':''}</span></div>
<div class="sec"><h3>A · score (${r.n_orbits} orbits)</h3><div class="kv">${orb||'<span class="empty">unparsed</span>'}</div></div>
<div class="sec"><h3><span class="corner cA">A</span>score — ${r.n_orbits} orbits
&nbsp;<span class="toggle" onclick="toggleSrc(${i},'${r.track}')">▾ view .tidal source</span></h3>
<div class="kv">${orb||'<span class="empty">unparsed</span>'}</div>
<pre class="code" id="src-${i}" style="display:none" data-open="0"></pre></div>
<div class="sec"><h3>A↔C diff</h3> <div class="sec"><h3>A↔C diff</h3>
<div class="diff"><div><b class="muted" style="font-size:11px">shared</b><br>${diff('shared')}</div> <div class="diff"><div><b class="muted" style="font-size:11px">shared</b><br>${diff('shared')}</div>
<div style="margin-top:6px"><b class="muted" style="font-size:11px">score-only (often drums C omits)</b><br>${diff('score_only')}</div> <div style="margin-top:6px"><b class="muted" style="font-size:11px">score-only (often drums C omits)</b><br>${diff('score_only')}</div>
<div style="margin-top:6px"><b class="muted" style="font-size:11px">metadata-only (claimed ∉ score)</b><br>${diff('metadata_only')}</div></div></div> <div style="margin-top:6px"><b class="muted" style="font-size:11px">metadata-only (claimed ∉ score)</b><br>${diff('metadata_only')}</div></div></div>
${alias} ${alias}
<div class="sec"><h3>B · candidate recordings</h3>${takes}</div>
<div class="sec"><h3>gigs (${r.gigs.length})</h3>${gigs}</div>`; <div class="sec"><h3><span class="corner cB">B</span>recordings — ${r.n_takes} candidate${r.n_takes!==1?'s':''}</h3>${takes}</div>
<div class="sec"><h3><span class="corner cC">C</span>gigs — ${r.gigs.length}</h3>${gigs}</div>
<div class="sec"><h3>ingredients (site's claim · ${(r.ingredients||[]).length})</h3>${ings}</div>`;
document.getElementById('drawer').classList.add('open'); document.getElementById('drawer').classList.add('open');
} }
function close_(){document.getElementById('drawer').classList.remove('open');SEL=null;render()} function close_(){document.getElementById('drawer').classList.remove('open');SEL=null;render()}
......
...@@ -36,6 +36,14 @@ export interface CatalogStats { ...@@ -36,6 +36,14 @@ export interface CatalogStats {
gigs_total: number gigs_total: number
} }
/** A raw corner-C ingredient (the site's claim) — ground truth for the drawer. */
export interface Ingredient {
type?: string
code?: string
description?: string
gig?: string
}
export interface LoudTrace { export interface LoudTrace {
trace: number[] trace: number[]
stepS: number stepS: number
...@@ -109,6 +117,7 @@ export interface TrackRow { ...@@ -109,6 +117,7 @@ export interface TrackRow {
score?: Record<string, string> score?: Record<string, string>
score_sounds?: string[] score_sounds?: string[]
claimed_sounds?: string[] claimed_sounds?: string[]
ingredients?: Ingredient[]
ac: AgreeResult ac: AgreeResult
takes?: TakeRef[] takes?: TakeRef[]
n_takes: number n_takes: number
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment