Skip to content

Import a GP guitar/bass track into an open session (add or replace) - #37

Merged
byrongamatos merged 1 commit into
mainfrom
feat/import-guitar-bass-track
Jul 1, 2026
Merged

Import a GP guitar/bass track into an open session (add or replace)#37
byrongamatos merged 1 commit into
mainfrom
feat/import-guitar-bass-track

Conversation

@byrongamatos

Copy link
Copy Markdown
Collaborator

Closes #36

What

Adds the missing path to bring a Guitar Pro guitar or bass track into the song you're already editing — either as a new arrangement or to replace an existing one. The core conversion (lib/gp2rs convert_file) already produces guitar/bass arrangements; this is the "into an existing session" wiring plus a modal, mirroring the existing Add Keys flow.

Design (as specified)

  1. Add + Replace. One modal: pick a GP guitar/bass track, then choose a destination — Add as a new arrangement or Replace an existing arrangement.
  2. Keep the song's timing. Imported notes are aligned to the session's existing audio via the current _effectiveAudioOffset() (passed as the convert audio_offset). The song-level beats / sections / audio are untouched; there's no re-sync from the GP (finer alignment stays the existing offset tool).

Changes

Backend (routes.py)

  • New POST /api/plugins/editor/import-guitar-track — mirrors import-keys but without the piano forcing (single guitar/bass track). Rejects piano/drums/percussion/vocal tracks and validates a finite audio_offset.
  • Hoisted _arr_to_data(arr, name) from inside import_keys_track to module scope so keys + guitar import share one builder (no copy-paste). Also gives chord notes harmonic_pinch parity with single notes.

Frontend (screen.js, screen.html)

  • New "+ Guitar/Bass" toolbar button + Import Guitar / Bass from GP modal (track picker filtered to guitar/bass; destination Add/Replace with a same-family target dropdown).
  • Add reuses the generic append helper (now label- and modal-agnostic).
  • ReplaceReplaceArrangementChartCmd: a snapshot/rollback swap of notes/chords/templates/tuning that keeps the target's name, tones and the song-level timeline, in one undo step.
  • Bass naming: a bass arrangement is named to match /bass/i so it renders on E/A/D/G (4 lanes), not shifted.

Correctness notes (from the review loop)

  • Replace targets are family-filtered (same guitar/bass family; Keys/Drums excluded) so a swap can't strand a chart in the wrong render mode.
  • The command deep-copies the incoming chart and flattens inside exec(), so redo is idempotent (no double-flatten / duplicated chord notes).
  • Replace recomputes lane height (_resizeForLaneChange) for 4/5/6-bass and 6/7/8-guitar swaps, and clears selection state so a stale ref can't hit an imported note.
  • arr.anchors (note-derived auto hand positions) are cleared on replace and regenerate from the new notes on save; arr.phrases are kept — they're time-anchored to the unchanged sections and their levels repopulate from the new chart on save.

Testing (local)

  • python -m pytest174 passed (incl. new tests/test_arr_to_data.py).
  • All node tests/*.test.js green (incl. new tests/import_guitar_track.test.js, 19 assertions: track filter, naming/bass/dedupe/misroute, swap/rollback exactness, deep-copy reference-independence, redo idempotency, phrases-kept).
  • node --check screen.js clean.
  • Independent Codex review run on the diff and iterated to clean.

Not yet exercised end-to-end in a running app with a real GP file — worth a manual pass (add a guitar track; replace a Bass; confirm bass lands on E/A/D/G and replace is one undo).

🤖 Generated with Claude Code

… or replace)

Adds the missing path to bring a Guitar Pro GUITAR or BASS track into the
song you're already editing — either as a new arrangement or to replace an
existing one. The core conversion (lib/gp2rs convert_file) already produces
guitar/bass arrangements; this is the "into an existing session" wiring plus
a modal, mirroring the existing Add-Keys flow.

Timing: imported notes are aligned to the session's existing audio by passing
the current _effectiveAudioOffset() as the convert audio_offset. The song-level
beats / sections / audio are left untouched (finer alignment stays the existing
offset tool).

Backend:
- New POST /api/plugins/editor/import-guitar-track — mirrors import-keys but
  without piano forcing (single guitar/bass track). Rejects piano/drums/
  percussion/vocal tracks and validates a finite audio_offset.
- Hoisted _arr_to_data(arr, name) from inside import_keys_track to module
  scope so keys + guitar import share one builder (no copy-paste). Also gives
  chord notes harmonic_pinch parity with single notes.

Frontend:
- New "+ Guitar/Bass" toolbar button + "Import Guitar / Bass from GP" modal:
  upload a GP, pick a guitar/bass track (filtered), choose Add or Replace.
- Add reuses the generic append helper (now labelled + modal-agnostic).
- Replace goes through ReplaceArrangementChartCmd — snapshot/rollback swap of
  notes/chords/templates/tuning that keeps the target's name, tones and the
  song-level timeline in one undo step. The command deep-copies the incoming
  chart and flattens inside exec() so redo is idempotent, recomputes lane
  height (LANE_H) for 4/5/6-bass and 6/7/8-guitar swaps, and clears selection
  state so a stale ref can't hit an imported note.
- Bass tracks are named to match /bass/i so they render on E/A/D/G (4 lanes),
  not shifted. Replace targets are family-filtered (same guitar/bass family,
  Keys/Drums excluded) so a swap can't leave a chart in the wrong render mode.
- arr.anchors (note-derived auto hand positions) are cleared on replace and
  regenerate from the new notes on save; arr.phrases are deliberately KEPT —
  they're time-anchored to the unchanged sections and their levels repopulate
  from the new chart on save.

Tests: node tests for the pure helpers (track filter, naming, swap/rollback,
deep-copy/redo idempotency) + a pytest for the shared _arr_to_data dict shape.
Independent Codex review run to clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 1, 2026 13:28
@byrongamatos
byrongamatos merged commit 270d564 into main Jul 1, 2026
1 check passed
@byrongamatos
byrongamatos deleted the feat/import-guitar-bass-track branch July 1, 2026 13:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the missing “import Guitar Pro guitar/bass track into the currently-open editor session” path, including both Add as a new arrangement and Replace an existing arrangement flows, while preserving the session’s song-level timeline and aligning imported notes via the current audio offset.

Changes:

  • Backend: hoists shared _arr_to_data builder and adds POST /api/plugins/editor/import-guitar-track for importing a single GP guitar/bass track into an existing session.
  • Frontend: adds “+ Guitar/Bass” button + modal, plus ReplaceArrangementChartCmd (swap/rollback) and supporting pure helpers.
  • Tests/docs: adds unit tests for the new pure helpers and _arr_to_data, and updates CHANGELOG.md.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
routes.py Adds import-guitar-track endpoint and hoists _arr_to_data for shared arrangement wire-shape building.
screen.js Implements modal logic, track filtering/naming, and replace-chart command + chord-flatten refactor.
screen.html Adds “+ Guitar/Bass” toolbar button and the import modal UI.
tests/import_guitar_track.test.js Unit tests for the new pure JS helpers and replace snapshot/rollback behavior.
tests/test_arr_to_data.py Unit tests for the shared _arr_to_data arrangement-dict builder.
CHANGELOG.md Documents the new import guitar/bass feature and its behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread screen.js
Comment on lines +7754 to +7757
const data = await resp.json();
if (data.error) { statusEl.textContent = 'Error: ' + data.error; goBtn.disabled = false; return; }
// The modal was closed / a new file picked while this was in flight.
if (reqSeq !== _importGuitarReqSeq) return;
Comment thread routes.py
Comment on lines +4835 to +4846
# Reject a malformed/non-finite offset rather than silently aligning to
# 0.0 (mirrors convert_gp) — a wrong offset imports mis-timed notes.
_raw_offset = data.get("audio_offset")
if _raw_offset is None:
audio_offset = 0.0
else:
try:
audio_offset = float(_raw_offset)
except (TypeError, ValueError):
return JSONResponse({"error": "audio_offset must be a number"}, 400)
if not math.isfinite(audio_offset):
return JSONResponse({"error": "audio_offset must be finite"}, 400)
Comment thread tests/test_arr_to_data.py
Comment on lines +26 to +33
def _chord_note(**kw):
# Chord notes carry the same tech fields minus `harmonic_pinch`.
base = dict(
time=2.0, string=1, fret=7, sustain=0.0,
bend=None, slide_to=-1, slide_unpitch_to=-1, hammer_on=False,
pull_off=False, harmonic=False, harmonic_pinch=False, palm_mute=False,
mute=False, tremolo=False, accent=False, tap=False, link_next=False,
)
Comment thread CHANGELOG.md
Comment on lines +33 to +34
song-level timeline. New backend endpoint `POST /api/plugins/editor/
import-guitar-track` (reuses the shared `_arr_to_data` arrangement builder);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Editor: import a GP guitar/bass track into an existing session (add or replace)

2 participants