m2-edit-operations.md 4.1 KB

M2: edit operations

Branch: m2-edit-operations → main

What changed

  • internal/project: the domain model (build plan 4.2, 4.3). It has no Wails code.
    • New(name) builds from DemoBlank.xpj, embedded with go:embed (copy in internal/project/template/; a test checks it still matches testdata/device/DemoBlank.xpj).
    • Load(path) reads a project, takes the name from the file name, maps each pool file to <name>_[ProjectData]/, and warns when the firmware header or data.version differ from the tested 1.3.0.12 / 28.
    • Pads: AssignSample, ClearPad, CopyPad, MovePad, SwapPads, SetTune (clamped to ±24 st / ±90 ct, pitch rounded to 32-bit), SetMuteGroup, PadHasNotes, plus a read-only Pad(slot) view.
    • Sequences: AddSequence, RemoveSequence, SetSequence (name, BPM, bars; writes all 26 length fields), plus Sequences() in slot order.
    • SetName, ValidName (no empty names, no <>:"/\|?* or control characters, no trailing dot/space, no Windows reserved names), SlotName (0 → A1, 17 → B2).
    • Pool rules: the same source file on several pads gives one entry; a different file with a taken name gets 001, 002, compared without case; an entry is dropped when its last pad is cleared; 128-sample limit.
  • internal/wav: WAV header reader (format, channels, rate, bits, frames), moved forward from M3 because AssignSample needs the frame count. It handles extensible headers and extra chunks, and Validate() applies the 1.0 rule: 8/16/24/32-bit PCM or 32-bit float, mono/stereo, at most 20 minutes. The preview decoder and export stay in M3.
  • Docs: corrected the pitch example (see notes); added open question 7 on currentSequence.

Notes

  • Device ordering, copied exactly: new pool entries are appended to samples[] but inserted first in tracks[0].samples[], and a new sequence goes first in sequences[]. The T1 golden match depends on both.
  • Pitch text: the device wrote 12.4 as 12.399999618530274. Our writer prints 12.399999618530273, the same float64 in shortest form, which is what the device-accepted T1/T3 files use. The docs said the device wrote …273; corrected.
  • currentSequence: treated as a slot number, as the plan says. The files cannot tell slot from array index (always 0, and slot 0 is stored second), so I added it as open question 7 for checkpoint B4.
  • Limits not yet confirmed on the device: mute groups 0–32 (checkpoint A3) and at most 999 bars (A5). Both are constants in the code (MaxMuteGroup, MaxBars) and easy to change.
  • 128-sample limit: with one sample per pad the pool can't overflow through the API, since every pad replaced frees its entry. The limit only matters for loaded projects with unused or multi-layer entries, and the test covers that case.
  • Loaded pool entries keep their metadata; new ones use loadImpl 0, tempo 0.0, root 60, tune 0.0, key "C Natural Minor".

Testing

go vet ./... and go test ./... pass (coverage: project 90%, wav 94%, xpj 89%).

M2 done-when:

  • T1 and T3: building from the blank template with the prototype's edits gives output byte-identical to testdata/accepted/MpckitT1.xpj and MpckitT3.xpj (decompressed).
  • Demo02 rebuild: matches the device's file except tracks[0].colour, assignableEnvelopeFollowerAssignments.currentControlIndex, recorded events, the pad-5 LoopCrossfadeLength (device value on its trimmed pad vs our -1, as the format doc already records) and the pitch spelling above. Trim (a 2.0 feature) is set straight on the tree in the test to complete the rebuild.
  • Unit tests: clear, copy, move, swap, pool reuse, name collision, the 128 limit, sequence add/remove/length (all 26 fields), PadHasNotes against DemoFile (true for A1–A4 only), loading DemoFile and writing it back unchanged, bad WAVs, range checks.
  • WAV reader: all 10 files in the WAV test pack and the 7 device-saved samples give the frame counts the projects record (sliceInfo.End + 1); synthetic extensible, ADPCM, 3-channel, 64-bit float, RF64, truncated and zero-size-data files.

Next

M3: export to folder and zip, the preview decoder, and the small CLI for device checkpoint A.