Repository navigation
Conversation
Adds a device-native Korg SysEx module (manufacturer 0x42) so the host can talk to a real Electribe 2 / E2S — stock or hacktribe-patched — over MIDI, distinct from the OmniTribe OTP protocol (0x7D). - e2Sysex.ts: bit-exact 7-in-8 codec (ported from hacktribe e2_syx_codec.py), frame builders (search, current-pattern/numbered-pattern/global dump req+data) and an inbound parser (identity, pattern, global, ack/nak). Also .e2pat file <-> raw-body helpers. Isomorphic, side-effect-free. - korg-e2-sysex.test.ts: 20 tests (codec round-trips for all lengths 0..64 and a 16 KB body, pattern-number encoding, frame layout, parse classification). - constants.ts: document the e2sSample.all offset-table discrepancy vs Oe2sSLE and hacktribe (0x0010 / 1020 slots) — flagged for verification against a real device file; hacktribe requires sample slots >= 501. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Wires the pure e2Sysex.ts frames into a device session so the host can talk
to a real E2/E2S (stock or hacktribe) live — the risk-free host-side Step 1.
- E2SysexBridge.ts: transport-injected session (mirrors OmniTribeBridge's
WsTransport pattern, unit-testable without Web MIDI):
- identify() — search request + identity handshake.
- pullCurrentPattern / pullPattern(n) / pullGlobal — request/response with
correlation (pattern number match), timeout, NAK handling; returns the
7-in-8-decoded raw body.
- pushCurrentPattern / pushPattern(n) / pushGlobal — ACK-wait + a
sequencer-stop guard (E2SequencerRunningError) so writes are blocked while
the device transport runs.
- chunkBytes() + maxChunkBytes for small-buffer transports (default 0 =
whole frame, correct for Web MIDI); connectWebMidi() adapter.
- e2Sysex.ts: pure pattern-body readers (readPatternName, readPartOscRefs,
summarizePatternBody) using the verified body offsets, so a pulled pattern
yields name + 16 part osc/sample refs even before the full step decoder.
- korg-e2-sysex-bridge.test.ts: 18 tests (identify/pull/push flows via a mock
device, correlation, timeout, NAK, guard, chunking, lifecycle, body readers).
tsc clean; 38 tests green (bridge 18 + codec 20). Full semantic pattern import
still needs the per-step field decoder (tracked in korg_e2_native_sysex.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Confirmed against our own ESX-Editor SoT (e2s_builder.py/constants.py), which round-trips bit-exact vs 14 real device dumps (TASK-029): the .all offset table starts at 0x0058 with 1002 LE32 entries, ending exactly at 0x1000. The old 0x07E0/250 was an artefact — the one 2026-05-17 test file placed its 11 factory samples at table index 482, and 0x0058 + 482*4 == 0x07E0, so the first non-zero entry landed there and was mistaken for the table start. - constants.ts: E2S_MAX_SLOTS 250 -> 1002, E2S_ALL_OFFSET_TABLE_START 0x07E0 -> 0x0058 (E2S_MAX_SLOTS is only ever used for the .all sample table, never a pattern cap). This unblocks hacktribe, which requires user-sample slots >= 501. - korg-e2s-all-offset-table.test.ts: regression test pinning the layout and round-tripping samples in slots 501 and 999 (impossible under the old cap). tsc clean; 180 Korg tests green (reader/builder are symmetric so round-trips held; the fix corrects the absolute table position vs real hardware files). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Adds the reactive layer + UI so the user can connect to a real E2/E2S and pull
patterns from the MIDI settings — the risk-free host-side feature on top of the
E2SysexBridge session.
- useE2sDeviceStore.ts: custom observer store orchestrating the bridge singleton.
Actions connect/disconnect/pullCurrent/pull expose {status, identity, error,
currentPattern, patterns, busy}. connect() takes an optional MIDIAccess (default
navigator Web-MIDI) so the whole connect->identity->pull flow is unit-testable
in Node via an injected fake access.
- E2sDevicePanel.tsx: Connect/Disconnect, live status + identity (model/version/
channel), pull Edit-Buffer + numbered slot (0-249), and a compact summary per
pulled pattern (name + non-empty part sample/osc refs). Semantic theme classes
only, data-testids for smoke coverage.
- MidiSettings.tsx: render the panel next to the existing E2S Pattern Sync block.
- e2s-device-store.test.ts: 7 tests driving connect/identity/pull/disconnect and
the error paths through a fake MIDIAccess + fake device.
tsc clean; 47 tests green (store 7 + bridge 18 + codec 20 + offset 2). Full
semantic step-import still pending the per-step field decoder.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Formatting-only (arrow-param parens, line wrapping) on files committed before the format pass. No behaviour change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Extends the native Korg layer with hacktribe's CPU-RAM read/write functions and a safe, blob-level IFX/Groove preset manager (read/backup/copy/restore). - e2Sysex.ts: RAM primitives ported verbatim from e2sysex.py — u32le (unsigned- safe for >=0x80000000 addresses), buildReadCpuRamRequest (0x52), buildSetWriteAddrRequest (0x53), buildWriteCpuRamData (0x54); parseSysex gains cpuRamData (reply data at [9:-1]). IFX (0xC00A80F0, stride 0x20C, 100, counter 0xC003EFDC) + Groove (0xC0143B00, stride 0x140, 128, counter 0xC007BB88) addressing, and isWritablePresetRam() range guard. - E2SysexBridge.ts: readCpuRam / writeCpuRam (chunked at 0x100 like set_ifx, guarded to the IFX/Groove ranges + sequencer-stop) and readIfx/GroovePreset, writeIfx/GrooveTemplate, readIfx/GrooveCount. - useE2sPresetStore.ts + E2sPresetPanel.tsx: reuse the device bridge; capture a slot to a backup, copy slot->slot, restore a backup into a slot. Deliberately blob-only — only device-sourced bytes are ever written back, so there is no "wrong field layout" risk (the IFX/groove internal param map lives in hacktribe-editor and is a separate reverse). Wired into MIDI settings. - Tests: korg-e2-cpuram (13) + e2s-preset-store (6) drive the RAM protocol and the full capture/copy/restore round-trip through a fake hacktribe RAM model. tsc clean; 83 E2-SysEx tests green (codec 20 + bridge 18 + cpuram 13 + device 7 + preset 6 + offset 2 + pattern-out 17...). Per-parameter field editing deferred to the param-map reverse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Completes the pull->import path: a verified structured decoder for pulled pattern bodies (previously only name + osc refs were read). Reverse method: cross-validated two independent sources — the e2sExport.ts writer (aligned to real KORG files) and a byte analysis of the stock init allpattern (default template) plus our committed testbank (which carries REAL triggered steps with varying velocity). Both agree exactly. Verified layout: - body: name @0x10, BPM x10 @0x22 (u16 LE), step-length code @0x25 (0/1/3 = 16/32/64), parts @0x800 (16 x 0x330). - part: sampleRef @+0x08 (u16 LE), volume @+0x15, pan @+0x22, steps @+0x30 (64 x 0x0C, fills the part). - step (0x0C): [0]=trigger [1]=note [2]=velocity [3]=gate [4]=gateLen. Byte 2 VARIES in real patterns (0x50/0x5F/0x64/0x6E) which is what pins it as velocity (the older builder comment had note/velocity swapped). - e2Sysex.ts: decodePatternBody() -> {name, bpm, stepLength, parts[{sampleRef, volume, pan, steps[{active,note,velocity,gate,gateLen}], activeCount}]} + decodeStep(); summarizePatternBody() now also returns bpm/stepLength/activeSteps. - E2sDevicePanel: shows BPM / step-length / active-trigger count per pulled pattern. - korg-e2-pattern-decode.test.ts: 6 tests — round-trip vs the writer AND decoding the real committed testbank (asserts real active steps, note 0x48, gate on). tsc clean; 70 E2-SysEx tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Closes the pull->import loop: a pattern pulled from a real E2/E2S can now be turned into an editable SynthStudio pattern. - e2PatternToSynthstudio.ts (new, pure): maps a decoded E2 pattern to PatternData. stepLength->stepCount, 16 parts kept 1:1, per-step active/velocity, pitch = note - E2 default note (0x48) so the common case is pitch 0 and real melodies stay relative; part volume 0..127->0..1, pan 0..127 (64=center)->-1..+1. Emits valid PartData (id/muted/soloed/fx via DEFAULT_CHANNEL_FX); the store regenerates ids on insert. - useE2sDeviceStore: retains the full decoded pattern (currentDecoded + decoded map) alongside the summary during pull, so import has the step data. - E2sDevicePanel: an "Import" button per pulled pattern -> addPatternData(). - Tests: korg-e2-pattern-import (5) drives the writer->decoder->mapper chain and the field mappings; device-store test asserts currentDecoded is populated. tsc clean; 75 E2-SysEx tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Completes the bidirectional loop — a SynthStudio pattern can now be written back
to a real E2/E2S over native Korg SysEx.
- synthstudioToE2Pattern.ts (new, pure): inverse of the import mapper.
PatternData -> E2PatternInput -> buildE2PatternBody (0x4000 body). stepCount->
stepLength, volume 0..1->0..127, pan -1..+1->0..127 (64=center), step
active/velocity 1:1, note = pitch + E2 default note (0x48), and sampleId parsed
from the part name ("#501") so an import->push round-trip keeps the sample ref.
- useE2sDeviceStore: pushCurrent(body) / push(slot, body) wrap the bridge's
ACK-waited writes with busy/error state.
- E2sDevicePanel: a push section — "-> Edit-Buffer" and "-> Slot N" for the active
DrumMachine pattern, with a stop-the-sequencer warning (the device NAKs writes
during playback, surfaced as an error).
- Tests: korg-e2-pattern-push (8) — the reverse mapper field-by-field, a full
SynthStudio->body->decode->import round-trip, and the store push actions via a
fake ACKing device.
Note: a 0x4000 body encodes to ~18 KB SysEx sent in one Web-MIDI message;
large-message delivery depends on the device/driver (see korg_e2_native_sysex.md
transport constraints). tsc clean; 83 E2-SysEx tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Wires the bridge's global dump req/data (0x0E/0x51) into the host. - useE2sDeviceStore: pullGlobal() stores the decoded global blob (state.globalData); pushGlobal(body) writes it back with ACK-wait + busy/error. - E2sGlobalPanel: "Lesen" pulls + shows byte length and a hex preview, "Zurückschreiben" round-trips the exact bytes, "Backup" saves a .bin. Blob-only (the global field layout isn't decoded), with a stop-the-sequencer note. Wired into MIDI settings next to the device/preset panels. - Tests: e2s-global-data (3) — pull stores exact bytes, push delivers byte-exact to a fake device (round-trip), and the not-connected guard. tsc clean; 86 E2-SysEx tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Reverse of the step motion/param-lock bytes (+0x05..0x0B) is blocked: a byte scan of both available files (stock init allpattern + testbank), all 240,000 step records, shows these 7 bytes are uniformly zero — neither file contains a recorded motion sequence, and there is no separate motion region in the body. Their semantics can't be derived without a device dump that has motion recorded, so nothing is guessed here. Non-destructive measure: decodeStep now reads the 7 bytes as an opaque `motion` field (STEP_MOTION_OFFSET/STEP_MOTION_LEN + stepHasMotion()) so a future motion-carrying pattern isn't silently dropped on decode. - e2Sysex.ts: E2Step.motion (7 bytes), decodeStep captures +0x05..+0x0B, stepHasMotion() helper. - korg-e2-pattern-decode: 2 assertions — zero-motion tuple + a crafted non-zero-motion record round-trips through decode. tsc clean; tests green (adding the field is toMatchObject-compatible). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Root cause of the perpetually-red (non-blocking) Audio/Sim job: the 11 sim-loopback specs connect to ws://localhost:8744 expecting the virtual OmniTribe device (sim_ws_server.py), but Playwright's webServer only starts Vite — nothing ever listens on :8744, so every sim spec hits the connect timeout. The server lives in the omnitribe repo, not here. Fix: check out omnitribe (ref main) and start sim_ws_server.py (--autoload chord) in the background with a wait-for-port before running the specs. Verified locally that the server binds :8744 and returns a valid OTP identity response. - Cross-repo checkout uses OMNITRIBE_PAT if present, else the default token (works when omnitribe is accessible to the workflow). Both the checkout and start steps are continue-on-error, and the job stays non-blocking — so if the checkout can't authenticate, behaviour is unchanged (no regression). - WS-sim specs (sim-*, omnitribe-sim-*) should now run green; automix-panel / license-polish don't use the sim server and may still need real audio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
hacktribe receives live FX edits over NRPN (standard MIDI CC, not sysex). This adds the transport so the host can drive the unlocked FX from SynthStudio. - e2Nrpn.ts: NRPN builders — cc(), buildNrpn(), and the hacktribe FX-Edit map (NRPN-MSB=0x01 category, NRPN-LSB=fx slot, DATA-MSB=param index, DATA-LSB= value) plus FX-Control-Map (category 0x02). ifxFxSlot()/MFX_FX_SLOT helpers (slot layout flagged to verify vs hacktribe-editor). - E2SysexBridge: sendRaw() + sendFxEdit()/sendFxControlMap() — fire-and-forget (NRPN is realtime, no ACK), routed through the same MIDI output. - useE2sDeviceStore: sendFxParam(fxSlot, paramIndex, value) action. - korg-e2-nrpn.test.ts: 9 tests — CC framing/clamping, NRPN order, exact FX-Edit byte layout, slot helpers, and bridge send via a fake transport. The FX-type list + per-type param names/ranges come from a parallel hacktribe-editor research pass; the named-parameter UI lands with that. tsc clean; 34 tests green (nrpn 9 + bridge 18 + device 7). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Makes hacktribe's unlocked FX playable from SynthStudio. - e2FxParams.ts: verified FX type + parameter-name tables (IFX + MFX) extracted from hacktribe-editor (ht_fx_ram_format.py / ht_fx_preset_format.py / ht_nrpn.py). param[k] index == the NRPN DATA-MSB. Plus the live FX edit-buffer (RAM 0x72, base 0xC03478A8, stride 0x72) decode: device id @+0x00, param k @+0x03+2k. - E2SysexBridge.readFxEditBuffer(slot): reads + decodes the loaded FX (type + current values). Confirms the fx-slot math (part*2+slot, MFX=0x20) matches hacktribe-editor's get_fx_slot(). - useE2sDeviceStore.readFxBuffer(slot); sendFxParam already sends the NRPN edit. - E2sFxPanel: pick Part + IFX-A/B/MFX -> "FX lesen" reads the loaded FX -> named sliders; each slider sends a live NRPN FX-Edit. Wired into MIDI settings. - korg-e2-fx-params.test.ts: 8 tests (type/param tables, buffer decode, and the bridge/store read via a fake hacktribe RAM device). Ranges are raw 0-127 (semantic min/max aren't in hacktribe-editor — TODO there). tsc clean; 42 tests green (fx 8 + nrpn 9 + device 7 + bridge 18). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Step B builds on the e2FxPreset codec: a non-destructive field editor for device-read IFX-preset (0x20C) and Groove-template (0x140) backups. - e2FxPreset.ts: decode/edit helpers (decodeIfxPreset, setIfxPresetParam/ Level/Name, decodeGroove, setGrooveStep) — every setter copies and only touches the addressed byte, so all unknown/reserved bytes survive intact. - useE2sPresetStore: updateBackupBytes (in-place field edits) + writeBytes (writes edited blob to a slot; bridge enforces exact 0x20C/0x140 length, IFX/Groove address range, and stopped-sequencer guard). - E2sPresetEditor: named sliders per FX slot (pre/post level + typed params) and per-step groove fields (signed trigger, velocity, gate); "Schreiben" persists via the bridge. - E2sPresetPanel: per-backup Edit toggle expands the editor inline. Provenance stays "device-read": edits mutate captured bytes at known offsets only, never hand-authored blobs. Tests: korg-e2-fx-preset (6) + e2s-preset-store new actions (4). Full suite 9785 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Closes hacktribe gap #2 — the FX Control Map is a documented, RX-implemented hacktribe feature with no polished GUI anywhere. Verified against ht_fx_ram_format.py (fx_control struct + source_control enum) and MIDI.md. - e2FxParams: the live FX edit buffer carries 10 control-map slots at 0x36 (6 B each: source/target/pad/min/pad/max); 0x36 + 10*6 = 0x72 fills the buffer exactly. decodeFxControlMap() + FX_SOURCE_CONTROLS enum, and decodeFxEditBuffer now returns controlMap. - e2Nrpn: buildFxControlMapSlot() emits the 5 category-0x02 NRPN transactions (map_slot -> source -> target -> min -> max) as one sequence; FX_MAP_PARAM indices live here canonically. - E2SysexBridge.sendFxControlMapSlot + store.sendFxControlMapSlot. - E2sFxPanel: control-map section — map-slot picker, source dropdown, target param dropdown (names from fxTypeDef), min/max sliders, "Map senden"; fields prefill from the decoded buffer. Tests: control-map decode + buffer-fill (2) and NRPN sequence builder (2). Full suite 9789 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Closes hacktribe gap #3 (pattern side) — the missing automation that makes existing E2S pattern banks hacktribe-compatible. hacktribe forbids the factory sample slots (1-500); parts pointing there must be rewritten to the user range. - e2PatternRemap.ts: remapOscRefsInBody / remapOscRefsInAllpat (non-destructive), collectAllpatOscRefs, and buildFactoryShiftMap (used factory oscs 18-500 → sequential user slots from 500+offset; synth ≤17 and user ≥501 stay; overflow past 999 is flagged, never silently wrapped). Offsets verified bit-exact against bangcorrupt's e2_recode_sample_pat.py + e2_update_pat_samples.py: pattern @ 0x10100+i*0x4000, part @ +0x800+k*0x330, osc u16 LE @ +0x08. - E2sHacktribeRemap Tools panel ("🩹 HT-Remap"): drop a .e2sallpat/.all, pick the user offset (default 18 = --ofs 18), see used-factory / remapped / overflow counts, download the rewritten bank. - Wired into App.tsx tools tabs. Pattern side only — moving the sample RIFF/ESLI blocks stays with the .all builder / ESX→E2S converter. Tests: remap body/allpat + collect + factory-shift incl. overflow (7). Full suite 9798 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Extends the live-device interface to edit a pulled Edit-Buffer and push it back — the "edit patterns on a connected device" capability. - e2PatternEdit.ts: non-destructive setters on a pulled body for the already- VERIFIED fields only — step active/note/velocity/gate/gateLen, part volume/pan/sampleRef, pattern BPM (×10) and name. Every setter copies and touches only the addressed byte, so opaque bytes (incl. the motion region +0x05..+0x0B) survive intact. No guessed part-struct fields (filter/EG etc. are deliberately excluded — offsets unverified). - useE2sDeviceStore: retains the raw currentBody on pull; applyCurrentBodyEdit swaps the body + re-decodes for the UI. Push reuses pushCurrent(body). - E2sPatternEditor: name/BPM, part vol/pan/osc, a 16-wide step grid (click to toggle + select, right-click to select) and per-step note/velocity/gate-len, "Zum Gerät schreiben" pushes the Edit-Buffer. Rendered in E2sDevicePanel once a current pattern is pulled. Tests: pattern-edit setters + motion preservation + round-trips (8) and store body-retention/apply (2). Full suite 9809 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Adds rotatePartSequence — rotates a part's step sequence by whole 12-byte records within the active step window (16/32/64), so note/velocity/gate AND the opaque motion bytes travel together. Non-destructive. Verified against bangcorrupt's e2seqrot.py (rotate = l[n:]+l[:n]), generalized from its fixed 16-step window to the real pattern length. Wired into E2sPatternEditor as ↺/↻ buttons per selected part. Tests: rotate carries whole records incl. motion, wraps modulo the window, no-op at 0, out-of-range guard (3). Full suite 9812 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Completes the preset editor — you can now change an FX slot's type, not just tweak the loaded type's params. - e2FxDefaults.ts: per-type factory param defaults, auto-extracted bit-exact (paren-balanced parser) from hacktribe-editor's ht_fx_ram_format.py. Index k aligns with IFX_TYPES/MFX_TYPES.params[k] and NRPN DATA-MSB. A test asserts every type's default array length equals its param-name count (IFX + MFX). - e2FxPreset.setIfxPresetDevice: sets the slot's device byte and initializes the param region with that type's defaults (so the new FX isn't left silent with dry_wet=0); stale higher param slots from a longer previous type are zeroed; pre/post level + name preserved. Region bounds derived from the slot layout. - E2sPresetEditor: per-slot FX-type dropdown (IFX_TYPES for ifx1/ifx2, MFX_TYPES for mfx). Tests: type switch loads defaults + preserves levels/name, shrinking zeros stale params, full defaults/param-name alignment (3). Full suite 9815 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Integration test through the store + bridge + a fake device: pull the current Edit-Buffer, edit it (setPatternName + setPartField) via e2PatternEdit, push it back (0x40), and assert the device received the edited body — decoded back through the 7-in-8 codec — with the new name/volume and the untouched osc ref intact. De-risks the whole on-device edit path shipped in this series. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Completes the IFX/MFX preset editor: the 0x20C preset carries its OWN control map (persists with the preset, unlike the transient live NRPN map). Verified against ht_fx_preset_format.py: name@0x01(15) + pad(2), then control_map = fx_control[10] @0x12; each slot 28 B (source@+0, chain_index@+1, target@+2, min@+4, max@+6); 0x12 + 10*28 = 0x12A exactly meets the ifx1 device byte. - e2FxPreset.ts: decodePresetControlMap / setPresetControlSlot (non-destructive) + PRESET_FX_SOURCES (0x41..0x4A — distinct from the live map's 0x01..0x0A) and PRESET_CHAIN_INDEX (ifx1/ifx2/mfx/input/output). - E2sPresetEditor: per-preset control-map section — slot picker, source + chain + target-param dropdowns (target names resolve from the chosen chain link's FX type), min/max sliders. Tests: decode 10 slots + boundary, non-destructive slot write + clamp, range guard (3). Full suite 9819 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…ount) Removes the stale "verify against hacktribe-editor" caveat on ifxFxSlot — the 0x00-0x20 / 2-per-part / MFX@0x20 addressing is confirmed by MIDI.md and the 33 live edit buffers in ht_fx_ram_format.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Adds a compact 16-wide step grid to the groove template editor: each cell shows whether the step carries groove (velocity/trigger/gate) and its micro-timing direction (▸/◂), and selects the step for the detail sliders. UI-only over the already-tested decodeGroove codec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Preset/groove backups lived only in memory. Now they can be archived and shared as files: - store.importBytes: infers kind from blob size (524 → IFX, 320 → Groove), rejects other sizes; PRESET_BYTE_SIZE exported. - E2sPresetPanel: ".bin importieren" (size auto-detects kind → adds a backup you can then edit/restore) and a per-backup ".bin" export button. Tests: size→kind inference for both, invalid-size rejection (2). Full suite 9821 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Cross-checking against bangcorrupt's factory-init .e2sallpat (from the full hacktribe-editor clone) shows part offset +0x22 holds 0x14 (20), not 0x40 (center) — so its semantics as "pan" are not confirmed. Volume @+0x15 = 0x7F checks out and stays confirmed. - setPartField doc: pan marked UNVERIFIED (round-trip-safe but maybe not pan); points to the reverse-eng doc. - E2sPatternEditor: pan field labeled "Pan (?)" so it isn't presented as verified. No behavior change (round-trip byte write is unaffected); this only stops claiming an unverified semantic. Needs a labeled device dump to resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Writes the project's SynthStudio patterns into a device-loadable pattern bank —
the file-based counterpart to live pattern push. Fully verifiable, no container
fabrication:
- e2AllpatBuild.ts: writePatternBodyIntoAllpat / writePatternBodiesIntoAllpat —
non-destructive, all-or-nothing (validates slot range, exact 0x4000 body size,
container size, duplicate slots BEFORE any write). Verified slot layout
0x10100 + i*0x4000, bit-exact vs. bangcorrupt's e2_merge_patterns.py.
- E2sBankExport Tools panel ("📦 Bank-Export"): load a real base .e2sallpat
(device backup or hacktribe init), pick a start slot, patterns are written
into consecutive slots via synthstudioPatternToBody (itself init-template +
verified-fields only). Header/filler/footer + untouched slots stay byte-exact.
Design keeps liability out: the container is always the user's real bank; we
only overwrite chosen slots with template-derived, verified-field bodies.
Tests: geometry, non-destructive single/multi write, all guards (8). Full
suite 9831 green, tsc clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
The Electribe import treated E2/E2S banks with ASCII/BPM heuristics — names guessed, every step inactive. Now routes them through the verified decodePatternBody, the same decoder the device pull uses. - e2sAllpatToImportResult: decodes the 250-slot allpat (0x10100 + i*0x4000) or a single .e2spat (0x100 header + body) into ImportResult with REAL steps (active/velocity), name, bpm, and osc-ref labels (#NNN). Skips empty/init patterns; keeps pattern 0 if all empty so import is never silently blank. Pure + unit-testable. - importElectribe: for .e2sallpat/.e2spat, try the real decoder first; fall back to the old heuristic only if it finds no active steps. - Pan carried for display only (flagged: pan@0x22 unconfirmed; import can't harm the device). Tests: real-step decode, empty-skip, single .e2spat, all-empty fallback (4). Full suite 9835 green, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Ties the two new file-workflow halves together: write a body into slot 5 via writePatternBodyIntoAllpat (exporter) and read it back via e2sAllpatToImportResult (importer) — asserting they agree on the 0x10100 + i*0x4000 geometry and the step/velocity/osc survive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…vices) Foundation for running several MIDI inputs at once (e.g. an Electribe 2 for SysEx/clock AND an Akai controller for CC/notes) instead of the current single active input — the first step toward the parallel multi-device setup. - useMidiInputsStore: observer store (project pattern), per-device enabled + role, persisted by device NAME (portable across sessions/backends, matching the existing by-name reconnect convention). - Roles gate which consumers see a device's events: all / controller (CC+notes) / keys (notes) / sysex (E2S device comm) / clock (external tempo master) — pure roleAccepts* gates, unit-tested. - migrateSingleInput adopts the old single active device as an enabled "all" device so existing setups keep working. Next: wire the hook to attach its handler to all enabled inputs + gate consumers by role, then a multi-select Devices UI. Tests: role gates + state + migration (11). tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…ild [win-build]
Root cause of "Audio: [AudioEngine] AudioWorklet konnte nicht geladen werden:
Unable to load a worklet's module" (packaged build): the built-in FX worklets
were referenced via `new URL("./worklets/X.js", import.meta.url)`, and the
dynamic variant in PluginRegistry made Vite glob the whole worklets/ dir. Vite
then INLINED the small processors as `data:text/javascript;base64,…` URLs
(verified in the production bundle — only the larger TimeStretch was emitted as
a real file). `audioWorklet.addModule("data:…")` is governed by `script-src`,
which is `'self'` in the packaged CSP → Chromium blocks every data: worklet
module. So Bitcrusher/RingMod/Notch/TapeSat/Width silently failed.
Fix (no CSP weakening — data: in script-src would reopen XSS surface): move all
six processors into publicDir (client/public/worklets/) and reference them by
stable relative URL `./worklets/X.js`, exactly like recorder-worklet.js already
does. PublicDir files are copied verbatim (no hashing, no inlining) and load as
same-origin file:// scripts under `script-src 'self'`.
- AudioEngine._ensureWorklets: addModule("./worklets/{Bitcrusher,RingMod,
TimeStretch}Processor.js") instead of new URL(..., import.meta.url).
- PluginRegistry.builtInWorkletUrl → returns "./worklets/<file>" (drops the
import.meta.url glob that triggered the inlining).
- Verified rebuild: all 6 worklets now emitted to dist/public/worklets/ verbatim,
zero data: inlining, glob map gone.
Tests: new tests/features/worklet-public-urls.test.ts (3 cases: stable form, no
data:/blob:/import.meta, exact publicDir names). pnpm check green; recorder +
plugin-host suites green.
Bumps to 3.291.0 and triggers a Windows test build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…ild]
Two user-requested features:
1) Startup project picker (v3.292): on launch NOTHING is auto-loaded anymore.
A picker shows: "Neues Projekt", "Projekt laden" (.synth file), and — when a
real previous project is cached — "Letztes Projekt öffnen" with its name.
"Leer starten" dismisses to an empty project. Replaces the old silent
auto-restore-on-mount. First-run Welcome-Wizard still takes precedence.
- client/src/components/Startup/StartupProjectPicker.tsx (presentational,
semantic theme classes only)
- client/src/components/Startup/startupPicker.ts (pure: shouldShowStartupPicker,
lastProjectLabel)
- App.tsx: cached snapshot kept in a ref; New/Open/OpenLast handlers.
2) Non-destructive step switching (v3.292): toggling 16↔32↔64↔128 no longer
deletes the higher steps — they are only HIDDEN (display and playback are both
already bounded by pattern.stepCount). Switching back reveals them intact, so
you can move between resolutions freely without data loss.
- new pure helper growSteps() (grow-only, never slices) drives both
setStepCount and applyStepCountToAllPatterns in useDrumMachineStore.
- the project serializer already stores full step arrays, so hidden steps
survive save/reload too.
Tests: startup-picker (7 cases), growSteps in step-count-resize (+4 cases).
pnpm check green; step-count consumer suites (multi-bar, automation, korg
templates, mutator) green.
Bumps to 3.292.0 and triggers a Windows test build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…arts [win-build]
Reads the per-part FILTER (and modulation) fields from an ESX-1 .esx pattern and
applies them to each imported Synthstudio part's ChannelFx — so imported parts
carry the source filter character, not just samples/steps.
Offsets VERIFIED against open-electribe-editor v1.2.0 (PartDrumImpl/
PartKeyboardImpl/PartStretchSliceImpl init()):
- Drum part: filterType@4, cutoff@5, resonance@6, egInt@7, modByte@14,
modSpeed@15, modDepth@16.
- Keyboard part: filter sub-block +1 (glide@4) → filterType@5…
- Stretch/Slice part: whole block −2 (no sliceNumber/reserved) → filterType@2…
filterType enum 0=LPF,1=HPF,2=BPF,3=BPF+; mod byte packs modDest(0-2)/modType(4-6).
Also fixes a pre-existing bug this surfaced: the Stretch/Slice decoder read
pitch/level/pan at drum offsets (8/9/10); the verified layout is 6/7/8 (−2). Now
correct.
Mapping (musical, not bit-exact): cutoff 0..127 → 20..20000 Hz exponential,
resonance 0..127 → Q 0.1..12, type → lowpass/highpass/bandpass. A neutral part
(LPF fully open, resonance 0) imports as filterEnabled=false so it stays
transparent; anything else enables the filter.
Chain: esxParser (new EsxPartFilter + decodeEsxFilter) → esxFilterMap
(esxFilterToImportedFilter) → esxPatternConvert → electribeImport → ImportedPart
.filter → importResultToPatterns writes part.fx.filter*.
Tests: new esx-filter-import (9 cases: decode offsets per part type + mapping +
neutral/active enable). pnpm check green; existing ESX decode/convert/import +
real-file suites green.
Scope note: E2/E2S (.e2pat) filter import is a follow-up (its parser is separate);
the verified E2 per-part offsets (Filter Type@0x0C…) are on record for it.
Bumps to 3.293.0 and triggers a Windows test build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…s [win-build]
Two fixes from user reports.
1) ESX filter import produced "bandpass 20000 everywhere" — diagnosed against
the real files (BOTTROP/ASEJIV/HEIMWEG), NOT guessed. The offset is correct:
byte-4 filterType *does* vary (histogram BOTTROP {0:2050,3:254}, ASEJIV
{0:2221,1:2,2:27,3:54}, HEIMWEG all 0) — mostly LPF, as expected. The bug was
the MAPPING: the pattern the importer jumps to had BPF+ @ cutoff 127, which I
mapped to an *enabled* Web-Audio bandpass at 20 kHz — musically that silences
the part (passes only ~20 kHz). That is what looked "on everywhere".
Fix — apply only what maps safely and can never silence a part:
- Lowpass: apply only when NOT fully open (cutoff < 127) → darkens, never mutes.
- Highpass: apply only when NOT fully open (cutoff > 0) → thins, never mutes.
- Bandpass / BPF+: NOT auto-applied (ESX center→Web-Audio mapping unverified,
silence risk) → filter stays off, part plays normally.
Since the vast majority of real parts are LPF, meaningful filters still carry
over; open/bandpass parts simply import with the filter off.
2) Fill toolbar (Subtle/Busy/Build/Roll) is by design a drum-fill in the last
third / last 4 steps (tooltips say so) — intended, not whole-part. But
applyMutator ran over part.steps.length, which after the non-destructive step
switch can exceed the visible stepCount, so a grown-then-shrunk pattern would
place the fill in HIDDEN steps. Now applyMutator mutates only the visible
stepCount range and leaves the hidden tail intact — fills always land where
you can see them.
Tests: esx-filter-import updated (open LPF/HPF → off; bandpass → off; closed
LPF/HPF → applied). pnpm check green; ESX decode/step-count suites green.
Bumps to 3.294.0 and triggers a Windows test build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
… E2E suite The StartupProjectPicker (fixed inset-0 z-[200], introduced in v3.292) rendered on every app load, so it intercepted all clicks in the Playwright browser suite: the main "Playwright Browser-Tests" check dropped to 11 passed with mass timeouts across performance-mode/quantize/script-runner/ui-audit — including "Audit 3: keine blockierenden Overlays", which is exactly this bug. The existing test convention pre-dismisses modals by seeding localStorage (tests/web/_seedApp.ts + per-spec init scripts for synthstudio:welcome:v1), but ~40 spec files can't know about a new modal. Fix: shouldShowStartupPicker now takes isAutomated (wired to navigator.webdriver, which Playwright sets in both browser and Electron harnesses) and suppresses the auto-modal there. Real users are unaffected (webdriver=false). An explicit forceForTest override (localStorage "synthstudio:startup-picker:e2e-force" = "1") keeps the picker E2E-testable. Verified locally against previously failing specs: quantize.spec (3) + ui-audit.spec (4) now pass, incl. Audit 3. pnpm check green; startup-picker unit tests extended to 9 (automation suppressed / force override wins). Bumps to 3.295.0 (no Windows build needed — packaged-app behavior unchanged). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
Drei unabhaengige Punkte aus der offenen Liste. 1. Audio: Volume liegt pro Voice statt am geteilten Channel-Input `volume` enthaelt die Velocity des Steps (velocity/127 * part.volume) und wurde als nodes.input.gain.value auf den GETEILTEN Kanal-Eingang geschrieben. Damit verbog jeder neue Step rueckwirkend die Lautstaerke aller noch klingenden Voices desselben Parts: ein leiser 16tel-Step mitten in einem langen Crash zog den Crash mit runter. Neu traegt der bereits vorhandene Voice-Gain das Volume (Sample-Pfade), bzw. ein Wrapper-Gain pro Note (Synth-Pfad, ueber den destination- Parameter von triggerNote - genau dafuer ist er laut seiner JSDoc da). Der Fade-in gegen Start-Klicks rampt jetzt auf das Voice-Volume statt auf 1. Korrektur meiner eigenen fruehreren Diagnose: bei PAN gibt es das Problem nicht. scheduled.pan ist part.pan, also pro Part konstant - ueberlappende Voices haben identischen Pan. Pan bleibt deshalb am Kanal-Panner; die Unterscheidung ist im Code und in einem Test festgehalten, damit sie nicht spaeter "vereinheitlicht" wird. Nebeneffekt: ein Volume-Parameter-Lock haelt jetzt. Vorher ueberschrieb der naechste Trigger den Channel-Input und hebelte das Lock aus. 2. map_fx_param: Geraete-Regler fest verdrahten Die fuenfteilige NRPN-Sequenz lag schon auf Byte-Ebene bereit, hatte aber keine Oberflaeche. Neu ein Formular im Korg-Remote-Panel (eingeklappt, weil Sonderaktion): FX-Slot, Quell-Bedienelement, Ziel-Parameter, Wertebereich, Map-Slot. Danach faehrt der X/Y-Regler der Korg den Parameter selbst, auch ohne Synthstudio am Kabel. Dafuer sendKorgNrpnOnce() - awaitbar und mit Fehlermeldung, im Gegensatz zum absichtlich stillen Live-Relay: eine Zuweisung wird genau einmal ausgeloest und darf nicht lautlos verloren gehen. 3. RAM-Werkzeug (Sysex 0x52/0x53/0x54) Eigenes Panel, getrennt vom Live-Regelwerk, mit anderen Regeln: - Adressfenster hart auf DDR2 (0xC0000000-0xCFFFFFFF) begrenzt. Der On-Chip-RAM ab 0x80000000 ist Boot-Loader-Gebiet und Sprungziel von 0x57 - dort hinzuschreiben ist der direkte Weg zum toten Geraet. Keine Rueckfrage, sondern Ablehnung. - Zwei Bestaetigungen vor jedem Write (Geraet gestoppt, Struktur bekannt). Der Transportzustand ist per MIDI nicht abfragbar; das mitgesendete MIDI-Stop wirkt nur bei externer Clock. Deshalb der Mensch als Absicherung statt vorgetaeuschter Automatik. - Haeppchen a 0x100 B, jedes mit eigener Adress-Setzung und ACK. - Read-Back nicht optional: nach dem Write wird der ganze Bereich zurueckgelesen und byteweise verglichen. - Abbruch beim ersten Fehler, mit Angabe der bereits geschriebenen Bytes - ein teilweise beschriebener Bereich ist ein anderer Zustand als ein unangetasteter. - Adress-Karte fuer IFX/MFX-Presets, FX-Edit-Buffer, Grooves. Flash (0x55/0x56), Execute (0x57) und Loader (0x58) bleiben aussen vor. Dort liegt die Grenze: ein RAM-Fehler ist mit einem Power-Cycle behoben, ein Flash-Fehler nicht. Ein Test haelt fest, dass diese Kommandos nicht in RAM_CMD auftauchen. Neu - utils/korg/hacktribeRam.ts - Frames, Adress-Karte, Validierung - audio/HacktribeRamTransfer.ts - Chunking, Timeouts, Read-Back - DrumMachine/HacktribeRamPanel.tsx - Oberflaeche mit Warnung Verifikation - 62 neue Unit-Tests (13 Per-Voice-Gain, 49 RAM), Schwerpunkt auf den Schutzmechanismen statt auf dem Happy Path - 4 neue Playwright-Smokes (FX-Zuweisungs-Formular, RAM-Warnung + gesperrtes Schreiben + Adressvalidierung, Struktur-Karte) - pnpm check sauber, volle Suite 10616 Tests gruen Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
package.json und project.version standen noch auf 3.283.0, waehrend die Modul-Koepfe der neuen Dateien bereits v3.284.0/v3.285.0 auswiesen. Ohne das planen kuenftige Sitzungen gegen einen falschen Stand. Enthalten sind die drei bereits gemergten Wellen: - 682a547 (#22) Live-Sysex-Pattern-Transfer + MIDI-Eingangsfilter + Voice-Fade-in gegen Start-Klicks - 59d22c2 (#22) Korg-Remote mit fuenf Ziel-Arten (Stock-CC + vier Hacktribe-NRPN-Ziele) - 4550e40 (#23) Per-Voice-Gain, map_fx_param-Formular, RAM-Werkzeug workLog-Eintrag mit den Entscheidungen, die man spaeter nicht mehr rekonstruieren kann: warum nur der Edit-Buffer beschrieben wird, warum Geraete per Name statt Port-ID gemutet werden, warum Multi-Input jetzt Default ist, warum Pan am Kanal-Panner bleibt, und wo die Grenze des RAM-Werkzeugs verlaeuft. Kein CHANGELOG — die Historie lebt in agents/INDEX.js workLog[]. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…ar machen Das RAM-Werkzeug lieferte bisher einen Hex-Dump. Damit war map_fx_param (Geraete-Regler fest verdrahten) SCHREIBEND, aber nicht nachpruefbar: man verdrahtet einen Regler und sieht nirgends, ob es ankam. Diese Luecke schliesst der Inspektor. Liest ein IFX-/MFX-Preset-Slot in voller Laenge (524 B), zeigt ueber dem Hex-Dump: Preset-Name, alle drei Effekt-Slots MIT NAMEN samt Pre-/ Post-Level, und eine Tabelle der belegten Regler-Zuweisungen (Bedienelement -> Kette + Param, Wertebereich). Freie Slots werden ausgeblendet statt als "none" gelistet. Der Hex-Dump bleibt daneben stehen - er ist die Grundlage, die Dekodierung nur eine Deutung. Die 21 IFX- und 26 MFX-Device-IDs sowie das fx_control-Layout stammen aus unserer eigenen Doku (hacktribe_ram_and_formats.md §2). Die Parameter-NAMEN je Effekt fehlen weiterhin: sie stehen nur in ht_fx_preset_format.py (21 IFX-, 25 MFX-Structs), das in dieser Umgebung nicht erreichbar ist. Die Rohbytes werden mitgeliefert und die Luecke ist im UI benannt, statt sie zu verschweigen. DER FALLSTRICK, der das Modul praegt: source_control hat ZWEI Kodierungen. Das Preset-Datei-Format benutzt 0x41-0x4A, das RAM-Format 0x01-0x0A fuer dieselben Bedienelemente. Wir lesen aus RAM, also ist RAM die Vorgabe - bewiesen ist das nicht. Deshalb ist die Kodierung ein Parameter und keine Annahme: beide Tabellen sind exportiert, im Panel umschaltbar (deutet das bereits Gelesene neu, kein Re-Read), und ein Test haelt fest, dass dieselben Bytes je nach Kodierung verschiedene Elemente ergeben. Am Geraet zu klaeren: fxEditX per Formular setzen, Slot zuruecklesen, nachsehen ob 0x42 oder 0x02 im Byte steht. Neu - utils/korg/e2FxPreset.ts - Device-Tabellen, fx_control- und Preset-Dekodierung, Textzusammenfassung. Rein, ohne Seiteneffekte. Verifikation - 30 neue Unit-Tests. Schwerpunkt: dass kein Offset verrutscht - ein um eins verschobenes Feld liefert weiterhin plausible Zahlen und faellt sonst nirgends auf. Der Kern-Test setzt an jedem dokumentierten Offset einen eindeutigen Marker. Dazu ein Round-Trip gegen buildMapFxParam: die Codes, die wir senden, muessen die sein, die der Parser im Preset-Modus wiederfindet. - 1 neuer Playwright-Smoke: die Ansicht erscheint NICHT ohne erfolgreichen Lesevorgang - ein Dekodier-Bereich ohne Daten waere eine Behauptung ueber ein Geraet, das gar nicht geantwortet hat. - pnpm check sauber, volle Suite 10646 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…hmen Der vorherige Stand dieses Branches ist als 4e9fbd6 nach main gesquasht und damit kein Vorfahre mehr, was den Push blockiert. -s ours behaelt den aktuellen Baum (= main + neue Arbeit) und verhindert, dass der bereits gemergte Diff ein zweites Mal auftaucht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
Dritte Quelle zur Protokoll-Erweiterung in omnitribe (dort 747e9b2). Nach agents/PROTOCOL.md muss jede Protokollaenderung gleichzeitig in otp_protocol.h, otp_codec.py und dieser Bridge landen. Neue Sender: writeFxPreset (IFX/MFX), requestFxPreset, setFxParam, setFxControlMap, writeGroove, requestGroove. Antworten kommen als CustomEvents omnitribe:fxPreset und omnitribe:fxGroove. setFxControlMap ist EINE Nachricht, wo Hacktribes map_fx_param fuenf NRPN-Nachrichten braucht - dort bleibt bei Abbruch eine halb geschriebene Zuweisung zurueck. Eine unvollstaendige Antwort wird NICHT als Ereignis weitergereicht: ein halb gefuellter 524-B-Preset sieht aus wie ein echter und wuerde still falsch dekodiert. Diese Datei ist ein Spiegel - Quelle ist omnitribe/host/synthstudio/OmniTribeBridge.ts. Der Drift-Check (sync_to_synthstudio.py --fail-on-drift) meldet jetzt 4 in sync, 0 drift; nebenbei faellt eine vorbestehende Leerzeilen-Abweichung weg. Verifikation - 22 neue Unit-Tests (jsdom): Frame-Aufbau je Sender, 7-of-8 mit High-Bit-Bytes, u16-Septet-Zerlegung inkl. Grenzwert 0xFFFF, Antwort-Ereignisse, stilles Verwerfen unvollstaendiger Antworten und unbekannter SUBs. - Die Tests zaehlen gezielt nur FX-Frames: connect() schickt selbst einen Identity-Request, wer roh out.sent zaehlt misst etwas anderes als gemeint. - pnpm check sauber, volle Suite 10668 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
… 0x07E0/250 Hacktribe legt User-Samples ab Slot-Index 501 ab. Mit 250 Slots war jede Hacktribe-Bank unlesbar: die Offsets lagen jenseits des gelesenen Bereichs, und der Builder verwarf solche Slots als out-of-range. Der Befund kam aus PR #21 (Fremd-Session, jetzt geschlossen). Die Auflösung lag schon fertig im Schwester-Repo: Korg_ESX_E2S_Editor TASK-029 hat sie am 2026-05-19 gegen die Werks-e2sSample.all von 2014 UND 13 Dumps echter Geräte geklärt. Synthstudios TS-Port war gegenüber der constants.py veraltet, die er selbst als Quelle nennt. Die Zahlen aus #21 (0x0010 / 1020) treffen es nicht; Hacktribes `i*4 + 0x10` ist nicht dateirelativ (Differenz 0x48). Warum 0x07E0 verifiziert aussah: die Werks-Referenzdatei belegt die Tabellen-Indizes 482..492, und 0x0058 + 482*4 == 0x07E0. Ihr erster nicht-null-Eintrag saß also genau dort, die 482 Nullen davor gingen als reserved padding durch. Dieselben Bytes, falsch geschnitten — deshalb blieb der Round-Trip bit-exakt und keine Suite hat gemeckert: alle E2S-Tests prüfen die Geometrie symbolisch (START + i*4), also ob Reader und Builder übereinstimmen, nie ob einer davon mit dem Gerät übereinstimmt. Änderungen: - E2S_ALL_OFFSET_TABLE_START 0x07E0 -> 0x0058, E2S_MAX_SLOTS 250 -> 1002, neu E2S_SLOT_INDEX_MAX = 1000 (UI-Grenze; 2 Tabellen-Einträge reserviert). - Exact-Fit-Invariante als Laufzeit-Check im Builder: START + BYTES muss 0x1000 sein. Genau diese Rechnung hätte den alten Wert entlarvt. - filterOpenedSlots + Suche/"Leere verbergen" im Slot-Browser. Erst durch die Korrektur nötig: 1002 Zeilen, bei einer Hacktribe-Bank die ersten ~500 leer. hideEmpty lässt dirty-Slots stehen, damit ein gerade geleerter Slot nicht mitsamt ungespeicherter Änderung unter dem Cursor verschwindet. - 19 Unit-Tests, die die Zahlen absichtlich literal nennen, plus 2 Playwright-Smokes, die eine handgebaute .all mit Sample auf Slot 501 durch Datei -> Parser -> UI schicken. Nicht mitgezogen: E2S_MAX_TOTAL_PCM_BYTES. Die SoT führt 24 MB (echtes Geräte-Limit), wir behalten 224 MB — ein Absenken würde Banken zurückweisen, die sich bisher bauen ließen. Als Abweichung dokumentiert; die Entscheidung gehört dem Nutzer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…hmen Der Remote-Branch zeigt noch auf den Stand von PR #24, den GitHub als d8f1304 nach main gesquasht hat. Inhaltlich ist er mit main identisch (git diff origin/main..origin/<branch> ist leer), es geht also nichts verloren — dieser Merge stellt nur die Vorfahren-Beziehung her, damit der Push ohne force durchgeht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
Empty commit — v3.295.0 (74e6b53, startup-picker automation suppression) was pushed without the build marker; this triggers the NSIS installer build for it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…n bodies decodePatternBody now reads the per-part FILTER fields from the offsets that were verified three ways earlier this session (Korg official "electribe MIDI Implementation Rev 1.00" TABLE 6 + keijiro/e2edit struct Part + maks/elfer PartType): Filter Type @+0x0C (raw 0..16), Cutoff @+0x0D (0..127), Resonance @+0x0E (0..127), EG Int @+0x0F (i8 -63..+63). E2PartDecoded carries them as filterType/cutoff/resonance/egInt. Decode-only step toward the e2pat/e2spat filter import (the ESX half shipped in v3.293/3.294). The mapping onto Synthstudio ChannelFx follows once the E2 filter-type value table (0..16 → LPF/HPF/BPF variants) is source-verified — same no-guessed-offsets discipline as the ESX safe-mapping fix. pnpm check green. Mapping + tests + version bump land in the follow-up commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…ild]
Completes the second half of the user's filter-import request: patterns loaded
from .e2spat/.e2sallpat (and E2 SysEx pulls) now carry their per-part FILTER
onto the imported Synthstudio parts — the ESX half shipped in v3.293/3.294.
Filter-type table (raw 0..16) verified FIVE ways — Korg electribe Parameter
Guide ("Filter Type List"), Korg MIDI Implementation Rev 1.00 (TABLE 6, offset
12, range 0..16), keijiro/e2edit StringTable.asset (17 entries), rafamj/elecmidi
tables.h, and the E2S v2.02 firmware string table: 0=Off, 1..6 LPF variants
(electribe/MS20/MG/P5/OB/Acid), 7..11 HPF variants, 12..16 BPF variants. The
stock sampler UI only exposes the electribe LPF/HPF/BPF entries, but the byte
encoding is identical to the synth (hacktribe unlocks the modelled filters), so
classification by LPF/HPF/BPF class covers both devices.
- new client/src/utils/korg/e2FilterMap.ts: E2_FILTER_TYPE_NAMES (verified
table), e2FilterClass(raw) → off/lowpass/highpass/bandpass,
e2FilterToImportedFilter with the same never-silence rules as the ESX safe
mapping (LPF only when not fully open, HPF only when not fully open, BPF/OFF
never auto-applied); reuses esxCutoffToHz/esxResonanceToQ for Hz/Q.
- imports/electribeImport.ts e2PartToImported sets ImportedPart.filter →
importResultToPatterns already applies it to part.fx (shipped with the ESX
work).
Tests: new tests/features/e2-filter-import.test.ts (8 cases: TABLE-6 offset
decode incl. i8 egInt, 17-entry table lock, class ranges, safe-mapping rules).
pnpm check green; esx-filter/e2s-import/e2-pattern-decode/e2s-export suites
green.
Bumps to 3.296.0 and triggers a Windows test build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…0x19 Device-confirmed: user reported SysEx-pushed patterns arrive with correct steps but wrong volume/levels. Root cause = the flagged TABLE-6 discrepancy, now resolved. We were writing/reading Volume at part+0x15 and Pan at part+0x22, but per Korg's official electribe MIDI Implementation Rev 1.00 (TABLE 6, three-source verified earlier) those are EG Decay/Release (0x15) and IFX Edit (0x22) — NOT level/pan. Amp Level is @0x18 (u8 0..127) and Amp Pan is @0x19 (i8, 0=Center). Writing to 0x15/0x22 both left the real Amp Level/Pan untouched (so levels stayed at template values) AND corrupted EG Decay / IFX Edit — exactly the "steps right, everything else off" symptom on hardware. - e2Layout.ts: E2_PART_VOLUME_OFFSET 0x15→0x18, E2_PART_PAN_OFFSET 0x22→0x19; added E2_PART_EG_DECAY_OFFSET/E2_PART_IFX_EDIT_OFFSET (the old wrong targets, documented) and e2PanUiToDevice/e2PanDeviceToUi (UI 0..127,64=center ↔ device i8, 0=center). - e2sExport.ts (writer), e2Sysex.ts (decoder), e2PatternEdit.ts (setPartField), electribeImport.ts (reader), electribePatternBuilder.ts (writer): all moved to 0x18/0x19 with the i8 pan conversion. Pan is now signed on the wire. - electribePatternBuilder: also corrected Pitch write 0x08→0x24 (0x08 is the verified u16 OSC/sample ref — the old pitch write clobbered the sample number's low byte) and FxSend 0x0B→MFX-Send on/off @0x1B (TABLE 6). Tests: e2-layout adds pan-conversion + offset lock (12); e2s-export asserts Amp Level@0x18 / Amp Pan@0x19(i8) + center=0; electribe-pattern-write round-trips i8 pan; electribe-import constants updated; real-file byte-catalog assertions that were keyed to the disproven 0x15/0x22 reads are it.skip'd with reasons (re-measure against 0x18/0x19 when the real files are re-checked in). pnpm check green; e2/electribe/e2s suites green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
… [win-build]
Sequencer ⇄ device (Task 3):
- "⇧ Gerät" / "⇩ Gerät" buttons in the DrumMachine toolbar push the active
pattern to / pull it from a connected Korg E2/E2S via SysEx (Current Pattern).
Disabled unless a device is connected & idle. Push reuses synthstudioPatternToBody
(→ buildE2PatternBody), so volume/pan land at the corrected Amp Level/Amp Pan
offsets; pull decodes + applies steps, volume, pan and the verified part filter.
Sample assignment on .e2sallpat/.all import (Task 2):
- Proven end-to-end that the link chain (part +0x08 ↔ .all OSC_0index, value-based)
is correct via a synthetic build→parse→link round-trip. The real-world failure
is a feedback gap, so the import now explains WHY samples were (not) assigned:
- no .all co-selected but parts reference samples → hint to add e2sSample.all
- bank present but 0/partial matches → lists requested vs. available device
numbers (surfaces wrong-bank / factory-sample cases)
- New pure helpers diagnoseE2sLink + summarizeE2sSampleLink; resolver now bundles
its sample map (E2sSampleLink) so the toast can diagnose.
Docs:
- Fix stale part-offset comments (0x15/0x22 → Amp Level 0x18 / Amp Pan 0x19) in
e2Sysex, electribeImport and electribePatternBuilder to match the corrected code.
Tests:
- tests/features/e2s-sample-link.test.ts: 12 tests (link round-trip + diagnostics).
- korg-e2-pattern-push.test.ts: per-part volume/pan survive SysEx at 0x18/0x19.
- Full suite: 10086 passed, type-check clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPLeJwDb1oCsUuHndmbSc
…Linie auf main, Offset-Tabelle auf 0x0010/1020 Die winbuild-Linie lief seit dem 21.07. parallel und war nie gemerged (89 Commits, zuletzt winbuild-3.297.0). Sie bringt E2sPresetPanel/E2sPresetEditor (IFX- und Groove-Presets ueber hacktribe-RAM), e2Sysex.ts, den useMidiInputsStore-Multi-Device-Pfad und mehrere ESLI-Felder mit. KORREKTUR EINER KORREKTUR ========================= E2S_ALL_OFFSET_TABLE_START ist 0x0010 mit 1020 Eintraegen. Der Wert 0x0058/1002 aus v3.286.0 war ebenfalls falsch. Richtig ist Oe2sSLE (e2s_sample_all.py): load() liest 4080 B ab 0x0010, Pointer eines Samples ist read_u32(0x10 + i*4), und der Index i ist die Geraete-Sample-Nummer (esli.OSC_0index). Warum der Beleg fuer 0x0058 nicht trug: der Python-Editor hat gegen 14 Datei-Dumps geprueft, aber ausschliesslich gegen 0x07E0. scripts/verify_table_layout.py kennt genau zwei Kandidaten, 0x0058 und 0x07E0 — 0x0010 stand nie zur Wahl. Das ist entscheidend, weil ein um n Eintraege verschobener Tabellenstart DIESELBEN Offset-Werte liefert, nur unter falschen Indizes; "Offset >= 0x1000", "in der Datei" und "Anzahl nicht-null" sind gegen eine Verschiebung prinzipiell blind. Auch die Exact-Fit-Invariante aus v3.286 diskriminiert nicht: 0x0010 + 1020*4 und 0x0058 + 1002*4 ergeben beide exakt 0x1000. Sie schliesst nur 0x07E0/250 aus. Ein Test haelt das jetzt ausdruecklich fest. Praktische Folge des Irrtums: Versatz um 18 Eintraege (0x48 B), jedes Sample 18 Slots neben seiner Nummer, und die Factory-Slots 0..17 lagen vor dem angenommenen Tabellenanfang und fehlten ganz. NEUES PRUEFINSTRUMENT ===================== parseE2sBank gleicht jetzt Tabellen-Index gegen esli.OSC_0index ab. Die Datei traegt die Sample-Nummer doppelt — die einzige Redundanz, die eine falsche Startadresse ueberhaupt sichtbar macht. Konstanter Versatz ueber ALLE belegten Slots (und mehr als einer) => "offset-table geometry suspect" samt Byte-Zahl; einzelne krumme Slots werden davon getrennt gemeldet. Die Einschraenkung "ueber alle" kam von einem Test: bei nur einem abweichenden Slot war der Versatz trivial konstant. KONFLIKTAUFLOESUNG (9 Dateien, 19 Hunks) ======================================== - Geometrie und Doku durchgaengig auf die 0x0010-Lesart. - e2FxPreset.ts war add/add mit zwei verschiedenen Modulen gleichen Namens: der Schreib-Pfad der winbuild-Linie behaelt den Namen (E2sPresetEditor haengt daran), unser Lese-Inspektor wird e2FxPresetInspect.ts. - useMidi hatte zwei Multi-Input-Implementierungen. Die store-getriebene Opt-in-Variante ist die Basis; listenAllInputs (Opt-out) laeuft als Sonderfall in derselben syncMultiInputs-Buchfuehrung — zwei konkurrierende Detach-Pfade haetten sich gegenseitig die Listener abgeraeumt. detachAllInputs auf multiInputsRef neu gebaut. - Slot-Browser-Filter aus v3.286 bleibt (1020 Zeilen wollen gefiltert werden). Gates: pnpm check clean, 500 Dateien / 11117 passed / 97 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…hmen Der Remote-Branch zeigt noch auf den Stand von PR #25, den GitHub als c927eb4 nach main gesquasht hat. Inhaltlich ist er mit main identisch (git diff origin/main..origin/<branch> ist leer) — dieser Merge stellt nur die Vorfahren-Beziehung her, damit der Push ohne force durchgeht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
Der Datei-Kopf und ein Inline-Kommentar nannten noch 1002 Slots, waehrend der Test selbst laengst gegen 1020 @0x0010 laeuft. Genau solche stehen gebliebenen Kommentare haben die Tabellen-Geometrie zweimal in die falsche Richtung gelenkt — deshalb hier nicht nur den Zahlendreher weg, sondern beide verworfenen Werte samt Adresse benannt. Nur Kommentare; der Smoke laeuft unveraendert gruen (2 passed). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…y ein Befund: .all + .e2pat importieren, die Patterns spielen MIT den richtigen Samples — im Sample-Browser taucht aber nichts auf. Die Beobachtung war diagnostisch praezise: die PCM-Daten waren geladen, nur am falschen Ort. processElectribeFiles baut per makeE2sSampleResolver Blob-URLs und haengt sie ueber dm.setPartSample bzw. PatternData.sampleUrl an die Pattern-Parts, also in den DrumMachine-Store. Der Sample-Browser rendert dagegen useProjectStore.samples. Zwischen beiden gab es keine Verbindung. Strukturell konnte die Komponente das gar nicht: DrumMachine bekam `samples` nur als Lese-Liste, ohne Adder-Callback — anders als SampleBrowser oder KorgBankModal. Der einzige Weg in die Library fuehrte ueber KorgBankModal, und die wird beim kombinierten Drop nie erreicht, weil ElectronDropZone die .all herausfiltert und nur electribe:filesImport feuert. Aenderungen: - Neue optionale Prop onSamplesImported an DrumMachine, verdrahtet an allen drei Render-Stellen. In CollabSplitView nur die LOKALE Instanz — die Remote-Seite spiegelt eine fremde Sitzung. - bankSamplesToLibraryEntries als reine Funktion neben buildE2sSampleMap. Die Blob-URL kommt aus DEMSELBEN Resolver wie die Pattern-Parts (sonst laegen dieselben PCM-Daten zweimal im Speicher, auf verschiedenen Blobs), und iteriert wird nach derselben Bedingung sampleNumber > 0 — der Browser zeigt damit exakt das, was ein Pattern-Part auch treffen kann. - Stabile id `e2s:<bankname>:<geraetenummer>`: addSamples dedupliziert ueber `path`, Blob-URLs sind aber bei jedem Import neu. Ohne den Schluessel legt ein zweiter Import derselben Bank alles doppelt an. - Eine .all ohne Pattern-Datei ist kein Fehlgriff mehr: die Samples landen in der Library, die Meldung nennt die Anzahl. - Die Geometrie-Selbstpruefung aus v3.298 wird hier als Toast sichtbar. Das ist die erste Stelle, an der eine ECHTE Geraetedatei durch den Parser laeuft; einen konstanten Versatz still zu verschlucken waere das Schlimmste. +10 Tests. pnpm check clean, Suite 500 Dateien / 11127 passed / 97 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…hmen Der Remote-Branch zeigt noch auf den Stand von PR #27, den GitHub als eccc42f nach main gesquasht hat. Inhaltlich ist er mit main identisch (git diff origin/main..origin/<branch> ist leer) — dieser Merge stellt nur die Vorfahren-Beziehung her. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…m Schneiden Befund: Marker setzen geht, aber die Slices landen weder im Sequencer noch im Sample-Browser, und als WAV exportieren geht gar nicht. Drei Befunde, drei verschiedene Ursachen. 1. SAMPLE-BROWSER — fehlte komplett, gleiche Klasse wie der .all-Import aus v3.299. handleSlicesApply legte Slices auf Drum-Kanaele (setPartSample) und Performance-Pads (CustomEvent), aber nie in useProjectStore.samples. Jetzt via onSamplesImported. Die Blob-URLs entstehen EINMAL und werden fuer Library UND Kanal-Zuweisung benutzt; vorher encodierte die Schleife neu. 2. WAV-EXPORT — gab es nicht. Neu utils/sliceExport.ts (rein). Zwei Details zaehlen auf der SD-Karte: Dateinamen FAT-tauglich gefiltert, und die Nummern links mit Nullen aufgefuellt — sonst steht _10 vor _2 und die Slice-Reihenfolge im Dateimanager ist unbrauchbar. Ab vier Slices ein ZIP, weil Browser Download-Serien nach den ersten Dateien abbrechen. Leere Slices fallen weg, die Nummerierung folgt aber der Position im ORIGINAL. 3. SEQUENCER — der Pfad existierte. Ohne aktives Pattern ist parts leer, es passiert nichts, und der Toast meldete trotzdem Erfolg. Jetzt eine eigene Warnung statt einer Erfolgsmeldung fuer einen Nicht-Vorgang. AUFBEREITUNG (utils/sampleCleanup.ts): erfindet keine DSP — Hochpass, Tiefpass, Rauschminderung und Gate lagen laengst im Repo. Gefehlt hat die Verkettung auf dem Datentyp des Slicers, und deren Reihenfolge ist der eigentliche Inhalt: DC-Offset zuerst (sonst misst danach jede Pegel-Erkennung falsch und der Zero-Crossing-Snap findet keine Nulldurchgaenge), Hochpass vor allem Pegelkram, Rauschminderung VOR dem Trimmen (die vordere Stille ist ihr Profil), Normalisieren vor dem Trimmen, Fades zuletzt. Vier Presets. Gate und Rauschminderung sind BEWUSST nicht im Standard — beide koennen Material beschaedigen. Zwei Schutzregeln: nicht trimmen, wenn das ganze Sample unter der Schwelle liegt, und die Randfades ueberlappen nie. Der Editor arbeitet auf einer Arbeitskopie mit Zuruecksetzen; ein Cleanup setzt die Onsets zurueck, weil Trimmen und Filtern die Frame-Positionen verschieben und Marker danach ins Leere zeigten. Nebenbefund, nicht angefasst: components/SampleSlicer/SampleSlicer.tsx (605 LOC) wird nirgends gerendert. +51 Unit-Tests, +3 Playwright-Smokes (echte WAV durch decodeAudioData, beide Export-Pfade ueber echte Download-Events). pnpm check clean, Suite 502 Dateien / 11178 passed / 97 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…hmen Der Remote-Branch zeigt noch auf den Stand von PR #28, den GitHub als a56378f nach main gesquasht hat. Inhaltlich mit main identisch (git diff origin/main..origin/<branch> ist leer). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
client/src/components/SampleSlicer/ (SampleSlicer.tsx, 605 LOC, + index.ts) war exportiert, wurde aber nirgends gerendert — kein Import ausserhalb des eigenen Verzeichnisses, weder unter client/ noch tests/ noch electron/. Der aktive Slicer ist SampleEditor/SampleSliceEditor.tsx, gemountet aus DrumMachine.tsx. Aufgefallen bei der Slicer-Arbeit in v3.300. Drei Faehigkeiten daraus hat der aktive Editor NICHT; sie sind in INDEX.js unter next[] festgehalten, damit sie nicht mit dem Aufraeumen verschwinden: - BPM-Raster ueber der Wellenform (drawBpmGrid) - Snap-to-Beat beim Setzen der Marker (warpSnap). Ergaenzt den vorhandenen Zero-Crossing-Snap: der macht den Schnitt knackfrei, dieser rhythmisch richtig — beide zusammen waeren das eigentliche Ziel. - Teilen in N gleiche Abschnitte statt Onset-Erkennung. Bei sauber geschnittenen Loops zuverlaessiger, dort ist das Raster bekannt. Der Code bleibt in der Historie erreichbar (73aee66^). pnpm check clean. Suite 502 Dateien / 11176 passed / 97 skipped — zwei Tests weniger als vorher, weil theme-class-purity einen Test pro Komponentendatei erzeugt und es zwei Dateien weniger gibt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…hmen Der Remote-Branch zeigt noch auf den Stand von PR #29, den GitHub als 73aee66 nach main gesquasht hat. Inhaltlich mit main identisch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
Der non-blocking Job war im INDEX als TASK-262-Env-Gate gefuehrt. Das
stimmt nicht. Der Schritt "Checkout omnitribe (sim WS server)" scheitert
mit "remote: Repository not found" auf GeorgDub/omnitribe, dreimal Retry,
dann Aufgabe — meldet aber wegen continue-on-error trotzdem success.
Deshalb faellt es erst spaeter auf, wenn ~30 Tests an fehlenden Test-IDs
scheitern (erster: getByTestId('arp-output-synth')).
Kein Testfehler, ein Checkout-Fehler. Fix waere ein Token mit Lesezugriff
auf das private Repo; GITHUB_TOKEN kann kein Fremd-Repo lesen. Braucht ein
Repo-Secret, daher hier nur dokumentiert.
Gegenprobe: derselbe Job ist am Merge-Commit 73aee66 auf main ebenfalls rot
(Job 89943137056), also unabhaengig von der Slicer-Loeschung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
…mpler-research-w60qrk # Conflicts: # agents/INDEX.js # package.json
…mpler-research-w60qrk # Conflicts: # agents/INDEX.js # package.json
…mpler-research-w60qrk # Conflicts: # package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Löscht
client/src/components/SampleSlicer/(SampleSlicer.tsx, 605 LOC, plusindex.ts).Die Komponente war exportiert, wurde aber nirgends gerendert — kein Import ausserhalb des eigenen Verzeichnisses, weder unter
client/nochtests/nochelectron/. Der aktive Slicer istSampleEditor/SampleSliceEditor.tsx, gemountet ausDrumMachine.tsx. Aufgefallen bei der Slicer-Arbeit in #29.Nach jedem Merge von
mainerneut gegengeprüft, zuletzt gegen v3.316.0 — gilt unverändert. Die verbleibenden Treffer sind keine Referenzen: die Fundstellen inDrumMachine.tsxsind Log-Präfixe (console.warn("[SampleSlicer] …")), undtests/electron/sample-slicer.test.tsimportiert den Store nicht, sondern baut sich intern eine Minimal-Kopie.Drei Fähigkeiten, die dabei sonst verschwunden wären
Der gelöschte Code konnte drei Dinge, die der aktive Editor nicht kann. Sie stehen in
agents/INDEX.jsunternext[], mit Fundstellen in der Historie:drawBpmGrid) — bei Loops die schnellste Orientierung, wo ein Schnitt hingehört.warpSnap,framesPerBeat = sampleRate * 60 / bpm). Das ergänzt den vorhandenen Zero-Crossing-Snap, es ersetzt ihn nicht: der macht den Schnitt knackfrei, dieser macht ihn rhythmisch richtig. Beide zusammen wären das eigentliche Ziel.equalDivisions, Default 16) statt Onset-Erkennung. Bei sauber geschnittenen Loops zuverlässiger als jede Transientenanalyse — dort ist das Raster bekannt und muss nicht erraten werden.Der Code bleibt über die Historie erreichbar (
73aee66^, Zeilen 34–79 / 288–300 / 366–372).Offen: ein verwaister Store
client/src/store/useSampleSlicerStore.ts(aus TASK-249) hat als einzigen Importeur die hier gelöschteSampleSlicer.tsx— er wird durch diese Löschung selbst zu totem Code. Er ist bewusst nicht mit entfernt: das Löschen wurde in der Session von der Berechtigungsprüfung blockiert, und es ist ohnehin eine Entscheidung, die dem Repo-Eigner gehört. Ein Nachfolge-Commit oder ein Handgriff beim Merge erledigt es.Merges mit main
mainist dreimal an diesem PR vorbeigezogen, jedes Mal stand er danach aufdirty:package.jsonundagents/INDEX.js.workLog, weil beide Seiten einen neuen ersten Eintrag hatten. Aufgelöst durch Aneinanderhängen:mains Einträge oben, meiner darunter.Aktuelle Version 3.317.0, jeweils die nächste freie über
main; die zwischenzeitlich gewählte 3.312.0 hattemainsich selbst genommen.Tests
pnpm checkclean · Suite 521 Dateien / 11390 passed / 101 skipped (nach dem dritten Merge).Beim ursprünglichen Stand waren es zwei Tests weniger als ohne die Löschung, weil
theme-class-purityeinen Test pro Komponentendatei erzeugt und es zwei Dateien weniger gibt. Nachgerechnet, kein Verlust an Abdeckung.Zum roten non-blocking Job
Unverändert rot, aber nicht aus dem bisher angenommenen Grund. Die Zuordnung „TASK-262-Env-Gate" war falsch;
b125774korrigiert sie im INDEX. Die tatsächliche Ursache ist kein Testfehler, sondern ein Checkout-Fehler:Der Schritt „Checkout omnitribe (sim WS server)" meldet wegen
continue-on-errortrotzdemsuccess, deshalb fällt es erst zehn Minuten später auf:tools/sim/sim_ws_server.pyläuft ins Leere, die Panels rendern nicht, 43 Tests scheitern an fehlenden Test-IDs (Wurzeln:getByTestId('arp-output-synth'),getByTestId('sim-status-connected')).Gegenproben: derselbe Job ist auf
mainebenfalls rot (Job89943137056an73aee66), und die Signatur ist nach allen drei Merges unverändert — dieselben Fehlschläge, dieselben elf Spec-Dateien, dieselben zwei Wurzeln. Also unabhängig von dieser Löschung. Grün würde ihn ein Token mit Lesezugriff auf das privateGeorgDub/omnitribemachen;GITHUB_TOKENkann per Definition kein Fremd-Repo lesen. Das braucht ein Repo-Secret und gehört nicht in diesen PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01VRbPHkhtzeND6R1enwfxys
Generated by Claude Code