Skip to content

Latest commit

 

History

History
89 lines (60 loc) · 4.86 KB

File metadata and controls

89 lines (60 loc) · 4.86 KB

AGENTS.md

Project guidance for AI coding assistants working in this repository.

Commands

# Development
npm run dev                # Vite dev server at http://localhost:4404 (browser build)
npm run electron:dev       # Electron dev mode (renderer at port 4406)

# Build
npm run build              # Build React library → dist/
npm run electron:build     # Build Electron app → out/
npm run electron:dist      # Package distributable (Mac)
npm run build-kits         # Regenerate public/kits/ from MPC-Sample/Projects/

# Quality
npm run typecheck          # tsc --noEmit across lib + node tsconfigs
npm run lint               # Biome lint
npm run check              # Biome lint + format (auto-fix)

# Tests
npm test                   # Run all tests once (Vitest, no watch)

Run a single test file:

npx vitest run src/xpj/__tests__/codec.test.ts

Package manager: npm only — do not use pnpm.

Architecture

This project is simultaneously a React component library (dist/) and a desktop Electron app (out/). The same React source (src/) powers both targets; electron/ contains only the main-process code.

Dual-build targets

Target Entry Config Output
Library (npm) src/index.tsApp vite.config.ts dist/
Browser dev src/main.tsx vite.config.ts served
Electron main electron/main.ts electron.vite.config.ts out/main/
Electron renderer index.html electron.vite.config.ts out/renderer/

package.json main field points to out/main/index.js (Electron entry); library consumers use the exports map which resolves to dist/index.js.

State: Zustand store (src/state/store.ts)

Single global store (useMPCStore) is the source of truth. Key state:

  • padMap: Record<GlobalPadIdx, SamplePad | null> — 128 pad slots (banks A–H). This is the live source of truth; activeKit.pads is only the initial snapshot.
  • activeKit, activeKitId — loaded kit metadata
  • engineRef: AudioEngineLike | null — registered audio engine reference
  • uiScale, uiOffset — pan/zoom transform, persisted to localStorage
  • Per-session settings persisted under mpc.settings and mpc.uiTransform localStorage keys

Audio: SampleEngine (src/audio/SampleEngine.ts)

Implements AudioEngineLike interface using Tone.js (one Tone.Player per pad, lazy-loaded). Audio graph: Tone.Player → per-pad Tone.Gain → master Tone.Gain → Tone.Waveform + Tone.FFT → destination. Buffers are loaded on first trigger and inflight fetches are deduplicated.

.xpj file format (src/xpj/)

Akai MPC project files are gzip-compressed with a 5-line ASCII header followed by JSON. Critical constraint: the firmware expects float-typed fields to have decimal points (1.0 not 1). jsonLossless.ts carries raw number lexemes through parse/stringify to preserve this. codec.ts exposes encodeXpj / decodeXpj and the convenience aliases floatTag / serializeJson.

Kit system (src/kits/)

  • public/kits/<id>/manifest.json — generated by scripts/build-kits.mjs from MPC-Sample/Projects/
  • public/kits/<id>/*.wav — preset sample files (gitignored, ~300 MB)
  • SampleKit / SamplePad types in src/kits/kit.types.ts are frozen contracts shared across all modules
  • GlobalPadIdx 0..127: bankIdx = idx >> 4, localIdx = idx & 0xf

Desktop bridge (src/desktop/)

isDesktop() / desktop() guard renderer code from browser vs Electron divergence. window.mpcDesktop is injected by electron/preload.ts via contextBridge. Desktop-only features: open/save .xpj file dialogs, auto-open export folder, SD card eject.

Styling

Tailwind CSS v4 via @tailwindcss/vite plugin. CSS tokens in src/styles/tokens.css. No CSS modules — class names applied directly. Biome enforces formatting (2-space indent, double quotes, 100-char line width); CSS files are excluded from Biome.

Key conventions

  • Pad addressing: Always use GlobalPadIdx (0–127) internally. Local pad number (1–16) is display only. Bank A = indices 0–15, Bank B = 16–31, etc.
  • padMap is the source of truth for exports — never read activeKit.pads when building the export kit; use buildExportKit(activeKit, padMap) from the store.
  • Float serialisation: Any number written to .xpj that the firmware treats as a float must go through floatTag() before serializeJson().
  • Engine interaction: Store actions call engineRef?.method() directly; components never call the engine — they dispatch store actions.
  • Tests live in __tests__/ subdirectories alongside source; electron tests are under electron/__tests__/ with their own vitest.config.ts.