# M5: Pads tab Branch: `m5-pads-tab` → `main` ## What changed **Go service** (`internal/app`), new methods: - `SetName`, `AssignSampleDialog`, `AssignSamplePath` (used by file drop), `ClearPad`, `SetTune`, `SetMuteGroup`. - Export: `ChooseExportFolder` / `ExportFolder(dir, overwrite)` and `ChooseZipPath` / `ExportZip(path, overwrite)`. - If the target exists, nothing is written and `exists` lists it; the frontend asks, then calls again with overwrite. - A zip path without `.zip` gets it added. - A successful export clears the unsaved-changes flag. - Every edit returns the new state, even when it fails. The frontend refetches the state when a call is rejected. - `internal/app` still imports nothing from Wails, so its tests run anywhere. The native dialogs are behind a small `Dialogs` interface; `dialogs.go` implements it with Wails. **Preview audio** (`internal/app/audio.go`, `frontend/src/lib/audio.ts`), build plan 4.5: - Go serves each pad's source WAV at `/audio/?k=…` through the asset server's middleware. The key changes with the source file, so the preview never plays a stale cached sample. - The frontend decodes with Web Audio. If the webview can't decode a format, it fetches `/audio/?pcm=1`, which Go decodes (the M3 decoder). - Tuning is applied as detune (coarse × 100 + fine cents), so pitch and speed change together as on the device. Only the slice region is played. - Pads are monophonic: a new click restarts the pad. Starting a pad cuts off every sounding pad in the same non-zero mute group with an 8 ms fade. **File drop** (`main.go`): the window accepts files. Pad cells and the empty-pad panel are drop targets carrying `data-slot`, and they highlight while a file is over them. Go assigns the first `.wav` dropped and sends the new state as a `state` event; problems come as an `error` event. Dropping a non-WAV file shows a message. **Frontend:** - Project name is editable (Enter or leaving the field saves; a refused name goes back). - Export zip and Export to folder work, with a confirm dialog before replacing files. - Clicking a pad selects it and plays it. - The pad panel is live: Choose WAV file, Play, Replace, Clear, tuning sliders (they play the pad when let go), mute group (Off, 1–16). - Errors show in a dismissable banner. The footer shows "Unsaved changes" and where the last export went. - Move/Copy stay disabled until M6. Load stays disabled until M8. **`tools/xpjdiff`**: `go run ./tools/xpjdiff A.xpj B.xpj` compares two projects (the .xpj values and the data-folder files) and prints the differing paths, or `identical`. It's for checking this milestone. ## Testing On the server: - `go vet` and `go test ./...` pass (Linux with `-tags purego`, and `GOOS=windows go vet`). - The service tests cover the dialog-cancel path, assign/clear/tune/mute through the service, name validation, folder and zip export with the overwrite round-trip, an export through the service that matches the model byte for byte, and the audio route (WAV bytes, decoded PCM header and peak, 404s, other paths passed through). - `xpjdiff` is tested against T1/T2/T3. The server has no Node, so the frontend is untested here. **On the laptop**, from the repo root (create `C:\temp\m5` first). Use quotes as shown; in PowerShell an unquoted comma splits an argument. 1. `npm run check` in `frontend`: 0 errors, 0 warnings. 2. `wails3 dev`. In the app: - Name the project `UiKit`. - A01: **Choose WAV file** → `testdata\device\DemoFile_[ProjectData]\PadLp-London Cm114 SA.wav`. - A02: **drag** `testdata\device\DemoFile_[ProjectData]\Bass-Pure C.wav` from Explorer onto the A02 cell. - A02: Semitones −12, Cents +50. - A01 and A02: mute group 1. - **Export to folder** → `C:\temp\m5`. 3. The same edits with the CLI: ``` go run ./cmd/mpckit-cli -name CliKit -out C:\temp\m5 -pad "A1=testdata\device\DemoFile_[ProjectData]\PadLp-London Cm114 SA.wav" -pad "A2=testdata\device\DemoFile_[ProjectData]\Bass-Pure C.wav" -tune "A2=-12,50" -mute "A1=1" -mute "A2=1" ``` 4. `go run ./tools/xpjdiff C:\temp\m5\UiKit.xpj C:\temp\m5\CliKit.xpj` prints `identical`. **(M5 done-when, part 1.)** 5. Preview: click A01 (a long pad sound), then A02 while it plays: A01 stops. Set A02's group to Off and repeat: both play together. A02 sounds 11.5 semitones lower than untuned (just under an octave). **(M5 done-when, part 2.)** 6. Export again to the same folder: a dialog asks before replacing; Cancel writes nothing. Export zip saves `UiKit.zip`. 7. Clear A02: the pad empties and the footer drops to 1 sample. Rename to `a/b`: an error shows and the name goes back.