Compare commits
7
Commits
main
...
exp/accuracy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b7a1cc6c9 | ||
|
|
48ae9780a6 | ||
|
|
79130c0aca | ||
|
|
132659a99c | ||
|
|
19ac52c93a | ||
|
|
c3e751b9e7 | ||
|
|
0f14fbc574 |
@@ -0,0 +1,44 @@
|
||||
# Accuracy experiments — 2026-10-31
|
||||
|
||||
Branch `exp/accuracy`. Every experiment is judged on **both** the synthetic tiers
|
||||
(median merged-guitar pitch F1, ±50 ms) and the real reference song
|
||||
*Last Night on Earth* (`lnoe`, the only reference with a trustworthy alignment:
|
||||
constant +0.03 s, precision 93%). Swimming/Holes alignments are still flagged and
|
||||
are not used for decisions.
|
||||
|
||||
Baseline before these experiments: synthetic full-mode guitar F1
|
||||
t1a 0.43 · t1b 0.51 · t1c 0.39 · t1d 0.60; lnoe guitar F1 0.390.
|
||||
|
||||
| # | Experiment | Synthetic (median guitar F1) | lnoe guitar F1 | Verdict |
|
||||
|---|---|---|---|---|
|
||||
| E1 | Transcribe guitar from Demucs guitar + piano + other (instead of guitar stem only) | t1a 0.87 · t1b 0.81 · t1c 0.73 · t1d 0.72 | 0.403 | **Adopted** (commit 0f14fbc). Big synthetic gain is mostly a SONiVOX-timbre artefact (Demucs routes the synth guitar to piano/other); real gain is small but positive. t1a now passes. |
|
||||
| E2 | Basic Pitch thresholds grid (onset 0.3–0.6, frame 0.2–0.4, min note 58/100 ms) | best ≈ default (0.5/0.3/58) | best 0.411 vs 0.403 | Not adopted — defaults are at the optimum on both. |
|
||||
| E3 | Error taxonomy | — | fp: 46% no reference note near, 16% octave, 21% 50–150 ms timing; fn: 47% different pitch at the onset (partial chords), 25% octave, 24% missed | Octave fixing upper bound only 0.40 → 0.46 (pitch-class F1) with no systematic direction. |
|
||||
| E4 | Complete partial strummed chords to standard guitar voicings (open + E/A-shape barres; maj/min/5/7/m7/sus/maj7) | −0.03…+0.03 | +0.016 (min 3 notes) | Not adopted yet — small, single-song evidence. |
|
||||
| E5 | Repetition voting on the guitar transcription (`src/pipeline/repeat.ts`): bars of the estimated grid are repeats when their 16th-slot × pitch occupancy is similar (cosine ≥ 0.6, smoothed ±1 bar along the diagonal) **and** ≥ 30% of notes match exactly (pitch, ±50 ms); per bar, drop notes found in < 15% of its repeats, add notes found in ≥ 25% and ≥ 3 repeats | t1a 0.885 · t1b 0.840 · t1c 0.752 · t1d 0.737 (+0.016…+0.026) | **0.507** (P 0.45→0.53, R 0.37→0.49) | **Adopted** (commit 132659a), default on for guitar (`createPipeline(models, { repeatVote })`). |
|
||||
|
||||
## Learnings
|
||||
|
||||
- Synthetic gains do not transfer 1:1 to real recordings while the synthetic guitar
|
||||
is the SONiVOX GM patch; a more realistic guitar sound in the renderer would make
|
||||
the synthetic benchmark more predictive.
|
||||
- On real audio the limits are Basic Pitch's partial-chord recall on strummed
|
||||
rhythm guitar and octave confusions, not thresholds.
|
||||
- An undocumented MT3 ONNX export exists (HF `ModernMube/HTDemucs_onnx`, ~300 MB);
|
||||
not pursued — input/token format would need reverse-engineering and MT3 is weak
|
||||
on real recordings.
|
||||
- E5 repetition voting: the two halves behave differently. Dropping non-recurring notes is what
|
||||
helps synthetic (precision; t1 A-sections have only 2 repeats, so the add rule — which needs ≥ 3 — never
|
||||
fires there). On lnoe adding is the big win (add-only, before the exact-match gate: recall 0.37 → 0.56, F1 0.500): Basic Pitch drops *different*
|
||||
chord tones in each repeat of a strummed part, so the union across repeats completes chords (cf. E3/E4).
|
||||
The add thresholds are therefore tuned on one real song — re-check when another trustworthy alignment exists.
|
||||
- Controls: treating every bar as a repeat (no repetition detection) gives lnoe 0.440 and wrecks synthetic
|
||||
(t1a 0.48), so the gain comes from the detected repeats, not from song-level note priors.
|
||||
- Voting needs an accurate grid. With only the coarse (slot ±1) similarity, a 1% tempo error made distant bars
|
||||
"similar" but 40 ms/bar misaligned and dropped lnoe to 0.23; the exact-match gate (≥ 0.3) turns that case into
|
||||
a no-op (0.403). A per-pair time-shift search (±½ beat) to absorb drift picked spurious shifts and cost
|
||||
0.03 on lnoe — not used. Half/double/1.5× tempo errors and ¼–½ beat downbeat errors still gain (lnoe 0.44–0.51).
|
||||
- Evaluated on cached transcriptions + estimated grids (`.scratch/exp/cases_full.ts`, baseline reproduces E1: 0.866 · 0.814 · 0.734 · 0.721); full pipeline spot check t1c/1 0.708 → 0.751 matches.
|
||||
- Per-song effect is not uniformly positive (t1a/2 −0.011, t1d/5 −0.026); medians and lnoe improve.
|
||||
|
||||
Experiment scripts live in the git-ignored `.scratch/exp/`.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
# STSheets benchmark report: exp-gpo-2026-10-31
|
||||
|
||||
- Run id: exp-gpo-2026-10-31
|
||||
- Date: 2026-10-30T19:46:32.213Z
|
||||
- Machine: AMD Ryzen 9 9950X 16-Core Processor x16, linux x64, bun 1.3.14
|
||||
- Models (sha256): htdemucs_6s.onnx 35cb61d4d35173ff1d16df7393f41f05651ffdfe13d3c10e06abc26708bd6933; nmp.onnx 2c3c1d144bfa61ad236e92e169c13535c880469a12a047d4e73451f2c059a0ec
|
||||
- SoundFont: alphaTab bundled sonivox.sf2 (Apache-2.0)
|
||||
- Modes: full (stems=false, tempo=false, notes=false)
|
||||
|
||||
> Caveats: vocals are rendered with a GM "Voice Oohs" patch, not real singing, so separation/transcription numbers for vocals are indicative only. Drums use a heuristic baseline transcriber. The SONiVOX guitar is partly routed to Demucs' piano stem (known synthetic-timbre gap). Pitch is pass/fail (song passes at merged-guitar pitch F1 >= 0.85); rhythm is secondary; fingering (string agreement) is informational. Cells are median (min–max).
|
||||
|
||||
## Tier t1a
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.87 (0.64–0.93) ✓ 4/5 | 0.0 (0.0–0.0) | 0.19 (0.13–0.53) | n/a | n/a (424 spurious) | n/a (1260 spurious) | n/a (80 spurious) | n/a (70 spurious) | n/a (200 spurious) | n/a (1592 spurious) | 0.97 (0.52–1.00) | 0.92 (0.08–0.94) | 1.00 (1.00–1.00) | 0 (-3–0) | 0.85 (0.71–0.92) | 0.89 (0.71–0.92) | 0 (0–1) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1a: PASS** (median merged-guitar F1 0.87 in `full` mode, threshold 0.85)
|
||||
|
||||
## Tier t1b
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.81 (0.72–0.93) ✗ 2/5 | 0.0 (0.0–2.0) | 0.22 (0.12–0.43) | n/a | n/a (371 spurious) | 0.37 (0.22–0.38) | 0.98 (0.95–1.00) | 0.49 (0.16–0.76) | 1.00 (0.99–1.00) | n/a (1774 spurious) | 0.99 (0.98–1.00) | 0.88 (0.75–0.96) | 1.00 (1.00–1.00) | 0 (0–0) | 0.95 (0.80–0.99) | 0.83 (0.75–0.94) | 0 (0–0) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1b: FAIL** (median merged-guitar F1 0.81 in `full` mode, threshold 0.85)
|
||||
|
||||
## Tier t1c
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.73 (0.55–0.77) ✗ 0/5 | 1.0 (0.0–8.0) | 0.38 (0.35–0.60) | n/a | 0.21 (0.05–0.24) | 0.32 (0.18–0.43) | 1.00 (0.92–1.00) | 0.75 (0.37–0.92) | 0.99 (0.99–1.00) | 0.11 (0.06–0.18) | 0.96 (0.95–0.98) | 0.74 (0.59–0.80) | 1.00 (1.00–1.00) | 0 (-2–0) | 0.63 (0.27–0.89) | 0.75 (0.58–0.76) | 0 (0–1) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1c: FAIL** (median merged-guitar F1 0.73 in `full` mode, threshold 0.85)
|
||||
|
||||
## Tier t1d
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.72 (0.68–0.76) ✗ 0/5 | 8.0 (0.0–12.0) | 0.28 (0.26–0.35) | 0.68 (0.59–0.70) | 0.29 (0.13–0.52) | 0.37 (0.34–0.45) | 1.00 (0.98–1.00) | 0.41 (0.07–0.58) | 0.99 (0.89–1.00) | 0.10 (0.07–0.15) | 0.93 (0.85–0.96) | 0.66 (0.48–0.78) | 1.00 (1.00–1.00) | 0 (0–0) | 0.78 (0.36–0.96) | 0.74 (0.72–0.78) | 0 (0–0) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1d: FAIL** (median merged-guitar F1 0.72 in `full` mode, threshold 0.85)
|
||||
|
||||
## Per-song appendix
|
||||
|
||||
| Tier | Seed | Mode | Guitar pass | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques | Seconds |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| t1a | 1 | full | ✓ | 0.87 | 0.0 | 0.19 | n/a | n/a (77 spurious) | n/a (320 spurious) | n/a (38 spurious) | n/a (2 spurious) | n/a (3 spurious) | n/a (349 spurious) | 1.00 | 0.93 | 1.00 | 0 | 0.88 | 0.85 | 0 | 1 | n/a (tier 1 has no technique marks) | 20.5 |
|
||||
| t1a | 2 | full | ✗ | 0.64 | 0.0 | 0.53 | n/a | n/a (94 spurious) | n/a (125 spurious) | n/a | n/a (16 spurious) | n/a (84 spurious) | n/a (223 spurious) | 0.56 | 0.25 | 1.00 | -1 | 0.71 | 0.71 | 0 | 1 | n/a (tier 1 has no technique marks) | 21.7 |
|
||||
| t1a | 3 | full | ✓ | 0.86 | 0.0 | 0.21 | n/a | n/a (96 spurious) | n/a (250 spurious) | n/a (42 spurious) | n/a (1 spurious) | n/a (15 spurious) | n/a (258 spurious) | 0.97 | 0.94 | 1.00 | 0 | 0.85 | 0.89 | 0 | 1 | n/a (tier 1 has no technique marks) | 13.1 |
|
||||
| t1a | 4 | full | ✓ | 0.90 | 0.0 | 0.18 | n/a | n/a (77 spurious) | n/a (225 spurious) | n/a | n/a (19 spurious) | n/a (64 spurious) | n/a (309 spurious) | 1.00 | 0.92 | 1.00 | 0 | 0.84 | 0.92 | 0 | 1 | n/a (tier 1 has no technique marks) | 14.7 |
|
||||
| t1a | 5 | full | ✓ | 0.93 | 0.0 | 0.13 | n/a | n/a (80 spurious) | n/a (340 spurious) | n/a | n/a (32 spurious) | n/a (34 spurious) | n/a (453 spurious) | 0.52 | 0.08 | 1.00 | -3 | 0.92 | 0.91 | 1 | 1 | n/a (tier 1 has no technique marks) | 11.9 |
|
||||
| t1b | 1 | full | ✗ | 0.81 | 0.0 | 0.24 | n/a | n/a (91 spurious) | 0.23 | 0.95 | 0.25 | 1.00 | n/a (411 spurious) | 0.99 | 0.75 | 1.00 | 0 | 0.99 | 0.81 | 0 | 1 | n/a (tier 1 has no technique marks) | 26.3 |
|
||||
| t1b | 2 | full | ✗ | 0.72 | 2.0 | 0.43 | n/a | n/a (77 spurious) | 0.38 | 1.00 | 0.54 | 0.99 | n/a (301 spurious) | 0.99 | 0.76 | 1.00 | 0 | 0.80 | 0.75 | 0 | 1 | n/a (tier 1 has no technique marks) | 18.3 |
|
||||
| t1b | 3 | full | ✗ | 0.81 | 0.0 | 0.22 | n/a | n/a (80 spurious) | 0.22 | 0.96 | 0.16 | 1.00 | n/a (307 spurious) | 0.98 | 0.88 | 1.00 | 0 | 0.92 | 0.83 | 0 | 1 | n/a (tier 1 has no technique marks) | 14.4 |
|
||||
| t1b | 4 | full | ✓ | 0.89 | 1.0 | 0.18 | n/a | n/a (60 spurious) | 0.37 | 0.98 | 0.76 | 1.00 | n/a (373 spurious) | 0.99 | 0.88 | 1.00 | 0 | 0.95 | 0.90 | 0 | 1 | n/a (tier 1 has no technique marks) | 22.1 |
|
||||
| t1b | 5 | full | ✓ | 0.93 | 0.0 | 0.12 | n/a | n/a (63 spurious) | 0.38 | 1.00 | 0.49 | 1.00 | n/a (382 spurious) | 1.00 | 0.96 | 1.00 | 0 | 0.95 | 0.94 | 0 | 1 | n/a (tier 1 has no technique marks) | 25.8 |
|
||||
| t1c | 1 | full | ✗ | 0.71 | 0.0 | 0.41 | n/a | 0.21 | 0.18 | 0.92 | 0.37 | 1.00 | 0.11 | 0.96 | 0.59 | 1.00 | -2 | 0.89 | 0.70 | 1 | 1 | n/a (tier 1 has no technique marks) | 28.4 |
|
||||
| t1c | 2 | full | ✗ | 0.55 | 8.0 | 0.60 | n/a | 0.24 | 0.32 | 1.00 | 0.86 | 0.99 | 0.15 | 0.96 | 0.61 | 1.00 | 0 | 0.62 | 0.58 | 0 | 1 | n/a (tier 1 has no technique marks) | 25.9 |
|
||||
| t1c | 3 | full | ✗ | 0.73 | 2.0 | 0.38 | n/a | 0.05 | 0.18 | 0.99 | 0.53 | 1.00 | 0.09 | 0.95 | 0.80 | 1.00 | 0 | 0.69 | 0.75 | 0 | 1 | n/a (tier 1 has no technique marks) | 20.1 |
|
||||
| t1c | 4 | full | ✗ | 0.76 | 1.0 | 0.37 | n/a | 0.09 | 0.36 | 1.00 | 0.92 | 0.99 | 0.18 | 0.98 | 0.74 | 1.00 | 0 | 0.27 | 0.76 | 0 | 1 | n/a (tier 1 has no technique marks) | 32.7 |
|
||||
| t1c | 5 | full | ✗ | 0.77 | 0.0 | 0.35 | n/a | 0.24 | 0.43 | 1.00 | 0.75 | 0.99 | 0.06 | 0.97 | 0.74 | 1.00 | 0 | 0.63 | 0.76 | 0 | 1 | n/a (tier 1 has no technique marks) | 19.8 |
|
||||
| t1d | 1 | full | ✗ | 0.72 | 2.0 | 0.26 | 0.69 | 0.38 | 0.45 | 1.00 | 0.41 | 1.00 | 0.07 | 0.94 | 0.78 | 1.00 | 0 | 0.76 | 0.72 | 0 | 1 | n/a (tier 1 has no technique marks) | 27.7 |
|
||||
| t1d | 2 | full | ✗ | 0.76 | 8.0 | 0.28 | 0.70 | 0.52 | 0.34 | 1.00 | 0.50 | 0.99 | 0.15 | 0.93 | 0.75 | 1.00 | 0 | 0.78 | 0.78 | 0 | 1 | n/a (tier 1 has no technique marks) | 27.8 |
|
||||
| t1d | 3 | full | ✗ | 0.68 | 12.0 | 0.35 | 0.61 | 0.22 | 0.40 | 0.98 | 0.07 | 0.89 | 0.11 | 0.88 | 0.57 | 1.00 | 0 | 0.82 | 0.74 | 0 | 1 | n/a (tier 1 has no technique marks) | 25.0 |
|
||||
| t1d | 4 | full | ✗ | 0.76 | 8.0 | 0.26 | 0.68 | 0.13 | 0.37 | 0.98 | 0.58 | 0.99 | 0.10 | 0.96 | 0.66 | 1.00 | 0 | 0.36 | 0.78 | 0 | 1 | n/a (tier 1 has no technique marks) | 17.5 |
|
||||
| t1d | 5 | full | ✗ | 0.68 | 0.0 | 0.29 | 0.59 | 0.29 | 0.34 | 1.00 | 0.11 | 0.96 | 0.10 | 0.85 | 0.48 | 1.00 | 0 | 0.96 | 0.72 | 0 | 1 | n/a (tier 1 has no technique marks) | 15.3 |
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
# STSheets benchmark report: exp-repeat-2026-10-31
|
||||
|
||||
- Run id: exp-repeat-2026-10-31
|
||||
- Date: 2026-10-30T20:14:56.152Z
|
||||
- Machine: AMD Ryzen 9 9950X 16-Core Processor x16, linux x64, bun 1.3.14
|
||||
- Models (sha256): htdemucs_6s.onnx 35cb61d4d35173ff1d16df7393f41f05651ffdfe13d3c10e06abc26708bd6933; nmp.onnx 2c3c1d144bfa61ad236e92e169c13535c880469a12a047d4e73451f2c059a0ec
|
||||
- SoundFont: alphaTab bundled sonivox.sf2 (Apache-2.0)
|
||||
- Modes: full (stems=false, tempo=false, notes=false)
|
||||
|
||||
> Caveats: vocals are rendered with a GM "Voice Oohs" patch, not real singing, so separation/transcription numbers for vocals are indicative only. Drums use a heuristic baseline transcriber. The SONiVOX guitar is partly routed to Demucs' piano stem (known synthetic-timbre gap). Pitch is pass/fail (song passes at merged-guitar pitch F1 >= 0.85); rhythm is secondary; fingering (string agreement) is informational. Cells are median (min–max).
|
||||
|
||||
## Tier t1a
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.89 (0.63–0.94) ✓ 4/5 | 0.0 (0.0–0.0) | 0.15 (0.12–0.53) | n/a | n/a (424 spurious) | n/a (1260 spurious) | n/a (80 spurious) | n/a (70 spurious) | n/a (200 spurious) | n/a (1592 spurious) | 0.98 (0.52–1.00) | 0.92 (0.08–0.95) | 1.00 (1.00–1.00) | 0 (-3–0) | 0.88 (0.76–0.95) | 0.90 (0.69–0.93) | 0 (0–1) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1a: PASS** (median merged-guitar F1 0.89 in `full` mode, threshold 0.85)
|
||||
|
||||
## Tier t1b
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.84 (0.75–0.95) ✗ 2/5 | 0.0 (0.0–2.0) | 0.18 (0.09–0.39) | n/a | n/a (371 spurious) | 0.37 (0.22–0.38) | 0.98 (0.95–1.00) | 0.49 (0.16–0.76) | 1.00 (0.99–1.00) | n/a (1774 spurious) | 1.00 (0.99–1.00) | 0.89 (0.78–0.96) | 1.00 (1.00–1.00) | 0 (0–0) | 0.95 (0.80–1.00) | 0.84 (0.77–0.96) | 0 (0–0) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1b: FAIL** (median merged-guitar F1 0.84 in `full` mode, threshold 0.85)
|
||||
|
||||
## Tier t1c
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.75 (0.56–0.80) ✗ 0/5 | 1.0 (0.0–8.0) | 0.34 (0.30–0.59) | n/a | 0.21 (0.05–0.24) | 0.32 (0.18–0.43) | 1.00 (0.92–1.00) | 0.75 (0.37–0.92) | 0.99 (0.99–1.00) | 0.11 (0.06–0.18) | 0.98 (0.96–1.00) | 0.75 (0.60–0.80) | 1.00 (1.00–1.00) | 0 (-2–0) | 0.64 (0.34–0.94) | 0.76 (0.60–0.79) | 0 (0–1) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1c: FAIL** (median merged-guitar F1 0.75 in `full` mode, threshold 0.85)
|
||||
|
||||
## Tier t1d
|
||||
|
||||
| Mode | Songs | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| full | 5 | 0.74 (0.65–0.78) ✗ 0/5 | 4.0 (2.0–13.0) | 0.23 (0.22–0.33) | 0.68 (0.57–0.72) | 0.29 (0.13–0.52) | 0.37 (0.34–0.45) | 1.00 (0.98–1.00) | 0.41 (0.07–0.58) | 0.99 (0.89–1.00) | 0.10 (0.07–0.15) | 0.94 (0.85–0.97) | 0.66 (0.48–0.78) | 1.00 (1.00–1.00) | 0 (0–0) | 0.82 (0.39–0.97) | 0.75 (0.69–0.80) | 0 (0–0) | 1 (1–1) | n/a (tier 1 has no technique marks) |
|
||||
|
||||
**Tier t1d: FAIL** (median merged-guitar F1 0.74 in `full` mode, threshold 0.85)
|
||||
|
||||
## Per-song appendix
|
||||
|
||||
| Tier | Seed | Mode | Guitar pass | Guitar merged F1 | Guitar octave errors | Spurious rate | Part-split F1 (t1d) | Vocals F1 | Bass F1 | Drums kick F1 | Drums snare F1 | Drums hihat F1 | Keys F1 | Rhythm onBeat | Rhythm durationOk | Tempo factor | Beat offset | String agreement | GP-file guitar F1 | Bar count match | Time sig match | Techniques | Seconds |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| t1a | 1 | full | ✓ | 0.89 | 0.0 | 0.15 | n/a | n/a (77 spurious) | n/a (320 spurious) | n/a (38 spurious) | n/a (2 spurious) | n/a (3 spurious) | n/a (349 spurious) | 1.00 | 0.94 | 1.00 | 0 | 0.88 | 0.87 | 0 | 1 | n/a (tier 1 has no technique marks) | 24.2 |
|
||||
| t1a | 2 | full | ✗ | 0.63 | 0.0 | 0.53 | n/a | n/a (94 spurious) | n/a (125 spurious) | n/a | n/a (16 spurious) | n/a (84 spurious) | n/a (223 spurious) | 0.56 | 0.24 | 1.00 | -1 | 0.76 | 0.69 | 0 | 1 | n/a (tier 1 has no technique marks) | 23.5 |
|
||||
| t1a | 3 | full | ✓ | 0.87 | 0.0 | 0.20 | n/a | n/a (96 spurious) | n/a (250 spurious) | n/a (42 spurious) | n/a (1 spurious) | n/a (15 spurious) | n/a (258 spurious) | 0.98 | 0.95 | 1.00 | 0 | 0.85 | 0.90 | 0 | 1 | n/a (tier 1 has no technique marks) | 20.7 |
|
||||
| t1a | 4 | full | ✓ | 0.92 | 0.0 | 0.15 | n/a | n/a (77 spurious) | n/a (225 spurious) | n/a | n/a (19 spurious) | n/a (64 spurious) | n/a (309 spurious) | 1.00 | 0.92 | 1.00 | 0 | 0.94 | 0.93 | 0 | 1 | n/a (tier 1 has no technique marks) | 21.5 |
|
||||
| t1a | 5 | full | ✓ | 0.94 | 0.0 | 0.12 | n/a | n/a (80 spurious) | n/a (340 spurious) | n/a | n/a (32 spurious) | n/a (34 spurious) | n/a (453 spurious) | 0.52 | 0.08 | 1.00 | -3 | 0.95 | 0.91 | 1 | 1 | n/a (tier 1 has no technique marks) | 19.5 |
|
||||
| t1b | 1 | full | ✗ | 0.84 | 0.0 | 0.18 | n/a | n/a (91 spurious) | 0.23 | 0.95 | 0.25 | 1.00 | n/a (411 spurious) | 1.00 | 0.78 | 1.00 | 0 | 0.99 | 0.84 | 0 | 1 | n/a (tier 1 has no technique marks) | 25.3 |
|
||||
| t1b | 2 | full | ✗ | 0.75 | 2.0 | 0.39 | n/a | n/a (77 spurious) | 0.38 | 1.00 | 0.54 | 0.99 | n/a (301 spurious) | 0.99 | 0.78 | 1.00 | 0 | 0.80 | 0.77 | 0 | 1 | n/a (tier 1 has no technique marks) | 26.3 |
|
||||
| t1b | 3 | full | ✗ | 0.82 | 0.0 | 0.19 | n/a | n/a (80 spurious) | 0.22 | 0.96 | 0.16 | 1.00 | n/a (307 spurious) | 0.99 | 0.91 | 1.00 | 0 | 0.93 | 0.83 | 0 | 1 | n/a (tier 1 has no technique marks) | 17.2 |
|
||||
| t1b | 4 | full | ✓ | 0.90 | 1.0 | 0.15 | n/a | n/a (60 spurious) | 0.37 | 0.98 | 0.76 | 1.00 | n/a (373 spurious) | 1.00 | 0.89 | 1.00 | 0 | 0.95 | 0.91 | 0 | 1 | n/a (tier 1 has no technique marks) | 43.1 |
|
||||
| t1b | 5 | full | ✓ | 0.95 | 0.0 | 0.09 | n/a | n/a (63 spurious) | 0.38 | 1.00 | 0.49 | 1.00 | n/a (382 spurious) | 1.00 | 0.96 | 1.00 | 0 | 1.00 | 0.96 | 0 | 1 | n/a (tier 1 has no technique marks) | 31.0 |
|
||||
| t1c | 1 | full | ✗ | 0.75 | 0.0 | 0.34 | n/a | 0.21 | 0.18 | 0.92 | 0.37 | 1.00 | 0.11 | 0.98 | 0.63 | 1.00 | -2 | 0.94 | 0.74 | 1 | 1 | n/a (tier 1 has no technique marks) | 41.9 |
|
||||
| t1c | 2 | full | ✗ | 0.56 | 8.0 | 0.59 | n/a | 0.24 | 0.32 | 1.00 | 0.86 | 0.99 | 0.15 | 0.96 | 0.60 | 1.00 | 0 | 0.63 | 0.60 | 0 | 1 | n/a (tier 1 has no technique marks) | 47.0 |
|
||||
| t1c | 3 | full | ✗ | 0.75 | 1.0 | 0.35 | n/a | 0.05 | 0.18 | 0.99 | 0.53 | 1.00 | 0.09 | 0.96 | 0.80 | 1.00 | 0 | 0.74 | 0.76 | 0 | 1 | n/a (tier 1 has no technique marks) | 23.0 |
|
||||
| t1c | 4 | full | ✗ | 0.79 | 1.0 | 0.33 | n/a | 0.09 | 0.36 | 1.00 | 0.92 | 0.99 | 0.18 | 1.00 | 0.76 | 1.00 | 0 | 0.34 | 0.79 | 0 | 1 | n/a (tier 1 has no technique marks) | 16.1 |
|
||||
| t1c | 5 | full | ✗ | 0.80 | 0.0 | 0.30 | n/a | 0.24 | 0.43 | 1.00 | 0.75 | 0.99 | 0.06 | 0.98 | 0.75 | 1.00 | 0 | 0.64 | 0.78 | 0 | 1 | n/a (tier 1 has no technique marks) | 20.0 |
|
||||
| t1d | 1 | full | ✗ | 0.74 | 2.0 | 0.22 | 0.71 | 0.38 | 0.45 | 1.00 | 0.41 | 1.00 | 0.07 | 0.94 | 0.78 | 1.00 | 0 | 0.82 | 0.74 | 0 | 1 | n/a (tier 1 has no technique marks) | 25.3 |
|
||||
| t1d | 2 | full | ✗ | 0.78 | 7.0 | 0.23 | 0.72 | 0.52 | 0.34 | 1.00 | 0.50 | 0.99 | 0.15 | 0.95 | 0.77 | 1.00 | 0 | 0.80 | 0.80 | 0 | 1 | n/a (tier 1 has no technique marks) | 29.2 |
|
||||
| t1d | 3 | full | ✗ | 0.69 | 13.0 | 0.33 | 0.61 | 0.22 | 0.40 | 0.98 | 0.07 | 0.89 | 0.11 | 0.89 | 0.58 | 1.00 | 0 | 0.87 | 0.75 | 0 | 1 | n/a (tier 1 has no technique marks) | 20.1 |
|
||||
| t1d | 4 | full | ✗ | 0.77 | 4.0 | 0.22 | 0.68 | 0.13 | 0.37 | 0.98 | 0.58 | 0.99 | 0.10 | 0.97 | 0.66 | 1.00 | 0 | 0.39 | 0.78 | 0 | 1 | n/a (tier 1 has no technique marks) | 21.7 |
|
||||
| t1d | 5 | full | ✗ | 0.65 | 4.0 | 0.28 | 0.57 | 0.29 | 0.34 | 1.00 | 0.11 | 0.96 | 0.10 | 0.85 | 0.48 | 1.00 | 0 | 0.97 | 0.69 | 0 | 1 | n/a (tier 1 has no technique marks) | 20.1 |
|
||||
@@ -0,0 +1,728 @@
|
||||
[
|
||||
{
|
||||
"id": "lnoe",
|
||||
"title": "Last Night on Earth (Green Day)",
|
||||
"alignment": {
|
||||
"source": "saved",
|
||||
"manual": 0,
|
||||
"auto": 1,
|
||||
"savedAt": "2026-10-30T18:50:04.512Z",
|
||||
"chosen": "baseline",
|
||||
"baseline": {
|
||||
"offsetSec": 0.03,
|
||||
"precision": 0.9296187683284457,
|
||||
"cosine": 0.7456730914857798
|
||||
},
|
||||
"dtwPrecision": 0.9237536656891495
|
||||
},
|
||||
"fit": {
|
||||
"precision": 0.9296187683284457,
|
||||
"lift": 0.5562072336265884,
|
||||
"medianResidualMs": 1.6048588715733558,
|
||||
"audibleRate": 0.4055299539170507,
|
||||
"windows": [
|
||||
{
|
||||
"startSec": 0,
|
||||
"recOnsets": 28,
|
||||
"precision": 0.8928571428571429,
|
||||
"lift": 0.4761904761904762,
|
||||
"medianResidualMs": 1.455394808147048,
|
||||
"audibleRate": 0.3359683794466403,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 30,
|
||||
"recOnsets": 43,
|
||||
"precision": 0.9302325581395349,
|
||||
"lift": 0.6046511627906976,
|
||||
"medianResidualMs": 1.1654104135097043,
|
||||
"audibleRate": 0.3695150115473441,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 60,
|
||||
"recOnsets": 48,
|
||||
"precision": 0.9583333333333334,
|
||||
"lift": 0.5069444444444444,
|
||||
"medianResidualMs": 1.1717814137455207,
|
||||
"audibleRate": 0.3784860557768924,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 90,
|
||||
"recOnsets": 54,
|
||||
"precision": 0.9074074074074074,
|
||||
"lift": 0.5061728395061729,
|
||||
"medianResidualMs": 1.803905423017227,
|
||||
"audibleRate": 0.42736842105263156,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 120,
|
||||
"recOnsets": 48,
|
||||
"precision": 0.9166666666666666,
|
||||
"lift": 0.5625,
|
||||
"medianResidualMs": 1.4688324335949687,
|
||||
"audibleRate": 0.41435185185185186,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 150,
|
||||
"recOnsets": 55,
|
||||
"precision": 1,
|
||||
"lift": 0.6848484848484848,
|
||||
"medianResidualMs": 1.8973122094507744,
|
||||
"audibleRate": 0.4789356984478936,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 180,
|
||||
"recOnsets": 57,
|
||||
"precision": 0.9122807017543859,
|
||||
"lift": 0.5438596491228069,
|
||||
"medianResidualMs": 2.066004036294089,
|
||||
"audibleRate": 0.43863636363636366,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 210,
|
||||
"recOnsets": 8,
|
||||
"precision": 0.75,
|
||||
"lift": 0.375,
|
||||
"medianResidualMs": 10.922815385200124,
|
||||
"audibleRate": 0.11538461538461539,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
}
|
||||
],
|
||||
"stretches": {
|
||||
"total": 2,
|
||||
"warned": 0,
|
||||
"over15": 0
|
||||
}
|
||||
},
|
||||
"perRole": {
|
||||
"guitar": {
|
||||
"tp": 1123,
|
||||
"fp": 1392,
|
||||
"fn": 1940,
|
||||
"precision": 0.44652087475149105,
|
||||
"recall": 0.366634018935684,
|
||||
"f1": 0.40265328074578705,
|
||||
"octaveErrors": 122
|
||||
},
|
||||
"vocals": {
|
||||
"tp": 108,
|
||||
"fp": 388,
|
||||
"fn": 131,
|
||||
"precision": 0.21774193548387097,
|
||||
"recall": 0.45188284518828453,
|
||||
"f1": 0.2938775510204082,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"bass": {
|
||||
"tp": 84,
|
||||
"fp": 312,
|
||||
"fn": 79,
|
||||
"precision": 0.21212121212121213,
|
||||
"recall": 0.5153374233128835,
|
||||
"f1": 0.3005366726296959,
|
||||
"octaveErrors": 1
|
||||
},
|
||||
"drums": {
|
||||
"tp": 417,
|
||||
"fp": 427,
|
||||
"fn": 159,
|
||||
"precision": 0.4940758293838863,
|
||||
"recall": 0.7239583333333334,
|
||||
"f1": 0.5873239436619718,
|
||||
"octaveErrors": 16
|
||||
},
|
||||
"keys": {
|
||||
"tp": 329,
|
||||
"fp": 1553,
|
||||
"fn": 853,
|
||||
"precision": 0.17481402763018067,
|
||||
"recall": 0.27834179357022,
|
||||
"f1": 0.21475195822454307,
|
||||
"octaveErrors": 94
|
||||
}
|
||||
},
|
||||
"lenient": {
|
||||
"guitar": 0.47615632843313016,
|
||||
"vocals": 0.39183673469387753,
|
||||
"bass": 0.35062611806797855,
|
||||
"drums": 0.5985915492957746,
|
||||
"keys": 0.25718015665796345
|
||||
},
|
||||
"drumsPerClass": {
|
||||
"kick": {
|
||||
"tp": 98,
|
||||
"fp": 53,
|
||||
"fn": 0,
|
||||
"precision": 0.6490066225165563,
|
||||
"recall": 1,
|
||||
"f1": 0.7871485943775101,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"snare": {
|
||||
"tp": 90,
|
||||
"fp": 211,
|
||||
"fn": 8,
|
||||
"precision": 0.29900332225913623,
|
||||
"recall": 0.9183673469387755,
|
||||
"f1": 0.45112781954887216,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"hihat": {
|
||||
"tp": 217,
|
||||
"fp": 156,
|
||||
"fn": 3,
|
||||
"precision": 0.5817694369973191,
|
||||
"recall": 0.9863636363636363,
|
||||
"f1": 0.7318718381112985,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"cymbal": {
|
||||
"tp": 12,
|
||||
"fp": 7,
|
||||
"fn": 142,
|
||||
"precision": 0.631578947368421,
|
||||
"recall": 0.07792207792207792,
|
||||
"f1": 0.13872832369942195,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"tom": {
|
||||
"tp": 0,
|
||||
"fp": 0,
|
||||
"fn": 6,
|
||||
"precision": null,
|
||||
"recall": 0,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"excludedTracks": [],
|
||||
"capo": {},
|
||||
"seconds": 108.26291943300001
|
||||
},
|
||||
{
|
||||
"id": "swimming",
|
||||
"title": "Swimming (Lunar Vacation)",
|
||||
"alignment": {
|
||||
"source": "saved",
|
||||
"manual": 0,
|
||||
"auto": 24,
|
||||
"savedAt": "2026-10-30T18:50:09.863Z",
|
||||
"chosen": "dtw",
|
||||
"baseline": {
|
||||
"offsetSec": 0.23,
|
||||
"precision": 0.6965944272445821,
|
||||
"cosine": 0.6263260975223001
|
||||
},
|
||||
"dtwPrecision": 0.7956656346749226
|
||||
},
|
||||
"fit": {
|
||||
"precision": 0.7956656346749226,
|
||||
"lift": 0.14138286893704843,
|
||||
"medianResidualMs": 15.842554880606485,
|
||||
"audibleRate": 0.2524440582228981,
|
||||
"windows": [
|
||||
{
|
||||
"startSec": 0,
|
||||
"recOnsets": 20,
|
||||
"precision": 0.9,
|
||||
"lift": 0.5166666666666667,
|
||||
"medianResidualMs": 7.930399045602776,
|
||||
"audibleRate": 0.4263157894736842,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 30,
|
||||
"recOnsets": 38,
|
||||
"precision": 0.8421052631578947,
|
||||
"lift": 0.4561403508771929,
|
||||
"medianResidualMs": 8.935720572655015,
|
||||
"audibleRate": 0.4797047970479705,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 60,
|
||||
"recOnsets": 30,
|
||||
"precision": 0.9666666666666667,
|
||||
"lift": 0.5,
|
||||
"medianResidualMs": 8.143065510864744,
|
||||
"audibleRate": 0.361244019138756,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 90,
|
||||
"recOnsets": 27,
|
||||
"precision": 0.7777777777777778,
|
||||
"lift": 0.39506172839506176,
|
||||
"medianResidualMs": 15.735118333282117,
|
||||
"audibleRate": 0.41919191919191917,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 120,
|
||||
"recOnsets": 45,
|
||||
"precision": 0.4666666666666667,
|
||||
"lift": -0.2666666666666667,
|
||||
"medianResidualMs": 2.775248941929931,
|
||||
"audibleRate": 0.1555944055944056,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"precision 47%",
|
||||
"lift -0.27"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 150,
|
||||
"recOnsets": 55,
|
||||
"precision": 0.9454545454545454,
|
||||
"lift": 0.16363636363636358,
|
||||
"medianResidualMs": 24.587909988653678,
|
||||
"audibleRate": 0.3125763125763126,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 180,
|
||||
"recOnsets": 52,
|
||||
"precision": 0.6923076923076923,
|
||||
"lift": -0.11538461538461553,
|
||||
"medianResidualMs": 33.98722951001787,
|
||||
"audibleRate": 0.19047619047619047,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift -0.12"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 210,
|
||||
"recOnsets": 47,
|
||||
"precision": 0.8297872340425532,
|
||||
"lift": -1.1102230246251565e-16,
|
||||
"medianResidualMs": 23.422793802808428,
|
||||
"audibleRate": 0.21311475409836064,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift -0.00"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 240,
|
||||
"recOnsets": 9,
|
||||
"precision": 1,
|
||||
"lift": 0.14814814814814825,
|
||||
"medianResidualMs": 1.804172777298163,
|
||||
"audibleRate": 0.09375,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 270,
|
||||
"recOnsets": 0,
|
||||
"precision": null,
|
||||
"lift": null,
|
||||
"medianResidualMs": null,
|
||||
"audibleRate": null,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
}
|
||||
],
|
||||
"stretches": {
|
||||
"total": 25,
|
||||
"warned": 0,
|
||||
"over15": 0
|
||||
}
|
||||
},
|
||||
"perRole": {
|
||||
"guitar": {
|
||||
"tp": 1169,
|
||||
"fp": 3386,
|
||||
"fn": 3823,
|
||||
"precision": 0.2566410537870472,
|
||||
"recall": 0.2341746794871795,
|
||||
"f1": 0.2448936838797528,
|
||||
"octaveErrors": 250
|
||||
},
|
||||
"vocals": {
|
||||
"tp": 35,
|
||||
"fp": 571,
|
||||
"fn": 166,
|
||||
"precision": 0.057755775577557754,
|
||||
"recall": 0.17412935323383086,
|
||||
"f1": 0.08674101610904585,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"bass": {
|
||||
"tp": 185,
|
||||
"fp": 716,
|
||||
"fn": 345,
|
||||
"precision": 0.20532741398446172,
|
||||
"recall": 0.3490566037735849,
|
||||
"f1": 0.2585604472396925,
|
||||
"octaveErrors": 9
|
||||
},
|
||||
"drums": {
|
||||
"tp": 392,
|
||||
"fp": 1408,
|
||||
"fn": 856,
|
||||
"precision": 0.21777777777777776,
|
||||
"recall": 0.3141025641025641,
|
||||
"f1": 0.2572178477690289,
|
||||
"octaveErrors": 142
|
||||
},
|
||||
"keys": {
|
||||
"tp": 0,
|
||||
"fp": 2032,
|
||||
"fn": 0,
|
||||
"precision": 0,
|
||||
"recall": null,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"lenient": {
|
||||
"guitar": 0.3762438462344192,
|
||||
"vocals": 0.12391573729863693,
|
||||
"bass": 0.3535988819007687,
|
||||
"drums": 0.3353018372703412,
|
||||
"keys": 0
|
||||
},
|
||||
"drumsPerClass": {
|
||||
"kick": {
|
||||
"tp": 175,
|
||||
"fp": 393,
|
||||
"fn": 163,
|
||||
"precision": 0.30809859154929575,
|
||||
"recall": 0.5177514792899408,
|
||||
"f1": 0.38631346578366443,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"snare": {
|
||||
"tp": 97,
|
||||
"fp": 363,
|
||||
"fn": 145,
|
||||
"precision": 0.2108695652173913,
|
||||
"recall": 0.40082644628099173,
|
||||
"f1": 0.27635327635327633,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"hihat": {
|
||||
"tp": 0,
|
||||
"fp": 465,
|
||||
"fn": 0,
|
||||
"precision": 0,
|
||||
"recall": null,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"cymbal": {
|
||||
"tp": 120,
|
||||
"fp": 187,
|
||||
"fn": 510,
|
||||
"precision": 0.39087947882736157,
|
||||
"recall": 0.19047619047619047,
|
||||
"f1": 0.256136606189968,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"tom": {
|
||||
"tp": 0,
|
||||
"fp": 0,
|
||||
"fn": 38,
|
||||
"precision": null,
|
||||
"recall": 0,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"excludedTracks": [],
|
||||
"capo": {},
|
||||
"seconds": 155.53003568399998
|
||||
},
|
||||
{
|
||||
"id": "holes",
|
||||
"title": "Holes (Passenger)",
|
||||
"alignment": {
|
||||
"source": "saved",
|
||||
"manual": 0,
|
||||
"auto": 1,
|
||||
"savedAt": "2026-10-30T18:50:13.626Z",
|
||||
"chosen": "baseline",
|
||||
"baseline": {
|
||||
"offsetSec": -0.62,
|
||||
"precision": 0.7642585551330798,
|
||||
"cosine": 0.5150959661213577
|
||||
},
|
||||
"dtwPrecision": 0.7452471482889734
|
||||
},
|
||||
"fit": {
|
||||
"precision": 0.7642585551330798,
|
||||
"lift": 0.026615969581749055,
|
||||
"medianResidualMs": 26.02722634637189,
|
||||
"audibleRate": 0.2326544701415257,
|
||||
"windows": [
|
||||
{
|
||||
"startSec": 0,
|
||||
"recOnsets": 57,
|
||||
"precision": 0.8596491228070176,
|
||||
"lift": 0.04093567251462005,
|
||||
"medianResidualMs": 29.71729302428372,
|
||||
"audibleRate": 0.24890190336749635,
|
||||
"beatShift": {
|
||||
"label": "+1 beat",
|
||||
"gain": 0.07068144786351394
|
||||
},
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.04",
|
||||
"beat shift suspected: +1 beat"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 30,
|
||||
"recOnsets": 53,
|
||||
"precision": 0.8867924528301887,
|
||||
"lift": 0.07547169811320753,
|
||||
"medianResidualMs": 22.94817039821595,
|
||||
"audibleRate": 0.29635499207606975,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.08"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 60,
|
||||
"recOnsets": 55,
|
||||
"precision": 0.8727272727272727,
|
||||
"lift": 0.07272727272727275,
|
||||
"medianResidualMs": 20.39969875089298,
|
||||
"audibleRate": 0.2913793103448276,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.07"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 90,
|
||||
"recOnsets": 36,
|
||||
"precision": 0.5833333333333334,
|
||||
"lift": -0.08333333333333326,
|
||||
"medianResidualMs": 26.357498430371606,
|
||||
"audibleRate": 0.24187725631768953,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"precision 58%",
|
||||
"lift -0.08"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 120,
|
||||
"recOnsets": 7,
|
||||
"precision": null,
|
||||
"lift": null,
|
||||
"medianResidualMs": null,
|
||||
"audibleRate": 0.05371900826446281,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 150,
|
||||
"recOnsets": 35,
|
||||
"precision": 0.6571428571428571,
|
||||
"lift": -0.10476190476190472,
|
||||
"medianResidualMs": 26.66406226160234,
|
||||
"audibleRate": 0.13317191283292978,
|
||||
"beatShift": {
|
||||
"label": "+1 bar",
|
||||
"gain": 0.09281354253241003
|
||||
},
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift -0.10",
|
||||
"beat shift suspected: +1 bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 180,
|
||||
"recOnsets": 20,
|
||||
"precision": 0.5,
|
||||
"lift": 0.2,
|
||||
"medianResidualMs": 35.25757102913474,
|
||||
"audibleRate": 0.18309859154929578,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"precision 50%",
|
||||
"lift 0.20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 210,
|
||||
"recOnsets": 0,
|
||||
"precision": null,
|
||||
"lift": null,
|
||||
"medianResidualMs": null,
|
||||
"audibleRate": null,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
}
|
||||
],
|
||||
"stretches": {
|
||||
"total": 2,
|
||||
"warned": 0,
|
||||
"over15": 0
|
||||
}
|
||||
},
|
||||
"perRole": {
|
||||
"guitar": {
|
||||
"tp": 481,
|
||||
"fp": 2673,
|
||||
"fn": 2183,
|
||||
"precision": 0.15250475586556753,
|
||||
"recall": 0.18055555555555555,
|
||||
"f1": 0.16534891715366104,
|
||||
"octaveErrors": 189
|
||||
},
|
||||
"vocals": {
|
||||
"tp": 57,
|
||||
"fp": 623,
|
||||
"fn": 387,
|
||||
"precision": 0.0838235294117647,
|
||||
"recall": 0.12837837837837837,
|
||||
"f1": 0.10142348754448399,
|
||||
"octaveErrors": 1
|
||||
},
|
||||
"bass": {
|
||||
"tp": 15,
|
||||
"fp": 508,
|
||||
"fn": 110,
|
||||
"precision": 0.028680688336520075,
|
||||
"recall": 0.12,
|
||||
"f1": 0.046296296296296294,
|
||||
"octaveErrors": 2
|
||||
},
|
||||
"drums": {
|
||||
"tp": 192,
|
||||
"fp": 1431,
|
||||
"fn": 534,
|
||||
"precision": 0.11829944547134935,
|
||||
"recall": 0.2644628099173554,
|
||||
"f1": 0.16347381864623245,
|
||||
"octaveErrors": 29
|
||||
},
|
||||
"keys": {
|
||||
"tp": 34,
|
||||
"fp": 1057,
|
||||
"fn": 520,
|
||||
"precision": 0.031164069660861594,
|
||||
"recall": 0.061371841155234655,
|
||||
"f1": 0.04133738601823708,
|
||||
"octaveErrors": 18
|
||||
}
|
||||
},
|
||||
"lenient": {
|
||||
"guitar": 0.2911653489171537,
|
||||
"vocals": 0.15480427046263345,
|
||||
"bass": 0.07407407407407407,
|
||||
"drums": 0.2835249042145594,
|
||||
"keys": 0.0729483282674772
|
||||
},
|
||||
"drumsPerClass": {
|
||||
"kick": {
|
||||
"tp": 21,
|
||||
"fp": 223,
|
||||
"fn": 99,
|
||||
"precision": 0.0860655737704918,
|
||||
"recall": 0.175,
|
||||
"f1": 0.11538461538461539,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"snare": {
|
||||
"tp": 69,
|
||||
"fp": 261,
|
||||
"fn": 183,
|
||||
"precision": 0.20909090909090908,
|
||||
"recall": 0.27380952380952384,
|
||||
"f1": 0.23711340206185566,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"hihat": {
|
||||
"tp": 101,
|
||||
"fp": 603,
|
||||
"fn": 194,
|
||||
"precision": 0.1434659090909091,
|
||||
"recall": 0.3423728813559322,
|
||||
"f1": 0.2022022022022022,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"cymbal": {
|
||||
"tp": 1,
|
||||
"fp": 344,
|
||||
"fn": 5,
|
||||
"precision": 0.002898550724637681,
|
||||
"recall": 0.16666666666666666,
|
||||
"f1": 0.005698005698005698,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"tom": {
|
||||
"tp": 0,
|
||||
"fp": 0,
|
||||
"fn": 1,
|
||||
"precision": null,
|
||||
"recall": 0,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"excludedTracks": [
|
||||
"Brass",
|
||||
"Crowd"
|
||||
],
|
||||
"capo": {
|
||||
"Acoustic Guitar ": 9
|
||||
},
|
||||
"seconds": 127.737292572
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,78 @@
|
||||
# STSheets real-song benchmark: gpo-2026-10-31
|
||||
|
||||
> Reference = the team's Guitar Pro-exported MIDI warped onto the recording by the saved alignment; scores are note-onset matches (pitch must equal; drums compared by class). The fit line reports the *consistency of the alignment*, not an independent validation of it: precision = share of the recording's onset-flux peaks with a warped MIDI onset within ±70 ms (flag < 60% per 30 s window), lift = precision minus its value with the MIDI shifted off-grid (flag < 0.25), a symbolic-chroma check for whole-beat/bar shifts, and "MIDI onsets audible as attacks" (information only; a full mix exposes far fewer attacks than the score has notes). A poor alignment lowers every score below. Capo is informational and not applied.
|
||||
|
||||
## Last Night on Earth (Green Day) (lnoe)
|
||||
|
||||
- Alignment: saved 2026-10-30T18:50:04.512Z (0 manual / 1 auto anchors)
|
||||
- Method: chosen: baseline (DTW precision 92.4% not better by ≥ 2 points) · constant-offset baseline +0.03 s: precision 93.0%
|
||||
- Consistency of the alignment: precision 93.0% (lift +0.56) · MIDI onsets audible as attacks 40.6% · median residual 1.6 ms · 0/8 windows flagged · stretches: 0/2 warned, 0 > 15%
|
||||
- Capo: none listed
|
||||
- Excluded tracks (no role): none
|
||||
- Pipeline + scoring: 108.3 s
|
||||
|
||||
| Role | Ref notes | Est notes | P ±50 ms | R ±50 ms | F1 ±50 ms | F1 ±100 ms |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| guitar | 3063 | 2515 | 0.45 | 0.37 | 0.40 | 0.48 |
|
||||
| vocals | 239 | 496 | 0.22 | 0.45 | 0.29 | 0.39 |
|
||||
| bass | 163 | 396 | 0.21 | 0.52 | 0.30 | 0.35 |
|
||||
| drums | 576 | 844 | 0.49 | 0.72 | 0.59 | 0.60 |
|
||||
| keys | 1182 | 1882 | 0.17 | 0.28 | 0.21 | 0.26 |
|
||||
|
||||
| Drum class | Ref | Est | F1 ±50 ms |
|
||||
| --- | --- | --- | --- |
|
||||
| kick | 98 | 151 | 0.79 |
|
||||
| snare | 98 | 301 | 0.45 |
|
||||
| hihat | 220 | 373 | 0.73 |
|
||||
| cymbal | 154 | 19 | 0.14 |
|
||||
| tom | 6 | 0 | 0.00 |
|
||||
|
||||
## Swimming (Lunar Vacation) (swimming)
|
||||
|
||||
- Alignment: saved 2026-10-30T18:50:09.863Z (0 manual / 24 auto anchors)
|
||||
- Method: chosen: DTW (precision 79.6%) · constant-offset baseline +0.23 s: precision 69.7%
|
||||
- Consistency of the alignment: precision 79.6% (lift +0.14) · MIDI onsets audible as attacks 25.2% · median residual 15.8 ms · 5/10 windows flagged (2:00 precision 47%, lift -0.27; 2:30 lift 0.16; 3:00 lift -0.12; 3:30 lift -0.00; 4:00 lift 0.15) · stretches: 0/25 warned, 0 > 15%
|
||||
- Capo: none listed
|
||||
- Excluded tracks (no role): none
|
||||
- Pipeline + scoring: 155.5 s
|
||||
|
||||
| Role | Ref notes | Est notes | P ±50 ms | R ±50 ms | F1 ±50 ms | F1 ±100 ms |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| guitar | 4992 | 4555 | 0.26 | 0.23 | 0.24 | 0.38 |
|
||||
| vocals | 201 | 606 | 0.06 | 0.17 | 0.09 | 0.12 |
|
||||
| bass | 530 | 901 | 0.21 | 0.35 | 0.26 | 0.35 |
|
||||
| drums | 1248 | 1800 | 0.22 | 0.31 | 0.26 | 0.34 |
|
||||
| keys | 0 | 2032 | 0.00 | n/a | 0.00 | 0.00 |
|
||||
|
||||
| Drum class | Ref | Est | F1 ±50 ms |
|
||||
| --- | --- | --- | --- |
|
||||
| kick | 338 | 568 | 0.39 |
|
||||
| snare | 242 | 460 | 0.28 |
|
||||
| hihat | 0 | 465 | 0.00 |
|
||||
| cymbal | 630 | 307 | 0.26 |
|
||||
| tom | 38 | 0 | 0.00 |
|
||||
|
||||
## Holes (Passenger) (holes)
|
||||
|
||||
- Alignment: saved 2026-10-30T18:50:13.626Z (0 manual / 1 auto anchors)
|
||||
- Method: chosen: baseline (DTW precision 74.5% not better by ≥ 2 points) · constant-offset baseline −0.62 s: precision 76.4%
|
||||
- Consistency of the alignment: precision 76.4% (lift +0.03) · MIDI onsets audible as attacks 23.3% · median residual 26.0 ms · 6/8 windows flagged (0:00 lift 0.04, beat shift suspected: +1 beat; 0:30 lift 0.08; 1:00 lift 0.07; 1:30 precision 58%, lift -0.08; 2:30 lift -0.10, beat shift suspected: +1 bar; 3:00 precision 50%, lift 0.20) · stretches: 0/2 warned, 0 > 15%
|
||||
- Capo: Acoustic Guitar : 9
|
||||
- Excluded tracks (no role): Brass, Crowd
|
||||
- Pipeline + scoring: 127.7 s
|
||||
|
||||
| Role | Ref notes | Est notes | P ±50 ms | R ±50 ms | F1 ±50 ms | F1 ±100 ms |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| guitar | 2664 | 3154 | 0.15 | 0.18 | 0.17 | 0.29 |
|
||||
| vocals | 444 | 680 | 0.08 | 0.13 | 0.10 | 0.15 |
|
||||
| bass | 125 | 523 | 0.03 | 0.12 | 0.05 | 0.07 |
|
||||
| drums | 726 | 1623 | 0.12 | 0.26 | 0.16 | 0.28 |
|
||||
| keys | 554 | 1091 | 0.03 | 0.06 | 0.04 | 0.07 |
|
||||
|
||||
| Drum class | Ref | Est | F1 ±50 ms |
|
||||
| --- | --- | --- | --- |
|
||||
| kick | 120 | 244 | 0.12 |
|
||||
| snare | 252 | 330 | 0.24 |
|
||||
| hihat | 295 | 704 | 0.20 |
|
||||
| cymbal | 6 | 345 | 0.01 |
|
||||
| tom | 1 | 0 | 0.00 |
|
||||
@@ -0,0 +1,728 @@
|
||||
[
|
||||
{
|
||||
"id": "lnoe",
|
||||
"title": "Last Night on Earth (Green Day)",
|
||||
"alignment": {
|
||||
"source": "saved",
|
||||
"manual": 0,
|
||||
"auto": 1,
|
||||
"savedAt": "2026-10-30T18:50:04.512Z",
|
||||
"chosen": "baseline",
|
||||
"baseline": {
|
||||
"offsetSec": 0.03,
|
||||
"precision": 0.9296187683284457,
|
||||
"cosine": 0.7456730914857798
|
||||
},
|
||||
"dtwPrecision": 0.9237536656891495
|
||||
},
|
||||
"fit": {
|
||||
"precision": 0.9296187683284457,
|
||||
"lift": 0.5562072336265884,
|
||||
"medianResidualMs": 1.6048588715733558,
|
||||
"audibleRate": 0.4055299539170507,
|
||||
"windows": [
|
||||
{
|
||||
"startSec": 0,
|
||||
"recOnsets": 28,
|
||||
"precision": 0.8928571428571429,
|
||||
"lift": 0.4761904761904762,
|
||||
"medianResidualMs": 1.455394808147048,
|
||||
"audibleRate": 0.3359683794466403,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 30,
|
||||
"recOnsets": 43,
|
||||
"precision": 0.9302325581395349,
|
||||
"lift": 0.6046511627906976,
|
||||
"medianResidualMs": 1.1654104135097043,
|
||||
"audibleRate": 0.3695150115473441,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 60,
|
||||
"recOnsets": 48,
|
||||
"precision": 0.9583333333333334,
|
||||
"lift": 0.5069444444444444,
|
||||
"medianResidualMs": 1.1717814137455207,
|
||||
"audibleRate": 0.3784860557768924,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 90,
|
||||
"recOnsets": 54,
|
||||
"precision": 0.9074074074074074,
|
||||
"lift": 0.5061728395061729,
|
||||
"medianResidualMs": 1.803905423017227,
|
||||
"audibleRate": 0.42736842105263156,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 120,
|
||||
"recOnsets": 48,
|
||||
"precision": 0.9166666666666666,
|
||||
"lift": 0.5625,
|
||||
"medianResidualMs": 1.4688324335949687,
|
||||
"audibleRate": 0.41435185185185186,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 150,
|
||||
"recOnsets": 55,
|
||||
"precision": 1,
|
||||
"lift": 0.6848484848484848,
|
||||
"medianResidualMs": 1.8973122094507744,
|
||||
"audibleRate": 0.4789356984478936,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 180,
|
||||
"recOnsets": 57,
|
||||
"precision": 0.9122807017543859,
|
||||
"lift": 0.5438596491228069,
|
||||
"medianResidualMs": 2.066004036294089,
|
||||
"audibleRate": 0.43863636363636366,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 210,
|
||||
"recOnsets": 8,
|
||||
"precision": 0.75,
|
||||
"lift": 0.375,
|
||||
"medianResidualMs": 10.922815385200124,
|
||||
"audibleRate": 0.11538461538461539,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
}
|
||||
],
|
||||
"stretches": {
|
||||
"total": 2,
|
||||
"warned": 0,
|
||||
"over15": 0
|
||||
}
|
||||
},
|
||||
"perRole": {
|
||||
"guitar": {
|
||||
"tp": 1485,
|
||||
"fp": 1314,
|
||||
"fn": 1578,
|
||||
"precision": 0.5305466237942122,
|
||||
"recall": 0.48481880509304603,
|
||||
"f1": 0.5066530194472876,
|
||||
"octaveErrors": 118
|
||||
},
|
||||
"vocals": {
|
||||
"tp": 108,
|
||||
"fp": 388,
|
||||
"fn": 131,
|
||||
"precision": 0.21774193548387097,
|
||||
"recall": 0.45188284518828453,
|
||||
"f1": 0.2938775510204082,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"bass": {
|
||||
"tp": 84,
|
||||
"fp": 312,
|
||||
"fn": 79,
|
||||
"precision": 0.21212121212121213,
|
||||
"recall": 0.5153374233128835,
|
||||
"f1": 0.3005366726296959,
|
||||
"octaveErrors": 1
|
||||
},
|
||||
"drums": {
|
||||
"tp": 417,
|
||||
"fp": 427,
|
||||
"fn": 159,
|
||||
"precision": 0.4940758293838863,
|
||||
"recall": 0.7239583333333334,
|
||||
"f1": 0.5873239436619718,
|
||||
"octaveErrors": 16
|
||||
},
|
||||
"keys": {
|
||||
"tp": 329,
|
||||
"fp": 1553,
|
||||
"fn": 853,
|
||||
"precision": 0.17481402763018067,
|
||||
"recall": 0.27834179357022,
|
||||
"f1": 0.21475195822454307,
|
||||
"octaveErrors": 94
|
||||
}
|
||||
},
|
||||
"lenient": {
|
||||
"guitar": 0.539065165472535,
|
||||
"vocals": 0.39183673469387753,
|
||||
"bass": 0.35062611806797855,
|
||||
"drums": 0.5985915492957746,
|
||||
"keys": 0.25718015665796345
|
||||
},
|
||||
"drumsPerClass": {
|
||||
"kick": {
|
||||
"tp": 98,
|
||||
"fp": 53,
|
||||
"fn": 0,
|
||||
"precision": 0.6490066225165563,
|
||||
"recall": 1,
|
||||
"f1": 0.7871485943775101,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"snare": {
|
||||
"tp": 90,
|
||||
"fp": 211,
|
||||
"fn": 8,
|
||||
"precision": 0.29900332225913623,
|
||||
"recall": 0.9183673469387755,
|
||||
"f1": 0.45112781954887216,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"hihat": {
|
||||
"tp": 217,
|
||||
"fp": 156,
|
||||
"fn": 3,
|
||||
"precision": 0.5817694369973191,
|
||||
"recall": 0.9863636363636363,
|
||||
"f1": 0.7318718381112985,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"cymbal": {
|
||||
"tp": 12,
|
||||
"fp": 7,
|
||||
"fn": 142,
|
||||
"precision": 0.631578947368421,
|
||||
"recall": 0.07792207792207792,
|
||||
"f1": 0.13872832369942195,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"tom": {
|
||||
"tp": 0,
|
||||
"fp": 0,
|
||||
"fn": 6,
|
||||
"precision": null,
|
||||
"recall": 0,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"excludedTracks": [],
|
||||
"capo": {},
|
||||
"seconds": 99.844065053
|
||||
},
|
||||
{
|
||||
"id": "swimming",
|
||||
"title": "Swimming (Lunar Vacation)",
|
||||
"alignment": {
|
||||
"source": "saved",
|
||||
"manual": 0,
|
||||
"auto": 24,
|
||||
"savedAt": "2026-10-30T18:50:09.863Z",
|
||||
"chosen": "dtw",
|
||||
"baseline": {
|
||||
"offsetSec": 0.23,
|
||||
"precision": 0.6965944272445821,
|
||||
"cosine": 0.6263260975223001
|
||||
},
|
||||
"dtwPrecision": 0.7956656346749226
|
||||
},
|
||||
"fit": {
|
||||
"precision": 0.7956656346749226,
|
||||
"lift": 0.14138286893704843,
|
||||
"medianResidualMs": 15.842554880606485,
|
||||
"audibleRate": 0.2524440582228981,
|
||||
"windows": [
|
||||
{
|
||||
"startSec": 0,
|
||||
"recOnsets": 20,
|
||||
"precision": 0.9,
|
||||
"lift": 0.5166666666666667,
|
||||
"medianResidualMs": 7.930399045602776,
|
||||
"audibleRate": 0.4263157894736842,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 30,
|
||||
"recOnsets": 38,
|
||||
"precision": 0.8421052631578947,
|
||||
"lift": 0.4561403508771929,
|
||||
"medianResidualMs": 8.935720572655015,
|
||||
"audibleRate": 0.4797047970479705,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 60,
|
||||
"recOnsets": 30,
|
||||
"precision": 0.9666666666666667,
|
||||
"lift": 0.5,
|
||||
"medianResidualMs": 8.143065510864744,
|
||||
"audibleRate": 0.361244019138756,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 90,
|
||||
"recOnsets": 27,
|
||||
"precision": 0.7777777777777778,
|
||||
"lift": 0.39506172839506176,
|
||||
"medianResidualMs": 15.735118333282117,
|
||||
"audibleRate": 0.41919191919191917,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 120,
|
||||
"recOnsets": 45,
|
||||
"precision": 0.4666666666666667,
|
||||
"lift": -0.2666666666666667,
|
||||
"medianResidualMs": 2.775248941929931,
|
||||
"audibleRate": 0.1555944055944056,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"precision 47%",
|
||||
"lift -0.27"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 150,
|
||||
"recOnsets": 55,
|
||||
"precision": 0.9454545454545454,
|
||||
"lift": 0.16363636363636358,
|
||||
"medianResidualMs": 24.587909988653678,
|
||||
"audibleRate": 0.3125763125763126,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 180,
|
||||
"recOnsets": 52,
|
||||
"precision": 0.6923076923076923,
|
||||
"lift": -0.11538461538461553,
|
||||
"medianResidualMs": 33.98722951001787,
|
||||
"audibleRate": 0.19047619047619047,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift -0.12"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 210,
|
||||
"recOnsets": 47,
|
||||
"precision": 0.8297872340425532,
|
||||
"lift": -1.1102230246251565e-16,
|
||||
"medianResidualMs": 23.422793802808428,
|
||||
"audibleRate": 0.21311475409836064,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift -0.00"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 240,
|
||||
"recOnsets": 9,
|
||||
"precision": 1,
|
||||
"lift": 0.14814814814814825,
|
||||
"medianResidualMs": 1.804172777298163,
|
||||
"audibleRate": 0.09375,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 270,
|
||||
"recOnsets": 0,
|
||||
"precision": null,
|
||||
"lift": null,
|
||||
"medianResidualMs": null,
|
||||
"audibleRate": null,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
}
|
||||
],
|
||||
"stretches": {
|
||||
"total": 25,
|
||||
"warned": 0,
|
||||
"over15": 0
|
||||
}
|
||||
},
|
||||
"perRole": {
|
||||
"guitar": {
|
||||
"tp": 1219,
|
||||
"fp": 3001,
|
||||
"fn": 3773,
|
||||
"precision": 0.2888625592417062,
|
||||
"recall": 0.24419070512820512,
|
||||
"f1": 0.26465479808944853,
|
||||
"octaveErrors": 208
|
||||
},
|
||||
"vocals": {
|
||||
"tp": 35,
|
||||
"fp": 571,
|
||||
"fn": 166,
|
||||
"precision": 0.057755775577557754,
|
||||
"recall": 0.17412935323383086,
|
||||
"f1": 0.08674101610904585,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"bass": {
|
||||
"tp": 185,
|
||||
"fp": 716,
|
||||
"fn": 345,
|
||||
"precision": 0.20532741398446172,
|
||||
"recall": 0.3490566037735849,
|
||||
"f1": 0.2585604472396925,
|
||||
"octaveErrors": 9
|
||||
},
|
||||
"drums": {
|
||||
"tp": 392,
|
||||
"fp": 1408,
|
||||
"fn": 856,
|
||||
"precision": 0.21777777777777776,
|
||||
"recall": 0.3141025641025641,
|
||||
"f1": 0.2572178477690289,
|
||||
"octaveErrors": 142
|
||||
},
|
||||
"keys": {
|
||||
"tp": 0,
|
||||
"fp": 2032,
|
||||
"fn": 0,
|
||||
"precision": 0,
|
||||
"recall": null,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"lenient": {
|
||||
"guitar": 0.3864524533217542,
|
||||
"vocals": 0.12391573729863693,
|
||||
"bass": 0.3535988819007687,
|
||||
"drums": 0.3353018372703412,
|
||||
"keys": 0
|
||||
},
|
||||
"drumsPerClass": {
|
||||
"kick": {
|
||||
"tp": 175,
|
||||
"fp": 393,
|
||||
"fn": 163,
|
||||
"precision": 0.30809859154929575,
|
||||
"recall": 0.5177514792899408,
|
||||
"f1": 0.38631346578366443,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"snare": {
|
||||
"tp": 97,
|
||||
"fp": 363,
|
||||
"fn": 145,
|
||||
"precision": 0.2108695652173913,
|
||||
"recall": 0.40082644628099173,
|
||||
"f1": 0.27635327635327633,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"hihat": {
|
||||
"tp": 0,
|
||||
"fp": 465,
|
||||
"fn": 0,
|
||||
"precision": 0,
|
||||
"recall": null,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"cymbal": {
|
||||
"tp": 120,
|
||||
"fp": 187,
|
||||
"fn": 510,
|
||||
"precision": 0.39087947882736157,
|
||||
"recall": 0.19047619047619047,
|
||||
"f1": 0.256136606189968,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"tom": {
|
||||
"tp": 0,
|
||||
"fp": 0,
|
||||
"fn": 38,
|
||||
"precision": null,
|
||||
"recall": 0,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"excludedTracks": [],
|
||||
"capo": {},
|
||||
"seconds": 76.04858104300001
|
||||
},
|
||||
{
|
||||
"id": "holes",
|
||||
"title": "Holes (Passenger)",
|
||||
"alignment": {
|
||||
"source": "saved",
|
||||
"manual": 0,
|
||||
"auto": 1,
|
||||
"savedAt": "2026-10-30T18:50:13.626Z",
|
||||
"chosen": "baseline",
|
||||
"baseline": {
|
||||
"offsetSec": -0.62,
|
||||
"precision": 0.7642585551330798,
|
||||
"cosine": 0.5150959661213577
|
||||
},
|
||||
"dtwPrecision": 0.7452471482889734
|
||||
},
|
||||
"fit": {
|
||||
"precision": 0.7642585551330798,
|
||||
"lift": 0.026615969581749055,
|
||||
"medianResidualMs": 26.02722634637189,
|
||||
"audibleRate": 0.2326544701415257,
|
||||
"windows": [
|
||||
{
|
||||
"startSec": 0,
|
||||
"recOnsets": 57,
|
||||
"precision": 0.8596491228070176,
|
||||
"lift": 0.04093567251462005,
|
||||
"medianResidualMs": 29.71729302428372,
|
||||
"audibleRate": 0.24890190336749635,
|
||||
"beatShift": {
|
||||
"label": "+1 beat",
|
||||
"gain": 0.07068144786351394
|
||||
},
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.04",
|
||||
"beat shift suspected: +1 beat"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 30,
|
||||
"recOnsets": 53,
|
||||
"precision": 0.8867924528301887,
|
||||
"lift": 0.07547169811320753,
|
||||
"medianResidualMs": 22.94817039821595,
|
||||
"audibleRate": 0.29635499207606975,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.08"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 60,
|
||||
"recOnsets": 55,
|
||||
"precision": 0.8727272727272727,
|
||||
"lift": 0.07272727272727275,
|
||||
"medianResidualMs": 20.39969875089298,
|
||||
"audibleRate": 0.2913793103448276,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift 0.07"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 90,
|
||||
"recOnsets": 36,
|
||||
"precision": 0.5833333333333334,
|
||||
"lift": -0.08333333333333326,
|
||||
"medianResidualMs": 26.357498430371606,
|
||||
"audibleRate": 0.24187725631768953,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"precision 58%",
|
||||
"lift -0.08"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 120,
|
||||
"recOnsets": 7,
|
||||
"precision": null,
|
||||
"lift": null,
|
||||
"medianResidualMs": null,
|
||||
"audibleRate": 0.05371900826446281,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
},
|
||||
{
|
||||
"startSec": 150,
|
||||
"recOnsets": 35,
|
||||
"precision": 0.6571428571428571,
|
||||
"lift": -0.10476190476190472,
|
||||
"medianResidualMs": 26.66406226160234,
|
||||
"audibleRate": 0.13317191283292978,
|
||||
"beatShift": {
|
||||
"label": "+1 bar",
|
||||
"gain": 0.09281354253241003
|
||||
},
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"lift -0.10",
|
||||
"beat shift suspected: +1 bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 180,
|
||||
"recOnsets": 20,
|
||||
"precision": 0.5,
|
||||
"lift": 0.2,
|
||||
"medianResidualMs": 35.25757102913474,
|
||||
"audibleRate": 0.18309859154929578,
|
||||
"beatShift": null,
|
||||
"flagged": true,
|
||||
"reasons": [
|
||||
"precision 50%",
|
||||
"lift 0.20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"startSec": 210,
|
||||
"recOnsets": 0,
|
||||
"precision": null,
|
||||
"lift": null,
|
||||
"medianResidualMs": null,
|
||||
"audibleRate": null,
|
||||
"beatShift": null,
|
||||
"flagged": false,
|
||||
"reasons": []
|
||||
}
|
||||
],
|
||||
"stretches": {
|
||||
"total": 2,
|
||||
"warned": 0,
|
||||
"over15": 0
|
||||
}
|
||||
},
|
||||
"perRole": {
|
||||
"guitar": {
|
||||
"tp": 540,
|
||||
"fp": 2916,
|
||||
"fn": 2124,
|
||||
"precision": 0.15625,
|
||||
"recall": 0.20270270270270271,
|
||||
"f1": 0.17647058823529413,
|
||||
"octaveErrors": 207
|
||||
},
|
||||
"vocals": {
|
||||
"tp": 57,
|
||||
"fp": 623,
|
||||
"fn": 387,
|
||||
"precision": 0.0838235294117647,
|
||||
"recall": 0.12837837837837837,
|
||||
"f1": 0.10142348754448399,
|
||||
"octaveErrors": 1
|
||||
},
|
||||
"bass": {
|
||||
"tp": 15,
|
||||
"fp": 508,
|
||||
"fn": 110,
|
||||
"precision": 0.028680688336520075,
|
||||
"recall": 0.12,
|
||||
"f1": 0.046296296296296294,
|
||||
"octaveErrors": 2
|
||||
},
|
||||
"drums": {
|
||||
"tp": 192,
|
||||
"fp": 1431,
|
||||
"fn": 534,
|
||||
"precision": 0.11829944547134935,
|
||||
"recall": 0.2644628099173554,
|
||||
"f1": 0.16347381864623245,
|
||||
"octaveErrors": 29
|
||||
},
|
||||
"keys": {
|
||||
"tp": 34,
|
||||
"fp": 1057,
|
||||
"fn": 520,
|
||||
"precision": 0.031164069660861594,
|
||||
"recall": 0.061371841155234655,
|
||||
"f1": 0.04133738601823708,
|
||||
"octaveErrors": 18
|
||||
}
|
||||
},
|
||||
"lenient": {
|
||||
"guitar": 0.3055555555555556,
|
||||
"vocals": 0.15480427046263345,
|
||||
"bass": 0.07407407407407407,
|
||||
"drums": 0.2835249042145594,
|
||||
"keys": 0.0729483282674772
|
||||
},
|
||||
"drumsPerClass": {
|
||||
"kick": {
|
||||
"tp": 21,
|
||||
"fp": 223,
|
||||
"fn": 99,
|
||||
"precision": 0.0860655737704918,
|
||||
"recall": 0.175,
|
||||
"f1": 0.11538461538461539,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"snare": {
|
||||
"tp": 69,
|
||||
"fp": 261,
|
||||
"fn": 183,
|
||||
"precision": 0.20909090909090908,
|
||||
"recall": 0.27380952380952384,
|
||||
"f1": 0.23711340206185566,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"hihat": {
|
||||
"tp": 101,
|
||||
"fp": 603,
|
||||
"fn": 194,
|
||||
"precision": 0.1434659090909091,
|
||||
"recall": 0.3423728813559322,
|
||||
"f1": 0.2022022022022022,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"cymbal": {
|
||||
"tp": 1,
|
||||
"fp": 344,
|
||||
"fn": 5,
|
||||
"precision": 0.002898550724637681,
|
||||
"recall": 0.16666666666666666,
|
||||
"f1": 0.005698005698005698,
|
||||
"octaveErrors": 0
|
||||
},
|
||||
"tom": {
|
||||
"tp": 0,
|
||||
"fp": 0,
|
||||
"fn": 1,
|
||||
"precision": null,
|
||||
"recall": 0,
|
||||
"f1": 0,
|
||||
"octaveErrors": 0
|
||||
}
|
||||
},
|
||||
"excludedTracks": [
|
||||
"Brass",
|
||||
"Crowd"
|
||||
],
|
||||
"capo": {
|
||||
"Acoustic Guitar ": 9
|
||||
},
|
||||
"seconds": 58.31966715700002
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,78 @@
|
||||
# STSheets real-song benchmark: repeat-2026-10-31
|
||||
|
||||
> Reference = the team's Guitar Pro-exported MIDI warped onto the recording by the saved alignment; scores are note-onset matches (pitch must equal; drums compared by class). The fit line reports the *consistency of the alignment*, not an independent validation of it: precision = share of the recording's onset-flux peaks with a warped MIDI onset within ±70 ms (flag < 60% per 30 s window), lift = precision minus its value with the MIDI shifted off-grid (flag < 0.25), a symbolic-chroma check for whole-beat/bar shifts, and "MIDI onsets audible as attacks" (information only; a full mix exposes far fewer attacks than the score has notes). A poor alignment lowers every score below. Capo is informational and not applied.
|
||||
|
||||
## Last Night on Earth (Green Day) (lnoe)
|
||||
|
||||
- Alignment: saved 2026-10-30T18:50:04.512Z (0 manual / 1 auto anchors)
|
||||
- Method: chosen: baseline (DTW precision 92.4% not better by ≥ 2 points) · constant-offset baseline +0.03 s: precision 93.0%
|
||||
- Consistency of the alignment: precision 93.0% (lift +0.56) · MIDI onsets audible as attacks 40.6% · median residual 1.6 ms · 0/8 windows flagged · stretches: 0/2 warned, 0 > 15%
|
||||
- Capo: none listed
|
||||
- Excluded tracks (no role): none
|
||||
- Pipeline + scoring: 99.8 s
|
||||
|
||||
| Role | Ref notes | Est notes | P ±50 ms | R ±50 ms | F1 ±50 ms | F1 ±100 ms |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| guitar | 3063 | 2799 | 0.53 | 0.48 | 0.51 | 0.54 |
|
||||
| vocals | 239 | 496 | 0.22 | 0.45 | 0.29 | 0.39 |
|
||||
| bass | 163 | 396 | 0.21 | 0.52 | 0.30 | 0.35 |
|
||||
| drums | 576 | 844 | 0.49 | 0.72 | 0.59 | 0.60 |
|
||||
| keys | 1182 | 1882 | 0.17 | 0.28 | 0.21 | 0.26 |
|
||||
|
||||
| Drum class | Ref | Est | F1 ±50 ms |
|
||||
| --- | --- | --- | --- |
|
||||
| kick | 98 | 151 | 0.79 |
|
||||
| snare | 98 | 301 | 0.45 |
|
||||
| hihat | 220 | 373 | 0.73 |
|
||||
| cymbal | 154 | 19 | 0.14 |
|
||||
| tom | 6 | 0 | 0.00 |
|
||||
|
||||
## Swimming (Lunar Vacation) (swimming)
|
||||
|
||||
- Alignment: saved 2026-10-30T18:50:09.863Z (0 manual / 24 auto anchors)
|
||||
- Method: chosen: DTW (precision 79.6%) · constant-offset baseline +0.23 s: precision 69.7%
|
||||
- Consistency of the alignment: precision 79.6% (lift +0.14) · MIDI onsets audible as attacks 25.2% · median residual 15.8 ms · 5/10 windows flagged (2:00 precision 47%, lift -0.27; 2:30 lift 0.16; 3:00 lift -0.12; 3:30 lift -0.00; 4:00 lift 0.15) · stretches: 0/25 warned, 0 > 15%
|
||||
- Capo: none listed
|
||||
- Excluded tracks (no role): none
|
||||
- Pipeline + scoring: 76.0 s
|
||||
|
||||
| Role | Ref notes | Est notes | P ±50 ms | R ±50 ms | F1 ±50 ms | F1 ±100 ms |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| guitar | 4992 | 4220 | 0.29 | 0.24 | 0.26 | 0.39 |
|
||||
| vocals | 201 | 606 | 0.06 | 0.17 | 0.09 | 0.12 |
|
||||
| bass | 530 | 901 | 0.21 | 0.35 | 0.26 | 0.35 |
|
||||
| drums | 1248 | 1800 | 0.22 | 0.31 | 0.26 | 0.34 |
|
||||
| keys | 0 | 2032 | 0.00 | n/a | 0.00 | 0.00 |
|
||||
|
||||
| Drum class | Ref | Est | F1 ±50 ms |
|
||||
| --- | --- | --- | --- |
|
||||
| kick | 338 | 568 | 0.39 |
|
||||
| snare | 242 | 460 | 0.28 |
|
||||
| hihat | 0 | 465 | 0.00 |
|
||||
| cymbal | 630 | 307 | 0.26 |
|
||||
| tom | 38 | 0 | 0.00 |
|
||||
|
||||
## Holes (Passenger) (holes)
|
||||
|
||||
- Alignment: saved 2026-10-30T18:50:13.626Z (0 manual / 1 auto anchors)
|
||||
- Method: chosen: baseline (DTW precision 74.5% not better by ≥ 2 points) · constant-offset baseline −0.62 s: precision 76.4%
|
||||
- Consistency of the alignment: precision 76.4% (lift +0.03) · MIDI onsets audible as attacks 23.3% · median residual 26.0 ms · 6/8 windows flagged (0:00 lift 0.04, beat shift suspected: +1 beat; 0:30 lift 0.08; 1:00 lift 0.07; 1:30 precision 58%, lift -0.08; 2:30 lift -0.10, beat shift suspected: +1 bar; 3:00 precision 50%, lift 0.20) · stretches: 0/2 warned, 0 > 15%
|
||||
- Capo: Acoustic Guitar : 9
|
||||
- Excluded tracks (no role): Brass, Crowd
|
||||
- Pipeline + scoring: 58.3 s
|
||||
|
||||
| Role | Ref notes | Est notes | P ±50 ms | R ±50 ms | F1 ±50 ms | F1 ±100 ms |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| guitar | 2664 | 3456 | 0.16 | 0.20 | 0.18 | 0.31 |
|
||||
| vocals | 444 | 680 | 0.08 | 0.13 | 0.10 | 0.15 |
|
||||
| bass | 125 | 523 | 0.03 | 0.12 | 0.05 | 0.07 |
|
||||
| drums | 726 | 1623 | 0.12 | 0.26 | 0.16 | 0.28 |
|
||||
| keys | 554 | 1091 | 0.03 | 0.06 | 0.04 | 0.07 |
|
||||
|
||||
| Drum class | Ref | Est | F1 ±50 ms |
|
||||
| --- | --- | --- | --- |
|
||||
| kick | 120 | 244 | 0.12 |
|
||||
| snare | 252 | 330 | 0.24 |
|
||||
| hihat | 295 | 704 | 0.20 |
|
||||
| cymbal | 6 | 345 | 0.01 |
|
||||
| tom | 1 | 0 | 0.00 |
|
||||
+3
-3
@@ -44,18 +44,18 @@ async function main() {
|
||||
const seeds = parseSeeds(arg(rest, "seeds", "1-5"));
|
||||
const modes = arg(rest, "modes", "full,oracle-stems,oracle-tempo,oracle-notes").split(",");
|
||||
const runId = arg(rest, "run-id", new Date().toISOString().replace(/[:.]/g, "-"));
|
||||
await runBench({ tiers, seeds, modes, runId, root: arg(rest, "root", "data"), out: arg(rest, "out", "runs") });
|
||||
await runBench({ tiers, seeds, modes, runId, root: arg(rest, "root", "data"), out: arg(rest, "out", "runs"), repeatVote: !rest.includes("--no-repeat-vote") });
|
||||
console.log(`results/${runId}.md`);
|
||||
break;
|
||||
}
|
||||
case "real": {
|
||||
const runId = arg(rest, "run-id", new Date().toISOString().replace(/[:.]/g, "-"));
|
||||
await runReal({ runId, auto: rest.includes("--auto"), root: arg(rest, "root", "reference"), out: arg(rest, "out", "runs") });
|
||||
await runReal({ runId, auto: rest.includes("--auto"), root: arg(rest, "root", "reference"), out: arg(rest, "out", "runs"), repeatVote: !rest.includes("--no-repeat-vote") });
|
||||
console.log(`results/real-${runId}.md`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error("usage: bun run bench gen [--tiers t1a,t1b] [--seeds 1-5] [--root data]\n bun run bench run [--tiers ..] [--seeds ..] [--modes full,oracle-stems,oracle-tempo,oracle-notes] [--run-id id] [--root data] [--out runs]\n bun run bench real [--auto] [--run-id id] [--root reference] [--out runs]");
|
||||
console.error("usage: bun run bench gen [--tiers t1a,t1b] [--seeds 1-5] [--root data]\n bun run bench run [--tiers ..] [--seeds ..] [--modes full,oracle-stems,oracle-tempo,oracle-notes] [--run-id id] [--root data] [--out runs] [--no-repeat-vote]\n bun run bench real [--auto] [--run-id id] [--root reference] [--out runs] [--no-repeat-vote]");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -36,13 +36,13 @@ const MODELS = { demucs: "models/demucs/htdemucs_6s.onnx", basicPitch: "models/b
|
||||
const SF2 = join(import.meta.dir, "../../node_modules/@coderline/alphatab/dist/soundfont/sonivox.sf2");
|
||||
const TOL = 0.05, LENIENT_TOL = 0.1;
|
||||
|
||||
export async function runReal(opts: { runId: string; auto: boolean; root?: string; out?: string; resultsDir?: string }): Promise<RealSongResult[]> {
|
||||
export async function runReal(opts: { runId: string; auto: boolean; root?: string; out?: string; resultsDir?: string; repeatVote?: boolean }): Promise<RealSongResult[]> {
|
||||
const root = opts.root ?? "reference", out = opts.out ?? "runs", rdir = opts.resultsDir ?? "results";
|
||||
const songs = await readManifest(root);
|
||||
const pipeline = await createPipeline({
|
||||
demucs: new Uint8Array(await Bun.file(MODELS.demucs).arrayBuffer()),
|
||||
basicPitch: new Uint8Array(await Bun.file(MODELS.basicPitch).arrayBuffer()),
|
||||
});
|
||||
}, { repeatVote: opts.repeatVote ?? true });
|
||||
const sf2 = opts.auto ? new Uint8Array(await Bun.file(SF2).arrayBuffer()) : null;
|
||||
const results: RealSongResult[] = [];
|
||||
const skipped: SkippedSong[] = [];
|
||||
|
||||
+3
-1
@@ -25,11 +25,13 @@ async function sha256(bytes: Uint8Array): Promise<string> {
|
||||
|
||||
export async function runBench(opts: {
|
||||
tiers: TierId[]; seeds: number[]; modes: string[]; runId: string; root: string; out: string; resultsDir?: string;
|
||||
/** Guitar repetition voting (default on); false = --no-repeat-vote. */
|
||||
repeatVote?: boolean;
|
||||
}): Promise<BenchRun> {
|
||||
for (const m of opts.modes) if (!Object.hasOwn(MODES, m)) throw new Error(`unknown mode: ${m}`);
|
||||
const demucs = new Uint8Array(await Bun.file(MODEL_PATHS.demucs).arrayBuffer());
|
||||
const basicPitch = new Uint8Array(await Bun.file(MODEL_PATHS.basicPitch).arrayBuffer());
|
||||
const pipeline = await createPipeline({ demucs, basicPitch }); // model load once per run
|
||||
const pipeline = await createPipeline({ demucs, basicPitch }, { repeatVote: opts.repeatVote ?? true }); // model load once per run
|
||||
const run: BenchRun = {
|
||||
runId: opts.runId,
|
||||
date: new Date().toISOString(),
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// Repetition voting. Songs repeat bars (riffs, verses, choruses); a transcriber misses or invents different notes
|
||||
// in each repeat. Bars whose transcriptions are similar are treated as repeats of one another, and notes are voted
|
||||
// per (pitch, position in bar): notes that almost never recur across a bar's repeats are dropped (precision) and
|
||||
// notes present in enough repeats but missing from the bar are added (recall). Repetition is estimated from the
|
||||
// transcription itself under the estimated constant-tempo grid. Pure: no I/O.
|
||||
import type { Grid, TimedNote } from "../core/types.ts";
|
||||
|
||||
export interface RepeatOpts {
|
||||
/** Minimum coarse similarity (16th-slot occupancy cosine, smoothed along the diagonal) for two bars to be repeats. */
|
||||
minSim?: number;
|
||||
/** Minimum exact similarity (equal pitch, onset within tol) for two bars to be repeats. Guards against tempo
|
||||
* drift: a slightly wrong grid makes distant bars coarse-similar but misaligned, and voting must not run then. */
|
||||
minExact?: number;
|
||||
/** Bars of context on each side used to smooth coarse similarity along the repetition diagonal. */
|
||||
context?: number;
|
||||
/** Minimum number of repeats (other bars) a bar needs before voting is applied. */
|
||||
minRepeats?: number;
|
||||
/** Drop a note when it recurs in fewer than this fraction of the bar's repeats. */
|
||||
keepFrac?: number;
|
||||
/** Add a note when it occurs in at least this fraction of the bar's repeats ... */
|
||||
addFrac?: number;
|
||||
/** ... and in at least this many repeats. */
|
||||
minAddSupport?: number;
|
||||
/** Onset tolerance (s) when matching notes across repeats. */
|
||||
tol?: number;
|
||||
drop?: boolean;
|
||||
add?: boolean;
|
||||
}
|
||||
|
||||
/** Defaults chosen in experiment E5 (docs/experiments/2026-10-31-accuracy.md). */
|
||||
export const REPEAT_DEFAULTS: Required<RepeatOpts> = {
|
||||
minSim: 0.6, minExact: 0.3, context: 1, minRepeats: 2, keepFrac: 0.15, addFrac: 0.25, minAddSupport: 3, tol: 0.05,
|
||||
drop: true, add: true,
|
||||
};
|
||||
|
||||
const SLOTS = 16;
|
||||
|
||||
interface Placed { n: TimedNote; rel: number }
|
||||
|
||||
/** Group notes into bars of the grid; rel = onset relative to the bar's downbeat (s). bar0 = time of bar 0. */
|
||||
function barsOf(notes: TimedNote[], grid: Grid): { byBar: Placed[][]; barSec: number; bar0: number } {
|
||||
const barSec = (60 / grid.bpm) * grid.beatsPerBar;
|
||||
const half = barSec / SLOTS / 2; // a note just before a downbeat belongs to that bar
|
||||
const startBar = notes.reduce((m, n) => Math.min(m, Math.floor((n.onset - grid.firstDownbeat + half) / barSec)), 0);
|
||||
const bar0 = grid.firstDownbeat + startBar * barSec;
|
||||
const byBar: Placed[][] = [];
|
||||
for (const n of notes) {
|
||||
const b = Math.floor((n.onset - bar0 + half) / barSec);
|
||||
(byBar[b] ??= []).push({ n, rel: n.onset - (bar0 + b * barSec) });
|
||||
}
|
||||
for (let b = 0; b < byBar.length; b++) byBar[b] ??= [];
|
||||
return { byBar, barSec, bar0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Bar x bar similarities. coarse: cosine of (16th slot, pitch) occupancy, a key counting as shared at the same or an
|
||||
* adjacent slot, averaged along the diagonal over +-context bars. exact: fraction of notes with an equal-pitch note
|
||||
* within tol at the same position, normalised by sqrt(|i| |j|).
|
||||
*/
|
||||
export function barSimilarity(
|
||||
byBar: { pitch: number; rel: number }[][], barSec: number, tol: number, context: number,
|
||||
): { coarse: Float64Array[]; exact: Float64Array[] } {
|
||||
const nB = byBar.length;
|
||||
const keys = byBar.map((ps) => new Set(ps.map((p) => Math.min(SLOTS - 1, Math.max(0, Math.round((p.rel / barSec) * SLOTS))) * 128 + p.pitch)));
|
||||
const raw = keys.map((ki) => Float64Array.from(keys, (kj) => {
|
||||
if (!ki.size || !kj.size) return 0;
|
||||
let dot = 0;
|
||||
for (const k of ki) if (kj.has(k) || kj.has(k - 128) || kj.has(k + 128)) dot++;
|
||||
return dot / Math.sqrt(ki.size * kj.size);
|
||||
}));
|
||||
const coarse = raw.map((_, i) => Float64Array.from(raw, (__, j) => {
|
||||
let s = 0, c = 0;
|
||||
for (let k = -context; k <= context; k++) {
|
||||
const a = i + k, b = j + k;
|
||||
if (a >= 0 && b >= 0 && a < nB && b < nB) { s += raw[a]![b]!; c++; }
|
||||
}
|
||||
return c ? s / c : 0;
|
||||
}));
|
||||
const exact = byBar.map((A) => Float64Array.from(byBar, (B) => {
|
||||
if (!A.length || !B.length) return 0;
|
||||
let m = 0;
|
||||
for (const p of A) if (B.some((q) => q.pitch === p.pitch && Math.abs(q.rel - p.rel) <= tol)) m++;
|
||||
return m / Math.sqrt(A.length * B.length);
|
||||
}));
|
||||
return { coarse, exact };
|
||||
}
|
||||
|
||||
/** Vote notes across repeated bars. Returns a new, onset-sorted note list; input notes are not modified. */
|
||||
export function repeatVote(notes: TimedNote[], grid: Grid, opts: RepeatOpts = {}): TimedNote[] {
|
||||
const o = { ...REPEAT_DEFAULTS, ...opts };
|
||||
const finite = Number.isFinite(grid.bpm) && Number.isFinite(grid.firstDownbeat) && Number.isFinite(grid.beatsPerBar);
|
||||
if (notes.length === 0 || !finite || grid.bpm <= 0 || grid.beatsPerBar <= 0) return notes.slice();
|
||||
const { byBar, barSec, bar0 } = barsOf(notes, grid);
|
||||
const nB = byBar.length;
|
||||
const { coarse, exact } = barSimilarity(byBar.map((ps) => ps.map((p) => ({ pitch: p.n.pitch, rel: p.rel }))), barSec, o.tol, o.context);
|
||||
const has = (bar: number, pitch: number, rel: number) => byBar[bar]!.some((q) => q.n.pitch === pitch && Math.abs(q.rel - rel) <= o.tol);
|
||||
|
||||
const out: TimedNote[] = [];
|
||||
for (let i = 0; i < nB; i++) {
|
||||
const bar = byBar[i]!;
|
||||
const reps: number[] = [];
|
||||
for (let j = 0; j < nB; j++) if (j !== i && coarse[i]![j]! >= o.minSim && exact[i]![j]! >= o.minExact) reps.push(j);
|
||||
if (reps.length < o.minRepeats) { for (const p of bar) out.push(p.n); continue; }
|
||||
|
||||
for (const p of bar) {
|
||||
if (!o.drop) { out.push(p.n); continue; }
|
||||
const support = reps.filter((j) => has(j, p.n.pitch, p.rel)).length;
|
||||
if (support / reps.length >= o.keepFrac) out.push(p.n);
|
||||
}
|
||||
if (!o.add) continue;
|
||||
|
||||
// Candidates from the repeats, clustered by pitch and position; one vote per repeat.
|
||||
const cands: { pitch: number; rel: number; rels: number[]; durs: number[]; bars: Set<number> }[] = [];
|
||||
for (const j of reps) for (const q of byBar[j]!) {
|
||||
if (has(i, q.n.pitch, q.rel)) continue;
|
||||
let c = cands.find((x) => x.pitch === q.n.pitch && Math.abs(x.rel - q.rel) <= o.tol);
|
||||
if (!c) cands.push((c = { pitch: q.n.pitch, rel: q.rel, rels: [], durs: [], bars: new Set() }));
|
||||
if (c.bars.has(j)) continue;
|
||||
c.bars.add(j); c.rels.push(q.rel); c.durs.push(q.n.offset - q.n.onset);
|
||||
}
|
||||
const added: TimedNote[] = [];
|
||||
// Near-duplicate guard: no added note within tol of a same-pitch note already in the bar (or its neighbours,
|
||||
// or already added) - an onset just outside tol in the bar itself would otherwise get a second copy.
|
||||
const near = (onset: number, pitch: number) => [...(byBar[i - 1] ?? []), ...bar, ...(byBar[i + 1] ?? [])]
|
||||
.some((q) => q.n.pitch === pitch && Math.abs(q.n.onset - onset) <= o.tol) ||
|
||||
added.some((a) => a.pitch === pitch && Math.abs(a.onset - onset) <= o.tol);
|
||||
for (const c of cands) {
|
||||
if (c.bars.size < o.minAddSupport || c.bars.size / reps.length < o.addFrac) continue;
|
||||
const onset = bar0 + i * barSec + median(c.rels);
|
||||
if (onset < 0 || near(onset, c.pitch)) continue;
|
||||
added.push({ onset, offset: onset + median(c.durs), pitch: c.pitch });
|
||||
}
|
||||
out.push(...added);
|
||||
}
|
||||
return out.sort((a, b) => a.onset - b.onset || a.pitch - b.pitch);
|
||||
}
|
||||
|
||||
function median(a: number[]): number {
|
||||
const s = [...a].sort((x, y) => x - y);
|
||||
const m = s.length >> 1;
|
||||
return s.length % 2 ? s[m]! : (s[m - 1]! + s[m]!) / 2;
|
||||
}
|
||||
+27
-3
@@ -10,12 +10,18 @@ import { transcribeDrums } from "./drums.ts";
|
||||
import { estimateGrid, gridFromTruth } from "./tempo.ts";
|
||||
import { quantize, snapTicks } from "./quantize.ts";
|
||||
import { assignTab } from "./tab.ts";
|
||||
import { repeatVote } from "./repeat.ts";
|
||||
|
||||
export interface PipelineInput {
|
||||
mix: Audio;
|
||||
oracle?: { stems: Map<string, Audio>; truth: { bpm: number; timeSig: [number, number]; tracks: TruthTrack[] } };
|
||||
}
|
||||
export interface PipelineResult { output: PipelineOutput; gp: Uint8Array; warnings: string[] }
|
||||
export interface PipelineOpts {
|
||||
/** Vote guitar notes across repeated bars (see repeat.ts). Default on: experiment E5 raised guitar F1 on
|
||||
* synthetic t1a–t1d (+0.02 median) and on "Last Night on Earth" (0.403 → 0.507). */
|
||||
repeatVote?: boolean;
|
||||
}
|
||||
|
||||
const PROGRAM: Record<Role, number> = { guitar: 27, bass: 33, keys: 0, vocals: 53, drums: 0 };
|
||||
const RANGE: Record<Exclude<Role, "drums">, TranscribeOpts> = {
|
||||
@@ -40,8 +46,22 @@ export function copyFingering(timed: TimedNote[], assigned: ScoreNote[], grid: G
|
||||
});
|
||||
}
|
||||
|
||||
/** Sample-wise sum of equally shaped audio buffers. */
|
||||
export function sumAudio(...as: Audio[]): Audio {
|
||||
const first = as[0]!;
|
||||
return {
|
||||
sampleRate: first.sampleRate,
|
||||
channels: first.channels.map((c, k) => {
|
||||
const o = new Float32Array(c.length);
|
||||
for (const a of as) { const x = a.channels[k]!; for (let i = 0; i < o.length; i++) o[i]! += x[i]!; }
|
||||
return o;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createPipeline(
|
||||
models: { demucs: Uint8Array; basicPitch: Uint8Array },
|
||||
opts: PipelineOpts = {},
|
||||
): Promise<(input: PipelineInput, flags: OracleFlags) => Promise<PipelineResult>> {
|
||||
const separate = await createSeparator(models.demucs);
|
||||
const transcribe = await createTranscriber(models.basicPitch);
|
||||
@@ -63,9 +83,10 @@ export async function createPipeline(
|
||||
}
|
||||
} else {
|
||||
const s = await separate(input.mix);
|
||||
warnings.push('demucs "other" stem unused');
|
||||
const mk = (name: string, role: Role, audio: Audio): Job => ({ name, role, audio, truthNotes: null });
|
||||
jobs.push(mk("Guitar", "guitar", s.guitar), mk("Bass", "bass", s.bass), mk("Keys", "keys", s.piano),
|
||||
// Guitar is transcribed from guitar + piano + other: Demucs-6s scatters guitar energy across all three
|
||||
// (synthetic t1 median guitar F1 0.39–0.60 → 0.72–0.87; real "Last Night on Earth" 0.390 → 0.403).
|
||||
jobs.push(mk("Guitar", "guitar", sumAudio(s.guitar, s.piano, s.other)), mk("Bass", "bass", s.bass), mk("Keys", "keys", s.piano),
|
||||
mk("Vocals", "vocals", s.vocals), mk("Drums", "drums", s.drums));
|
||||
}
|
||||
|
||||
@@ -89,7 +110,10 @@ export async function createPipeline(
|
||||
else if (flags.oracleNotes) {
|
||||
const src = job.truthNotes ?? oracle!.truth.tracks.filter((t) => t.role === job.role).flatMap((t) => t.notes);
|
||||
notes = src.map((n) => ({ onset: n.onset, offset: n.offset, pitch: n.pitch, velocity: n.velocity }));
|
||||
} else notes = await transcribe(job.audio!, RANGE[job.role]);
|
||||
} else {
|
||||
notes = await transcribe(job.audio!, RANGE[job.role]);
|
||||
if (job.role === "guitar" && (opts.repeatVote ?? true)) notes = repeatVote(notes, grid);
|
||||
}
|
||||
|
||||
let scoreNotes = quantize(notes, grid);
|
||||
const tuning = TUNING[job.role];
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import type { Grid, TimedNote } from "../../src/core/types.ts";
|
||||
import { repeatVote } from "../../src/pipeline/repeat.ts";
|
||||
|
||||
const grid: Grid = { bpm: 120, firstDownbeat: 0, beatsPerBar: 4 }; // 2 s bars
|
||||
const riff: [number, number][] = [[0, 52], [0.5, 55], [1, 57], [1.5, 59], [1.75, 60]]; // [pos s, pitch]
|
||||
|
||||
function bars(n: number, f: (bar: number, pos: number, pitch: number) => boolean = () => true): TimedNote[] {
|
||||
const out: TimedNote[] = [];
|
||||
for (let b = 0; b < n; b++) for (const [pos, pitch] of riff) {
|
||||
if (f(b, pos, pitch)) out.push({ onset: b * 2 + pos, offset: b * 2 + pos + 0.2, pitch });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const key = (n: TimedNote) => `${n.onset.toFixed(3)}:${n.pitch}`;
|
||||
|
||||
test("adds a note missing from one repeat", () => {
|
||||
const notes = bars(6, (b, pos) => !(b === 3 && pos === 1));
|
||||
const out = repeatVote(notes, grid);
|
||||
expect(out.length).toBe(30);
|
||||
const added = out.find((n) => Math.abs(n.onset - 7) < 1e-6 && n.pitch === 57);
|
||||
expect(added).toBeDefined();
|
||||
expect(added!.offset - added!.onset).toBeCloseTo(0.2, 6);
|
||||
});
|
||||
|
||||
test("drops a note that never recurs in a bar's repeats", () => {
|
||||
const notes = [...bars(6), { onset: 4.25, offset: 4.4, pitch: 70 }];
|
||||
const out = repeatVote(notes, grid);
|
||||
expect(out.map(key)).toEqual(bars(6).map(key));
|
||||
});
|
||||
|
||||
test("leaves non-repeating material unchanged", () => {
|
||||
const notes: TimedNote[] = [];
|
||||
for (let b = 0; b < 6; b++) notes.push({ onset: b * 2 + 0.3 * (b % 4), offset: b * 2 + 1, pitch: 45 + 3 * b });
|
||||
expect(repeatVote(notes, grid).map(key)).toEqual(notes.map(key));
|
||||
});
|
||||
|
||||
test("add/drop can be disabled and empty input is handled", () => {
|
||||
const notes = [...bars(6, (b, pos) => !(b === 3 && pos === 1)), { onset: 4.25, offset: 4.4, pitch: 70 }];
|
||||
expect(repeatVote(notes, grid, { add: false, drop: false }).length).toBe(notes.length);
|
||||
expect(repeatVote([], grid)).toEqual([]);
|
||||
});
|
||||
|
||||
test("notes before a late first downbeat are voted too", () => {
|
||||
// Grid downbeat at 4.3 s; the riff starts at 0.3 s, so bars -2 and -1 precede the first downbeat.
|
||||
const g: Grid = { ...grid, firstDownbeat: 4.3 };
|
||||
const notes = bars(6, (b, pos) => !(b === 0 && pos === 0.5)).map((n) => ({ ...n, onset: n.onset + 0.3, offset: n.offset + 0.3 }));
|
||||
expect(notes[0]!.onset).toBeLessThan(g.firstDownbeat);
|
||||
const out = repeatVote(notes, g);
|
||||
expect(out.length).toBe(30);
|
||||
expect(out.some((n) => Math.abs(n.onset - 0.8) < 1e-6 && n.pitch === 55)).toBe(true);
|
||||
});
|
||||
|
||||
test("non-finite or zero grid returns an unchanged copy", () => {
|
||||
const notes = bars(6, (b, pos) => !(b === 3 && pos === 1));
|
||||
for (const g of [
|
||||
{ ...grid, bpm: 0 }, { ...grid, bpm: NaN }, { ...grid, bpm: Infinity },
|
||||
{ ...grid, firstDownbeat: NaN }, { ...grid, firstDownbeat: -Infinity }, { ...grid, beatsPerBar: NaN },
|
||||
]) {
|
||||
const out = repeatVote(notes, g);
|
||||
expect(out).toEqual(notes);
|
||||
expect(out).not.toBe(notes);
|
||||
}
|
||||
});
|
||||
|
||||
test("notes after the last full bar are kept and voted", () => {
|
||||
// 6 full bars plus a trailing partial bar holding only the riff's first note.
|
||||
const notes = [...bars(6), { onset: 12, offset: 12.2, pitch: 52 }];
|
||||
const out = repeatVote(notes, grid);
|
||||
// Every input note survives (the trailing one is supported by the riff's downbeat note in all repeats) ...
|
||||
for (const n of notes) expect(out.some((m) => Math.abs(m.onset - n.onset) < 1e-6 && m.pitch === n.pitch)).toBe(true);
|
||||
// ... and anything added there stays inside that bar.
|
||||
expect(out.every((n) => n.onset < 14)).toBe(true);
|
||||
});
|
||||
|
||||
test("half-tempo grid (riff twice per bar) still fills a missing note", () => {
|
||||
const g: Grid = { ...grid, bpm: 60 }; // 4 s bars
|
||||
const notes = bars(8, (b, pos) => !(b === 5 && pos === 1));
|
||||
const out = repeatVote(notes, g);
|
||||
expect(out.some((n) => Math.abs(n.onset - 11) < 1e-6 && n.pitch === 57)).toBe(true);
|
||||
});
|
||||
|
||||
test("added notes do not duplicate a same-pitch note within tol in a neighbouring bar", () => {
|
||||
// The riff ends with a note 80 ms before the next downbeat. In bar 3 that note is 40 ms late, which moves it
|
||||
// across the bar line into bar 4: bar 3 looks like it misses the note, but adding it would duplicate the late one.
|
||||
const notes = bars(6).flatMap((n) => n.pitch === 52 ? [n, { onset: n.onset + 1.92, offset: n.onset + 2, pitch: 64 }] : [n])
|
||||
.map((n) => (n.pitch === 64 && Math.abs(n.onset - 7.92) < 1e-9 ? { ...n, onset: 7.96, offset: 8.04 } : n));
|
||||
const out = repeatVote(notes, grid, { drop: false });
|
||||
expect(out.filter((n) => n.pitch === 64 && Math.abs(n.onset - 7.94) < 0.1).length).toBe(1);
|
||||
// Without the late note, the gap is filled.
|
||||
const gap = notes.filter((n) => !(n.pitch === 64 && n.onset === 7.96));
|
||||
expect(repeatVote(gap, grid, { drop: false }).some((n) => n.pitch === 64 && Math.abs(n.onset - 7.92) < 1e-6)).toBe(true);
|
||||
});
|
||||
@@ -1,7 +1,9 @@
|
||||
import { test, expect, beforeAll } from "bun:test";
|
||||
import { join } from "node:path";
|
||||
import { buildSong, loadSongDir } from "../../src/bench/dataset.ts";
|
||||
import { createPipeline, copyFingering } from "../../src/pipeline/run.ts";
|
||||
import { createPipeline, copyFingering, sumAudio } from "../../src/pipeline/run.ts";
|
||||
import { createTranscriber } from "../../src/pipeline/transcribe.ts";
|
||||
import { repeatVote } from "../../src/pipeline/repeat.ts";
|
||||
import { snapTicks, quantize } from "../../src/pipeline/quantize.ts";
|
||||
import type { TimedNote } from "../../src/core/types.ts";
|
||||
import { truthFromGp } from "../../src/score/gp.ts";
|
||||
@@ -47,9 +49,9 @@ test("t1c/1 full pipeline yields all roles", async () => {
|
||||
const roles = new Set(output.tracks.map((t) => t.role));
|
||||
for (const r of ["guitar", "bass", "keys", "vocals", "drums"]) expect(roles.has(r as never)).toBe(true);
|
||||
expect(gp.length).toBeGreaterThan(0);
|
||||
expect(warnings.some((w) => w.includes("other"))).toBe(true);
|
||||
expect(warnings.some((w) => w.includes("other"))).toBe(false);
|
||||
expect(Math.abs(output.grid.bpm - d.truth.bpm)).toBeLessThan(2);
|
||||
expect(s.guitarMerged.f1!).toBeGreaterThan(0.3);
|
||||
expect(s.guitarMerged.f1!).toBeGreaterThan(0.6); // guitar from guitar+piano+other stems (was 0.57 from the guitar stem alone)
|
||||
}, SLOW);
|
||||
|
||||
test("t1d/1 all oracle flags: two guitar tracks, partSplit and merged F1 === 1", async () => {
|
||||
@@ -90,3 +92,30 @@ test("copyFingering matches by snapped tick + pitch (merged cluster, adjacent re
|
||||
copyFingering(timed, assigned, grid);
|
||||
expect(timed.map((x) => [x.string, x.fret])).toEqual([[2, 0], [2, 0], [3, 240], [1, 0]]);
|
||||
});
|
||||
|
||||
test("sumAudio adds buffers sample-wise", () => {
|
||||
const a = { sampleRate: 10, channels: [new Float32Array([1, 2]), new Float32Array([0, 1])] };
|
||||
const b = { sampleRate: 10, channels: [new Float32Array([3, -2]), new Float32Array([1, 1])] };
|
||||
const s = sumAudio(a, b);
|
||||
expect([...s.channels[0]!]).toEqual([4, 0]);
|
||||
expect([...s.channels[1]!]).toEqual([1, 2]);
|
||||
expect(s.sampleRate).toBe(10);
|
||||
});
|
||||
|
||||
test("repeatVote option: off = raw transcription, on (default) = repeat-voted", async () => {
|
||||
// Cheap fixture: oracle stems + oracle tempo (no separation), t1a/1 guitar stem.
|
||||
const bytes = async (p: string) => new Uint8Array(await Bun.file(join(root, p)).arrayBuffer());
|
||||
const off = await createPipeline({ demucs: await bytes("models/demucs/htdemucs_6s.onnx"), basicPitch: await bytes("models/basic-pitch/nmp.onnx") }, { repeatVote: false });
|
||||
const transcribe = await createTranscriber(await bytes("models/basic-pitch/nmp.onnx"));
|
||||
const d = await loadSongDir(join(root, "data/t1a/1"));
|
||||
const flags = { oracleStems: true, oracleTempo: true, oracleNotes: false };
|
||||
const input = { mix: d.mix, oracle: { stems: d.stems, truth: d.truth } };
|
||||
const gt = d.truth.tracks.find((t) => t.role === "guitar")!;
|
||||
const raw = await transcribe(d.stems.get(gt.name)!, { minPitch: 40, maxPitch: 88 });
|
||||
const strip = (ns: TimedNote[]) => ns.map((n) => [n.onset, n.offset, n.pitch]);
|
||||
const offNotes = (await off(input, flags)).output.tracks.find((t) => t.role === "guitar")!.notes;
|
||||
expect(strip(offNotes)).toEqual(strip(raw));
|
||||
const onRes = await run(input, flags);
|
||||
const onNotes = onRes.output.tracks.find((t) => t.role === "guitar")!.notes;
|
||||
expect(strip(onNotes)).toEqual(strip(repeatVote(raw, onRes.output.grid)));
|
||||
}, SLOW);
|
||||
|
||||
Reference in New Issue
Block a user