Explorar o código

Merge branch 'beta-0-5-0' of bajdev/mpckit into main

bajdev hai 1 día
pai
achega
a559ea3e0e

+ 14 - 12
docs/build-plan.md

@@ -1,8 +1,10 @@
-# mpckit 1.0 build plan
+# mpckit 0.5.0 build plan
 
-Written 2026-11-10. Companion to `mpckit/xpj-format-and-scope.md` (format reference, verified edit recipes, agreed 1.0 scope) and the "mpckit main screen mockup" design artifact. Read the format doc first; this plan does not repeat the field paths.
+0.5.0 is a beta release for testing. The milestones for 1.0 (including beta testers using pre-1.0 versions) will be added later.
 
-## 1. What 1.0 is
+Written 2026-11-10. Companion to `mpckit/xpj-format-and-scope.md` (format reference, verified edit recipes, agreed 0.5.0 scope) and the "mpckit main screen mockup" design artifact. Read the format doc first; this plan does not repeat the field paths.
+
+## 1. What 0.5.0 is
 
 A desktop app (Linux, Windows, Mac) for building MPC Sample kits on a computer and exporting them to the device.
 
@@ -13,14 +15,14 @@ A desktop app (Linux, Windows, Mac) for building MPC Sample kits on a computer a
 - Sequences, 128 slots: add, remove, name, BPM, bar length. No note editing.
 - WAV only.
 
-Not in 1.0: start/end trim and waveform, level and pan, loop and trigger modes, envelopes, filters, effects, pad colours, other audio formats, songs, note editing.
+Not in 0.5.0: start/end trim and waveform, level and pan, loop and trigger modes, envelopes, filters, effects, pad colours, other audio formats, songs, note editing.
 
 ## 2. Stack
 
 - Go for all file work.
 - Wails v3 (v3.1.0, the latest stable release when M4 started; it needs Go 1.26) for the window, dialogs, file drop and the Go-to-frontend bridge.
 - Svelte + TypeScript + Vite for the UI (plain Svelte, not SvelteKit).
-- No database, no network access, no settings file in 1.0.
+- No database, no network access, no settings file in 0.5.0.
 
 Wails pieces this plan relies on (confirm against the installed version at setup):
 - Services: Go structs registered with the app; TypeScript bindings generated with `wails3 generate bindings`.
@@ -61,7 +63,7 @@ The device accepts files only if we stay close to what it writes, so the tree mu
 - Parse with `json.Decoder` token by token and `UseNumber()`, building our own tree: objects keep key order (slice of key/value pairs), numbers keep their original text.
 - Write in the device's layout: one key or value per line, no indentation, `": "` after keys, no trailing newline. Do not escape `<`, `>`, `&` or `/`.
 - New numeric values: integer fields as integers (`4`); float fields always with a decimal point (`96.0`, `20.0`, `5.0`, never `96`). Tuning `pitch` is a 32-bit float widened to 64-bit and printed in shortest form (12.4 is written `12.399999618530273`; the device wrote the same float64 with 17 digits, `12.399999618530274`. Both read back identically, so the shortest form is kept.)
-- Float fields written in 1.0: `seq.bpm`, `pad.tempo`, `layer.pitch`, and `metadata.tempo` / `metadata.tune` on new pool entries. Everything else we write is an integer or a string.
+- Float fields written in 0.5.0: `seq.bpm`, `pad.tempo`, `layer.pitch`, and `metadata.tempo` / `metadata.tune` on new pool entries. Everything else we write is an integer or a string.
 - Helpers: get/set by path, deep clone of a subtree, insert and remove array elements.
 
 Why not `map[string]any` or a path-patching library: maps lose key order, and we need to insert whole objects (pool entries, sequences) in the device's layout.
@@ -89,7 +91,7 @@ Every edit is a method that changes the tree using the recipes in the format doc
 | `ClearPad(slot)` | Replaces the pad with the blank pad; drops the pool entry if no pad still uses it. |
 | `CopyPad(from, to)` / `MovePad` / `SwapPads` | Whole-pad copies. Copies share the pool entry (device-tested). |
 | `PadHasNotes(slot)` | True if any sequence has a note or automation event on note 36 + slot. Drives the move warning. |
-| `SetTune(slot, coarse, fine)` | Writes `coarseTune`, `fineTune`, `pitch` on the layer. Clamps to the 1.0 ranges. |
+| `SetTune(slot, coarse, fine)` | Writes `coarseTune`, `fineTune`, `pitch` on the layer. Clamps to the 0.5.0 ranges. |
 | `SetMuteGroup(slot, n)` | `whichMuteGroup`; 0 is off. |
 | `AddSequence(slot)` | Clones the template sequence; name `Sequence NN` (slot + 1); BPM copied from the most recently edited sequence, else 120; 2 bars. |
 | `RemoveSequence(slot)` | Refused when it is the last sequence. If it was `currentSequence`, point that at the lowest remaining slot. |
@@ -109,13 +111,13 @@ Validation at the edge of the model: WAV must be uncompressed (integer PCM or 32
 - Folder: the user picks a destination; write `<Name>.xpj` and `<Name>_[ProjectData]/` with every pool file. Create the data folder even when empty. If either already exists, ask before replacing. Write to a temporary name in the same directory and rename at the end.
 - Zip: the same two entries at the zip root.
 - Before writing, check every source file still exists; report all missing files in one message.
-- There is no separate Save in 1.0: Load reads a project, Export writes one.
+- There is no separate Save in 0.5.0: Load reads a project, Export writes one.
 
 ### 4.5 Playback preview
 
 - Go serves the selected pad's source WAV to the frontend at a local route (the app's asset handler, or the built-in file-server service).
 - The frontend decodes it with Web Audio and plays it through a buffer source with `detune` = coarse x 100 + fine (cents). Pitch and speed change together, as on the device.
-- For loaded pads that already carry a start/end trim, play only that region so the preview matches the device, even though trim is not editable in 1.0.
+- For loaded pads that already carry a start/end trim, play only that region so the preview matches the device, even though trim is not editable in 0.5.0.
 - Mute-group choking: the frontend tracks which pads are sounding and their mute group. Starting a pad first stops every other sounding pad in the same non-zero group, with a 5-10 ms fade so the cut does not click. Group 0 (off) never chokes. The fade length is our choice, not measured from the device.
 - A new click on the same pad restarts it (pads are monophonic on the device).
 - No effects.
@@ -193,7 +195,7 @@ Done when: Device checkpoint B passes.
 
 **M9. Packaging and release.**
 Icons, version stamping, builds for the three systems, README with install notes, licence. The About modal and README state that mpckit is not affiliated with Akai.
-Done when: a tagged 1.0 build runs on each system.
+Done when: a tagged v0.5.0 build runs on each system.
 
 ## 7. Device checkpoints
 
@@ -240,9 +242,9 @@ Conclusions:
 - `sliceInfo.End` is counted in the file's own frames at its own sample rate. (The 22.05 kHz pad settles this: read as 44.1 kHz frames it would have stopped halfway, before the beep.)
 - Accepted so far: 8, 16, 24 and 32-bit integer and 32-bit float; mono and stereo; 22.05, 44.1, 48 and 96 kHz.
 
-1.0 rule for assigning a file: accept any uncompressed WAV (integer PCM or 32-bit float), mono or stereo. Refuse compressed WAVs (ADPCM, MP3-in-WAV), more than two channels, and anything over 20 minutes.
+0.5.0 rule for assigning a file: accept any uncompressed WAV (integer PCM or 32-bit float), mono or stereo. Refuse compressed WAVs (ADPCM, MP3-in-WAV), more than two channels, and anything over 20 minutes.
 
-Not yet tested: sample rates above 96 kHz or below 22.05 kHz; 64-bit float (refused in 1.0). The extensible header that many audio tools write was confirmed in checkpoint A (PCM inside, 16 and 24-bit, mono and stereo); mpckit accepts it when it declares PCM or float inside.
+Not yet tested: sample rates above 96 kHz or below 22.05 kHz; 64-bit float (refused in 0.5.0). The extensible header that many audio tools write was confirmed in checkpoint A (PCM inside, 16 and 24-bit, mono and stereo); mpckit accepts it when it declares PCM or float inside.
 
 **B (after M8), round trips through the app:**
 1. Load `DemoFile` (recorded notes), change nothing, export: sequences still play the same on the device.

+ 22 - 22
docs/xpj-format-and-scope.md

@@ -1,4 +1,4 @@
-# mpckit: MPC Sample project format and 1.0 / 2.0 scope
+# mpckit: MPC Sample project format and 0.5.0 / 2.0 scope
 
 Sources: three projects saved on an MPC Sample, in `testdata/device/` of `bajdev/mpckit` (first committed as `demo-projects/`), analysed 2026-11-10.
 
@@ -15,7 +15,7 @@ Everything below is observed in those files unless marked as an open question. T
 - The `.xpj` reader/writer is its own Go package with no UI code.
 - New projects are built from the blank project as a template; edits change only known fields.
 
-## 1.0 scope (agreed 2026-11-10)
+## 0.5.0 scope (agreed 2026-11-10 as the 1.0 scope; renamed 2026-11-11: 0.5.0 is a beta release for testing)
 
 Intended use: set up new kits on the desktop, then move to the device to create sequences and tracks.
 
@@ -35,7 +35,7 @@ UI (mockup agreed 2026-11-10, "mpckit main screen mockup" artifact):
 - Pads: click to select and play; drag to move, drag onto a filled pad to swap, Alt-drag to copy; drop a WAV to assign.
 
 2.0 or later:
-- Start and end trim, together with a waveform view (moved out of 1.0: not useful without the waveform). The file fields are already verified (section 3).
+- Start and end trim, together with a waveform view (moved out of 0.5.0: not useful without the waveform). The file fields are already verified (section 3).
 - Level and pan (fields not yet confirmed from a device file).
 - Trigger and loop modes, envelopes, filters, effects, pad colours.
 - Sequence editing (notes), including making notes follow a moved pad.
@@ -91,7 +91,7 @@ A pad's settings hold nothing that depends on its slot: all 128 blank pads are i
 - Move: copy, then reset the source slot to the blank pad.
 - Swap: exchange the two `pad[n]` objects.
 
-Recorded notes follow the slot, not the sample (a note stores 36 + n). Moving a pad that has notes in a sequence changes what those notes play. Decided for 1.0: warn before moving a pad that has recorded notes, and leave the notes untouched. Rewriting notes to follow the pad belongs with sequence editing, a later feature.
+Recorded notes follow the slot, not the sample (a note stores 36 + n). Moving a pad that has notes in a sequence changes what those notes play. Decided for 0.5.0: warn before moving a pad that has recorded notes, and leave the notes untouched. Rewriting notes to follow the pad belongs with sequence editing, a later feature.
 
 ### Tune a pad
 Three fields on the layer, written together:
@@ -131,7 +131,7 @@ Copy the blank sequence, set `sequences[].key` to the slot (0-based), then name,
 
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
-| Project name | file name and `_[ProjectData]` folder name | DemoFile | 1.0 |
+| Project name | file name and `_[ProjectData]` folder name | DemoFile | 0.5.0 |
 | Master tempo | `masterTempo`, `masterTempoEnabled` | 128.0, false in all three | 2.0 (sequences carry their own BPM) |
 | Project key | `key` | "C Major" | 2.0 |
 | Info tags | `info.title`, `artist`, `originalArtist`, `album`, `year`, `genre`, `copyright`, `notes` | all empty | 2.0 |
@@ -144,28 +144,28 @@ Copy the blank sequence, set `sequences[].key` to the slot (0-based), then name,
 
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
-| Name | `samples[].name` | "Linn 9000 kick" | 1.0 |
-| File | `samples[].path` | "Linn 9000 kick.wav" | 1.0 |
-| Load mode | `samples[].loadImpl` | 0 for one-shots and the user sample; 1 for three factory loops | 1.0, write 0 |
-| Tempo | `samples[].metadata.tempo` | 0.0 for the user sample; 20-114 for factory | 1.0, write 0.0 |
-| Root note, tune | `samples[].metadata.rootNote`, `tune` | 60, 0.0 | 1.0, defaults |
-| Key | `samples[].metadata.key` | "C Natural Minor" on the user sample | 1.0, default |
+| Name | `samples[].name` | "Linn 9000 kick" | 0.5.0 |
+| File | `samples[].path` | "Linn 9000 kick.wav" | 0.5.0 |
+| Load mode | `samples[].loadImpl` | 0 for one-shots and the user sample; 1 for three factory loops | 0.5.0, write 0 |
+| Tempo | `samples[].metadata.tempo` | 0.0 for the user sample; 20-114 for factory | 0.5.0, write 0.0 |
+| Root note, tune | `samples[].metadata.rootNote`, `tune` | 60, 0.0 | 0.5.0, defaults |
+| Key | `samples[].metadata.key` | "C Natural Minor" on the user sample | 0.5.0, default |
 
 ### Pad: assignment, tuning, trim (verified)
 
 | Property | Path | Proposed |
 |---|---|---|
-| Sample | `layer.sampleName`, `layer.sampleFile` | 1.0 |
-| Slice end | `layer.sliceInfo.End` | 1.0, computed |
-| Pad tempo | `pad[n].tempo` | 1.0, computed |
-| Tune | `layer.coarseTune`, `fineTune`, `pitch` | 1.0 |
+| Sample | `layer.sampleName`, `layer.sampleFile` | 0.5.0 |
+| Slice end | `layer.sliceInfo.End` | 0.5.0, computed |
+| Pad tempo | `pad[n].tempo` | 0.5.0, computed |
+| Tune | `layer.coarseTune`, `fineTune`, `pitch` | 0.5.0 |
 | Start, end | `layer.sliceInfo.Start`, `LoopStart`, `End` | 2.0, with waveform view |
 
 ### Pad: playback
 
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
-| Mute group | `pad[n].whichMuteGroup` | 0 none; 1 on both hats in DemoFile; device offers 0-16 | 1.0 |
+| Mute group | `pad[n].whichMuteGroup` | 0 none; 1 on both hats in DemoFile; device offers 0-16 | 0.5.0 |
 | Level, pan | `pad[n].mixable.volume`, `pan` | 0.7079 (unity), 0.5 centre | 2.0 (not yet seen changed) |
 | Trigger mode | `pad[n].triggerMode` | 0; 3 on the looped pad | 2.0 until mapped |
 | Layer loop | `layer.loopMode`, `sliceInfo.LoopMode` | 0/1 | 2.0 until mapped |
@@ -193,10 +193,10 @@ Copy the blank sequence, set `sequences[].key` to the slot (0-based), then name,
 
 | Property | Path | Seen | Proposed |
 |---|---|---|---|
-| Slot | `sequences[].key` | 0, 1 | 1.0 |
-| Name | `seq.name` | "Intro", "Sequence 02" | 1.0 |
-| BPM | `seq.bpm`, `seq.tempoEnable` | 105, 110, 128; true | 1.0 |
-| Length | `seq.lengthBars` plus the 25 dependent fields | 2, 4 | 1.0 |
+| Slot | `sequences[].key` | 0, 1 | 0.5.0 |
+| Name | `seq.name` | "Intro", "Sequence 02" | 0.5.0 |
+| BPM | `seq.bpm`, `seq.tempoEnable` | 105, 110, 128; true | 0.5.0 |
+| Length | `seq.lengthBars` plus the 25 dependent fields | 2, 4 | 0.5.0 |
 | Time signature | `seq.timeSignatureTrack.timeSignatures[]` | 4/4 | 2.0 |
 | Transpose | `seq.transposition` | 0 | 2.0 |
 | Notes | `...eventList.events[]` type 3: `time`, `note`, `velocity`, `length`, `probability`, `ratchet` | up to 32 notes | preserve |
@@ -247,7 +247,7 @@ Four projects written by the Go writer (`tools/checkpoint-a`, built from commit
 - **Tempo** (`MpckitTempo`): sequences at 20, 30, 300 and 999 BPM all work. The device's tempo control runs from 20 to 999, and mpckit uses the same range.
 - **WAV headers** (`MpckitWav`): 48 kHz 24-bit stereo, 44.1 kHz 16-bit stereo and mono, all with the extensible header, play the same as a plain-header control.
 
-Every item on the original 1.0 list (project name, pad sample assignment, sequence name, BPM, bar length, adding a sequence) plus pad tuning has now been written by these recipes and accepted by the device.
+Every item on the original 0.5.0 list (project name, pad sample assignment, sequence name, BPM, bar length, adding a sequence) plus pad tuning has now been written by these recipes and accepted by the device.
 
 ## 8. Open questions
 
@@ -256,6 +256,6 @@ Every item on the original 1.0 list (project name, pad sample assignment, sequen
 2. Level and pan have not been seen changed; confirm they are written to `pad[n].mixable`.
 3. `triggerMode` values (0 and 3 seen) and how they pair with `loopMode`.
 4. When a sequence with notes is shortened, are notes past the new end kept or removed?
-5. Sample rates outside 22.05-96 kHz and 64-bit float are untested. (The extensible header was confirmed in checkpoint A; other WAV bit depths and sample rates are confirmed; non-WAV formats are out of 1.0.)
+5. Sample rates outside 22.05-96 kHz and 64-bit float are untested. (The extensible header was confirmed in checkpoint A; other WAV bit depths and sample rates are confirmed; non-WAV formats are out of 0.5.0.)
 6. What key and tempo to write for a sample mpckit imports (the device detects them; mpckit cannot yet).
 7. Is `currentSequence` a slot number or an index into `sequences[]`? Every file seen has 0, and slot 0 is stored second, so the files cannot tell. mpckit treats it as a slot (build plan 4.3); checkpoint B4 should remove the current sequence and check which one the device opens.

+ 2 - 2
internal/project/project.go

@@ -1,5 +1,5 @@
 // Package project is the mpckit domain model: one MPC Sample project and
-// every edit 1.0 makes to it. Edits follow the verified recipes in
+// every edit 0.5.0 makes to it. Edits follow the verified recipes in
 // docs/xpj-format-and-scope.md section 3 and change only the fields those
 // recipes name; everything else is written back as read.
 package project
@@ -29,7 +29,7 @@ const (
 	DataSuffix    = "_[ProjectData]"
 )
 
-// Tuning ranges for 1.0 (format doc, open question 1a).
+// Tuning ranges for 0.5.0 (format doc, open question 1a).
 const (
 	MinCoarse, MaxCoarse = -24, 24
 	MinFine, MaxFine     = -90, 90

+ 21 - 0
pull-requests/beta-0-5-0/plan-for-0-5-0-beta.md

@@ -0,0 +1,21 @@
+# Plan for 0.5.0 (beta) instead of 1.0
+
+Branch: `beta-0-5-0` → `main`
+
+## What changed
+
+The current milestones (M1–M9) produce a beta release for testing, 0.5.0, not 1.0. The milestones needed for 1.0, including beta testers using pre-1.0 versions, will be added later.
+
+- `docs/build-plan.md`:
+  - Every "1.0" is now "0.5.0": title, "What 0.5.0 is", "Not in 0.5.0", the design notes, and M9's done-when ("a tagged v0.5.0 build").
+  - A line under the title says 0.5.0 is a beta release for testing and the 1.0 milestones come later.
+- `docs/xpj-format-and-scope.md`: the same scope was labelled "1.0" there and the plan refers to it, so those labels are now "0.5.0" too. The scope heading notes it was agreed on 2026-11-10 as the 1.0 scope and renamed on 2026-11-11.
+- `internal/project/project.go`: two comments ("every edit 1.0 makes", "tuning ranges for 1.0") now say 0.5.0.
+
+Not changed: the format doc's "2.0" labels (features after this release), and the test comment calling start/end trim "a 2.0 feature". They can be revisited when the 1.0 milestones are planned.
+
+Merges cleanly with `m8-load-guard-rails`, which also edits the build plan (checked with a trial merge).
+
+## Testing
+
+Docs and comments only. `go vet ./internal/project` passes.