* fix(gp2rs): write beat times at 6-decimal precision
The editor/timeline derives per-bar BPM from beat spans
(bpm = beats*60/span), which amplifies rounding: at millisecond
(3-decimal) precision a constant-tempo GP import (e.g. 140 BPM) shows a
spurious per-bar "tempo drift" of ~0.05-0.7 BPM because most bar lengths
don't land on a ms boundary (worse for fast/odd meters). gp2rs computes
these beat times exactly from the GP tempo map, so the only precision
loss is the ebeat/startBeat format string. Writing them at 6 decimals
(microseconds) makes the derived tempo match GP's authored value.
Verified on GP5 imports (Highway to Hell 116, Equivalence 140, Living
After Midnight 138): the derived per-bar BPM collapses from two drifting
values to the single authored constant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013JgxKh99UAeQqmhzSc73tv
Signed-off-by: ChrisBeWithYou <christian.a.cowan@gmail.com>
* test(gp2rs): compare ebeat times by value, not string
The 6-decimal beat-time write makes _assert_ebeats' exact-string compare fail
("0.500" vs "0.500000"). These tests only assert spacing, so parse both sides
to float — precision-agnostic, no need to rewrite every parametrized list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Signed-off-by: ChrisBeWithYou <christian.a.cowan@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Byron Gamatos <xasiklas@gmail.com>
* feat(drums): capture velocities alongside times in unmapped-percussion reporting
Both drum converters opt-in out_unmapped capture (convert_drum_track_from_midi,
convert_drum_track_to_drumtab) gain an index-aligned `velocities` list next to
`times`, carrying each dropped note real dynamics — MIDI velocity verbatim; GP
velocity with the same 1-127 gate as mapped hits, falling back to the 100
import default. A hand-mapping UI (the editor unmapped-notes dialog) can then
restore mapped notes at their source dynamics instead of flattening to v:100
(editor-side consumer: feedBack-plugin-editor#111).
The GP path chronological sort now reorders times and velocities in LOCKSTEP
so multi-voice measures cannot silently reassign dynamics. Additive: callers
that ignore the new key are unaffected.
Tests: extended tests/test_midi_import_drums.py + tests/test_gp2rs_drums.py
(alignment, lockstep sort, out-of-range fallback) — 26 passing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu
* docs(gp2rs): clarify velocity-default comment, mark dead-path fallback
- The mapped-GP velocity comment conflated GP's authoring default (95,
Velocities.default) with the drumtab render default (100,
DEFAULT_VELOCITY in lib/drums.py) used when `v` is omitted. Clarify
both defaults and that only the latter applies to omitted hits.
- Mark the `else: times.sort()` fallback in the unmapped-percussion
time/velocity sort as belt-and-suspenders — times and velocities are
always appended together under the same len<100 guard, so lengths
can't actually diverge.
No behavior change; comment-only maintainability nits from PR review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
Four tester-reported GP-import issues, all in the converter/parse layer:
* String count (bugs 2 & 4): <tuning> is padded to 6 slots, so a 4-string
bass, 5-string bass and 6-string guitar were byte-identical and the real
count was lost — a 5-string bass played on 4 strings and a 4-string bass
showed a phantom B in the editor. Record the authoritative count in a new
<tuning stringCount=N> attribute (gp2rs._build_xml) and trim the padded
tail back to it on read (song.parse_arrangement). All consumers already
trust a non-6 tuning length (arrangement_string_count, the editor's
_stringCountFor and build-time _normalize_tuning_to_count), so this fixes
the create-mode preview AND the built sloppak with no consumer changes.
* Lead/Rhythm reversed (bug 3): guitar arrangements were named by appearance
order (first guitar -> Lead), swapping roles for files that list Rhythm
before Lead. Honor 'lead'/'rhythm' in the GP track name; unhinted tracks
keep positional fallback. Applied to both convert_file's fallback (the
editor's track_indices-without-names path) and _auto_select_gpx, with
cross-role dedup so name-based and positional labels can't collide.
* Preview note count (bug 1): the importer's per-track count included
tie-continuation notes, which are folded into the previous note's sustain
and never become separate RS notes (260 shown vs 241 imported). Exclude
tie destinations so the preview matches the imported result.
Adds regression tests for all three. Bug 5 (no stems from synced audio) is
environment-dependent (best-effort demucs backend) and not addressed here.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Update GitHub repo references from feedback* to feedBack*
* rename: slopsmith -> feedBack, byron -> got-feedBack
Renames across the entire codebase:
- slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack
- byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack
- /home/byron/ -> /opt/got-feedBack/
- byron@ougsoft.com -> hi@got-feedBack.org
- github.com/byrongamatos/ -> github.com/got-feedback/
- com.byron. -> com.got-feedback.
- SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback
- Protocol/storage strings migrated with read-old/write-new pattern
- window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias
Refs: #rename-slopsmith
* rename: complete regen against current main + fix backward-compat alias
Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of
current main (3 commits had landed since the branch: #572/#554/#574),
resolving the four content conflicts in favour of main's newer content
(autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge).
Completion fixes on top of the mechanical rename:
- Re-apply rename to post-branch content the original rename never saw:
window.slopsmith(.Tour) consumers in lessons.js / notifications.js /
onboarding-tour.js, and the matching JS + python tests (autoplay_exit,
progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env
vars now match server.py (which reads FEEDBACK_SYNC_STARTUP /
FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real
path again.
- Restore the window.slopsmith backward-compat alias dropped during conflict
resolution, and move the bus aliases to AFTER the _feedBackExisting merge
block so they reference the fully-assembled object (also fixes the
loop_api.test.js API-surface regex, which the original PR latently broke).
- Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR)
and gitignore it.
- Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped
source labels.
Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669
passed / 1683 collected with 0 import errors; zero residual slopsmith/byron
except the two intentional window.slopsmith aliases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* rename: implement advertised backward-compat + prune dead community plugins
Address gaps where PR #537's "Backward compatibility" section was advertised
but not implemented, and clean up the community plugin list.
Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured):
- New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py
(_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and
tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI /
SLOPSMITH_PLUGINS_DIR / etc. deployments keep working.
- Fix the rename collapsing plugins/__init__.py and minigames/routes.py from
`FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant
`FEEDBACK_ or FEEDBACK_` (the fallback was silently lost).
Storage (app.js update-channel):
- Read feedBack-update-channel, fall back to legacy slopsmith-update-channel,
and clear the legacy key on write — so a user's update-channel preference
survives the rename instead of resetting to "stable".
Community plugin list (README): the rename rewrote third-party repo URLs we
don't own. Probed every one; their owners never renamed, so:
- Restore the 13 live community plugins to their real slopsmith-* names.
- Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex
tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle).
- Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory).
Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors,
rename-sensitive + startup suites green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the three OPTIONAL per-note feedpak 1.5.0 teaching marks — fg (fret-hand
finger), ch (strum-group key), sd (scale degree) — to the Note model and wire
format, mirroring the bend-shape work (#531). These are DISPLAY/TEACHING ONLY:
nothing in the scoring / note-verification path reads them.
- lib/song.py: Note.fret_finger / strum_group / scale_degree, default-omitted
on the wire (fg/ch/sd) and decoded via _wire_int_optional; _parse_note reads
the GP-written fretFinger XML attr. Pure helpers key_to_tonic_pc (§7.7 key
name -> tonic pitch class) + scale_degree_for_pitch, plus base_open_string_midis
/ pitch_from_base / note_pitch_midi (tuning offsets + capo + fret -> MIDI,
mirroring app.js _TUNING_BASE_MIDI).
- lib/gp2rs.py: GP5 note.effect.leftHandFinger -> fg (RsNote field + fretFinger
XML attr), reusing the chord Fingering value convention.
- lib/gp2rs_gpx.py: GP8/GPIF per-note <LeftFingering> (p-i-m-a-c letter codes,
verified against real GP8 exports) -> fg.
- server.py highway_ws: derive sd for notes + chord notes from the active
keys.json key + sounding pitch when the author didn't author one (author value
wins); base hoisted out of the per-note loop.
Tests: round-trip + omit-when-default + malformed-tolerance for fg/ch/sd;
key_to_tonic_pc + scale_degree_for_pitch + note_pitch_midi (standard/drop-D/
capo/bass) units; GP5 leftHandFinger and GP8 <LeftFingering> import.
Part of got-feedback/feedback#334
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements feedpak spec §6.2.1 (feedpak 1.4.0) per-note bend shape on the
core side:
- `bn` stays the bend's peak magnitude in semitones (unchanged).
- `bt` — bend intent (0 up, 1 release, 2 pre-bend, 3 pre-bend-release,
4 round-trip), default 0, default-omitted on the wire.
- `bnv` — time-stamped bend curve [{t: seconds-from-onset, v: semitones}],
authoritative when present; default-omitted. Older readers ignore both.
Wire (lib/song.py): Note.bend_intent/bend_values; note_to_wire emits bt/bnv
only when set; note_from_wire reads them via _sanitize_bend_curve (drops
malformed entries, empty -> None never []). _parse_note reads them from the
GP-import XML (bendIntent attr + bendValues JSON) so GP curves survive
import -> XML -> wire -> highway.
GP5 (lib/gp2rs.py): _gp_bend_shape maps pyguitarpro BendPoints to a bnv
curve — semitones = value/2.0 (consistent with the existing scalar bn),
t = position/12 * duration — and _bend_intent_from_values derives bt from
the shape. Emitted for <note> and <chordNote> via the shared _build_xml.
GP8 (lib/gp2rs_gpx.py): _gpx_bend_shape builds a 3-point curve from the
GPIF origin/middle/destination value+offset Properties (value/divisor
semitones, offset/100 * sustain seconds), reusing the shared _build_xml.
GPIF offset Property names should be confirmed against a real GP8 export.
Tests cover wire round-trip + default-omit + sanitization, GP5 unit/time
mapping + intent classification end-to-end through the XML, and the GP8
curve builder.
Part of got-feedback/feedback#334
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-merge Codex review of E3 (PR #522) flagged two GP5 edge cases:
- P2: enrichment applied the diagram's name/fingers to the played template
without checking the diagram described the voicing actually played. A
mismatched chord label/diagram could mis-name/finger the played template, and
the back-fill spread it to other strums of the same played pattern. Now gated
on an exact, full-span fret-pattern match (new _chord_diagram_frets), mirroring
the GP8 guard — and comparing over max(played width, num_strings) so a
7/8-string diagram can't falsely match a narrower played voicing.
- P3: name and fingers back-fill were coupled (a name-only first annotation
blocked a later beat's fingers). Now independent.
Codex re-reviewed twice (the first match-gate trimmed extended strings; fixed by
the full-span compare); final pass clean. +4 tests (mismatch-not-applied,
name-then-fingers decoupled, higher-position absolute match, 7-string extended
string regression). 163 GP tests pass.
Follow-up to #522. Part of got-feedback/feedback#334.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GP imports previously landed chord templates with blank fingerings (GP5 +
GP8) and blank names (GP8), so the editor/highway had nothing to show even
though E0 preserves and E1 authors that data. E3 extracts the real chord
diagrams so imports arrive rich, keyed on the same fret-pattern join key the
editor (E0) and GP5 already use.
GP8 (lib/gp2rs_gpx.py): parse the per-track GPIF DiagramCollection
(Item @name + Diagram Fret/Fingering) into a fret-pattern -> {name, fingers}
map and enrich matching played voicings at the template build site. Diagram
string indices share the note String index space, so they go through the same
pitch-rank transform; <Fret fret> is treated as absolute.
GP5 (lib/gp2rs.py): pyguitarpro exposes the voicing on beat.effect.chord
(.strings indexed 0=highest string, .fingerings the parallel Fingering enum,
already RS finger ints). New _chord_fingers maps them to RS string order; the
template enrich now back-fills any still-blank template so the annotated chord
attaches even when an earlier unannotated strum of the same voicing created it.
Finger encoding (none/open=-1, thumb=0, index=1, middle=2, ring=3, pinky=4)
matches the editor E1 + RS serializer. Only enriches on exact fret-pattern
match; diagram-less charts import identically (blank). Verified end-to-end
against real files (GP8_Test.gp, joplin-janis-piece_of_my_heart.gp4).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reword comments/docstrings/strings and rename identifiers that referenced
the external game and its file formats:
- format-id "psarc" -> "archive"; local vars psarc_path -> song_path,
psarc_base -> tone_base
- lyrics provenance value "sng" -> "notechart" (legacy "sng" still accepted)
- highway_3d fret-ghost scope value "rocksmith" -> "chords" (invalid/legacy
values fall back to the default, preserving behaviour)
- neutralise references in prose, test names/data, .gitattributes and docs
No functional change beyond the renamed identifiers; all Python compiles.