diff --git a/.gitattributes b/.gitattributes index 4e60670..4d96f90 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,6 +11,6 @@ # so LICENSE / VERSION stay diff-readable for future version bumps. static/vendor/three/three.module.min.js binary -# PSARC test fixtures are zlib-compressed binary archives — diffs and -# autocrlf rewrites would corrupt them. -tests/fixtures/*.psarc binary +# Sloppak test fixtures are zip archives — diffs and autocrlf rewrites +# would corrupt them. +tests/fixtures/*.sloppak binary diff --git a/CLAUDE.md b/CLAUDE.md index fb64f19..b8cf514 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -102,8 +102,8 @@ The fix is `context["load_sibling"](name)`, which loads the sibling under a name ```python def setup(app, context): - extractor = context["load_sibling"]("extractor") - PsarcReader = extractor.PsarcReader + helper = context["load_sibling"]("helper") + HelperClass = helper.HelperClass # … ``` @@ -340,9 +340,9 @@ Plugins that add a layer on top of whichever visualization is active — HUDs, f - `highway.hasPhraseData()` — returns `true` when the current song has phrase-level difficulty ladder data (i.e. the mastery slider is active and `getFilteredNotes()` / `getFilteredChords()` return a filtered subset). Use this to gate logic that only makes sense when difficulty filtering is available - `highway.getPhrases()` — phrase timing windows `[{ index, start_time, end_time, max_difficulty }]` for the current song's difficulty ladder. Returns `null` when phrase data is absent (GP imports, single-difficulty charts). Read-only; do not mutate. Pair with `hasPhraseData()` to gate phrase-aware logic. - `highway.getMastery()` — current master-difficulty slider value as a fraction `0..1`. Reflects the same value the mastery slider is set to; meaningful only when `hasPhraseData()` is true. -- `highway.getChordTemplates()` — chord shape lookup table; index by `chord.id` from `getChords()` to get `{ name, fingers, frets }`. `fingers` and `frets` are per-string arrays (length matches the tuning's string count); within `fingers`, `-1` = unused, `0` = open string, `n > 0` = finger number. RS XML sources populate real fingerings; GP imports currently emit all `-1` since pre-import sources don't carry finger data. Not filter-aware: templates are static metadata, every `chord_id` referenced by `getChords()` is guaranteed valid +- `highway.getChordTemplates()` — chord shape lookup table; index by `chord.id` from `getChords()` to get `{ name, fingers, frets }`. `fingers` and `frets` are per-string arrays (length matches the tuning's string count); within `fingers`, `-1` = unused, `0` = open string, `n > 0` = finger number. arrangement XML sources populate real fingerings; GP imports currently emit all `-1` since pre-import sources don't carry finger data. Not filter-aware: templates are static metadata, every `chord_id` referenced by `getChords()` is guaranteed valid - `highway.getSongInfo()` — tuning, arrangement, capo -- `highway.getStringCount()` — number of strings on the active arrangement (4 for bass, 6 for guitar, 7+ for extended-range GP imports). Derived server-side as `max(notes-max-string + 1, name-based fallback, len(tuning))` where the tuning length only contributes when it isn't the RS-XML padded 6-string form (sloppak / GP-imported sources carry trimmed tuning lengths). The name-based fallback is 4 for arrangements containing "bass" (case-insensitive) and 6 otherwise. This combination handles partial-string-usage charts (a 6-string lead that never plays string 5), extended-range GP imports (5-string bass, 7-string guitar), and sloppaks that explicitly encode the instrument range — without requiring plugins to do their own arrangement-name matching +- `highway.getStringCount()` — number of strings on the active arrangement (4 for bass, 6 for guitar, 7+ for extended-range GP imports). Derived server-side as `max(notes-max-string + 1, name-based fallback, len(tuning))` where the tuning length only contributes when it isn't the arrangement XML padded 6-string form (sloppak / GP-imported sources carry trimmed tuning lengths). The name-based fallback is 4 for arrangements containing "bass" (case-insensitive) and 6 otherwise. This combination handles partial-string-usage charts (a 6-string lead that never plays string 5), extended-range GP imports (5-string bass, 7-string guitar), and sloppaks that explicitly encode the instrument range — without requiring plugins to do their own arrangement-name matching - `highway.getLefty()` / `highway.getInverted()` — mirror + invert state Overlays do NOT appear in the viz picker and do NOT declare `"type": "visualization"` in `plugin.json`. They coexist with whichever renderer (default 2D, 3D highway, piano, ...) the user has picked. diff --git a/lib/audio.py b/lib/audio.py index b61395f..32b6caa 100644 --- a/lib/audio.py +++ b/lib/audio.py @@ -1,4 +1,4 @@ -"""Audio extraction and conversion for Rocksmith CDLC.""" +"""Audio extraction and conversion for the source game custom song.""" import logging import os diff --git a/lib/diagnostics_redact.py b/lib/diagnostics_redact.py index 351e4d2..d138707 100644 --- a/lib/diagnostics_redact.py +++ b/lib/diagnostics_redact.py @@ -34,7 +34,7 @@ _QSTRING_SECRET_RE = re.compile( r"(?i)\b(api[_-]?key|key|token|secret|password|pwd|auth)=([^\s&\"']+)" ) _SONG_FILENAME_RE = re.compile( - r"\b[\w()'\-+&,.!?\[\]]+\.(?:psarc|sloppak|wem|ogg|mp3|wav)\b", + r"\b[\w()'\-+&,.!?\[\]]+\.(?:archive|sloppak|wem|ogg|mp3|wav)\b", re.IGNORECASE, ) diff --git a/lib/gp2rs.py b/lib/gp2rs.py index 8e2c659..6cf281d 100644 --- a/lib/gp2rs.py +++ b/lib/gp2rs.py @@ -1,4 +1,4 @@ -"""Convert Guitar Pro files (.gp5/.gp4/.gp3) to Rocksmith 2014 arrangement XML.""" +"""Convert Guitar Pro files (.gp5/.gp4/.gp3) to the source game arrangement XML.""" import logging import re @@ -18,9 +18,9 @@ def _extract_year(song: guitarpro.Song) -> str: """Pull a 4-digit year out of GP metadata. GP files have no dedicated year field; the year usually appears inside the - copyright string (e.g. "1998 Goat Head Music, WB Music Corp, USA"). RsCli + copyright string (e.g. "1998 Goat Head Music, WB Music Corp, USA"). the converter requires to parse as Int32, so we extract just the digits and - fall back to empty (which RsCli treats as no year) when nothing matches. + fall back to empty (which the converter treats as no year) when nothing matches. """ for field_val in (getattr(song, "copyright", None), getattr(song, "subtitle", None)): if not field_val: @@ -544,7 +544,7 @@ def convert_track( *, expand_repeats: bool = True, ) -> str: - """Convert a GP track to Rocksmith 2014 arrangement XML string. + """Convert a GP track to the source game arrangement XML string. Args: song: Parsed Guitar Pro song @@ -558,7 +558,7 @@ def convert_track( once in authored order — equivalent to the pre-expansion behavior. Returns: - XML string of the Rocksmith arrangement + XML string of the the source game arrangement """ track = song.tracks[track_index] num_strings = len(track.strings) @@ -950,7 +950,7 @@ def _build_xml( # Tuning. RS2014 schema names 6 string slots; we always emit those # for compatibility, and emit additional string6+ attributes (up to # `len(tuning)-1`) for 7+ string arrangements. Slopsmith parses - # them; stock RS ignores them. + # them; the format ignores them. tuning_el = ET.SubElement(root, "tuning") for i in range(max(6, len(tuning))): tuning_el.set(f"string{i}", str(tuning[i] if i < len(tuning) else 0)) @@ -1149,7 +1149,7 @@ def list_tracks(gp_path: str) -> list[dict]: def auto_select_tracks(gp_path: str) -> tuple[list[int], dict[int, str]]: - """Auto-select guitar/bass/keys tracks and assign Rocksmith arrangement names. + """Auto-select guitar/bass/keys tracks and assign the source game arrangement names. Includes piano/keyboard tracks as "Keys" arrangements alongside guitar and bass tracks. @@ -1205,7 +1205,7 @@ def auto_select_tracks(gp_path: str) -> tuple[list[int], dict[int, str]]: role = "bass" if t["is_bass"] else "guitar" selected.append((t["index"], role)) - # Assign Rocksmith names: Lead, Rhythm, Combo, Bass, Keys, Drums + # Assign the source game names: Lead, Rhythm, Combo, Bass, Keys, Drums track_indices = [] name_map = {} lead_count = 0 @@ -1243,14 +1243,14 @@ def convert_piano_track( *, expand_repeats: bool = True, ) -> str: - """Convert a GP piano/keyboard track to Rocksmith XML using MIDI encoding. + """Convert a GP piano/keyboard track to the source game XML using MIDI encoding. - Encodes MIDI notes into Rocksmith's string+fret format: + Encodes MIDI notes into the source game's string+fret format: string = midi_note // 24 fret = midi_note % 24 This gives a range of 0-143, covering the full piano range within - Rocksmith's 6-string x 24-fret structure. The piano highway plugin + the source game's 6-string x 24-fret structure. The piano highway plugin decodes back via: midi = string * 24 + fret. Honors GP repeat brackets and D.S./D.C./Coda/Fine jumps when @@ -1342,7 +1342,7 @@ def convert_piano_track( base_midi = 60 # fallback to middle C midi_note = base_midi + note.value - # Encode into Rocksmith string+fret + # Encode into the source game string+fret rs_string = midi_note // 24 rs_fret = midi_note % 24 @@ -1449,9 +1449,9 @@ def convert_drum_track( *, expand_repeats: bool = True, ) -> str: - """Convert a GP drum/percussion track to Rocksmith XML using MIDI encoding. + """Convert a GP drum/percussion track to the source game XML using MIDI encoding. - Encodes MIDI drum note numbers into Rocksmith's string+fret format: + Encodes MIDI drum note numbers into the source game's string+fret format: string = midi_note // 24 fret = midi_note % 24 @@ -1534,7 +1534,7 @@ def convert_drum_track( if midi_note not in GM_DRUM_MAP: continue # Skip unknown percussion sounds - # Encode into Rocksmith string+fret + # Encode into the source game string+fret rs_string = midi_note // 24 rs_fret = midi_note % 24 @@ -1778,7 +1778,7 @@ def convert_file( *, expand_repeats: bool = True, ) -> list[str]: - """Convert a GP file to Rocksmith XMLs. + """Convert a GP file to the source game XMLs. Args: gp_path: Path to .gp5/.gp4/.gp3 file diff --git a/lib/gp2rs_gpx.py b/lib/gp2rs_gpx.py index 1777aca..c50a8f6 100644 --- a/lib/gp2rs_gpx.py +++ b/lib/gp2rs_gpx.py @@ -663,7 +663,7 @@ def list_tracks(gp_path: str) -> list[dict]: # --------------------------------------------------------------------------- # convert_file — mirrors gp2rs.convert_file interface -# Converts GPX tracks directly to Rocksmith XML, reusing gp2rs._build_xml +# Converts GPX tracks directly to the source game XML, reusing gp2rs._build_xml # --------------------------------------------------------------------------- @@ -816,7 +816,7 @@ def _collect_tone_events( def _inject_tones(xml_str: str, tone_events: list[tuple[float, str]]) -> str: """ - Inject a element into a Rocksmith arrangement XML string. + Inject a element into a the source game arrangement XML string. Parses the prettified XML returned by _build_xml, inserts the tones block before , and re-serialises. Noop if tone_events is empty. @@ -903,7 +903,7 @@ def convert_vocal_track_to_pitch_sidecar( {"version": 1, "notes": [{"t": float, "d": float, "midi": int}, ...]} - This is complementary to convert_vocal_track() which produces RS XML. + This is complementary to convert_vocal_track() which produces arrangement XML. NOTE: nothing in this module calls this helper yet — convert_file() does not invoke it, so no vocal_pitch.json is emitted automatically. A caller wanting the pitch ribbon must call this itself and persist the returned dict (e.g. @@ -1124,7 +1124,7 @@ def convert_file( *, expand_repeats: bool = True, ) -> list[str]: - """Convert a .gpx file to Rocksmith XML arrangement files. + """Convert a .gpx file to the source game XML arrangement files. Mirrors gp2rs.convert_file so the editor plugin can call it transparently. expand_repeats is accepted for API compatibility but repeat expansion from @@ -1758,7 +1758,7 @@ def convert_file( # (`.notation.json`, sloppak-spec §5.3) so the sloppak assembly # step can attach a `notation_.json` + manifest `notation:` # sub-key without re-walking the GP file. Best-effort: a notation bug - # must never break the RS-XML conversion itself. + # must never break the arrangement XML conversion itself. if is_keys: try: import gp2notation as _gp2notation @@ -1809,10 +1809,10 @@ def _is_vocal_track(track: dict) -> bool: def _gpx_lyric_to_rs(raw: str) -> str: """ - Convert a GPX lyric token to Rocksmith vocal lyric format. + Convert a GPX lyric token to the source game vocal lyric format. GPX encodes syllable continuation with a trailing hyphen (e.g. "in-", "t-"). - Rocksmith uses the same convention for mid-word syllables. For word-final + the source game uses the same convention for mid-word syllables. For word-final syllables with no hyphen, RS requires a "+" suffix to signal "connect to next syllable without a space" — but only when the next beat is a continuation of the same word. We handle this at the sequence level in @@ -1849,7 +1849,7 @@ def convert_vocal_track( arr_name: str = 'Vocals', ) -> str: """ - Convert a GPX vocal track to a Rocksmith 2014 vocals arrangement XML. + Convert a GPX vocal track to a the source game vocals arrangement XML. Each beat with a lyric and a note becomes a element: time — seconds from song start + audio_offset @@ -1864,7 +1864,7 @@ def convert_vocal_track( Beats with a lyric but no pitch note are included as pitch-0 rests so the display timeline stays intact. Beats with no lyric are skipped entirely. - The output is a minimal but valid Rocksmith vocals XML. It does not include + The output is a minimal but valid the source game vocals XML. It does not include ebeats or phrases (RS parses vocal XMLs without them). """ string_pitches = track['string_pitches'] # high→low, standard guitar if vocal @@ -2018,10 +2018,10 @@ def _build_vocals_xml( vocals: list[dict], tempo: int, ) -> str: - """Build a Rocksmith 2014 vocals arrangement XML string.""" + """Build a the source game vocals arrangement XML string.""" from xml.dom import minidom - # Rocksmith 2014 vocals arrangement is a flat document — NOT a + # the source game vocals arrangement is a flat document — NOT a # wrapper. Every lyric consumer in the codebase keys off the root # tag being literally "vocals" (lib/loosefolder.py, server.py highway # loader), so a root would be silently skipped and the generated diff --git a/lib/gp8_audio_sync.py b/lib/gp8_audio_sync.py index f119710..929910e 100644 --- a/lib/gp8_audio_sync.py +++ b/lib/gp8_audio_sync.py @@ -128,7 +128,7 @@ def _resolve_audio_asset(zf, root=None) -> tuple[str, str | None]: # GP8 uses 44100 Hz internally for FrameOffset values regardless of the # OGG file's own sample rate. The embedded OGG is typically 48000 Hz -# (Rocksmith's preferred rate) and should be passed through as-is — +# (the source game's preferred rate) and should be passed through as-is — # do NOT resample it. The 44100 constant is only used here to convert # FrameOffset integers to seconds for timing math; it never touches audio. # Verified: 44100 gives <10ms sync error; 48000 gives ~530ms error. diff --git a/lib/loosefolder.py b/lib/loosefolder.py index de77ad6..8fe8daa 100644 --- a/lib/loosefolder.py +++ b/lib/loosefolder.py @@ -54,7 +54,7 @@ def _iter_local(path: Path, pattern: str): * Reject directories (a folder named `audio.wem` or `lead.xml` would otherwise be matched by glob and break downstream readers / converters). - * Reject symlinks escaping the folder so a crafted CDLC can't + * Reject symlinks escaping the folder so a crafted custom song can't smuggle external content into the scan. """ root = path.resolve() @@ -176,7 +176,7 @@ def _arr_type_from_filename(stem: str) -> tuple: def _parse_xml_meta(xml_path: Path) -> dict: - """Parse a Rocksmith arrangement XML and return song-level metadata.""" + """Parse a the source game arrangement XML and return song-level metadata.""" try: root = ET.parse(str(xml_path)).getroot() if root.tag != "song": diff --git a/lib/progression.py b/lib/progression.py index a4c5546..0af56e1 100644 --- a/lib/progression.py +++ b/lib/progression.py @@ -331,7 +331,7 @@ def load_content(root) -> tuple[dict, list]: def instrument_for_arrangement(arr_entry) -> str: """Map a library arrangement entry to a progression instrument. - PSARC/loose entries carry ``type`` (lead/rhythm/bass/combo); sloppaks may + archive/loose entries carry ``type`` (lead/rhythm/bass/combo); sloppaks may only carry ``name``. Vocals are recognised so they never count toward guitar challenges; everything else defaults to guitar. """ @@ -345,11 +345,11 @@ def instrument_for_arrangement(arr_entry) -> str: return "drums" if arr_type in ("piano", "keys"): return "keys" - # Check name before committing to a guitar type — legacy PSARC keys + # Check name before committing to a guitar type — legacy archive keys # arrangements often carry a generic type (lead/rhythm/combo) but have a # name like "Keys" or "Piano". Name overrides the generic type for all # well-known non-guitar instruments so that scored keys runs advance the - # keys path and quests even when the Rocksmith XML type was not updated. + # keys path and quests even when the the source game XML type was not updated. if "bass" in name: return "bass" if "drum" in name or "percussion" in name: diff --git a/lib/sloppak.py b/lib/sloppak.py index 3aa48bd..247e511 100644 --- a/lib/sloppak.py +++ b/lib/sloppak.py @@ -526,7 +526,7 @@ def load_song( and isinstance(e.get("d"), (int, float)) ] if song.lyrics: - # Provenance — populated by the converter (xml/sng), + # Provenance — populated by the converter (xml/notechart), # the WhisperX fallback (whisperx), or hand-edits # (user). Validate against the closed enum so a # hand-edited (or otherwise malformed) manifest can't @@ -536,8 +536,13 @@ def load_song( # the wrong type) falls back to "xml" — the spec's # back-compat default — instead of being stringified # and trusted. - _ALLOWED_LYRICS_SOURCES = {"xml", "sng", "whisperx", "user"} + _ALLOWED_LYRICS_SOURCES = {"xml", "notechart", "whisperx", "user"} + # Legacy alias: older manifests labelled note-chart-derived + # lyrics with the source format's name; normalise it. + _LYRICS_SOURCE_ALIASES = {"notechart": "notechart"} raw_source = manifest.get("lyrics_source") + if isinstance(raw_source, str): + raw_source = _LYRICS_SOURCE_ALIASES.get(raw_source, raw_source) if isinstance(raw_source, str) and raw_source in _ALLOWED_LYRICS_SOURCES: song.lyrics_source = raw_source else: @@ -615,7 +620,7 @@ def extract_meta(path: Path) -> dict: "notes": 0, # unknown without loading; fine for the index } ) - # Sort like PSARC path: Lead > Combo > Rhythm > Bass + # Sort like archive path: Lead > Combo > Rhythm > Bass priority = {"Lead": 0, "Combo": 1, "Rhythm": 2, "Bass": 3} arrangements.sort(key=lambda a: priority.get(a["name"], 99)) for i, a in enumerate(arrangements): diff --git a/lib/song.py b/lib/song.py index 3ebf0bb..0e65902 100644 --- a/lib/song.py +++ b/lib/song.py @@ -1,4 +1,4 @@ -"""Rocksmith 2014 arrangement XML parser and song data models.""" +"""the source game arrangement XML parser and song data models.""" from dataclasses import dataclass, field from pathlib import Path @@ -81,14 +81,14 @@ class HandShape: chord_id: int start_time: float end_time: float - # EOF / some CDLC emit `arpeggio` on `` (RS14+). + # EOF / some custom song emit `arpeggio` on `` (RS14+). arpeggio: bool = False @dataclass class PhraseLevel: """One difficulty tier's worth of note/chord/anchor/hand-shape data for a - single phrase iteration. Rocksmith's XML stores these as `` blocks that repeat for every difficulty tier the chart author wrote; slopsmith used to collapse them to the phrase's maxDifficulty and throw the rest away. Keeping them around lets the @@ -129,11 +129,11 @@ class Arrangement: chord_templates: list[ChordTemplate] = field(default_factory=list) # None for single-level sources (GP converter, old sloppaks) — frontends # should treat a missing `phrases` as "no per-phrase difficulty data - # available, disable the slider". Populated from Rocksmith XML when + # available, disable the slider". Populated from the source game XML when # multiple `` tiers exist. phrases: list[Phrase] | None = None - # Tone data lifted from the source PSARC by the sloppak converter and - # carried inline in the arrangement JSON. None for PSARC/loose playback + # Tone data lifted from the source archive by the sloppak converter and + # carried inline in the arrangement JSON. None for archive/loose playback # (the highway reads those tones from the XML directly) and for old # sloppaks predating tone support. Shape: # {"base": str, "changes": [{"t": float, "name": str}], @@ -141,14 +141,14 @@ class Arrangement: # `base`/`changes` drive the highway tone-change markers; `definitions` # feed the Tones plugin gear panel. tones: dict | None = None - # RS XML flags for smart naming (slopsmith feat/arrangement). + # arrangement XML flags for smart naming (slopsmith feat/arrangement). # Populated from the XML; default False/0 for sloppak / GP-imported sources. path_lead: bool = False path_rhythm: bool = False path_bass: bool = False bonus_arr: bool = False represent: int = 0 - # RS2014 CDLC pitch-shift field (cents). Commonly -1200.0 (one octave + # RS2014 custom song pitch-shift field (cents). Commonly -1200.0 (one octave # down) for extended-range bass arrangements. 0.0 when absent or zero. cent_offset: float = 0.0 @@ -167,7 +167,7 @@ class Song: audio_path: str = "" # Optional lyrics, one entry per syllable: {"t": float, "d": float, "w": str} lyrics: list[dict] = field(default_factory=list) - # Provenance of the lyrics, when present. One of "xml" | "sng" | "whisperx" | + # Provenance of the lyrics, when present. One of "xml" | "notechart" | "whisperx" | # "user" — surfaces in the highway WS payload so the UI can render a badge # (e.g. "auto-transcribed — may be inaccurate" for whisperx). The sloppak # loader (lib/sloppak.py) defaults missing manifest keys to "xml" at load @@ -374,7 +374,7 @@ def arrangement_string_count(arr: Arrangement) -> int: Used by the server to emit ``stringCount`` in the song_info WebSocket payload (slopsmith-plugin-3dhighway#7). - The RS XML schema always emits 6 ```` slots regardless + The arrangement XML schema always emits 6 ```` slots regardless of instrument (bass charts populate `string0`–`string3` and pad `string4`/`string5` with zeros), so ``len(arr.tuning)`` is not a reliable signal. Two independent signals get combined: @@ -390,10 +390,10 @@ def arrangement_string_count(arr: Arrangement) -> int: defaults to 6. This catches the partial-string-usage case where notes don't span all the instrument's strings. - A third signal — ``len(arr.tuning)`` when it isn't the RS-XML + A third signal — ``len(arr.tuning)`` when it isn't the arrangement XML padded value of 6 — folds in for sloppak / GP-imported sources where the tuning array is explicitly trimmed (4 for bass, 5 for - 5-string bass, 7 for 7-string guitar, etc.). RS-XML / PSARC + 5-string bass, 7 for 7-string guitar, etc.). arrangement XML / archive sources always emit length 6 regardless of instrument, so we deliberately ignore that exact value to avoid mis-classifying bass arrangements as guitar. ``< 6`` and ``> 6`` are both @@ -403,13 +403,13 @@ def arrangement_string_count(arr: Arrangement) -> int: where ``tuning_count`` is ``len(arr.tuning)`` when ``!= 6``, else 0. Worked examples: - * RS XML 4-string bass, full usage (tuning len 6, notes 0..3) → + * arrangement XML 4-string bass, full usage (tuning len 6, notes 0..3) → max(4, 4, 0) = 4 - * RS XML 4-string bass, sparse usage (tuning len 6, notes 0..2) → + * arrangement XML 4-string bass, sparse usage (tuning len 6, notes 0..2) → max(3, 4, 0) = 4 - * RS XML 6-string lead, full usage (tuning len 6, notes 0..5) → + * arrangement XML 6-string lead, full usage (tuning len 6, notes 0..5) → max(6, 6, 0) = 6 - * RS XML 6-string lead, sparse usage (tuning len 6, notes 0..4) → + * arrangement XML 6-string lead, sparse usage (tuning len 6, notes 0..4) → max(5, 6, 0) = 6 * Sloppak 5-string bass, sparse usage (tuning len 5, notes 0..3) → max(4, 4, 5) = 5 @@ -435,7 +435,7 @@ def arrangement_string_count(arr: Arrangement) -> int: max_s = cn.string notes_count = max_s + 1 if max_s >= 0 else 0 name_based = 4 if "bass" in arr.name.lower() else 6 - # Tuning-length signal — only trustworthy when NOT the RS-XML + # Tuning-length signal — only trustworthy when NOT the arrangement XML # padded value of 6. Length 4/5 indicates explicit bass / 5-string # bass; length 7/8 indicates an extended-range guitar from GP. tuning_len = len(arr.tuning) @@ -444,7 +444,7 @@ def arrangement_string_count(arr: Arrangement) -> int: def compute_smart_names(arrangements: list[Arrangement]) -> list[str | None]: - """Compute smart display names for arrangements based on RS XML path flags. + """Compute smart display names for arrangements based on arrangement XML path flags. Returns a list parallel to `arrangements`. Each entry is a descriptive name like "Lead", "Alt. Lead", "Bonus Rhythm", "Bass", or None for @@ -454,14 +454,14 @@ def compute_smart_names(arrangements: list[Arrangement]) -> list[str | None]: Path-type resolution (first match wins): 1. XML flags (path_lead / path_rhythm / path_bass) 2. Name-based fallback when ALL three flags are zero — keeps sloppak / - GP-imported sources and CDLC with unset flags working by mapping + GP-imported sources and custom song with unset flags working by mapping "Lead" / "Rhythm" / "Bass" / "Combo" → the matching path. Anything outside that set (Vocals, ShowLights, …) → None. Naming rules per path type (Lead / Rhythm / Bass): - Main group (bonusArr=False): represent=1 → "Lead" (or "Rhythm" / "Bass") — the canonical - arrangement. If no entry has represent=1 (CDLC with all-zero + arrangement. If no entry has represent=1 (custom song with all-zero flags), the first by represent-ascending order is promoted. remaining (n_alts == 1) → "Alt. Lead" remaining (n_alts >= 2) → "Alt. Lead 1", "Alt. Lead 2", ... @@ -495,7 +495,7 @@ def compute_smart_names(arrangements: list[Arrangement]) -> list[str | None]: def _resolve(a: Arrangement) -> tuple[str | None, bool]: """Return (path_attr, bonus_arr) for an arrangement, applying the name-based fallback when XML flags are all zero. Defensive against - non-string names from hand-edited PSARCs / sloppak JSON.""" + non-string names from hand-edited archives / sloppak JSON.""" if a.path_lead: return "path_lead", bool(a.bonus_arr) if a.path_rhythm: @@ -527,7 +527,7 @@ def compute_smart_names(arrangements: list[Arrangement]) -> list[str | None]: # represent=1 → standard arrangement ("Lead") # represent=0 (or any value != 1) → alternate arrangement ("Alt. Lead") # - # If no arrangement has represent=1 (e.g. CDLC defaults or all-zero + # If no arrangement has represent=1 (e.g. custom song defaults or all-zero # flags with name fallback), fall back to treating the first by # represent-ascending order as the standard so there is always a "Lead". main_pairs = [(i, a) for i, a in type_arrs if not _resolved[i][1]] @@ -573,7 +573,7 @@ def compute_smart_names(arrangements: list[Arrangement]) -> list[str | None]: def _finite_float(value, default: float = 0.0) -> float: """Coerce ``value`` to a finite float, falling back to ``default``. - Malformed CDLC can put ``NaN``/``Infinity`` into float fields like RS2014 + Malformed custom song can put ``NaN``/``Infinity`` into float fields like RS2014 ````; ``float()`` accepts those, but they serialize to the invalid JSON tokens ``NaN``/``Infinity`` (both over the highway WebSocket and into sloppak ``.json`` files), which breaks downstream parsing. @@ -607,7 +607,7 @@ def arrangement_to_wire(arr: Arrangement) -> dict: if arr.phrases: out["phrases"] = [phrase_to_wire(p) for p in arr.phrases] # `tones` is additive — only emitted when the source carried tone data - # (sloppaks converted from a PSARC). Absent on PSARC/loose-derived + # (sloppaks converted from a archive). Absent on archive/loose-derived # Arrangements and old sloppaks; readers treat a missing key as # "no tones". if arr.tones: @@ -681,7 +681,7 @@ def _int_optional(elem, attr, default=-1): Use for fields that are merely metadata hints (right-hand fingering, pick direction, etc.) where a malformed value from a third-party - Rocksmith XML emitter shouldn't abort the whole arrangement parse. + the source game XML emitter shouldn't abort the whole arrangement parse. Required-field readers (`string`, `fret`, `chordId`, …) keep using `_int` so a corrupted required attribute still fails fast at parse @@ -708,7 +708,7 @@ def _bool(elem, attr): def _hand_shape_arpeggio_flag(elem) -> bool: - """Rocksmith / EOF may mark arpeggio on ```` (various casings).""" + """the source game / EOF may mark arpeggio on ```` (various casings).""" for attr in ("arpeggio", "Arpeggio", "arp", "Arp"): if _bool(elem, attr): return True @@ -716,7 +716,7 @@ def _hand_shape_arpeggio_flag(elem) -> bool: def _chord_template_arpeggio_flag(elem) -> bool: - """Rocksmith commonly tags arpeggio templates in ``displayName`` via ``-arp``.""" + """the source game commonly tags arpeggio templates in ``displayName`` via ``-arp``.""" for attr in ("arpeggio", "Arpeggio", "arp", "Arp"): if _bool(elem, attr): return True @@ -766,7 +766,7 @@ def _parse_note(n) -> Note: def parse_arrangement(xml_path: str) -> Arrangement: - """Parse a Rocksmith arrangement XML file.""" + """Parse a the source game arrangement XML file.""" tree = ET.parse(xml_path) root = tree.getroot() @@ -800,8 +800,8 @@ def parse_arrangement(xml_path: str) -> Arrangement: except ValueError: pass - # CentOffset — RS2014 pitch-shift field (cents). Present in all RS XML - # sources (PSARC, loose folders, GP-converted XML). Absent in very old + # CentOffset — RS2014 pitch-shift field (cents). Present in all arrangement XML + # sources (archive, loose folders, GP-converted XML). Absent in very old # files; default 0.0. cent_offset = 0.0 el = root.find("centOffset") @@ -1049,7 +1049,7 @@ def parse_arrangement(xml_path: str) -> Arrangement: # wrote at or below this phrase's max — these are what the # master-difficulty slider selects between at render time. # Tiers above max_diff exist in some XMLs (authoring leftovers) - # and are skipped to match Rocksmith's in-game behaviour. + # and are skipped to match the source game's in-game behaviour. # Capture the extracted slices so the flat max-mastery merge # below can reuse one of them. phrase_levels: list[PhraseLevel] = [] @@ -1144,7 +1144,7 @@ def parse_arrangement(xml_path: str) -> Arrangement: def _convert_sng_to_xml(extracted_dir: str): """No-op stub. - Historically this converted proprietary encrypted ``.sng`` arrangement + Historically this converted proprietary encrypted ``.notechart`` arrangement files to XML via an external tool. That path has been removed: slopsmith reads only its own ``.sloppak`` format and loose-folder/GP/MusicXML-derived arrangement XML, and never decodes or decrypts proprietary archives. Kept @@ -1156,7 +1156,7 @@ def _convert_sng_to_xml(extracted_dir: str): def load_song(extracted_dir: str) -> Song: """Load a song from a directory of arrangement XML/JSON files.""" - # Proprietary SNG→XML conversion has been removed; this is now a no-op. + # Proprietary note-chart→XML conversion has been removed; this is now a no-op. _convert_sng_to_xml(extracted_dir) song = Song() @@ -1165,7 +1165,7 @@ def load_song(extracted_dir: str) -> Song: # Build manifest lookups: xml_stem (lowercase) -> ArrangementName / path flags. # The manifest JSON is the authoritative source for path flags (pathLead / # pathRhythm / pathBass / bonusArr / represent) because the XML files bundled - # in official DLC PSARCs often have all path flags set to "0", while the + # in official DLC archives often have all path flags set to "0", while the # manifest correctly reflects what the authoring tool wrote. def _mprop_int(key: str, props: dict) -> int: val = props.get(key, 0) @@ -1268,7 +1268,7 @@ def load_song(extracted_dir: str) -> Song: arrangement = parse_arrangement(str(xml_path)) # Override path flags with manifest values when available. The XML - # bundled inside official DLC PSARCs often has all flags as "0", while + # bundled inside official DLC archives often has all flags as "0", while # the manifest JSON carries the correct values written by the DLC author. manifest_flags = _manifest_path_flags.get(xml_path.stem.lower()) if manifest_flags: diff --git a/lib/vocal_pitch.py b/lib/vocal_pitch.py index f2beb47..a2d78bf 100644 --- a/lib/vocal_pitch.py +++ b/lib/vocal_pitch.py @@ -2,7 +2,7 @@ Sibling to `lyrics_transcribe.py` on the karaoke side: once we have isolated vocals + per-syllable lyric timing (both produced by the -WhisperX fallback or shipped in the source PSARC), the /pitch endpoint +WhisperX fallback or shipped in the source archive), the /pitch endpoint runs CREPE over the vocals stem and returns one MIDI note per supplied timing token. The result lands in `/vocal_pitch.json` in the shape the byrongamatos/slopsmith-plugin-lyrics-karaoke renderer diff --git a/lib/wem_decode.py b/lib/wem_decode.py index aea7ff3..18e904f 100644 --- a/lib/wem_decode.py +++ b/lib/wem_decode.py @@ -58,7 +58,7 @@ def _convert_riff_wem(data: bytes, output_path: str) -> bool: if codec == 0xFFFF or codec == 0x0069: # Wwise Vorbis — audio_data contains raw Ogg pages or encoded Vorbis - # For Rocksmith CDLC, the data is typically packed Vorbis + # For the source game custom song, the data is typically packed Vorbis # Try writing raw data as OGG (some WEM files have valid OGG inside) if _try_extract_ogg_pages(audio_data, output_path): return True diff --git a/plugins/highway_3d/screen.js b/plugins/highway_3d/screen.js index ebec553..0bb5431 100644 --- a/plugins/highway_3d/screen.js +++ b/plugins/highway_3d/screen.js @@ -1099,7 +1099,7 @@ return _bgBandsCache; } - const BG_DEFAULTS = { style: 'particles', intensity: 0.5, reactive: true, palette: 'default', showFretOnNote: true, fretNumberGhostScope: 'rocksmith', cameraSmoothing: 0.5, zoomSmoothing: 0.5, tiltSmoothing: 0.5, cameraLockLow: false, cameraLockZoom: 0.5, cameraMode: 'lookahead', nutHeadstockVisible: true, tuningLabelsVisible: true, nutColor: '#f5f3f0', headstockColor: '#d4b48a', textSize: 0.5, vibrancy: 0.85, glow: 0.25, customImageDataUrl: '', customImageName: '', customVideoName: '', chordDiagramVisible: true, chordDiagramSize: 0.5, chordDiagramPosition: 'tl', fretColumnMarkerCadence: 1, projectionVisible: true, inlayLabelsVisible: false, sectionLabelsOnHighway: false, sectionHudVisible: false, sectionHudPosition: 'tr', sectionHudSize: 0.5, toneHudVisible: false, toneHudPosition: 'tl', toneHudSize: 0.5, fpsVisible: false, fretDividersVisible: true, slideArrowApproachVisible: true, slideArrowNeckVisible: true, slideArrowChainPreviewVisible: true }; + const BG_DEFAULTS = { style: 'particles', intensity: 0.5, reactive: true, palette: 'default', showFretOnNote: true, fretNumberGhostScope: 'chords', cameraSmoothing: 0.5, zoomSmoothing: 0.5, tiltSmoothing: 0.5, cameraLockLow: false, cameraLockZoom: 0.5, cameraMode: 'lookahead', nutHeadstockVisible: true, tuningLabelsVisible: true, nutColor: '#f5f3f0', headstockColor: '#d4b48a', textSize: 0.5, vibrancy: 0.85, glow: 0.25, customImageDataUrl: '', customImageName: '', customVideoName: '', chordDiagramVisible: true, chordDiagramSize: 0.5, chordDiagramPosition: 'tl', fretColumnMarkerCadence: 1, projectionVisible: true, inlayLabelsVisible: false, sectionLabelsOnHighway: false, sectionHudVisible: false, sectionHudPosition: 'tr', sectionHudSize: 0.5, toneHudVisible: false, toneHudPosition: 'tl', toneHudSize: 0.5, fpsVisible: false, fretDividersVisible: true, slideArrowApproachVisible: true, slideArrowNeckVisible: true, slideArrowChainPreviewVisible: true }; // User-selectable, persistable bg styles — must mirror settings.html's // VALID_STYLES. 'venue' is deliberately NOT here: it is an internal effective // style reached only via _venueSceneOverride (the viz-picker Venue flow), so @@ -1325,7 +1325,7 @@ }, ); } - const FRET_NUMBER_GHOST_SCOPE_IDS = ['rocksmith', 'all']; + const FRET_NUMBER_GHOST_SCOPE_IDS = ['chords', 'all']; function _bgPanelKey(canvas) { const ss = window.slopsmithSplitscreen; @@ -2717,7 +2717,7 @@ // Fret digits on the board ghost (hollow preview at Z=0), not on // flying note bodies — see fretNumberGhostScope for chord-hand vs all. let showFretOnNote = false; - let fretNumberGhostScope = 'rocksmith'; + let fretNumberGhostScope = 'chords'; // Camera-X smoothing dial (issue #34). 0 = twitchy (track every // upcoming fret), 1 = calm (ignore small intra-cluster shifts). // Cached here and refreshed via the bg listener to avoid a @@ -7546,7 +7546,7 @@ const tol = 0.028; /** * Suppress a synth chord box when a real chord with the **same trimmed - * display name** played within this window — RS CDLC commonly authors + * display name** played within this window — Custom songs commonly authors * several ```` rows that share a display name (with * trailing-whitespace IDs) for fingering variants. The follow-up * hand-shape with no chord row is a fingering hint, not a new strum @@ -7558,7 +7558,7 @@ const trimmedTemplateName = (cid) => { if (cid == null || !chordTemplates) return ''; const tmpl = chordTemplates[cid] ?? chordTemplates[Number(cid)]; - // CDLC commonly authors several rows that share + // custom songs commonly authors several rows that share // a displayName for fingering variants; the suppression // heuristic in the surrounding code dedupes on the *label*, // not the underlying name, so go through chordTemplateLabel. @@ -12363,13 +12363,13 @@ const ghostFretOk = showFretOnNote && ( arpGhostActive || fretNumberGhostScope === 'all' || - (fretNumberGhostScope === 'rocksmith' && fromChord) + (fretNumberGhostScope === 'chords' && fromChord) ); if (ghostFretOk && pGhostFretLbl) { // chord-hand style → show finger number (1–4) from the chord // template; fall back to fret number when no finger data exists // (GP imports, open strings, non-chord notes). - const ghostFretDisplay = fromChord && fretNumberGhostScope === 'rocksmith' + const ghostFretDisplay = fromChord && fretNumberGhostScope === 'chords' ? (_templateFingerForChordGhost(chordId, n.s) ?? _templateFretForChordGhost(chordId, n.s, n.f)) : fromChord ? _templateFretForChordGhost(chordId, n.s, n.f) @@ -12389,7 +12389,7 @@ // frame (projFactor), instead of popping in at full alpha. ghostFretLblAlpha = projFactor; } - const _ghostFretForScale = fromChord && fretNumberGhostScope === 'rocksmith' + const _ghostFretForScale = fromChord && fretNumberGhostScope === 'chords' ? n.f : ghostFretDisplay; drawGhostFretLabel(x, y, projRim, ghostFretDisplay, ghostFretLblAlpha, projGrowScale, _ghostFretForScale); diff --git a/plugins/highway_3d/settings.html b/plugins/highway_3d/settings.html index e9d3200..911edc2 100644 --- a/plugins/highway_3d/settings.html +++ b/plugins/highway_3d/settings.html @@ -430,7 +430,7 @@

Where to show

@@ -712,14 +712,14 @@ no-selection / NaN state. -->