Commit 96bc7bcd by PLN (Algolia)

feat(tide-table): pack-level sample grouping above the folder (#85)

PLN's insight: the FOLDER is a loose convention, not the true grouping. rample*
is ONE bank sliced A0..S57; 808* is one drum machine split per voice; *_commodore
is one chip sliced by role. So adds the PACK granularity above the folder —
per-pack / per-folder / per-sample, weakest-to-strongest binding (per-sample stays
the only ground truth).

sample_packs.py: a curated REGISTRY (drum_machine / bank / chip / source / genre)
+ an auto-grouper (prefix-before-digit & underscore head/tail tokens, ≥2 folders)
that PROPOSES packs the registry missed — it caught trance_* and voices_*, now
folded in. A pack needs ≥2 grounded folders (a 1-folder pack is just a folder; it
may gain siblings when #84 grounds all 718 — provenance notes this).

Rolls the per-SAMPLE family counts up to the pack and asks the real question:
does a pack live in one audio family, or span the wheel? 22 packs over 83/150
folders; 15 of them deliberately span multiple families:

  rample  → 8 families (snare/kick/hat/fx/break/pad…) — a bank IS a grab-bag
  jungle  → 11 families — a whole genre's kit
  808     → kick/hat/perc — one machine, three voices
  commodore → bass/fx/pad/lead/synth — the chip, every role

That origin-vs-sound mismatch is the point, not noise: a pack defined by ORIGIN
(a song, a machine, a console) is EXPECTED to scatter across the audio clusters.
Feeds the Unwrapped viz (group-by-pack vs audio-cluster as a story lens) and
vibe-search scoping. Never lets a pack boundary bias the audio analysis — it's a
hint to compare against, never a law.
parent 4e5d6a23
{
"schema": "sample packs \u2014 origin grouping above the folder convention",
"method": "curated registry over grounded sample_families (per-sample rollup)",
"n_folders": 150,
"n_packs": 22,
"n_folders_packed": 83,
"provenance": "sample_packs.py \u00b7 REGISTRY + sample_families.json (grounded subset of 150 folders; re-run after #84 grounds all 718)",
"packs": {
"808": {
"type": "drum_machine",
"source": "registry",
"folders": [
"808bd",
"808cy",
"808hc",
"808mc"
],
"n_samples": 34,
"n_resolved": 34,
"folder_dominants": [
"kick",
"hat",
"perc",
"perc"
],
"agree": 4,
"disagree": 0,
"na": 0,
"n_folders": 4,
"families_spanned": {
"kick": 12,
"hat": 12,
"perc": 10
},
"dominant_family": "kick",
"dominant_frac": 0.353,
"n_families": 3,
"cohesion": "multi",
"story": "drum_machine pack spans 3 families by design \u2014 kick 12, hat 12, perc 10"
},
"90s": {
"type": "genre",
"source": "registry",
"folders": [
"90s_matrix",
"90s_megafx",
"90s_synatm"
],
"n_samples": 31,
"n_resolved": 31,
"folder_dominants": [
"synth",
"bass",
"synth"
],
"agree": 2,
"disagree": 0,
"na": 1,
"n_folders": 3,
"families_spanned": {
"synth": 13,
"bass": 5,
"pad": 4,
"vox": 3,
"fx": 2,
"kick": 2,
"lead": 2
},
"dominant_family": "synth",
"dominant_frac": 0.419,
"n_families": 7,
"cohesion": "multi",
"story": "genre pack spans 7 families by design \u2014 synth 13, bass 5, pad 4, vox 3, fx 2, kick 2, lead 2"
},
"commodore": {
"type": "chip",
"source": "registry",
"folders": [
"bass_commodore",
"fx_commodore",
"synth_commodore"
],
"n_samples": 36,
"n_resolved": 36,
"folder_dominants": [
"bass",
"fx",
"pad"
],
"agree": 0,
"disagree": 3,
"na": 0,
"n_folders": 3,
"families_spanned": {
"bass": 12,
"fx": 12,
"pad": 8,
"lead": 2,
"synth": 2
},
"dominant_family": "bass",
"dominant_frac": 0.333,
"n_families": 5,
"cohesion": "multi",
"story": "chip pack spans 5 families by design \u2014 bass 12, fx 12, pad 8, lead 2, synth 2"
},
"gameboy": {
"type": "chip",
"source": "registry",
"folders": [
"bass_gameboy",
"fx_gameboy",
"synth_gameboy"
],
"n_samples": 36,
"n_resolved": 36,
"folder_dominants": [
"bass",
"fx",
"synth"
],
"agree": 3,
"disagree": 0,
"na": 0,
"n_folders": 3,
"families_spanned": {
"bass": 12,
"fx": 12,
"synth": 8,
"pad": 4
},
"dominant_family": "bass",
"dominant_frac": 0.333,
"n_families": 4,
"cohesion": "multi",
"story": "chip pack spans 4 families by design \u2014 bass 12, fx 12, synth 8, pad 4"
},
"nes": {
"type": "chip",
"source": "registry",
"folders": [
"bass_nes",
"drums_nes"
],
"n_samples": 24,
"n_resolved": 24,
"folder_dominants": [
"bass",
"break"
],
"agree": 2,
"disagree": 0,
"na": 0,
"n_folders": 2,
"families_spanned": {
"bass": 13,
"break": 8,
"fx": 2,
"snare": 1
},
"dominant_family": "bass",
"dominant_frac": 0.542,
"n_families": 4,
"cohesion": "multi",
"story": "chip pack spans 4 families by design \u2014 bass 13, break 8, fx 2, snare 1"
},
"breaks": {
"type": "genre",
"source": "registry",
"folders": [
"breaks125",
"breaks165"
],
"n_samples": 3,
"n_resolved": 3,
"folder_dominants": [
"break",
"break"
],
"agree": 2,
"disagree": 0,
"na": 0,
"n_folders": 2,
"families_spanned": {
"break": 3
},
"dominant_family": "break",
"dominant_frac": 1.0,
"n_families": 1,
"cohesion": "single",
"story": "coheres as break (break 3)"
},
"come": {
"type": "source",
"source": "registry",
"folders": [
"come_bass",
"come_eguitar",
"come_guitar",
"come_voice"
],
"n_samples": 33,
"n_resolved": 33,
"folder_dominants": [
"bass",
"keys",
"keys",
"vox"
],
"agree": 3,
"disagree": 0,
"na": 1,
"n_folders": 4,
"families_spanned": {
"vox": 13,
"keys": 12,
"bass": 4,
"lead": 3,
"synth": 1
},
"dominant_family": "vox",
"dominant_frac": 0.394,
"n_families": 5,
"cohesion": "multi",
"story": "source pack spans 5 families by design \u2014 vox 13, keys 12, bass 4, lead 3, synth 1"
},
"dr": {
"type": "drum_machine",
"source": "registry",
"folders": [
"dr",
"dr2",
"dr55",
"dr_few"
],
"n_samples": 30,
"n_resolved": 30,
"folder_dominants": [
"snare",
"hat",
"snare",
"kick"
],
"agree": 1,
"disagree": 0,
"na": 3,
"n_folders": 4,
"families_spanned": {
"snare": 12,
"kick": 9,
"hat": 6,
"fx": 1,
"lead": 1,
"perc": 1
},
"dominant_family": "snare",
"dominant_frac": 0.4,
"n_families": 6,
"cohesion": "multi",
"story": "drum_machine pack spans 6 families by design \u2014 snare 12, kick 9, hat 6, fx 1, lead 1, perc 1"
},
"atari": {
"type": "chip",
"source": "registry",
"folders": [
"drums_atari",
"synth_atari"
],
"n_samples": 24,
"n_resolved": 24,
"folder_dominants": [
"fx",
"synth"
],
"agree": 2,
"disagree": 0,
"na": 0,
"n_folders": 2,
"families_spanned": {
"fx": 12,
"synth": 12
},
"dominant_family": "fx",
"dominant_frac": 0.5,
"n_families": 2,
"cohesion": "multi",
"story": "chip pack spans 2 families by design \u2014 fx 12, synth 12"
},
"fbreak": {
"type": "genre",
"source": "registry",
"folders": [
"fbreak100",
"fbreak120",
"fbreak80"
],
"n_samples": 36,
"n_resolved": 36,
"folder_dominants": [
"break",
"break",
"break"
],
"agree": 3,
"disagree": 0,
"na": 0,
"n_folders": 3,
"families_spanned": {
"break": 36
},
"dominant_family": "break",
"dominant_frac": 1.0,
"n_families": 1,
"cohesion": "single",
"story": "coheres as break (break 36)"
},
"h2ogm": {
"type": "drum_machine",
"source": "registry",
"folders": [
"h2ogmcp",
"h2ogmcy",
"h2ogmhh",
"h2ogmsn"
],
"n_samples": 37,
"n_resolved": 37,
"folder_dominants": [
"snare",
"hat",
"hat",
"snare"
],
"agree": 4,
"disagree": 0,
"na": 0,
"n_folders": 4,
"families_spanned": {
"hat": 24,
"snare": 11,
"perc": 2
},
"dominant_family": "hat",
"dominant_frac": 0.649,
"n_families": 3,
"cohesion": "multi",
"story": "drum_machine pack spans 3 families by design \u2014 hat 24, snare 11, perc 2"
},
"jungle": {
"type": "genre",
"source": "registry",
"folders": [
"jungbass",
"jungle",
"jungle_bass",
"jungle_breaks",
"jungle_pads",
"jungle_vocals",
"org_jungle_breaks"
],
"n_samples": 84,
"n_resolved": 84,
"folder_dominants": [
"bass",
"hat",
"bass",
"break",
"pad",
"vox",
"break"
],
"agree": 6,
"disagree": 0,
"na": 1,
"n_folders": 7,
"families_spanned": {
"break": 24,
"bass": 23,
"vox": 12,
"pad": 10,
"hat": 6,
"kick": 3,
"snare": 2,
"fx": 1,
"perc": 1,
"keys": 1,
"lead": 1
},
"dominant_family": "break",
"dominant_frac": 0.286,
"n_families": 11,
"cohesion": "multi",
"story": "genre pack spans 11 families by design \u2014 break 24, bass 23, vox 12, pad 10, hat 6, kick 3, snare 2, fx 1, perc 1, keys 1, lead 1"
},
"movie": {
"type": "source",
"source": "registry",
"folders": [
"movie_cat",
"movie_giant",
"movie_paris",
"movie_vitality",
"movie_wolf"
],
"n_samples": 60,
"n_resolved": 60,
"folder_dominants": [
"vox",
"vox",
"vox",
"vox",
"vox"
],
"agree": 5,
"disagree": 0,
"na": 0,
"n_folders": 5,
"families_spanned": {
"vox": 58,
"perc": 1,
"fx": 1
},
"dominant_family": "vox",
"dominant_frac": 0.967,
"n_families": 3,
"cohesion": "multi",
"story": "source pack spans 3 families by design \u2014 vox 58, perc 1, fx 1"
},
"nujazz": {
"type": "source",
"source": "registry",
"folders": [
"nujazz_bass120",
"nujazz_bass125",
"nujazz_beats120",
"nujazz_guitar120",
"nujazz_keys120",
"nujazz_keys125",
"nujazz_wahwah125"
],
"n_samples": 84,
"n_resolved": 84,
"folder_dominants": [
"bass",
"bass",
"break",
"keys",
"keys",
"lead",
"keys"
],
"agree": 6,
"disagree": 1,
"na": 0,
"n_folders": 7,
"families_spanned": {
"keys": 33,
"bass": 24,
"break": 12,
"lead": 10,
"synth": 5
},
"dominant_family": "keys",
"dominant_frac": 0.393,
"n_families": 5,
"cohesion": "multi",
"story": "source pack spans 5 families by design \u2014 keys 33, bass 24, break 12, lead 10, synth 5"
},
"rample": {
"type": "bank",
"source": "registry",
"folders": [
"rampleA0",
"rampleA2",
"rampleC5",
"rampleD2",
"rampleM1",
"rampleM6",
"rampleM8",
"rampleP3",
"rampleS0",
"rampleS13",
"rampleS34",
"rampleS37",
"rampleS57"
],
"n_samples": 128,
"n_resolved": 128,
"folder_dominants": [
"kick",
"kick",
"kick",
"hat",
"fx",
"break",
"snare",
"kick",
"hat",
"snare",
"snare",
"hat",
"kick"
],
"agree": 3,
"disagree": 3,
"na": 7,
"n_folders": 13,
"families_spanned": {
"snare": 44,
"kick": 43,
"hat": 19,
"fx": 10,
"break": 6,
"pad": 4,
"synth": 1,
"bass": 1
},
"dominant_family": "snare",
"dominant_frac": 0.344,
"n_families": 8,
"cohesion": "multi",
"story": "bank pack spans 8 families by design \u2014 snare 44, kick 43, hat 19, fx 10, break 6, pad 4, synth 1, bass 1"
},
"rhadamanthe": {
"type": "source",
"source": "registry",
"folders": [
"rhadamanthe_melo",
"rhadamanthe_vocal"
],
"n_samples": 24,
"n_resolved": 24,
"folder_dominants": [
"vox",
"vox"
],
"agree": 1,
"disagree": 0,
"na": 1,
"n_folders": 2,
"families_spanned": {
"vox": 12,
"fx": 3,
"pad": 3,
"keys": 2,
"bass": 2,
"synth": 1,
"hat": 1
},
"dominant_family": "vox",
"dominant_frac": 0.5,
"n_families": 7,
"cohesion": "multi",
"story": "source pack spans 7 families by design \u2014 vox 12, fx 3, pad 3, keys 2, bass 2, synth 1, hat 1"
},
"sunny": {
"type": "source",
"source": "registry",
"folders": [
"sunny_bass",
"sunny_brass"
],
"n_samples": 14,
"n_resolved": 14,
"folder_dominants": [
"bass",
"pad"
],
"agree": 1,
"disagree": 1,
"na": 0,
"n_folders": 2,
"families_spanned": {
"bass": 7,
"pad": 7
},
"dominant_family": "bass",
"dominant_frac": 0.5,
"n_families": 2,
"cohesion": "multi",
"story": "source pack spans 2 families by design \u2014 bass 7, pad 7"
},
"suns": {
"type": "source",
"source": "registry",
"folders": [
"suns_guitar",
"suns_keys",
"suns_voice"
],
"n_samples": 17,
"n_resolved": 17,
"folder_dominants": [
"keys",
"keys",
"vox"
],
"agree": 3,
"disagree": 0,
"na": 0,
"n_folders": 3,
"families_spanned": {
"keys": 12,
"vox": 5
},
"dominant_family": "keys",
"dominant_frac": 0.706,
"n_families": 2,
"cohesion": "single",
"story": "coheres as keys (keys 12, vox 5)"
},
"trance": {
"type": "genre",
"source": "registry",
"folders": [
"trance_amb",
"trance_pads"
],
"n_samples": 24,
"n_resolved": 24,
"folder_dominants": [
"lead",
"pad"
],
"agree": 1,
"disagree": 1,
"na": 0,
"n_folders": 2,
"families_spanned": {
"lead": 10,
"pad": 8,
"vox": 4,
"keys": 1,
"bass": 1
},
"dominant_family": "lead",
"dominant_frac": 0.417,
"n_families": 5,
"cohesion": "multi",
"story": "genre pack spans 5 families by design \u2014 lead 10, pad 8, vox 4, keys 1, bass 1"
},
"vec1": {
"type": "bank",
"source": "registry",
"folders": [
"vec1_acid",
"vec1_claps",
"vec1_snare"
],
"n_samples": 36,
"n_resolved": 36,
"folder_dominants": [
"lead",
"snare",
"snare"
],
"agree": 2,
"disagree": 1,
"na": 0,
"n_folders": 3,
"families_spanned": {
"snare": 24,
"lead": 12
},
"dominant_family": "snare",
"dominant_frac": 0.667,
"n_families": 2,
"cohesion": "single",
"story": "coheres as snare (snare 24, lead 12)"
},
"vocal": {
"type": "source",
"source": "registry",
"folders": [
"vocalOoh1",
"vocalScatAr",
"vocalScatJ"
],
"n_samples": 36,
"n_resolved": 36,
"folder_dominants": [
"pad",
"vox",
"vox"
],
"agree": 2,
"disagree": 1,
"na": 0,
"n_folders": 3,
"families_spanned": {
"vox": 24,
"pad": 12
},
"dominant_family": "vox",
"dominant_frac": 0.667,
"n_families": 2,
"cohesion": "single",
"story": "coheres as vox (vox 24, pad 12)"
},
"voices": {
"type": "source",
"source": "registry",
"folders": [
"voices_celtic",
"voices_persian"
],
"n_samples": 24,
"n_resolved": 24,
"folder_dominants": [
"vox",
"vox"
],
"agree": 2,
"disagree": 0,
"na": 0,
"n_folders": 2,
"families_spanned": {
"vox": 24
},
"dominant_family": "vox",
"dominant_frac": 1.0,
"n_families": 1,
"cohesion": "single",
"story": "coheres as vox (vox 24)"
}
},
"unpacked_folders": [
"909",
"ai_welcome",
"airports",
"amencutup",
"aoc_fr",
"armora",
"bass1",
"bogdan",
"cbow",
"ccc",
"celtic_guitar80",
"clubkick",
"cosmicg",
"cp",
"cpluck",
"db",
"desire",
"drum",
"drumtraks",
"electro1",
"fbass",
"feelfx",
"fepiano",
"fguitar",
"forgan",
"fpiano",
"fsynth",
"gfunk_lead",
"ghost",
"giorgio_syn",
"glitch",
"gretsch",
"gtkick",
"hh",
"hh27",
"ho",
"house",
"ifdrums",
"jane_wang",
"jazz",
"kicklinn",
"law_adri",
"like_sugar",
"meth_bass",
"moog",
"no_sunshine",
"oil",
"orage",
"pad",
"praise",
"private_number",
"punk",
"reverbkick",
"risers",
"scratch_long",
"shiloh",
"sn",
"sogood",
"take5",
"tech",
"techno",
"temptation",
"the_revolution",
"vec2_synth_acid",
"wap",
"weird_dialogs",
"xplosive"
],
"auto_proposals": {}
}
\ No newline at end of file
#!/usr/bin/env python3
"""sample_packs — derive the "pack" granularity ABOVE the loose folder convention.
Three granularities of sample grouping, weakest-binding to strongest:
per-pack (rample, 808, commodore…) ← THIS tool: a real-world origin
per-folder (rampleA0, 808bd…) loose convention; sometimes a kit
per-sample (BD0000.wav) the ONLY ground truth (sample_resolve)
PLN's insight (#85): the FOLDER is a loose convention, not the true grouping.
`rample*` (rampleA0..rampleS57) is ONE sample pack sliced into banks; `808*`
(808bd/cy/hc/mc) is one drum machine split per voice; `bass_commodore`/`fx_commodore`/
`synth_commodore` are one CHIP sliced by role. Folder maps to family "mostly but not
always" — so a pack maps to ONE audio family even less often, and that mismatch is
itself a story: a pack defined by *origin* (a song, a machine, a console) deliberately
spans the family wheel. We DERIVE packs and COMPARE them to the audio clusters; we
never let a pack boundary bias the audio analysis (it's a hint, never a law).
Reads `sample_families.json` (the grounded L1+L3 resolution) — no audio, fast.
python3 sample_packs.py # summary table (packs, cohesion, the mismatches)
python3 sample_packs.py --json # write sample_packs.json
python3 sample_packs.py --auto # also show auto-proposed packs not in the registry
See [[feedback_sample_grouping_loose]], [[reference_fleet_color_language]]."""
import json
import re
import sys
from collections import Counter, defaultdict
from pathlib import Path
HERE = Path(__file__).resolve().parent
FAMILIES = HERE / "sample_families.json"
OUT = HERE / "sample_packs.json"
# ── Curated pack registry ────────────────────────────────────────────────────
# A pack is a real-world ORIGIN that got sliced into several Dirt-Samples folders.
# `match` is a predicate over the folder name. Order matters: first hit wins.
# Types:
# drum_machine — one box, one folder per voice (808 → bd/cy/hc/mc)
# bank — one sample library sliced into indexed banks (rample A0..S57)
# chip — one console/computer sound chip, sliced by role (commodore)
# source — one song / recording / film sliced into stems (come_*, movie_*)
# genre — a themed collection spread over tempo/variant folders (nujazz_*)
# Each entry: (pack, type, matcher). Matcher gets the folder name, returns bool.
def _pre(*prefixes):
return lambda n: any(n.startswith(p) for p in prefixes)
def _exact(*names):
s = set(names)
return lambda n: n in s
REGISTRY = [
# drum machines / sliced-by-voice
("808", "drum_machine", _pre("808")),
("h2ogm", "drum_machine", _pre("h2ogm")), # General-MIDI drum kit, per voice
# sliced sample banks
("rample", "bank", _pre("rample")),
("vec1", "bank", _pre("vec1_")),
("vec2", "bank", _pre("vec2_")),
# chip / console families, sliced by role (the SAME chip across bass/synth/fx/drums)
("commodore", "chip", lambda n: n.endswith("_commodore")),
("gameboy", "chip", lambda n: n.endswith("_gameboy")),
("nes", "chip", lambda n: n.endswith("_nes")),
("atari", "chip", lambda n: n.endswith("_atari")),
# one song / film / recording, sliced into stems
("come", "source", _pre("come_")),
("suns", "source", _pre("suns_")),
("sunny", "source", _pre("sunny_")),
("movie", "source", _pre("movie_")),
("rhadamanthe","source", _pre("rhadamanthe_")),
("nujazz", "source", _pre("nujazz_")),
# themed collections over tempo/variant folders
("jungle", "genre", lambda n: "jungle" in n or n == "jungbass"),
("breaks", "genre", _pre("breaks")),
("fbreak", "genre", _pre("fbreak")),
("90s", "genre", _pre("90s_")),
("trance", "genre", _pre("trance_")),
# vocal collections (different singers/langs, grouped as a vocal-samples pack)
("voices", "source", _pre("voices_")),
("vocal", "source", _pre("vocal")), # vocalOoh1, vocalScatAr/J
("dr", "drum_machine", _exact("dr", "dr2", "dr55", "dr_few")),
]
def assign_pack(folder):
for pack, typ, match in REGISTRY:
if match(folder):
return pack, typ, "registry"
return None
def _auto_proposals(unpacked):
"""Heuristic packs from shared structure, for folders no registry rule claimed.
Two axes: prefix-before-digit and underscore tokens (head AND tail). A proposal
needs ≥2 member folders. These are SUGGESTIONS to review, never authoritative."""
pre, head, tail = defaultdict(list), defaultdict(list), defaultdict(list)
for f in unpacked:
m = re.match(r"^([a-z]+?)\d", f) # alpha run before first digit
if m and len(m.group(1)) >= 2:
pre[m.group(1)].append(f)
if "_" in f:
h, _, t = f.partition("_")
head[h].append(f)
tail[t].append(f)
out = {}
for axis, groups in (("prefix", pre), ("head", head), ("tail", tail)):
for key, members in groups.items():
if len(members) >= 2:
out.setdefault(key, {"axis": axis, "folders": sorted(members)})
return out
def build(families):
fam = families["families"]
packs = {}
unpacked = []
for folder, info in fam.items():
hit = assign_pack(folder)
if not hit:
unpacked.append(folder)
continue
pack, typ, src = hit
p = packs.setdefault(pack, {
"type": typ, "source": src, "folders": [],
"n_samples": 0, "n_resolved": 0,
"family_dist": Counter(), "folder_dominants": [],
"agree": 0, "disagree": 0, "na": 0,
})
p["folders"].append(folder)
p["n_samples"] += info.get("n", 0)
# roll the per-sample (ground-truth) family counts up to the pack
for r in info.get("per_index", []):
if r.get("family"):
p["family_dist"][r["family"]] += 1
p["n_resolved"] += 1
p["folder_dominants"].append(info.get("dominant"))
agr = info.get("folder_agrees")
p["agree" if agr is True else "disagree" if agr is False else "na"] += 1
# A pack needs ≥2 member folders to be a pack: within the currently-grounded folder
# set a 1-folder pack is just a folder (it may gain siblings when #84 grounds all 718).
for pack in [k for k, p in packs.items() if len(p["folders"]) < 2]:
unpacked.extend(packs.pop(pack)["folders"])
# finalize: cohesion verdict (does the pack live in one audio family or span the wheel?)
for pack, p in packs.items():
dist = p.pop("family_dist")
p["folders"].sort()
p["n_folders"] = len(p["folders"])
p["families_spanned"] = dict(dist.most_common())
n = sum(dist.values())
if n:
top, tn = dist.most_common(1)[0]
p["dominant_family"] = top
p["dominant_frac"] = round(tn / n, 3)
p["n_families"] = len(dist)
p["cohesion"] = "single" if tn / n >= 0.6 and len(dist) <= 2 else "multi"
else:
p["dominant_family"], p["dominant_frac"] = None, 0.0
p["n_families"], p["cohesion"] = 0, "unknown"
# story line: why this pack does/doesn't map to one family
fams = ", ".join(f"{k} {v}" for k, v in p["families_spanned"].items())
if p["cohesion"] == "single":
p["story"] = f"coheres as {p['dominant_family']} ({fams})"
else:
p["story"] = (f"{p['type']} pack spans {p['n_families']} families "
f"by design — {fams}")
return packs, sorted(unpacked)
def main():
args = sys.argv[1:]
families = json.loads(FAMILIES.read_text())
packs, unpacked = build(families)
auto = _auto_proposals(unpacked)
if "--json" in args:
OUT.write_text(json.dumps({
"schema": "sample packs — origin grouping above the folder convention",
"method": "curated registry over grounded sample_families (per-sample rollup)",
"n_folders": families["n_folders"],
"n_packs": len(packs),
"n_folders_packed": sum(p["n_folders"] for p in packs.values()),
"provenance": (f"sample_packs.py · REGISTRY + sample_families.json "
f"(grounded subset of {families['n_folders']} folders; "
f"re-run after #84 grounds all 718)"),
"packs": packs,
"unpacked_folders": unpacked,
"auto_proposals": auto,
}, indent=1))
print(f"✓ {OUT.name} — {len(packs)} packs over "
f"{sum(p['n_folders'] for p in packs.values())} folders")
return
print(f"⛵ {len(packs)} packs over {sum(p['n_folders'] for p in packs.values())} "
f"of {families['n_folders']} folders\n")
print(f" {'pack':<13}{'type':<13}{'fld':>4}{'smp':>5} cohesion story")
multi = []
for pack in sorted(packs, key=lambda k: -packs[k]["n_folders"]):
p = packs[pack]
mark = "·" if p["cohesion"] == "single" else "✶"
print(f" {pack:<13}{p['type']:<13}{p['n_folders']:>4}{p['n_samples']:>5}"
f" {mark} {p['cohesion']:<6} {p['story']}")
if p["cohesion"] == "multi":
multi.append(pack)
npk = sum(p["n_folders"] for p in packs.values())
print(f"\n {len(multi)} packs span multiple audio families (✶) — the origin-vs-sound "
f"mismatch the viz can tell:\n {', '.join(multi)}")
print(f" {len(unpacked)} folders are singletons (no pack) — folder == origin there.")
if "--auto" in args and auto:
print(f"\n auto-proposals (review, not in registry): {len(auto)}")
for key, a in sorted(auto.items()):
print(f" {key:<14}({a['axis']}) {', '.join(a['folders'])}")
if __name__ == "__main__":
main()
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