Commit d36b478b by PLN (Algolia)

feat(take95): the album exports, and the split was 11 seconds out

Master: 61.0 min, 2 ch, 48 kHz, 24-bit. I = -14.0 LUFS on the nose, peak
-0.9 dBFS under a -1.0 ceiling, LRA 12.7 LU. L-R difference RMS is -22.4 dB, so
it is genuinely stereo and not dual-mono — worth measuring rather than assuming,
given the first attempt collapsed to one channel.

THE BUG THAT WOULD HAVE SHIPPED. take-master trims leading silence before
loudnorm sees it, so the master starts 10.94 s later in the music than the raw
take does. The segments were computed against the RAW take. Splitting with
unshifted offsets puts every boundary eleven seconds late — each track beginning
eleven seconds into the previous one's tail. The output would have played
perfectly and been wrong throughout.

Two guards in take-segments:
  --shift   subtract the measured head trim. Clamped at 0, because the first
            track legitimately goes negative: PLN focuses a .tidal a few seconds
            BEFORE he evaluates it (log says 7.4 s, first sound is at 10.94 s).
  --master  probe the mastered file and clamp to its duration. The master is
            trimmed at the TAIL too (1.45 s), so without this the last segment
            ran past EOF and ffmpeg would have written a short final track in
            silence.
Now: contiguous, starts at 0, sums to 3661.2 s — exactly the master's length.

AND A SHADOWING BUG FOUND WHILE FIXING IT. The per-segment loop bound `start`
and `end`, the same names as the take window, so the summary line printed the
LAST SEGMENT as if it were the whole take: "1970-01-01 01:55 — 5.1 min". The
segments were correct the whole time; only the report lied. That is the kind of
defect that ships, because the payload looks right and nobody re-reads a header.

GENERATIVE METADATA, per PLN's ask, derived not typed:
  title    backlog.md via setlist.py (the SSOT reaches the ID3 tag)
  TBPM     backlog BPM, NOT setcps — reference_tempo_from_audio, setcps is not
           felt tempo. gimme_acid is setcps 80 played as 160 half-time DnB, and
           you_my_sunshine is setcps 144 against a felt 166. Tagging 80 would
           have been a measured number that misleads.
  grouping the set section (Ouverture / SUNSET / NuJazz / NUIT)
  comment  orbit inventory from the .tidal via pvlint's parser, plus the source
           path, so any track can be traced back to the code that made it

Metadata says REHEARSAL, not gig: album "OPAL 2026 — rehearsal, J-6", date
2026-08-02, no venue, no lineup, no poster (feedback_metadata_vs_mastering — gig
metadata is canonical only in Web/www/next/content/lives/).

lime_tacos is track 7 at 1:49. He played it, then cut it from the set an hour
later. The take records what happened, so it stays, tagged "not in the final
set".

Committed: segments.json, track_metadata.json, analysis.json — the provenance.
Gitignored: the FLACs and MP3s, ~1.3 GB of derived audio regenerable from the
Ardour stems in two commands.
parent 844f3dbd
...@@ -24,3 +24,11 @@ preload.scd ...@@ -24,3 +24,11 @@ preload.scd
gig-sclang.log gig-sclang.log
gig-leds.log gig-leds.log
recordings/ recordings/
# Rendered masters and album audio — GBs of derived output, regenerable from
# the Ardour stems with tools/take-master.py + take-segments.py. The segments
# and metadata JSON ARE committed: they are the provenance, not the payload.
output/Take95/*.flac
output/Take95/tracks/
output/Take95/album-mp3/
output/Take95/*.log
{
"Take95_Tidal 01-1%L.wav": {
"peak_dbfs": 8.020756,
"rms_dbfs": -11.80377,
"lufs_i": -11.2,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 01-1%R.wav": {
"peak_dbfs": 8.020756,
"rms_dbfs": -11.80377,
"lufs_i": -11.2,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 02-1%L.wav": {
"peak_dbfs": 6.994869,
"rms_dbfs": -28.784307,
"lufs_i": -19.7,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 02-1%R.wav": {
"peak_dbfs": 6.786762,
"rms_dbfs": -28.834698,
"lufs_i": -19.7,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 03-1%L.wav": {
"peak_dbfs": -3.945711,
"rms_dbfs": -36.584538,
"lufs_i": -31.7,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": false
},
"Take95_Tidal 03-1%R.wav": {
"peak_dbfs": -3.349991,
"rms_dbfs": -36.064853,
"lufs_i": -31.2,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": false
},
"Take95_Tidal 04-1%L.wav": {
"peak_dbfs": 13.334614,
"rms_dbfs": -11.57313,
"lufs_i": -11.0,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 04-1%R.wav": {
"peak_dbfs": 13.334679,
"rms_dbfs": -12.132219,
"lufs_i": -11.5,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 05-1%L.wav": {
"peak_dbfs": 10.606014,
"rms_dbfs": -18.823014,
"lufs_i": -15.8,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 05-1%R.wav": {
"peak_dbfs": 6.603453,
"rms_dbfs": -19.662553,
"lufs_i": -16.7,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 06-1%L.wav": {
"peak_dbfs": 2.363227,
"rms_dbfs": -27.992417,
"lufs_i": -18.2,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 06-1%R.wav": {
"peak_dbfs": 2.363211,
"rms_dbfs": -26.296192,
"lufs_i": -17.0,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 07-1%L.wav": {
"peak_dbfs": 8.020506,
"rms_dbfs": -24.949257,
"lufs_i": -16.8,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 07-1%R.wav": {
"peak_dbfs": 6.299871,
"rms_dbfs": -24.805478,
"lufs_i": -16.6,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 08-1%L.wav": {
"peak_dbfs": 6.046594,
"rms_dbfs": -26.189104,
"lufs_i": -18.3,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 3,
"abs_peak_count": 2,
"is_clipping": true
},
"Take95_Tidal 08-1%R.wav": {
"peak_dbfs": 6.202045,
"rms_dbfs": -26.4706,
"lufs_i": -18.5,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 4,
"abs_peak_count": 2,
"is_clipping": true
},
"Take95_Tidal 09-1%L.wav": {
"peak_dbfs": 4.530354,
"rms_dbfs": -33.301977,
"lufs_i": -21.4,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 09-1%R.wav": {
"peak_dbfs": 0.566371,
"rms_dbfs": -27.413866,
"lufs_i": -17.9,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 15,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 10-1%L.wav": {
"peak_dbfs": 6.411276,
"rms_dbfs": -24.850355,
"lufs_i": -13.4,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 10-1%R.wav": {
"peak_dbfs": 7.077708,
"rms_dbfs": -25.139516,
"lufs_i": -13.9,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 11-1%L.wav": {
"peak_dbfs": 5.299092,
"rms_dbfs": -32.758585,
"lufs_i": -19.5,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 11-1%R.wav": {
"peak_dbfs": 5.013659,
"rms_dbfs": -33.006126,
"lufs_i": -19.9,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 2,
"abs_peak_count": 1,
"is_clipping": true
},
"Take95_Tidal 12-1%L.wav": {
"peak_dbfs": 8.020757,
"rms_dbfs": -30.53279,
"lufs_i": -17.4,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 7,
"abs_peak_count": 3,
"is_clipping": true
},
"Take95_Tidal 12-1%R.wav": {
"peak_dbfs": 8.020728,
"rms_dbfs": -30.750715,
"lufs_i": -17.6,
"tp_dbfs": null,
"lra": null,
"dynamic_range_db": null,
"peak_count": 5,
"abs_peak_count": 4,
"is_clipping": true
}
}
\ No newline at end of file
...@@ -2,77 +2,77 @@ ...@@ -2,77 +2,77 @@
{ {
"track": 1, "track": 1,
"start": 0.0, "start": 0.0,
"end": 233.297, "end": 222.36,
"title": "Ceci n'est pas Une Bombe", "title": "Ceci n'est pas Une Bombe",
"source": "live/midi/nova/techno/bombe_dj.tidal" "source": "live/midi/nova/techno/bombe_dj.tidal"
}, },
{ {
"track": 2, "track": 2,
"start": 233.297, "start": 222.36,
"end": 432.301, "end": 421.364,
"title": "WAP", "title": "WAP",
"source": "live/midi/nova/dnb/wap.tidal" "source": "live/midi/nova/dnb/wap.tidal"
}, },
{ {
"track": 3, "track": 3,
"start": 432.301, "start": 421.364,
"end": 938.297, "end": 927.36,
"title": "Am i _Doing it Right_", "title": "Am i _Doing it Right_",
"source": "live/techno/do_it_right.tidal" "source": "live/techno/do_it_right.tidal"
}, },
{ {
"track": 4, "track": 4,
"start": 938.297, "start": 927.36,
"end": 1557.297, "end": 1546.36,
"title": "Take five Drops", "title": "Take five Drops",
"source": "live/midi/nova/lounge/take_5_drops.tidal" "source": "live/midi/nova/lounge/take_5_drops.tidal"
}, },
{ {
"track": 5, "track": 5,
"start": 1557.297, "start": 1546.36,
"end": 1827.297, "end": 1816.36,
"title": "Piment Bresilien", "title": "Piment Bresilien",
"source": "live/collab/raph/piment_bresilien.tidal" "source": "live/collab/raph/piment_bresilien.tidal"
}, },
{ {
"track": 6, "track": 6,
"start": 1827.297, "start": 1816.36,
"end": 2121.301, "end": 2110.364,
"title": "Eh ouais je Funk", "title": "Eh ouais je Funk",
"source": "live/midi/nova/lounge/ouais_je_funk.tidal" "source": "live/midi/nova/lounge/ouais_je_funk.tidal"
}, },
{ {
"track": 7, "track": 7,
"start": 2121.301, "start": 2110.364,
"end": 2231.297, "end": 2220.36,
"title": "Lime Tacos", "title": "Lime Tacos",
"source": "live/midi/nova/dnb/lime_tacos.tidal" "source": "live/midi/nova/dnb/lime_tacos.tidal"
}, },
{ {
"track": 8, "track": 8,
"start": 2231.297, "start": 2220.36,
"end": 2643.297, "end": 2632.36,
"title": "Perfect <3", "title": "Perfect <3",
"source": "live/midi/nova/remix/perfect.tidal" "source": "live/midi/nova/remix/perfect.tidal"
}, },
{ {
"track": 9, "track": 9,
"start": 2643.297, "start": 2632.36,
"end": 2962.297, "end": 2951.36,
"title": "Gimme Acid", "title": "Gimme Acid",
"source": "live/midi/nova/acid/gimme_acid.tidal" "source": "live/midi/nova/acid/gimme_acid.tidal"
}, },
{ {
"track": 10, "track": 10,
"start": 2962.297, "start": 2951.36,
"end": 3369.297, "end": 3358.36,
"title": "Vague de CRIME", "title": "Vague de CRIME",
"source": "live/techno/vague_de_crime.tidal" "source": "live/techno/vague_de_crime.tidal"
}, },
{ {
"track": 11, "track": 11,
"start": 3369.297, "start": 3358.36,
"end": 3673.621, "end": 3661.231,
"title": "Sunshine", "title": "Sunshine",
"source": "live/midi/nova/dnb/liquid/you_my_sunshine.tidal" "source": "live/midi/nova/dnb/liquid/you_my_sunshine.tidal"
} }
......
[
{
"track": 1,
"title": "Ceci n'est pas Une Bombe",
"start": 0.0,
"end": 222.36,
"duration": 222.36,
"stem": "bombe_dj",
"bpm": 124,
"setcps_bpm": 124.0,
"orbits": [
1,
2,
3,
4,
5,
7,
8,
9,
10
],
"section": "Ouverture",
"in_opal_set": true,
"source": "live/midi/nova/techno/bombe_dj.tidal"
},
{
"track": 2,
"title": "WAP",
"start": 222.36,
"end": 421.364,
"duration": 199.004,
"stem": "wap",
"bpm": 133,
"setcps_bpm": 133.0,
"orbits": [
1,
2,
3,
4,
5,
7,
8,
9,
10
],
"section": "Ouverture",
"in_opal_set": true,
"source": "live/midi/nova/dnb/wap.tidal"
},
{
"track": 3,
"title": "Am i _Doing it Right_",
"start": 421.364,
"end": 927.36,
"duration": 505.996,
"stem": "do_it_right",
"bpm": 89,
"setcps_bpm": 89.0,
"orbits": [
1,
2,
3,
4,
5,
6,
8,
10,
12
],
"section": "SUNSET",
"in_opal_set": true,
"source": "live/techno/do_it_right.tidal"
},
{
"track": 4,
"title": "Take five Drops",
"start": 927.36,
"end": 1546.36,
"duration": 619.0,
"stem": "take_5_drops",
"bpm": 124,
"setcps_bpm": 124.0,
"orbits": [
1,
2,
3,
4,
5,
7,
8,
10,
11,
12
],
"section": "We call it NuJazz",
"in_opal_set": true,
"source": "live/midi/nova/lounge/take_5_drops.tidal"
},
{
"track": 5,
"title": "Piment Bresilien",
"start": 1546.36,
"end": 1816.36,
"duration": 270.0,
"stem": "piment_bresilien",
"bpm": 124,
"setcps_bpm": 124.0,
"orbits": [
1,
2,
3,
4,
5,
7,
8,
10
],
"section": "We call it NuJazz",
"in_opal_set": true,
"source": "live/collab/raph/piment_bresilien.tidal"
},
{
"track": 6,
"title": "Eh ouais je Funk",
"start": 1816.36,
"end": 2110.364,
"duration": 294.004,
"stem": "ouais_je_funk",
"bpm": 120,
"setcps_bpm": 120.0,
"orbits": [
1,
2,
3,
4,
5,
7,
8,
9,
10,
11
],
"section": "We call it NuJazz",
"in_opal_set": true,
"source": "live/midi/nova/lounge/ouais_je_funk.tidal"
},
{
"track": 7,
"title": "Lime Tacos",
"start": 2110.364,
"end": 2220.36,
"duration": 109.996,
"stem": "lime_tacos",
"bpm": 110.0,
"setcps_bpm": 110.0,
"orbits": [
1,
2,
3,
4,
5,
7,
8,
9,
10,
11,
12
],
"section": null,
"in_opal_set": false,
"source": "live/midi/nova/dnb/lime_tacos.tidal"
},
{
"track": 8,
"title": "Perfect <3",
"start": 2220.36,
"end": 2632.36,
"duration": 412.0,
"stem": "perfect",
"bpm": 128,
"setcps_bpm": 128.0,
"orbits": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12
],
"section": "We call it NuJazz",
"in_opal_set": true,
"source": "live/midi/nova/remix/perfect.tidal"
},
{
"track": 9,
"title": "Gimme Acid",
"start": 2632.36,
"end": 2951.36,
"duration": 319.0,
"stem": "gimme_acid",
"bpm": 160,
"setcps_bpm": 80.0,
"orbits": [
1,
2,
3,
4,
5,
8,
9,
10,
11,
12
],
"section": "NUIT",
"in_opal_set": true,
"source": "live/midi/nova/acid/gimme_acid.tidal"
},
{
"track": 10,
"title": "Vague de CRIME",
"start": 2951.36,
"end": 3358.36,
"duration": 407.0,
"stem": "vague_de_crime",
"bpm": 120,
"setcps_bpm": 120.0,
"orbits": [
1,
2,
3,
4,
5,
6,
7,
8,
10
],
"section": "NUIT",
"in_opal_set": true,
"source": "live/techno/vague_de_crime.tidal"
},
{
"track": 11,
"title": "Sunshine",
"start": 3358.36,
"end": 3661.231,
"duration": 302.871,
"stem": "you_my_sunshine",
"bpm": 166,
"setcps_bpm": 144.0,
"orbits": [
1,
2,
3,
4,
5,
6,
8,
9,
10,
11
],
"section": "NUIT",
"in_opal_set": true,
"source": "live/midi/nova/dnb/liquid/you_my_sunshine.tidal"
}
]
\ No newline at end of file
...@@ -167,6 +167,13 @@ def main() -> int: ...@@ -167,6 +167,13 @@ def main() -> int:
ap.add_argument("--log", type=pathlib.Path, help="gig-log jsonl (default: newest)") ap.add_argument("--log", type=pathlib.Path, help="gig-log jsonl (default: newest)")
ap.add_argument("--min-dwell", type=float, default=60.0, ap.add_argument("--min-dwell", type=float, default=60.0,
help="seconds a track must hold to count as played (default 60)") help="seconds a track must hold to count as played (default 60)")
ap.add_argument("--shift", type=float, default=0.0,
help="seconds to subtract from every offset — the LEADING "
"silence take-master trimmed off the head of the master")
ap.add_argument("--master", type=pathlib.Path,
help="the mastered file these segments will slice; its "
"duration clamps the last segment (the master is "
"trimmed at the tail too)")
ap.add_argument("-o", "--output", type=pathlib.Path, ap.add_argument("-o", "--output", type=pathlib.Path,
help="write JSON here (default: stdout table only)") help="write JSON here (default: stdout table only)")
args = ap.parse_args() args = ap.parse_args()
...@@ -178,18 +185,50 @@ def main() -> int: ...@@ -178,18 +185,50 @@ def main() -> int:
raise SystemExit(f"take-segments: no track events inside {args.take}'s window — " raise SystemExit(f"take-segments: no track events inside {args.take}'s window — "
f"was gig-log running?") f"was gig-log running?")
# THE OFFSETS ARE RELATIVE TO THE RAW TAKE; THE MASTER IS EDGE-TRIMMED.
# take-master strips leading silence before loudnorm, so a master built from
# Take95 starts 10.94 s later in the music than the take does. Splitting the
# master with unshifted offsets puts every boundary ~11 s late — each track
# would begin eleven seconds into the previous one's tail. Measure the head
# trim (silencedetect at -60 dB on the summed stems) and pass it as --shift.
#
# Clamped at 0 because the first track legitimately goes negative: PLN
# focuses a .tidal file a few seconds BEFORE he evaluates it, so the log's
# first `track` event (7.4 s) precedes the first sound (10.94 s).
master_len = None
if args.master:
if not args.master.exists():
raise SystemExit(f"take-segments: no master at {args.master}")
r = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of", "csv=p=0",
str(args.master)], capture_output=True, text=True)
master_len = float(r.stdout.strip())
tmap = titles() tmap = titles()
out = [] out = []
# NOT `start`/`end` — those name the TAKE WINDOW above, and shadowing them
# here made the header report the last segment as if it were the whole take
# ("1970-01-01, 5.1 min"). The segments were right; only the summary lied,
# which is the kind of bug you ship because the payload looks fine.
for i, s in enumerate(segs, 1): for i, s in enumerate(segs, 1):
stem = pathlib.Path(s["path"]).stem stem = pathlib.Path(s["path"]).stem
out.append({"track": i, "start": round(s["start"], 3), t0 = max(0.0, s["start"] - args.shift)
"end": round(s["end"], 3), t1 = max(t0, s["end"] - args.shift)
if master_len is not None:
# The master is trimmed at BOTH ends. Without this the final segment
# runs 1.5 s past EOF and ffmpeg silently writes a short last track.
t0, t1 = min(t0, master_len), min(t1, master_len)
out.append({"track": i, "start": round(t0, 3),
"end": round(t1, 3),
"title": tmap.get(stem) or prettify(stem), "title": tmap.get(stem) or prettify(stem),
"source": s["path"]}) "source": s["path"]})
out = [s for s in out if s["end"] - s["start"] > 1.0]
print(f"{args.take}: {dt.datetime.fromtimestamp(start):%Y-%m-%d %H:%M} — " print(f"{args.take}: {dt.datetime.fromtimestamp(start):%Y-%m-%d %H:%M} — "
f"{(end - start) / 60:.1f} min, {len(out)} tracks " f"{(end - start) / 60:.1f} min, {len(out)} tracks "
f"(min-dwell {args.min_dwell:.0f}s)\n") f"(min-dwell {args.min_dwell:.0f}s"
+ (f", shift -{args.shift:.2f}s" if args.shift else "")
+ (f", clamped to master {master_len:.1f}s" if master_len else "") + ")\n")
for s in out: for s in out:
d = s["end"] - s["start"] d = s["end"] - s["start"]
print(f" {s['track']:>2}. {int(s['start'])//60:>3}:{int(s['start'])%60:02d}" print(f" {s['track']:>2}. {int(s['start'])//60:>3}:{int(s['start'])%60:02d}"
......
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