diff --git a/lib/routers/ws_highway.py b/lib/routers/ws_highway.py index 62cd152..7f4fc38 100644 --- a/lib/routers/ws_highway.py +++ b/lib/routers/ws_highway.py @@ -476,12 +476,22 @@ async def highway_ws(websocket: WebSocket, filename: str, arrangement: int = -1, _evict_audio_cache() # Send song metadata + # For drum-only sloppaks the placeholder arrangement has 0 notes + # (drum_tab hits live separately). Surface the real hit count so + # the UI shows e.g. "Drums (1922)" instead of "Drums (0)". + _dt_hit_count = 0 + if is_slop and loaded_slop is not None and loaded_slop.drum_tab is not None: + _dt_hit_count = len(loaded_slop.drum_tab.get("hits") or []) arr_list = [ { "index": i, "name": a.name, "smart_name": smart_names[i], - "notes": len(a.notes) + sum(len(c.notes) for c in a.chords), + "notes": ( + _dt_hit_count + if (len(a.notes) == 0 and not a.chords and _dt_hit_count > 0) + else len(a.notes) + sum(len(c.notes) for c in a.chords) + ), } for i, a in enumerate(song.arrangements) ] diff --git a/lib/sloppak.py b/lib/sloppak.py index 8f943e9..6e05dcd 100644 --- a/lib/sloppak.py +++ b/lib/sloppak.py @@ -895,16 +895,20 @@ def load_song( log.warning("sloppak: drum_tab %r failed validation: %s", drum_tab_rel, reason) - # Drum-only sloppak: every GP track was percussion, so it ships a - # drum_tab but no pitched arrangements. The highway WS rejects an empty - # arrangements list with "No arrangements found" *before* it serves the - # drum_tab, leaving the drums unplayable even in the drum highway. - # Synthesize a minimal placeholder arrangement so the stream proceeds and - # the drum_tab reaches the drum highway. It carries no notes (the guitar - # highway just shows an empty board) and, when the manifest omits a + # Drum sloppak: when a drum_tab is present but no existing arrangement is + # a drum part, synthesize a minimal placeholder so the drums appear in the + # arrangement picker and the drum_tab reaches the drum highway. The editor + # strips drum arrangements out of the manifest (converting them to + # drum_tab), so without this a drum+bass sloppak would show only Bass in + # the picker with no way to reach the drums. It carries no notes (the + # guitar highway just shows an empty board) and, when the manifest omits a # duration, derives a song length from the last drum hit so the timeline # isn't zero-length. - if not song.arrangements and drum_tab_data is not None: + _has_drum_arr = any( + (getattr(a, "name", "") or "").lower().startswith("drum") + for a in song.arrangements + ) + if not _has_drum_arr and drum_tab_data is not None: if song.song_length <= 0: # validate_drum_tab() intentionally does NOT type-check individual # hits (they're sanitized at WS-stream time), so a hit may carry a