Compare commits

..
Author SHA1 Message Date
zagatozeeandBret Mogilefsky 2d79e216b3 Change staff parameter to use _wire_int_optional
(cherry picked from commit 64656bda1edc3fe03b6ad5e747c7afa3abca31d6)
2026-06-17 23:42:08 -07:00
zagatozeeandBret Mogilefsky bd22951874 feed the rabbit
Updated the docstring for _load_gpif to clarify supported file formats and improved error messages for unrecognized containers.L

(cherry picked from commit 1866c72e53339a10d6f184d21c2261e9f0b21395)
2026-06-17 23:42:08 -07:00
zagatozeeandBret Mogilefsky eb2121b4bc Piano LH/RH seperation work
Add LH/RH piano chart seperation ground work

(cherry picked from commit 4c227f30347d7f0cf2f3a8c4f81f9ba7ec2dee68)
2026-06-17 23:42:07 -07:00
zagatozeeandBret Mogilefsky 297778864c Add staff attribute to note representation
Par tof the LH/RH seperation future schema

(cherry picked from commit 56ad235a1ad4875527abb69836bcace79fdadfdc)
2026-06-17 23:42:07 -07:00
zagatozeeandBret Mogilefsky 3ab57977c4 Update song.py
Groundwork for piano charts keeping LH and RH designations intact, but seperated, so they can be toggled via menu options etc in future

(cherry picked from commit 606b69a75eb5202c6fb2978f496bf97db0ec65e4)
2026-06-17 23:42:07 -07:00
5 changed files with 28 additions and 192 deletions
+3
View File
@@ -69,6 +69,7 @@ class RsNote:
tremolo: bool = False
tap: bool = False
link_next: bool = False
staff: int = -1 # Piano staff: -1=absent, 0=LH/bass clef, 1=RH/treble clef
@dataclass
@@ -1021,6 +1022,8 @@ def _build_xml(
"tap": "1" if n.tap else "0",
"ignore": "0",
}
if getattr(n, 'staff', -1) != -1:
attrs["staff"] = str(n.staff)
ET.SubElement(notes_el, "note", **attrs)
# Chords
+20 -30
View File
@@ -279,7 +279,6 @@ def _gpif_tracks(root: ET.Element) -> list[dict]:
midi_channel = 0
is_drums = False
if gm is not None:
# GP6 (.gpx): <GeneralMidi table="Percussion"><Program>...</Program>
try: midi_program = int(gm.findtext('Program') or 0)
except (ValueError, TypeError): pass
try:
@@ -289,25 +288,6 @@ def _gpif_tracks(root: ET.Element) -> list[dict]:
except (ValueError, TypeError): pass
if gm.get('table') == 'Percussion':
is_drums = True
else:
# GP7/GP8 (.gp): <InstrumentSet><Type>drumKit</Type>
# and <MidiConnection><PrimaryChannel>9</PrimaryChannel>
inst_set = t.find('InstrumentSet')
if inst_set is not None:
inst_type = (inst_set.findtext('Type') or '').lower()
if inst_type == 'drumkit':
is_drums = True
midi_conn = t.find('MidiConnection')
if midi_conn is not None:
try:
ch_text = (midi_conn.findtext('PrimaryChannel') or '').strip()
if ch_text:
ch = int(ch_text)
midi_channel = ch
if ch == 9:
is_drums = True
except (ValueError, TypeError):
pass
# String tuning
string_pitches: list[int] = []
@@ -624,15 +604,14 @@ def list_tracks(gp_path: str) -> list[dict]:
result = []
for i, t in enumerate(tracks):
is_bass = bool(
not t['is_drums'] # drums always have low/zero string pitches — must exclude
and (
(
not t['string_pitches'] # no string tuning = not guitar-family
and 32 <= t['midi_program'] <= 39
) or (
t['string_pitches']
and max(t['string_pitches']) <= 48 # bass top string ≤ C3
)
(
not t['is_drums']
and not t['string_pitches'] # no string tuning = not guitar-family
and 32 <= t['midi_program'] <= 39
) or (
not t['is_drums'] # explicit guard: drums can have low string pitches
and t['string_pitches']
and max(t['string_pitches']) <= 48 # bass top string ≤ C3
)
)
is_piano = (
@@ -1339,7 +1318,7 @@ def convert_file(
if bid != '-1' and bid:
bar = bars_by_id.get(bid)
if bar is not None:
for vid in bar.findtext('Voices', '').split():
for voice_pos, vid in enumerate(bar.findtext('Voices', '').split()):
if vid == '-1':
continue
voice = voices_dict.get(vid)
@@ -1440,6 +1419,13 @@ def convert_file(
fret=rs_fret,
sustain=sustain,
)
# Staff assignment for keys/piano (slopsmith staff schema).
# Derived from voice position as authored in the GP tab —
# not inferred from pitch — preserving hand crossings.
# Voice 0 = treble/RH staff (staff=1),
# Voice 1+ = bass/LH staff (staff=0).
if is_keys:
rn.staff = 1 if voice_pos == 0 else 0
# Techniques — GPIF stores these as <Property>
# elements (NOT child tags), so the old
@@ -1674,6 +1660,10 @@ def convert_file(
fret=_lh_midi % 24,
sustain=_lh_dur if _lh_dur > 0.2 else 0.0,
)
# LH track notes are always bass clef (staff=0).
# The LH track as a whole IS the left hand —
# voice position within the LH track is irrelevant.
_lh_rn.staff = 0
_lh_notes.append(_lh_rn)
_lh_last_per_key[_lh_midi] = _lh_rn
_lh_vt += _lh_dur
+5
View File
@@ -37,6 +37,7 @@ class Note:
right_hand: int = -1
pick_direction: int = -1
ignore: bool = False
staff: int = -1 # Piano staff: -1=absent, 0=LH/bass clef, 1=RH/treble clef
@dataclass
@@ -217,6 +218,8 @@ def note_to_wire(n: Note) -> dict:
out["pkd"] = n.pick_direction
if n.ignore:
out["ig"] = True
if n.staff != -1:
out["stf"] = n.staff
return out
@@ -307,6 +310,7 @@ def note_from_wire(d: dict, time: float | None = None) -> Note:
right_hand=_wire_int_optional(d.get("rh"), -1),
pick_direction=_wire_int_optional(d.get("pkd"), -1),
ignore=bool(d.get("ig", False)),
staff=_wire_int_optional(d.get("stf"), -1),
)
@@ -762,6 +766,7 @@ def _parse_note(n) -> Note:
right_hand=_int_optional(n, "rightHand", -1),
pick_direction=_int_optional(n, "pickDirection", -1),
ignore=_bool(n, "ignore"),
staff=_int_optional(n, "staff", -1),
)
-33
View File
@@ -1,33 +0,0 @@
# Implementation Plan: Note-Detection Capability Domain
**Status**: Draft stub. This plan records scope, dependencies, and the migration-gate posture so the slice can be scheduled. The full plan/data-model/contracts/tasks are generated when the slice starts.
## Scope
Introduce a `note-detection` capability domain: a per-binding, chart-decoupled, multi-consumer control plane over the existing detection DSP. It exposes two primitives — a monophonic pitch estimate and a polyphonic note-set verification verdict — each scored against a requester-supplied tuning context, and consolidates today's two fragmented surfaces (`slopsmithMinigames.scoring.createContinuous` and `window.noteDetect`) behind one contract. It does **not** implement DSP and does **not** own consumer judgment.
See `spec.md` for requirements, entities, and success criteria.
## Dependencies / ordering
- **Depends on** Spec 006 (audio-input domain — source identity, open-session state; consumed, not redefined) and Spec 007 (audio-monitoring domain). 007 is currently paused (PR #667); this slice should not start until 006/007 are settled enough to consume.
- **Builds on** the capability-pipeline runtime (Spec 002, PR #245) and follows the migration standard (Spec 003).
- **Interim bridge already shipped/in-flight**: notedetect `setVerifyTarget(notes, ctx)` (plugin PR #62). Its per-call tuning context is the forward-compatible seed of this domain's per-binding context; the SlopScale consumer adapter (fork PR) and Chord Sprint are the first non-chart requesters.
## Migration gate (per Spec 003)
This slice must pass the central + per-domain migration checklist:
- Per-slice legacy inventory: the chart-coupled `note_detect` scoring/verify path, Step Mode verify consumption, minigames YIN scoring, and the `setVerifyTarget` bridge.
- Staged deprecation gates + compatibility-bridge accounting (record legacy handoffs; native wins on overlap).
- Diagnostics/Inspector expectations: bindings, provider attribution, per-binding context summary, outcomes — redaction-safe, no raw audio.
- Removal gate: legacy detection handoffs removed only after consumers migrate, migration notes are published, and external usage review completes.
## Providers
- Desktop: JUCE engine verifier (harmonic-comb `scoreChord`, bass temporal-persistence floor) + monophonic pitch.
- Web/dev: JS harmonic-comb / YIN fallback.
Both sit behind one provider abstraction so DSP improvements land once and reach all consumers.
## Explicitly out of scope
Detection DSP/model accuracy, consumer judgment/scoring UX, audio-input source ownership (006), monitoring lifecycle (007), recording, playback transport (008), plugin installation, and tunings outside the provider's current tables (e.g. 6-string bass).
-129
View File
@@ -1,129 +0,0 @@
# Feature Specification: Note-Detection Capability Domain
**Status**: Draft stub — scheduling placeholder for the next capability-domain slice after audio-input (006) and audio-monitoring (007). Authored 2026-06-06 from a concrete non-chart consumer requirement (SlopScale, Chord Sprint). Full plan / data-model / contracts / tasks to be generated when the slice is scheduled, passing the Spec 003 migration gate.
## Why now
The capability-domain roadmap has been laying foundations *for* this slice from the start: Spec 002 names note detection on the roadmap; Spec 003 lists it among the domains that must pass the migration gate; Spec 004 defines `audio-input` named source identity explicitly so "a later note-detection domain needs per-source binding … without inheriting a single global detector assumption"; Specs 006/007 already carry `{ requesterId: 'note_detect', purpose: 'note-detection' }` requesters. This spec turns that anticipated slice into a concrete one, driven by a real consumer that the current surfaces cannot serve.
**Concrete trigger.** Detection capability is today fragmented across two surfaces and coupled to the host chart:
- `slopsmithMinigames.scoring.createContinuous` — monophonic YIN, reachable from contained playback but weak (no chords, 70 Hz floor, distortion-unprobed).
- `window.noteDetect` verify/scoring — the strong harmonic-comb verifier, but its tuning/arrangement state is mutated by the host's loaded song (`song:loaded`), so it is a **single global detector** that two consumers with different tuning needs fight over.
A contained-playback consumer (SlopScale runs its own transport and computes targets from the *player's real instrument*, not the chart's nominal tuning; Chord Sprint similarly) has no clean way to use the strong verifier against its own tuning. The interim bridge (notedetect `setVerifyTarget(notes, ctx)`, PR #62 on the plugin repo) proves the requirement and is forward-compatible with this domain's per-binding context, but it still relies on a single shared detector instance. This domain is the long-term home.
## Clarifications
### Session 2026-06-06
- Q: Does this domain perform detection DSP itself? → A: No. It is a capability/control plane over existing detection providers (the desktop JUCE engine verifier and the JS harmonic-comb / YIN fallback). The DSP stays where it is; the domain gives it a per-binding, chart-decoupled, multi-consumer contract.
- Q: Does this domain own scoring/judgment (hit windows, gems, tiers)? → A: No. Consumers own judgment semantics. The domain exposes detection PRIMITIVES only: a monophonic pitch estimate, and a "is this (string,fret) note-set ringing now?" verification verdict against caller-supplied tuning. (Doctrine: host owns detection DSP; consumers own judgment.)
- Q: Is detection bound to the host highway's loaded song? → A: No. That single-global-detector coupling is the problem this slice removes. Each requester binds its own tuning context.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Verify against the player's own tuning from contained playback (Priority: P1)
A contained-playback consumer (SlopScale) that runs its own transport and has no host song loaded asks "is the expected note/chord ringing now?" against the *player's* instrument tuning, and receives polyphonic, distortion-robust verdicts — without reading plugin-private globals and without the result being perturbed by whatever song the host highway has open.
**Why P1**: This is the requirement no current surface satisfies; it is the reason the slice exists.
**Acceptance**:
- **Given** no host song is loaded, **When** a requester opens a detection binding with its own arrangement + tuning and registers a target note set, **Then** it receives verification verdicts scored against that tuning.
- **Given** the host loads or switches a song underneath, **When** the requester's binding is active, **Then** its verdicts are unaffected (no shared-state perturbation).
### User Story 2 - Two consumers detect concurrently with different tunings (Priority: P2)
The highway (chart tuning) and a minigame (player tuning) request detection at the same time, each against its own context, without a single global detector's mutable arrangement/tuning state being clobbered by the other.
**Why P2**: Spec 004 deliberately avoided the single-global-detector assumption for exactly this; the domain realizes it.
**Acceptance**:
- **Given** two active detection bindings with different arrangement/tuning, **When** both score concurrently, **Then** neither alters the other's tuning context or verdicts.
### User Story 3 - One detection capability, two primitives (Priority: P3)
A consumer that needs a live monophonic pitch (a tuner, a pitch strip) and a consumer that needs polyphonic note-set verification (a chord drill) use the **same** capability domain — not two unrelated surfaces (`createContinuous` vs `noteDetect`) — so DSP improvements (the bass temporal-persistence floor, distortion handling, future models) land once and reach every consumer.
**Acceptance**:
- **Given** a single capability contract, **When** a consumer requests a monophonic pitch primitive or a polyphonic verify primitive, **Then** both are served by one provider over one input binding.
- **Given** a DSP improvement lands in the provider, **When** any consumer requests detection, **Then** it benefits without consumer changes.
### User Story 4 - Migrate detection consumers and providers safely (Priority: P4)
The existing chart-coupled `note_detect` path, the minigames YIN scoring, Step Mode's verify consumption, and the bridge `setVerifyTarget(notes, ctx)` all migrate onto the domain behind the Spec 003 migration gate, with compatibility bridges and a removal gate, leaving each app area cleaner.
**Acceptance**:
- **Given** the domain exists, **When** a legacy detection handoff occurs during migration, **Then** it is mapped into domain diagnostics and recorded as a compatibility bridge hit.
- **Given** a new detection consumer is added after this slice, **When** it needs detection, **Then** it uses the domain rather than a new legacy-only handoff.
### Edge Cases
- No microphone / insecure context / no detection provider → detection bindings report unavailable; consumers degrade (scoring disables, never blocks).
- A requester's declared tuning references strings the provider's tuning tables cannot represent (e.g. 6-string bass) → bounded `incompatible` outcome, not a silent NaN verdict.
- Host song-switch while a player-tuning binding is active → the binding's context is unchanged.
- Capo / drop tunings / re-tunings → the binding carries the real open-string pitches; no double transposition.
- Polyphony the provider cannot resolve (heavy distortion, sub-floor strings) → verdict reports it honestly rather than guessing.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: System MUST provide an authoritative note-detection control plane for opening a detection binding, registering/clearing a target, requesting verification verdicts, requesting a monophonic pitch estimate, and reporting provider/availability state.
- **FR-002**: System MUST let each requester supply its own tuning context (arrangement, per-string tuning as absolute open MIDI or standard-tuning offsets, capo, string count) per binding, and MUST score that binding only against that context.
- **FR-003**: System MUST NOT bind detection to the host highway's loaded song or any single global arrangement/tuning state; concurrent bindings with different contexts MUST NOT perturb one another.
- **FR-004**: System MUST consume audio-input (Spec 006) source identity and open-session state for its capture rather than redefining input or assuming one global detector.
- **FR-005**: System MUST expose detection as PRIMITIVES — a monophonic pitch estimate and a polyphonic note-set verification verdict — and MUST NOT perform consumer-side judgment (hit windows, streaks, gems, accuracy, tiers).
- **FR-006**: System MUST provide a timing-free verification mode (score a registered target every frame independent of any playhead) so a frozen-playhead or self-transported consumer can ask "is this note-set ringing now?".
- **FR-007**: System MUST surface per-binding verdict detail sufficient for consumer judgment (at minimum: overall hit, and per-string/per-note ring state for a multi-note target) without exposing raw audio buffers or sample data.
- **FR-008**: System MUST report distinct outcomes for unavailable, denied, degraded, failed, no-provider, unsupported-context, and incompatible-version, rather than silently producing a verdict.
- **FR-009**: System MUST reject or degrade tuning contexts the provider cannot represent (unsupported arrangement/string-count) with an `incompatible` outcome and MUST NOT emit NaN/garbage verdicts.
- **FR-010**: System MUST route every detection request through one provider abstraction so DSP improvements reach all consumers at once (single source of truth), with the desktop engine verifier and a JS fallback as interchangeable providers.
- **FR-011**: System MUST preserve the existing chart-coupled detection path during the compatibility period by mapping it onto the domain, recording compatibility bridge hits.
- **FR-012**: System MUST document the migration path for detection providers and requesters (the chart `note_detect` consumer, Step Mode verify, minigames YIN scoring, and the `setVerifyTarget` bridge), including a removal gate, per Spec 003.
- **FR-013**: System MUST emit observable events when bindings open/close, targets change, verdicts are produced, and availability changes.
- **FR-014**: System MUST include active bindings, provider attribution, per-binding context summary, availability, and recent outcomes in diagnostics, redaction-safe.
- **FR-015**: System MUST NOT expose raw audio buffers, sample/waveform data, or live capture handles through detection state, verdicts, diagnostics, or capability payloads.
- **FR-016**: System MUST leave audio-input source ownership, monitoring lifecycle, recording, playback transport, and plugin installation outside this feature except as state it consumes.
- **FR-017**: System MUST avoid creating new legacy-only detection integration points once the native domain exists.
### Key Entities
- **Detection Binding**: A requester-owned, context-scoped detection session over a selected audio-input source — carries the requester's tuning context and target, independent of any host song. Multiple bindings coexist.
- **Tuning Context**: Arrangement + per-string tuning (absolute open MIDI or standard offsets) + capo + string count, supplied by the requester; the only tuning a binding's verdicts are scored against.
- **Verify Target**: A registered note set (string/fret + technique flags) the binding scores against live audio every frame, independent of any playhead.
- **Verification Verdict**: A bounded result — overall hit, per-note/per-string ring state, score, hit/total counts — with no raw audio.
- **Pitch Estimate**: A monophonic frequency/MIDI + confidence primitive (the tuner/pitch-strip use case), the consolidation target for `createContinuous`.
- **Detection Provider**: The participant performing DSP — the desktop JUCE engine verifier, or the JS harmonic-comb / YIN fallback — interchangeable behind the domain contract.
- **Detection Requester**: A consumer (note_detect chart path, Step Mode, SlopScale, Chord Sprint, a tuner) that needs detection primitives but owns its own judgment.
- **Compatibility Bridge Hit**: A record that a legacy chart-coupled or minigames-YIN detection handoff was used during migration.
- **Detection Outcome**: A bounded diagnostic record (provider, binding, requester, status, outcome, safe reason) with no live handles or sample data.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: A contained-playback requester with no host song loaded receives verification verdicts scored against its own tuning in 100% of focused scenarios.
- **SC-002**: A host song-switch while a player-tuning binding is active changes that binding's verdicts in 0% of focused scenarios.
- **SC-003**: Two concurrent bindings with different arrangement/tuning never alter each other's context or verdicts in 100% of focused scenarios.
- **SC-004**: A polyphonic (chord) target produces a real per-note/overall verdict — not an all-or-nothing exemption — in 100% of focused chord scenarios.
- **SC-005**: A DSP improvement landed in the provider reaches every domain consumer with zero consumer code changes in representative cases.
- **SC-006**: Unsupported tuning contexts produce an `incompatible` outcome and zero NaN/garbage verdicts in 100% of focused scenarios.
- **SC-007**: 100% of detection verdicts, state snapshots, and diagnostics contain zero raw audio buffers, sample/waveform data, or live capture handles.
- **SC-008**: New detection consumers added after this slice use the domain rather than a new legacy-only handoff in 100% of reviewed cases.
- **SC-009**: A maintainer can identify provider, binding context, availability, and outcome for a representative detection failure in under 5 minutes from diagnostics/inspector.
## Assumptions
- Audio-input (006) and audio-monitoring (007) slices are available as foundation; this slice consumes their source identity and monitoring facts rather than redefining them.
- The detection DSP (desktop JUCE engine verifier; JS harmonic-comb / YIN fallback) already exists and is correct; this slice gives it a per-binding, chart-decoupled, multi-consumer contract — it does not reimplement DSP.
- Consumers retain ownership of judgment semantics (hit windows, streaks, gems, accuracy, tiers); the domain provides primitives only.
- The notedetect `setVerifyTarget(notes, ctx)` bridge (plugin PR #62) is the interim, forward-compatible step; its per-call context maps onto this domain's per-binding context.
- Detection is sensitive: raw audio must never cross the capability boundary.
- Existing chart-coupled and minigames-YIN detection paths may coexist during migration behind the Spec 003 gate.
## Out of scope
- Detection DSP/model implementation or accuracy improvements (owned by the provider plugins).
- Consumer judgment/scoring UX (gems, tiers, accuracy) — owned by each requester.
- Audio-input source ownership/selection (006), monitoring lifecycle (007), recording, playback transport (008), and plugin installation.
- 6-string bass and other tunings outside the provider's current tuning tables (tracked separately).