Skip to content

fix(editor): a MIDI file is a project — MIDI-only create + real multitrack unpack - #284

Merged
byrongamatos merged 7 commits into
mainfrom
feat/editor-midi-create
Jul 15, 2026
Merged

fix(editor): a MIDI file is a project — MIDI-only create + real multitrack unpack#284
byrongamatos merged 7 commits into
mainfrom
feat/editor-midi-create

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What

A MIDI file is a project now. Reported with dkcjungle-2.mid (format 1, 20 tracks): "why can't the editor upload this file and unpack it?" The backend parsed it fine all along — the create modal was the dead end, three ways deep:

  1. _stageMidi recorded only the name; the staged File was dropped on the floor.
  2. _createGateOpen never opened for MIDI-only — Create stayed disabled forever.
  3. Even the "+Keys later" path made you re-pick the file — where multi-select silently imported only the first track ("MIDI keys import is single-track").

The flow now

Stage a .mid → the title pre-fills from the filename → Create enables → Create runs a draft create and opens straight into the track picker with the file already parsed (no re-pick) → check any number of tracks → every one imports as its own chart track, named after its MIDI track under a keys-safe prefix (Keys — Rumble in da jungle) → the temporary placeholder the draft create needed is removed automatically once real tracks land → the MIDI's own tempo map is offered as the project grid, as before.

Load-bearing details

  • Keys-safe naming is not cosmetic: kind inference is name-driven and the imported notes use the roll's keys packing — a part named bare Bass, Baby. would render that packing as fretted lanes (garbage). _midiKeysArrNamePure pins the contract.
  • One batch request, not a per-track loop: import-keys-midi rmtrees its temp dir after responding, so a request loop found no file on track two. The endpoint now accepts tracks: [{index, channel_filter}] and converts everything before the one cleanup; the legacy single-track body is unchanged.
  • The placeholder is removed only when provably untouched (_midiSeedRemovablePure: flagged index, seeded name, zero notes/chords, never the last part) — user work always stays. The seed is Lead because the draft-create backend accepts Lead/Rhythm/Bass rosters only today.
  • A user-modified roster is real intent and is kept (their parts + the MIDI tracks); only the modal's untouched ['Lead'] boilerplate is replaced.

Testing

tests/midi_create.test.mjs (3 cases, fails on main) + the create-gate suite gains the MIDI-stage case. Full gates green: 159 JS files / 0 fail, pytest 256, lint at baseline. Live-verified with the reported file itself: staged → Create → picker auto-opened with all 15 note-bearing tracks → 3 selected → Keys — Rumble in da jungle (257 notes), Keys — Cheap Xylophone (208), Keys — Diddlydiddlydiddlydiddladeedade (17) — placeholder gone, zero page errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features
    • Create new songs directly from staged MIDI files—Create now opens with MIDI-only input, seeds the flow, parses MIDI up-front, and pre-fills the title from the filename.
    • When importing, you can select multiple MIDI tracks to generate separate chart tracks per checked MIDI track (preserving selection order and names).
  • Bug Fixes
    • Improved cleanup and safety for MIDI-only placeholder/seed arrangements, including conditional removal to prevent accidental deletion.
  • Tests
    • Added regression coverage for MIDI staging, title/name derivation, multitrack import selection, and seed/roster cleanup/provenance behavior.

ChrisBeWithYou and others added 4 commits July 14, 2026 22:59
…track unpack

Christian's report (dkcjungle-2.mid, format 1, 20 tracks): 'why can't
the editor upload this file and unpack it?' Diagnosis: the backend
parsed it fine — the CREATE MODAL dropped the staged file on the floor
(_stageMidi kept only the name), never opened the gate for MIDI-only
(_createGateOpen: gp/eof/roster+title only), and even the promised
'+Keys later' path made the user re-pick the file — where multi-select
silently imported ONLY THE FIRST track ('MIDI keys import is
single-track').

- _stageMidi keeps the File objects, pre-fills the title from the
  filename (_midiDefaultTitlePure — the blank-create backend requires
  one), and says what Create will do.
- _createGateOpen: a staged MIDI alone opens the gate, like a GP file.
- editorDoCreate routes MIDI-only to _editorDoMidiCreate: a blank
  (draft) create seeded with a flagged Keys placeholder, then the
  staged file feeds straight into the existing Add-Keys track picker
  (window._editorKeysHandleFile — the factored File-object form of
  editorKeysFileSelected). No re-pick.
- REAL unpack: the MIDI import loops EVERY selected track (one POST +
  one arrangement each). Names are keys-safe by construction
  (_midiKeysArrNamePure: 'Keys — <track name>' — kind inference is
  name-driven and the notes use keys packing; a bare 'Bass, Baby.'
  would render the packing as fretted lanes). The tempo-map offer
  still fires once (same file, same map).
- The placeholder is removed only when provably untouched
  (_midiSeedRemovablePure: flagged index, seeded name, zero
  notes/chords, never the last part) — user work always stays.

tests/midi_create.test.mjs (3, fails on main) + create_gate.test.mjs
gains the MIDI-stage case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 69e7a432-1f73-429e-b344-9dd60e80a95e

📥 Commits

Reviewing files that changed from the base of the PR and between a75deb1 and efa56e3.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • routes.py
  • src/create.js
  • src/import.js
  • src/main.js

📝 Walkthrough

Walkthrough

MIDI uploads can now open the Create flow, derive a project title, and enter the Keys track picker. Selected MIDI tracks are imported in batches as separate arrangements, with temporary seed cleanup and supporting tests.

Changes

MIDI creation and multitrack import

Layer / File(s) Summary
MIDI-only create entry
src/create.js, src/import.js, src/main.js
MIDI staging enables creation, derives a default title, routes files through the Keys picker, tracks roster provenance, and clears staged data when removed.
Batch Keys import and seed cleanup
routes.py, src/import.js
The MIDI endpoint converts selected tracks in one request and returns ordered arrangements; the editor names imported tracks and removes an untouched placeholder arrangement.
MIDI validation and workflow coverage
tests/create_gate.test.mjs, tests/midi_create.test.mjs, CHANGELOG.md
Tests cover staging, naming, batch selection, provenance-based cleanup, and create gating; the changelog records the workflow.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CreateModal
  participant KeysPicker
  participant ImportEndpoint
  participant EditorState
  CreateModal->>KeysPicker: open with staged MIDI file
  KeysPicker->>ImportEndpoint: submit selected track indexes and filters
  ImportEndpoint-->>KeysPicker: return arrangements and tempo_map
  KeysPicker->>EditorState: append named arrangements and remove seed placeholder
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: MIDI-only project creation plus true multitrack MIDI unpacking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-midi-create

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
routes.py (1)

5792-5847: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Batch conversion is all-or-nothing — one bad track fails the whole selection.

_convert() runs every selected track in a single worker call; if any one _convert_one() raises (bad track index, corrupt data, etc.), the exception aborts the entire batch and none of the already-converted tracks are returned — the user sees one generic 500 and has to retry the whole selection. Given this endpoint now accepts up to 64 tracks in one request, a per-track failure isolation (catch inside the loop, return partial results + a failed list) would give a much better multitrack-import experience, at the cost of a response-shape change on both ends.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@routes.py` around lines 5792 - 5847, Update _convert and its response
handling so _convert_one failures are isolated per selected track: catch each
track’s exception inside the batch loop, retain successful conversions, and
collect failed track identifiers and error details in a failed list. Return
partial results with the new failed field while preserving tempo_map generation
and successful track output; update the consuming frontend response handling to
accept and surface the changed response shape instead of returning a single 500
for one failed track.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/create.js`:
- Around line 1864-1889: Update the MIDI import flow around _editorDoBlankCreate
to track whether the roster is untouched boilerplate separately from the actual
roster value, so an intentional Lead-only selection is preserved and only the
default placeholder is marked for cleanup. Process all staged files in
createState.midiFiles rather than only index 0, passing each through the
existing editorShowAddKeysModal/_editorKeysHandleFile flow while preserving the
seeded arrangement handling.
- Around line 1878-1888: The _editorDoMidiCreate flow only forwards
createState.midiFiles[0], dropping additional staged MIDI files while the UI
claims they will be imported. Update _editorDoMidiCreate and the existing
keys-picker handoff to process every staged MIDI file, or constrain
editorContentImportSelected to stage only one file and keep the label consistent
with that limit.

In `@src/import.js`:
- Around line 300-308: Update the arrangement-renaming logic after the
arrangements normalization to run unconditionally, removing the
pickedList.length > 1 guard. Ensure every valid arrangement is paired with its
corresponding pickedList entry and renamed via _midiKeysArrNamePure, including
single-track imports.

---

Nitpick comments:
In `@routes.py`:
- Around line 5792-5847: Update _convert and its response handling so
_convert_one failures are isolated per selected track: catch each track’s
exception inside the batch loop, retain successful conversions, and collect
failed track identifiers and error details in a failed list. Return partial
results with the new failed field while preserving tempo_map generation and
successful track output; update the consuming frontend response handling to
accept and surface the changed response shape instead of returning a single 500
for one failed track.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e087663-c0f3-4310-b6c3-04beec267c47

📥 Commits

Reviewing files that changed from the base of the PR and between d7c5e3c and 24f983f.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • routes.py
  • src/create.js
  • src/import.js
  • src/main.js
  • tests/create_gate.test.mjs
  • tests/midi_create.test.mjs

Comment thread src/create.js Outdated
Comment thread src/create.js
Comment thread src/import.js Outdated
ChrisBeWithYou and others added 3 commits July 15, 2026 02:43
…ter provenance (review #284 items 19-21)

Item 19: _stageMidi silently kept every staged file while Create sent only
midiFiles[0]. The stage is now ONE honest slot — the last pick wins, replaces
anything staged earlier, and the status names both the kept and dropped files
(_midiStageSlotPure). Multi-file import stays a Create-window-redesign follow-up.

Item 20: the keys-safe source name (_midiKeysArrNamePure) applied only when
more than one track was selected; a single selection now gets
'Keys — <track>' too instead of the generic server-side 'Keys'.

Item 21: the seed-placeholder decision compared the roster VALUE to ['Lead'],
so an explicitly-chosen Lead was removed after import. createState.rosterTouched
now tracks provenance (set on any roster add/remove), and _midiSeedRosterPure
seeds only for an untouched or emptied roster — an explicit rebuild of the
default is kept.

All three regressions fail on the pre-fix code (2-files-staged, generic 'Keys',
rosterTouched undefined).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
_editorDoMidiCreate set S._midiSeedArrIdx on shared state but only
_maybeRemoveMidiSeed cleared it. If the user cancelled the auto-opened
track picker (no import) the flag survived — loadCDLC/editorApplyCreateResult
never clear it — so a later Add-Keys import on a DIFFERENT song would delete
that song's arrangement 0 whenever it was an untouched empty 'Lead' (a common
fresh-blank-project state): silent cross-session arrangement loss.

Root cause: the seed flag is session-lifetime state with no session identity.
Bind it to the session that created it (S._midiSeedSession) and refuse the
cleanup — deleting both flags either way — when the current session no longer
matches. Regression test drives editorDoAddKeys on song B carrying song A's
stale flag; the empty Lead now survives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
@byrongamatos
byrongamatos merged commit 577f929 into main Jul 15, 2026
3 of 4 checks passed
@byrongamatos
byrongamatos deleted the feat/editor-midi-create branch July 15, 2026 20:43
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.

2 participants