Metadata-Version: 2.4
Name: midicraft
Version: 0.3.0
Summary: Visual MIDI controller mapper that exports a JSON profile for programmatic control of hardware controllers.
Author: Mehdi.A
License-Expression: AGPL-3.0-or-later
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mido
Requires-Dist: python-rtmidi
Requires-Dist: sv_ttk
Dynamic: license-file

# midicraft

Visual editor for mapping the physical layout of a MIDI controller (buttons, knobs, pad grids...) and exporting a self-contained JSON profile that can be used to programmatically control the device — send LED colors, react to inputs — from any code, without midicraft.

## Installation

```bash
pip install midicraft
```

## Launch

```bash
midicraft
```

## How it works

1. Recreate the physical layout of your controller on the canvas (drag & drop from the palette)
2. Map each element to its MIDI binding via MIDI learn (click Learn, press the physical control)
3. Assign LED behaviors per element from the catalog or define custom color→value tables
4. Save the profile as JSON

The exported profile is self-sufficient: it contains everything needed to drive the controller from external code.

## JSON profile format

Example of a mapped button:

```json
{
  "element_type": "grid_button",
  "control_id": "46e33976",
  "label": "Clip 1",
  "x": 0, "y": 0, "width": 1.0, "height": 1.0,
  "binding": { "kind": "NOTE", "number": 36, "channel": 0 },
  "led_behavior_key": "CUSTOM",
  "parent_id": null,
  "custom_led_values": [
    ["Orange blinking", 12],
    ["Green solid", 3],
    ["Off", 0]
  ]
}
```

- `binding`: how to address the control — `kind` (`NOTE` or `CONTROL_CHANGE`), `number`, `channel` (0-indexed)
- `custom_led_values`: color label → MIDI value pairs
- `control_id`: internal unique identifier, also used to link children via `parent_id`

Using the profile from Python:

```python
import mido, json

profile = json.load(open("my_controller.json"))
out = mido.open_output("CMD LC-1")

# Light up "Clip 1" in orange blinking
btn = next(e for e in profile["elements"] if e["label"] == "Clip 1")
value = dict(btn["custom_led_values"])["Orange blinking"]  # 12
out.send(mido.Message("note_on", channel=btn["binding"]["channel"],
                       note=btn["binding"]["number"], velocity=value))
```

## Architecture

- **Separate MIDI thread**: listens to the input port continuously, classifies each message and pushes it into a thread-safe `queue.Queue`. Never touches Tkinter widgets directly.
- **Main thread (Tkinter)**: drains the queue at a regular interval (`root.after(15, ...)`) and updates the UI.
- **LED catalog decoupled from layout**: an element references a behavior by key, not by duplicating values. Two buttons on the same controller can reference completely different behaviors.
- **Grid coordinates, not pixels**: `x/y/width/height` are expressed in logical grid units, converted to pixels only at render time. A profile remains valid regardless of display size.

## Canvas features

- Drag & drop from the palette onto the canvas (with ghost preview)
- Smooth element movement with grid snap on release, collision detection
- Scroll: scrollbars + mousewheel (Shift+wheel for horizontal)
- N×M grid groups (1×5, 4×4, 8×8...) that move as one block, with individually mappable cells
- Pre-fill grids with buttons or knobs at creation time
- MIDI learn per element/cell
- Visual flash on MIDI input in normal mode

## License

Copyright (c) 2026 Mehdi.A — AGPL-3.0-or-later
