"""Builds the Note Detect Benchmark sloppak (v2). A relaxed-pace test piece tuned for the player's strengths: half-note spacing throughout, no hammer-on / pull-off section, no bend section, no fast staccato. Adds explicit strumming sections (single chord repeated at half-note cadence) so the chord scorer is exercised across a sequence of strums on the same voicing — closer to how chords actually appear in real songs than v1's single-stroke voicings. Goals vs v1: - More breathing room between every event (half-notes, ~1.33 s at 90 BPM, instead of v1's quarter notes at ~0.667 s). - More chord events overall, with strumming patterns. - Drop the technique sections (HO/PO/bends) — the detector's technique handling is the next algorithm focus, separate from measuring "do basic single notes + chords score correctly?" How to run inside the feedBack container: docker cp docs/benchmarks/note_detect_v2/build_benchmark.py \\ feedBack-web-1:/tmp/build_benchmark_v2.py docker exec feedBack-web-1 python /tmp/build_benchmark_v2.py \\ /app/static/sloppak_cache/note_detect_benchmark_v2.sloppak After regenerating, copy the zip output to the tracked path with the `.sloppak` (not `.sloppak.zip`) suffix — same gotcha as v1: cp static/sloppak_cache/note_detect_benchmark_v2.sloppak.zip \\ docs/benchmarks/note_detect_v2/note_detect_benchmark_v2.sloppak """ import json import math import shutil import struct import subprocess import sys import wave from pathlib import Path import yaml # ── Benchmark parameters ──────────────────────────────────────────────── BPM = 90.0 SECONDS_PER_BEAT = 60.0 / BPM BEATS_PER_BAR = 4 BAR_S = BEATS_PER_BAR * SECONDS_PER_BEAT INTRO_BARS = 2 OUTRO_BARS = 2 EXERCISE_BARS = 8 # v2 uses 8-bar sections (was 6 in v1) for extra breathing room. # Standard E-tuning open MIDI per string, low → high. OPEN_MIDI = [40, 45, 50, 55, 59, 64] # E2 A2 D3 G3 B3 E4 SR = 44100 # ── Click-track audio generator ──────────────────────────────────────── def _sine_burst(freq_hz, duration_s, amplitude): n = int(SR * duration_s) out = [] fade = max(1, int(0.004 * SR)) for i in range(n): env = 1.0 if i < fade: env = i / fade elif i >= n - fade: env = (n - 1 - i) / fade s = math.sin(2 * math.pi * freq_hz * (i / SR)) * amplitude * env out.append(max(-1.0, min(1.0, s))) return out def write_click_wav(path: Path, duration_s: float): """Per-beat click track. Downbeats louder + higher pitch.""" total_samples = int(SR * duration_s) pcm = [0] * total_samples beat = 0 t = 0.0 while t < duration_s: is_downbeat = (beat % BEATS_PER_BAR == 0) freq = 1200 if is_downbeat else 800 amp = 0.6 if is_downbeat else 0.35 burst = _sine_burst(freq, 0.040, amp) start = int(t * SR) for i, s in enumerate(burst): j = start + i if 0 <= j < total_samples: pcm[j] = int(max(-1.0, min(1.0, pcm[j] / 32767 + s)) * 32767) t += SECONDS_PER_BEAT beat += 1 pcm = [struct.pack(' 1 else Path('./note_detect_benchmark_v2.sloppak') build(out)