mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
Clean release snapshot
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Tone helpers for sloppak playback.
|
||||
|
||||
A slopsmith arrangement may carry a tone block — the initial tone name plus
|
||||
in-song tone switches — embedded inline in the arrangement JSON (see
|
||||
``lib/song.py`` ``arrangement_to_wire`` / the ``tones`` wire key). This module
|
||||
turns that already-embedded block into the (base, changes) payload the highway
|
||||
WebSocket sends to the client.
|
||||
|
||||
The proprietary-archive tone-extraction path (lifting tone definitions out of
|
||||
an unpacked encrypted archive) has been removed. Slopsmith reads tones only
|
||||
from its own ``.sloppak`` / arrangement JSON; it never reads or decrypts
|
||||
proprietary archive formats.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
|
||||
log = logging.getLogger("slopsmith.lib.tones")
|
||||
|
||||
|
||||
def tokens(s: str) -> set[str]:
|
||||
"""Split a name or file stem into lowercased alphanumeric tokens.
|
||||
|
||||
Used for fuzzy arrangement↔XML matching: arrangement names carry spaces
|
||||
("Bonus Lead") while file stems are underscored ("song_bonus_lead"), and a
|
||||
plain substring check is ambiguous ("lead" is a substring of "bonuslead").
|
||||
Shared with the playback path in `server.py` so the two stay consistent.
|
||||
"""
|
||||
return {t for t in re.split(r"[^a-z0-9]+", (s or "").lower()) if t}
|
||||
|
||||
|
||||
def sloppak_tone_changes(arr_tones) -> tuple[str, list[dict]]:
|
||||
"""Build the highway tone-change payload from an arrangement's tone block.
|
||||
|
||||
Given ``Arrangement.tones`` (the dict embedded in the sloppak, or ``None``),
|
||||
returns ``(base, changes)`` where ``base`` is the initial tone name and
|
||||
``changes`` is a time-sorted ``[{"t", "name"}]`` list. Non-string names,
|
||||
non-dict entries, and non-numeric / non-finite times are skipped — a
|
||||
hand-edited or third-party sloppak must not crash the highway WebSocket
|
||||
or emit NaN/inf (which the client's ``JSON.parse`` rejects).
|
||||
"""
|
||||
if not isinstance(arr_tones, dict):
|
||||
return "", []
|
||||
base_val = arr_tones.get("base", "")
|
||||
base = base_val.strip() if isinstance(base_val, str) else ""
|
||||
|
||||
changes: list[dict] = []
|
||||
raw_changes = arr_tones.get("changes")
|
||||
if not isinstance(raw_changes, list):
|
||||
# A truthy non-list (e.g. `1`) would raise TypeError on iteration.
|
||||
raw_changes = []
|
||||
for c in raw_changes:
|
||||
if not isinstance(c, dict):
|
||||
continue
|
||||
t = c.get("t")
|
||||
name = c.get("name")
|
||||
if t is None or not isinstance(name, str) or not name:
|
||||
continue
|
||||
try:
|
||||
t = float(t)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if not math.isfinite(t):
|
||||
continue
|
||||
changes.append({"t": round(t, 3), "name": name})
|
||||
changes.sort(key=lambda x: x["t"])
|
||||
return base, changes
|
||||
Reference in New Issue
Block a user