Skip to content

joaodotwork/push-2-led

Repository files navigation

push-2-led

Python 3.9+ macOS License: MIT Push 2

Pipe live visuals from VDMX onto the Ableton Push 2's 960x160 LCD via Syphon.

A Python bridge that receives GPU-shared frames from VDMX 6 over Syphon, converts them to the Push 2's native BGR565 format, and sends them over USB at 30+ fps. No drivers, no kernel extensions — just brew install libusb and go.


Architecture

graph LR
    VDMX["VDMX 6"] -->|"Syphon (GPU shared memory)"| SR["syphon_receiver.py"]
    SR -->|"BGRA uint8"| CV["converter.py"]
    CV -->|"960x160 BGR565"| DP["display.py"]
    DP -->|"USB bulk transfer"| P2["Push 2 LCD"]
    BR["bridge.py"] -.->|orchestrates| SR
    BR -.->|orchestrates| CV
    BR -.->|orchestrates| DP

    style VDMX fill:#6366f1,stroke:#4f46e5,color:#fff
    style P2 fill:#6366f1,stroke:#4f46e5,color:#fff
    style SR fill:#1e293b,stroke:#334155,color:#e2e8f0
    style CV fill:#1e293b,stroke:#334155,color:#e2e8f0
    style DP fill:#1e293b,stroke:#334155,color:#e2e8f0
    style BR fill:#0f172a,stroke:#1e293b,color:#94a3b8
Loading

Frame Pipeline

graph LR
    A["Syphon GPU-to-CPU<br/>~1-3 ms"] --> B["Resize to 960x160<br/>~0.5 ms"]
    B --> C["BGR565 conversion<br/>~0.2 ms"]
    C --> D["USB bulk transfer<br/>~2-5 ms"]

    style A fill:#0ea5e9,stroke:#0284c7,color:#fff
    style B fill:#8b5cf6,stroke:#7c3aed,color:#fff
    style C fill:#8b5cf6,stroke:#7c3aed,color:#fff
    style D fill:#f59e0b,stroke:#d97706,color:#fff
Loading

Total latency: ~5-10 ms per frame at 30+ fps.

Quick Start

Prerequisites

  • macOS 11+ (Big Sur or later)
  • Python 3.9+
  • Homebrew
  • Ableton Push 2 connected via USB
  • Close Ableton Live (only one app can access the display at a time)

Install

brew install libusb

python3 -m venv .venv && source .venv/bin/activate
pip install -e .

Run

push2-bridge

Or:

python -m push2_bridge

VDMX Setup

  1. Open Workspace Inspector > Plugins > Syphon Output
  2. Create a Syphon server named Push2
  3. Set the output resolution to 960x160 for pixel-perfect mapping

The bridge auto-discovers a Syphon server named "Push2" by default. Designing content at native resolution avoids resize overhead and gives you full control over composition in VDMX.

Tip: The 6:1 ultrawide aspect ratio works great for waveforms, spectral visualizers, text crawls, horizontal meters, and abstract patterns.

CLI Options

push2-bridge [OPTIONS]

Options:
  --fps N                Target frame rate (default: 30)
  --syphon-server NAME   Syphon server name to connect to (default: Push2)
  --fallback-color R,G,B Fallback color when no frame available (default: 0,0,0)
  --interpolation MODE   Resize method: linear or nearest (default: linear)
  -v, --verbose          Enable debug logging
  --version              Show version
  --help                 Show help

Examples

# Run at 60 fps with verbose logging
push2-bridge --fps 60 -v

# Connect to a specific Syphon server
push2-bridge --syphon-server "My VDMX Output"

# Blue fallback screen when no Syphon signal
push2-bridge --fallback-color 0,0,255

# Nearest-neighbor interpolation (sharper pixels, no smoothing)
push2-bridge --interpolation nearest

Modules

src/push2_bridge/
    __init__.py          # Package version
    __main__.py          # python -m entry point
    cli.py               # CLI argument parsing
    bridge.py            # Main loop: receive -> convert -> send
    syphon_receiver.py   # Syphon client with auto-discovery
    converter.py         # Resize + BGRA -> BGR565 conversion
    display.py           # Push 2 USB display driver

scripts/
    benchmark.py         # Frame conversion benchmarking

tests/
    test_bridge.py
    test_cli.py
    test_converter.py
    test_syphon_receiver.py

How It Works

Display Protocol

The Push 2 LCD is driven via USB bulk transfers following Ableton's published spec:

Parameter Value
USB VID/PID 0x2982 / 0x1967
Interface 0
Endpoint 0x01 (bulk OUT)
Resolution 960 x 160
Pixel format BGR565 (16-bit)
Line stride 2048 bytes
Frame header FF CC AA 88 + 12x 00
Pixel data 327,680 bytes
XOR mask E7 F3 E7 FF (repeating)
Display timeout 2 seconds

Keep-Alive

The display blacks out after 2 seconds without a frame. The bridge handles this automatically:

flowchart TD
    A{New Syphon frame?} -->|Yes| B[Convert and send]
    A -->|No| C{Previous frame exists?}
    C -->|Yes| D[Resend last frame]
    C -->|No| E[Send fallback color]
    B --> F[Wait for next tick]
    D --> F
    E --> F
    F --> A

    style A fill:#1e293b,stroke:#334155,color:#e2e8f0
    style B fill:#22c55e,stroke:#16a34a,color:#fff
    style D fill:#f59e0b,stroke:#d97706,color:#fff
    style E fill:#ef4444,stroke:#dc2626,color:#fff
Loading

Error Recovery

  • Push 2 unplugged mid-stream — catches USB errors, attempts reconnection
  • VDMX closed / Syphon server drops — auto-rediscovers on each frame tick
  • Ctrl+C — clean shutdown, releases USB interface

Development

python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

# Run tests
pytest

# Run benchmark
python scripts/benchmark.py

# Lint
ruff check src/ tests/

Performance

Format FPS Notes
BGR565 (native) ~36 fps Direct 16-bit path, no internal conversion
RGB565 ~14 fps Library converts internally
RGB float ~14 fps Slowest — float64 intermediary

The bridge uses the BGR565 native path by default for maximum throughput.

Credits

Built with:

Support

If this project saved you some time or sparked an idea, consider buying me a coffee:

ko-fi


Made for VJs who want another screen.

About

VDMX → Syphon → Push 2 LCD display bridge for macOS

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors