Keep manual edits to a song across rebuilds #29

Merged
branderson merged 1 commits from edit-layer into main 2026-10-11 01:10:17 -07:00
Collaborator

Summary generated by LLM agent

Phase 1 of turning the review viewer into an editor that replaces Yass. This is the part that keeps manual edits safe across every kind of rebuild, including REBUILD ALL FROM… and a rebuild from scratch. There's no editing UI yet; that's the next phase and builds on this API.

How edits are kept

  • Separate file: edits live in work/<slug>/edits.json. No stage lists it as an output, so no rebuild removes it. Deleting the song does.
  • Whole lines: an edit is a whole lyric line as the user left it: every note's start, duration, pitch, syllable and kind. Editing one note saves the line.
  • Seconds, not beats: a rebuild that picks a new BPM re-snaps the edit instead of breaking it.

On every run, grid lays the edits over the freshly generated notes before snapping to beats:

  • An edited line replaces that line's generated notes.
  • Neighbouring generated lines are cut back so they don't overlap it. The edited line always wins.
  • Line breaks next to an edited line move to the middle of the gap. All other breaks are untouched.

The merged result goes to notes_final.json, with warnings recomputed so a line you fixed stops being reported. Warnings and the review read it when it exists.

When the lyrics change

Edits are stored with the lyrics they were made against. When the lyrics differ, each edited line is matched to its line in the new lyrics by text and order. Adding a line above an edited one moves the edit with its line. An edit whose line's words changed is set aside as stale rather than applied to different words: it's kept, reported as "N edited line(s) no longer match the lyrics", and can be discarded through the API.

API

  • GET /api/jobs/{id}/review now also returns each note's syllable and kind, whether each line is edited, any stale edits, and the chart's beat grid (BPM and GAP) for snapping. The fields that were already there are unchanged.
  • PUT /api/jobs/{id}/lines/{n} with {"notes": [...]} saves a line. Notes are validated: start ≥ 0, duration > 0, a known kind, non-empty text.
  • DELETE /api/jobs/{id}/lines/{n} reverts a line to the generated notes.
  • DELETE /api/jobs/{id}/stale/{n} discards a stale edit.

Each change rewrites the work chart and the packaged chart in place, keeping each one's headers and the BPM chosen at build time. Nothing is re-encoded, so a save is quick. Editing is refused with 409 while that song is queued or being rebuilt, since the build would overwrite the chart.

Other changes

  • Notes gain a kind (:, *, F, R, G), so golden, freestyle and rap notes reach the chart. Generated notes stay :, and older notes_sec.json files without the field still load.
  • grid now has two outputs, song.txt and notes_final.json. A song built before this change runs grid once more on its next rebuild. That's cheap.

Testing

All test lyrics are synthetic counting words.

  • tests/test_edits.py: merging, neighbours giving way, line breaks, edits following lines when lyrics are inserted above, stale edits, validation, the file being removed when no edits are left, and warnings.
  • grid stage: an edited line keeps its pitch and golden kind through two regenerations at different pitches. rechart swaps the notes and keeps the headers and tempo.
  • Web: saving rewrites both charts and clears the fixed line's warning; reverting; validation and out-of-range errors; 409 during a rebuild; discarding a stale edit.
  • Full suite passes, except test_version (needs the package installed, as CI does).

The design doc for the editor is kept outside the repo.

**Summary generated by LLM agent** Phase 1 of turning the review viewer into an editor that replaces Yass. This is the part that keeps manual edits safe across every kind of rebuild, including REBUILD ALL FROM… and a rebuild from scratch. There's no editing UI yet; that's the next phase and builds on this API. ## How edits are kept - **Separate file:** edits live in `work/<slug>/edits.json`. No stage lists it as an output, so no rebuild removes it. Deleting the song does. - **Whole lines:** an edit is a whole lyric line as the user left it: every note's start, duration, pitch, syllable and kind. Editing one note saves the line. - **Seconds, not beats:** a rebuild that picks a new BPM re-snaps the edit instead of breaking it. On every run, `grid` lays the edits over the freshly generated notes before snapping to beats: - An edited line replaces that line's generated notes. - Neighbouring generated lines are cut back so they don't overlap it. The edited line always wins. - Line breaks next to an edited line move to the middle of the gap. All other breaks are untouched. The merged result goes to `notes_final.json`, with warnings recomputed so a line you fixed stops being reported. Warnings and the review read it when it exists. ## When the lyrics change Edits are stored with the lyrics they were made against. When the lyrics differ, each edited line is matched to its line in the new lyrics by text and order. Adding a line above an edited one moves the edit with its line. An edit whose line's words changed is set aside as stale rather than applied to different words: it's kept, reported as "N edited line(s) no longer match the lyrics", and can be discarded through the API. ## API - `GET /api/jobs/{id}/review` now also returns each note's syllable and `kind`, whether each line is `edited`, any `stale` edits, and the chart's beat `grid` (BPM and GAP) for snapping. The fields that were already there are unchanged. - `PUT /api/jobs/{id}/lines/{n}` with `{"notes": [...]}` saves a line. Notes are validated: start ≥ 0, duration > 0, a known kind, non-empty text. - `DELETE /api/jobs/{id}/lines/{n}` reverts a line to the generated notes. - `DELETE /api/jobs/{id}/stale/{n}` discards a stale edit. Each change rewrites the work chart and the packaged chart in place, keeping each one's headers and the BPM chosen at build time. Nothing is re-encoded, so a save is quick. Editing is refused with 409 while that song is queued or being rebuilt, since the build would overwrite the chart. ## Other changes - Notes gain a `kind` (`:`, `*`, `F`, `R`, `G`), so golden, freestyle and rap notes reach the chart. Generated notes stay `:`, and older `notes_sec.json` files without the field still load. - `grid` now has two outputs, `song.txt` and `notes_final.json`. A song built before this change runs `grid` once more on its next rebuild. That's cheap. ## Testing All test lyrics are synthetic counting words. - `tests/test_edits.py`: merging, neighbours giving way, line breaks, edits following lines when lyrics are inserted above, stale edits, validation, the file being removed when no edits are left, and warnings. - `grid` stage: an edited line keeps its pitch and golden kind through two regenerations at different pitches. `rechart` swaps the notes and keeps the headers and tempo. - Web: saving rewrites both charts and clears the fixed line's warning; reverting; validation and out-of-range errors; 409 during a rebuild; discarding a stale edit. - Full suite passes, except `test_version` (needs the package installed, as CI does). The design doc for the editor is kept outside the repo.
dejima-agent added 1 commit 2026-10-11 01:04:23 -07:00
Keep manual edits to a song across rebuilds
tests / test (pull_request_target) Successful in 3m55s
4acd3f74ba
The groundwork for an in-app editor. Edits live in edits.json in the work
dir, which no stage produces, so no rebuild removes them. An edit is a
whole lyric line as the user left it, in seconds, so a rebuild with a new
BPM re-snaps it. grid lays the edits over the freshly generated notes
(neighbouring lines give way to an edited one), writes the result to
notes_final.json with warnings brought up to date, and charts that. When
the lyrics change, edits follow their lines by text and order; an edit
whose line's words changed is set aside as stale and reported, not applied.

Notes gain a kind, so golden, freestyle and rap notes can be charted. The
review endpoint returns the merged notes with their syllables, kinds and
edit state, plus the chart's beat grid. PUT/DELETE
/api/jobs/{id}/lines/{n} save and revert a line, DELETE
/api/jobs/{id}/stale/{n} discards a stale edit; each rewrites the work and
packaged charts in place with the tempo already chosen, so a save
re-encodes nothing.
branderson approved these changes 2026-10-11 01:10:11 -07:00
branderson merged commit cde56826a1 into main 2026-10-11 01:10:17 -07:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: branderson/songtrace#29