Skip to content

michaeldtimpe/k2-lights

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

k2-lights

Drive the Allen & Heath Xone:K2's 34 LEDs from preprogrammed patterns, scripted YAML scenes, MIDI clock, or live audio. Includes a calibration workflow, six audio-reactive effects, scrolling-text mode, and live control from the K2's bottom-row knobs.

What it does

  • Patterns. Built-in chaser, sweep, pulse, breathing, static, plus YAML keyframe scenes for arbitrary custom animations.
  • Audio-reactive. Six effects: rms, spectrum, bands, vu (8-band sideways meter), beat, drop (particle rain). All driven by a live mic / line-in input.
  • Live control. Press a bottom-row knob to cycle effects; turn the left knob for intensity; turn the right for sensitivity. Press both knobs to toggle a scrolling-text mode (cycle fonts with the right knob, set scroll speed with the left).
  • MIDI clock. Patterns can sync to incoming 24 ppqn MIDI clock with a 500 ms stall detector, or run on a fixed BPM.
  • Calibration. Two interactive tools (calibrate, map-leds) recover the K2's input/output note mapping for any unit in a few minutes.

Hardware setup

  • Connection: USB direct to a Mac (tested bus-powered on M-series).
  • Latch position: The K2's latch switch sets the MIDI channel. This project assumes channel 2 (0-indexed), the most common default. Other layers are exposed via --layer B|C|D.
  • Audio input: Any system input device. Pass --input-device NAME or --input-device 1 to pick by name or index, or rely on the OS default.

Install

python3 -m venv .venv
.venv/bin/pip install -e ".[test]"

Tested on Python 3.11+ (developed against 3.14.5). Dependencies: mido, python-rtmidi, sounddevice, numpy, pyyaml, click, rich.

Usage

# Discovery
.venv/bin/midi-lights list-ports        # MIDI in/out — should show "XONE:K2"
.venv/bin/midi-lights list-audio        # audio input devices

# Sanity check the device map
.venv/bin/midi-lights test --color red --duration 5
.venv/bin/midi-lights test --color cycle --duration 15

# Recovery if any LED gets stuck
.venv/bin/midi-lights blackout

# Patterns
.venv/bin/midi-lights pattern --name chaser --bpm 120
.venv/bin/midi-lights pattern --name breathing
.venv/bin/midi-lights pattern --file config/patterns/chaser.yaml

# Audio-reactive
.venv/bin/midi-lights audio                       # starts on `rms`
.venv/bin/midi-lights audio --effect vu           # 8-band sideways VU
.venv/bin/midi-lights audio --effect drop         # particle rain

# MIDI clock sync (DAW or hardware sending 24 ppqn over IAC)
.venv/bin/midi-lights pattern --name chaser --clock midi --clock-port "IAC Bus 1"

Live control during audio

  • Press either bottom-row knob alone → cycle to the next effect.
  • Press both bottom-row knobs together → toggle scrolling-text mode.
  • Turn the left knob → adjust intensity (also scroll speed in text mode).
  • Turn the right knob → adjust sensitivity (cycles font in text mode).

Cell naming: AxB

Buttons and lights are addressed by row × col, both 0-indexed, written as AxB. So 0x0 is the top-left indicator light, 4x0 is the top-left button of the lower block, 8x3 is the bottom-right under-fader button. See docs/k2-layout.md for the full diagram.

YAML pattern format

name: red-sweep
bpm: 120
loop: true
length_beats: 4
keyframes:
  - at: 0.0
    cells:
      - [1x0, red]
      - [1x1, red]
  - at: 1.0
    cells:
      - [1x0, off]
      - [1x2, amber]
  - at: 2.0
    cells: clear_matrix      # special: turn off every matrix cell

Cells use the AxB naming. Colors are red, amber, green, or off. The special directive clear_matrix turns off every cell in rows 1–7; clear_all clears every controllable LED.

Calibrating a new K2

The shipped device map (src/midi_lights/device/xone_k2.py) reflects the unit this project was developed against. If your K2 is in a different latch position or operating mode, recover the map with these two tools.

# 1. Capture input notes by pressing every button in row-major order.
#    Press SPACE when finished. The tool prints (row, col) → input_note.
.venv/bin/midi-lights calibrate

# 2. Discover LED notes by sweeping every MIDI note 0-127 on channel 2.
#    When a button lights, press it. SPACE to skip notes that light
#    nothing or only light row-0 indicators.
.venv/bin/midi-lights map-leds

Paste the resulting tables into xone_k2.py's INPUT_NOTES and _LED_RGB dicts. Also useful while exploring an unknown unit:

.venv/bin/midi-lights sniff           # log every incoming MIDI message
.venv/bin/midi-lights note --channel 2 --note 36 --duration 3   # single LED probe
.venv/bin/midi-lights scan --channels 2 --start-note 0 --end-note 127
.venv/bin/midi-lights cc-scan --channel 2                       # for CC-driven LEDs

Architecture

audio callback ──► lock-free ring buffer ──► Analyzer (RMS, FFT bands, onset)
                                                       │
                                                       ▼
   scheduler ─► Clock ─► ctx ─► Mode/Pattern.render(ctx) ─► Frame ─► Renderer ─► MIDI ─► K2
   (60Hz, drift-corrected
    on perf_counter)
  • engine/frame.pyLEDState enum, immutable sparse Frame.
  • engine/renderer.py — diff against last state, mutex-color enforcement (red→green sends red-off then green-on), 128-msgs-per-tick rate cap, and a blackout() primitive that brute-force sends velocity 0 to every LED note even after state desync.
  • engine/scheduler.py — drift-corrected 60Hz loop using perf_counter. Skips frames if behind by more than one interval rather than catching up.
  • engine/context.py — read-only per-tick state passed to modes.
  • audio/source.pysounddevice input → lock-free ring buffer.
  • audio/analysis.py — 8 log-spaced FFT bands, RMS, spectral-flux onset with adaptive threshold and a refractory cooldown.
  • audio/modes.py — six audio-reactive modes plus a registry.
  • audio/text_mode.py — 4×4-bitmap scrolling text with four fonts.
  • audio/controller.py — debounced chord detector for the bottom knobs: 150 ms window distinguishes a single press (cycle effect) from a chord (toggle text mode).
  • patterns/ — clocks (FixedBPMClock, MidiClock with stall handling), built-in patterns, YAML loader.
  • device/xone_k2.pyINPUT_NOTES, _LED_RGB, layer-to-channel map, format_cell / parse_cell for AxB names.

Testing

.venv/bin/python -m pytest

14 tests covering the renderer diff, blackout, mutex-color ordering, rate cap, MIDI clock stall detection, and YAML pattern loading. Renderer tests run against an in-memory FakeMidiOut (in tests/fixtures/fake_midi.py), so they require no hardware.

Project layout

k2-lights/
├── pyproject.toml
├── config/patterns/
│   ├── chaser.yaml
│   └── breathing.yaml
├── docs/
│   └── k2-layout.md          # K2 physical layout + AxB naming
├── src/midi_lights/
│   ├── cli.py                # click commands
│   ├── engine/               # frame, renderer, scheduler, context
│   ├── device/               # xone_k2.py, midi_io.py
│   ├── audio/                # source, analysis, modes, text_mode, controller
│   ├── patterns/             # clocks, ambient, tempo, scripted YAML loader
│   └── modes/                # CLI ↔ mode registry
└── tests/
    ├── fixtures/fake_midi.py # recording MIDI backend
    └── test_*.py

Acknowledgements

The Xone:K2 MIDI mapping was reverse-engineered live against one physical unit using calibrate and map-leds. Both tools are in the CLI; pull requests with mappings from other K2 units (or other Allen & Heath controllers with similar topology) are welcome.

About

Drive the Allen & Heath Xone:K2's 34 LEDs from patterns, MIDI clock, or live audio.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages