From 8db82ed8ef508e4d3723434bd44a373c5f058cc3 Mon Sep 17 00:00:00 2001 From: Rob Morgan Date: Tue, 21 Jul 2026 08:41:26 +0800 Subject: [PATCH] docs: refresh docs for the DJ-app pivot Rewrite README, CLAUDE.md, and CONTRIBUTING for the two-deck DJ app with integrated lighting console, replacing the old tokio Art-Net console docs. - README: new about/features/requirements/usage; point to ROADMAP.md and the timestretch-rs path dependency; add screenshot. - CLAUDE.md: two-crate layout (halo / halo-light), thread-based (no tokio) threading model, module map, persistence, conventions, CI. - CONTRIBUTING: sibling timestretch-rs clone, updated crate table and toolchain. - Move pre-pivot docs to docs/legacy/ with a docs/README.md pointer. - Remove stale pre-pivot artifacts: config.json, config.template.json, TODO.md, start.sh, start-capture.sh. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 236 +++++++----------- CONTRIBUTING.md | 25 +- README.md | 135 +++++----- TODO.md | 22 -- _docs/screenshot.png | Bin 0 -> 592079 bytes config.json | 27 -- config.template.json | 23 -- docs/README.md | 34 +-- docs/legacy/README.md | 32 +++ docs/{ => legacy}/architecture.md | 0 docs/{ => legacy}/cli-reference.md | 0 docs/{ => legacy}/examples.md | 0 docs/{ => legacy}/multi-destination-artnet.md | 0 docs/{ => legacy}/presets.md | 0 docs/{ => legacy}/troubleshooting.md | 0 start-capture.sh | 4 - start.sh | 17 -- 17 files changed, 205 insertions(+), 350 deletions(-) delete mode 100644 TODO.md create mode 100644 _docs/screenshot.png delete mode 100644 config.json delete mode 100644 config.template.json create mode 100644 docs/legacy/README.md rename docs/{ => legacy}/architecture.md (100%) rename docs/{ => legacy}/cli-reference.md (100%) rename docs/{ => legacy}/examples.md (100%) rename docs/{ => legacy}/multi-destination-artnet.md (100%) rename docs/{ => legacy}/presets.md (100%) rename docs/{ => legacy}/troubleshooting.md (100%) delete mode 100755 start-capture.sh delete mode 100755 start.sh diff --git a/CLAUDE.md b/CLAUDE.md index 9c56b8c..1dfb673 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,162 +2,98 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Halo is a 2-deck DJ app for macOS with an integrated lighting console (Art-Net output driven by per-track cues +and a live programmer). `ROADMAP.md` is the authoritative description of the architecture and feature plan — +read it before designing anything non-trivial. + ## Common Commands -### Building and Running - **Build**: `cargo build --release` -- **Run with Art-Net broadcast**: `cargo run --release -- --source-ip ` -- **Run with unicast and MIDI**: `cargo run --release -- --source-ip 192.168.1.100 --dest-ip 192.168.1.200 --enable-midi` -- **Run with multi-destination setup**: `cargo run --release -- --source-ip 192.168.1.100 --lighting-dest-ip 192.168.1.200 --pixel-dest-ip 192.168.1.201` -- **Load a show file**: `cargo run --release -- --source-ip --show-file shows/Jasons40th.json` - -### CLI Arguments -- `--source-ip ` - Art-Net source IP address (required) -- `--dest-ip ` - Single destination IP (legacy, optional) -- `--lighting-dest-ip ` - Lighting fixtures destination IP (multi-destination) -- `--pixel-dest-ip ` - Pixel fixtures destination IP (multi-destination) -- `--lighting-universe ` - Universe for lighting fixtures (default: 1) -- `--pixel-start-universe ` - Starting universe for pixel fixtures (default: 2) -- `--artnet-port ` - Art-Net port (default: 6454) -- `--broadcast` - Force broadcast mode -- `--enable-midi` - Enable MIDI support -- `--show-file ` - Path to show JSON file - -See `docs/multi-destination-artnet.md` for detailed multi-destination Art-Net setup. - -### Development Tools +- **Run**: `cargo run --release` (optionally pass an audio file path as the only positional arg to load it on launch) - **Check compilation**: `cargo check --workspace --all-targets` -- **Format code**: `cargo +nightly fmt --all` (requires nightly toolchain for unstable formatting options) -- **Lint**: `cargo clippy --workspace --all-targets --all-features -- -D warnings` +- **Format code**: `cargo +nightly fmt --all` (nightly required — `rustfmt.toml` uses unstable options) - **Test**: `cargo test --workspace` -- **Install nightly toolchain**: `rustup toolchain install nightly` (required for formatting only) +- **Lint**: `cargo clippy --workspace --all-targets` (currently not enforced in CI) +- **macOS app bundle**: `cargo bundle --release` (requires `cargo-bundle`) + +There are no CLI flags. Art-Net destination, audio device, and other settings are configured in-app and +persisted (see Persistence below). Logging is controlled with `RUST_LOG`. + +### Toolchain -### Toolchain Requirements -- **Stable Rust**: Used for building, testing, and linting (MSRV: 1.90.0) -- **Nightly Rust**: Required only for formatting due to unstable options in `rustfmt.toml` +- **Stable Rust** for building/testing (CI pins 1.90.0); **nightly** only for formatting. +- The `timestretch` crate is a **path dependency on a sibling clone**: `../timestretch-rs` next to this repo + (from `crates/halo`, `path = "../../../timestretch-rs"`). CI checks out `robmorgan/timestretch-rs` to that + location. Builds fail without it. ## Architecture Overview -Halo is a real-time lighting console built with Rust, designed for solo performers. It uses a multi-crate workspace architecture with async/tokio runtime: - -### Core Crates -- **`halo-core`**: Core lighting engine with async module system, DMX output, cue system, effects engine, pixel engine, and MIDI integration -- **`halo-fixtures`**: Fixture library and management system -- **`halo-ui`**: egui-based UI components and interface -- **`halo`**: CLI application and main entry point (uses `#[tokio::main]` async runtime) - -### Key Systems - -#### Lighting Console (`halo-core/src/console.rs`) -- Central `LightingConsole` struct manages all lighting operations -- Uses async `ModuleManager` to coordinate separate modules (`DmxModule`, `AudioModule`, `MidiModule`, `SmpteModule`) -- Channel-based communication using `ConsoleCommand` and `ConsoleEvent` via tokio `mpsc` -- Handles fixture patching, cue management, and MIDI integration -- Supports multi-destination Art-Net routing for different fixture types - -#### Cue System (`halo-core/src/cue/`) -- `CueManager` handles playback state and timecode synchronization -- Supports both internal and external SMPTE timecode -- `CueList` contains sequences of `Cue` objects with static values and effects -- Audio playback synchronization with Ableton Link - -#### Effect Engine (`halo-core/src/effect/`) -- Mathematical effect generators: sine, sawtooth, square waves -- Beat-synchronized effects using rhythm detection -- Effect distribution across multiple fixtures with customizable parameters - -#### MIDI Integration (`halo-core/src/midi/`) -- MPK49 controller support for live performance -- `MidiOverride` system for real-time control during shows -- Actions include static values and cue triggering - -#### Module System (`halo-core/src/modules/`) -- `ModuleManager` coordinates all async modules in separate tokio tasks -- Each module implements `AsyncModule` trait with `initialize()`, `run()`, and `shutdown()` methods -- Inter-module communication via `ModuleEvent` (DMX output, audio commands, timecode sync, MIDI input) -- Status/error reporting via `ModuleMessage` back to manager -- **DmxModule**: Real-time DMX output at 44Hz with multi-destination Art-Net routing -- **AudioModule**: Audio file playback in dedicated OS thread (not tokio task) using `rodio` and `symphonia` -- **MidiModule**: MIDI input handling and event forwarding -- **SmpteModule**: SMPTE timecode synchronization for external timecode sources - -#### Pixel Engine (`halo-core/src/pixel/`) -- `PixelEngine` manages all pixel bar fixtures with per-universe routing -- Pixel effects: `Chase`, `Wave`, `Strobe`, `ColorCycle` -- `PixelEffectScope` controls effect application: `Bar` (uniform) or `Individual` (per-pixel) -- Beat-synchronized effects using rhythm detection -- Supports effect distribution across multiple fixtures (All, Step, Wave) -- Renders RGB data per universe for pixel bar fixtures - -#### Configuration System (`halo-core/src/config.rs`) -- `ConfigManager` handles persistent configuration in `config.json` (repository root) -- `Settings` structure stores audio device, MIDI device, DMX settings, and fixture library preferences -- Configuration loaded at startup; CLI arguments override saved settings -- Settings UI panel allows runtime configuration changes -- Version-aware configuration with migration support - -#### UI Architecture (`halo-ui/src/`) -- `HaloApp` is the main egui application with tabbed interface -- Separate panels: Dashboard, Programmer, Cue Editor, Patch Panel, Show Manager -- Real-time fixture grid visualization and control -- Timeline view for cue sequencing - -### Network Configuration -- Multi-destination Art-Net architecture via `NetworkConfig` (`artnet/network_config.rs`) -- Multiple `ArtNetDestination` entries with independent broadcast/unicast modes -- Universe routing via `HashMap` maps universes to destination indices -- Common setup: Universe 1 for lighting fixtures, Universes 2+ for pixel fixtures -- CLI supports separate `--lighting-dest-ip` and `--pixel-dest-ip` for easy multi-destination setup -- Legacy `--dest-ip` still supported for single-destination backward compatibility -- Art-Net output on port 6454 (configurable via `--artnet-port`) - -### Show File Format -- JSON-based show files in `shows/` directory -- Contains cue lists with timing, effects, pixel effects, and fixture assignments -- Includes audio file paths for synchronized playback -- Loadable at runtime via `--show-file` parameter -- Managed through UI Show Manager with save/load functionality - -## Development Notes - -### Repository Conventions - -#### Rust Crates -- Crate names are prefixed with `halo-`. For example, the `core` folder's crate is named `halo-core` -- When using `format!` and you can inline variables into `{}`, always do that -- Never use `unsafe` blocks or functions in any code - -#### Code Formatting -After making any changes to Rust code, always run: -```bash -cargo +nightly fmt --all -``` - -### macOS Platform Requirement -- Development and execution require macOS due to system-specific audio and MIDI dependencies -- Uses Core Audio frameworks through `rodio` and `midir` crates - -### Async Architecture -- Built on tokio async runtime with async/await throughout the codebase -- Module system uses async tasks for concurrent operation -- Channel-based communication between UI and console (`tokio::sync::mpsc`) -- AudioModule uses dedicated OS thread (not async task) for real-time audio playback - -### Configuration Management -- `config.json` file in repository root stores persistent settings -- Auto-created with defaults on first run if not present -- CLI arguments override configuration file settings -- Editable through Settings panel in UI - -### Fixture Patching -- Fixtures are defined in the fixture library with channel layouts -- Traditional lighting fixtures typically on Universe 1 -- Pixel bar fixtures on Universes 2+ with per-universe routing -- DMX addressing starts from specified universe and channel - -### Performance Considerations -- DMX module outputs at 44Hz for smooth lighting output -- Async module architecture allows concurrent operation of DMX, audio, MIDI, and timecode -- UI runs on main thread with egui's native event loop and repaint system -- Channel-based communication between UI and console for thread-safe operation -- Audio playback in dedicated OS thread for consistent timing \ No newline at end of file +Two workspace crates: + +- **`crates/halo`** — the application: UI (egui/eframe, wgpu backend), audio engine, decks, mixer DSP, track + library, analysis workers, and the DMX engine thread. +- **`crates/halo-light`** — UI-free, audio-free lighting domain library: fixtures, cues, programmer resolution, + DMX rendering, Art-Net transport. + +### Threading model (no tokio) + +Plain threads + channels/atomics, mirroring the `timestretch` controller/processor/source split: + +- **UI thread** — egui at ~30 fps while playing. Talks to audio via atomics and the engine's wait-free control + mailbox; a mutex only guards cold UI state. +- **Feed/control thread per deck** — keeps the engine's source ring fed, handles warm-start seeks and gapless + loop wraps (`JumpMap` re-anchor), publishes the playhead. +- **Audio callback thread (cpal)** — owns both `EngineProcessor`s and the whole mixer chain + (engine → trim → EQ → filter → fader → crossfader → master → limiter). Must stay **allocation-free and + lock-free**; parameters arrive via atomics and are smoothed per block. +- **Worker threads** (`std::thread` + `mpsc`, drained per frame by the UI) — library import, track analysis. +- **DMX engine thread** — 44 Hz tick: reads deck playheads from atomics, `resolve()` → `render()` → Art-Net send. + +### Key modules — `crates/halo/src` + +- `main.rs` — eframe/wgpu entry point; positional file arg; `env_logger`. +- `app.rs` — `HaloApp`: views (Prepare/Perform), deck UI, mixer UI, rig ownership, PATCH tab, settings window. +- `deck.rs` — `Deck`: one `timestretch::Engine` per deck, feed thread, seeks, loops, EOF, playhead. +- `audio.rs` — cpal stream setup and the audio callback owning the mixer chain. +- `dsp.rs` — `IsolatorEq` (LR4 crossover, full-kill), `DjFilter` (RBJ biquad LP/HP), `Limiter`. +- `state.rs` — shared atomics (`DeckShared`, `MixerShared`), scrub state, meters, CPU load. +- `scrub.rs` — audible scrub: varispeed voice with momentum glide/settle, engine↔voice crossfade. +- `waveform/` — 3-band RGB peaks pyramid, overview strip, zoomed beat-grid view, trigger-lane strips + cue editor. +- `decoder.rs` — symphonia decode (mp3/flac/ogg/wav) to interleaved stereo `f32`. +- `worker.rs` — background import/analysis workers (own DB connections). +- `library.rs` — SQLite (rusqlite, bundled): `tracks`, `playlists`, `playlist_tracks`, `lighting_cues`, `settings`. +- `dmx.rs` — `spawn_dmx_engine`, the 44 Hz Art-Net output thread (has an end-to-end UDP test). +- `programmer_ui.rs` — lighting programmer surface (fixture grid, five parameter views, effect panels). +- `show.rs` — `simulate_show()`: deterministic demo cue generator, not real authored content. +- `fader.rs` / `knob.rs` — custom egui widgets. + +### Key modules — `crates/halo-light/src` + +- `fixture.rs` — `Rig`, grid/selection types, `default_rig()`. +- `fixture_library.rs` — `FixtureProfile`/`Channel`/`ChannelType`, hardcoded profile registry, patching. +- `cues.rs` — `CueSet` (runtime, non-overlapping per lane) + `CueFile` (persisted JSON, seconds). +- `programmer.rs` — `resolve()`: **the single merge point** (programmer override > track cues > off). +- `output.rs` — pure `render()`: resolved lanes + params → per-universe `[u8; 512]` DMX frames. +- `artnet.rs` — synchronous Art-Net over `std::net::UdpSocket` (broadcast/unicast, multi-destination). + +### Persistence + +- **eframe persistence** (`PERSIST_KEY = "halo"`) — UI/mixer state: trims, keylock, pitch range, quantize, + gated mode, audition volume, device/buffer, sort order. +- **Library DB `settings` table** — Art-Net config and rig patch. +- There is no config file; `config.json` in old branches/history is from the pre-pivot console. + +## Conventions + +- Crate names are prefixed with `halo-` (the `halo-light` crate lives in `crates/halo-light`). +- **Never use `unsafe`** blocks or functions in any code. +- When using `format!` (and friends) and a variable can be inlined into `{}`, always inline it. +- **No tokio** — concurrency is plain threads + `mpsc` + atomics. Don't reintroduce async scaffolding. +- The audio callback must never allocate, lock, or panic. +- `programmer::resolve()` stays the single merge point for lighting state; `output::render()` stays pure. +- After changing Rust code, always run `cargo +nightly fmt --all`. + +## CI + +`.github/workflows/rust.yml` runs on macOS arm64 and Linux x86_64: checks out `timestretch-rs` as a sibling, +then `cargo +nightly fmt --check`, `cargo build`, `cargo test` on stable 1.90.0. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1195e20..f9c32f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,13 +7,18 @@ ## ⏩ Quick‑Start ### 1. Fork, clone & branch + +Halo depends on the [`timestretch`](https://github.com/robmorgan/timestretch-rs) crate by local path, so clone +`timestretch-rs` alongside your halo clone: + ```bash git clone https://github.com//halo.git +git clone https://github.com/robmorgan/timestretch-rs.git cd halo git checkout -b feat/ ``` -### 2. Compile fast (uses stable toolchain from rust-toolchain.toml) +### 2. Compile fast (uses stable toolchain) ```bash cargo check --workspace --all-targets ``` @@ -49,21 +54,21 @@ git push origin feat/ ## Project Layout -| Crate | Role | -| ------------------- | -------------------------------------------------------- | -| **`halo-core`** | Core lighting engine. | -| **`halo-fixtures`** | Fixture library and management. | -| **`halo`** | CLI and main entrypoint | -| **`halo-ui`** | UI Components and Interface. | +| Crate | Role | +| ----------------- | ----------------------------------------------------------------------------------------- | +| **`halo`** | The application: UI, decks, audio engine, mixer DSP, track library, DMX engine thread. | +| **`halo-light`** | UI-free lighting domain library: fixtures, cues, programmer resolution, Art-Net output. | -All crates live in one Cargo **workspace**, so `cargo ` from the repo root affects everything. +All crates live in one Cargo **workspace**, so `cargo ` from the repo root affects everything. The +[`timestretch`](https://github.com/robmorgan/timestretch-rs) engine is a path dependency expected at +`../timestretch-rs`, next to this repository. ----- ## Dev Environment * **Platform**: Development and execution require **macOS**. - * **Rust (Build/Test)**: **Stable** toolchain, MSRV pinned in `rust-toolchain.toml` (currently *1.90.0*). Install via [rustup.rs][rustup.rs]. This is used by default for `cargo build`, `cargo check`, `cargo test`, etc. + * **Rust (Build/Test)**: **Stable** toolchain (CI runs *1.90.0*). Install via [rustup.rs][rustup.rs]. This is used by default for `cargo build`, `cargo check`, `cargo test`, etc. * **Rust (Format)**: **Nightly** toolchain is required *only* for formatting (`cargo fmt`) due to unstable options used in our `rustfmt.toml` configuration. * Install via: `rustup toolchain install nightly` * **Rust Components**: `rustfmt`, `clippy` – install via `rustup component add rustfmt clippy`. Make sure these components are available for *both* your default stable toolchain and the nightly toolchain. @@ -107,7 +112,7 @@ All crates live in one Cargo **workspace**, so `cargo ` from the repo root 1. Sync with `main`; rebase preferred. 2. Ensure your code is formatted correctly with `cargo +nightly fmt --all`. -3. Ensure CI is green (build, fmt check, clippy, tests on macOS using appropriate toolchains). +3. Ensure CI is green (fmt check, build, and tests on macOS arm64 and Linux x86_64). 4. Fill out the PR template; explain *why* + *how*. 5. Respond to review comments promptly – we’re friendly, promise! 6. Maintainers will *Squash & Merge* (unless history is already clean). diff --git a/README.md b/README.md index 48b224a..5f4b67c 100644 --- a/README.md +++ b/README.md @@ -6,114 +6,113 @@

- Lighting console bringing advanced features to solo performances. + A two-deck DJ app with a built-in lighting console.

## About -⭕️ Halo is a real-time lighting console, designed to bring modern, immersive experiences into the hands of solo -performers. Traditional consoles are typically deployed at front of house (FOH) and require a dedicated lighting -designer. On the other hand, software designed for solo performers is often limited in features and is difficult to -operate during a live show. Halo bridges this gap through a combination of pre-defined cues, beat-synchronized -effects, and live improvisation through MIDI overrides. This enables performers to elevate their shows with immersive -lighting that responds to their performance. +**⭕️ Halo is a DJ app with a built-in lighting console, designed for solo performers who want to deliver immersive +live shows.** + +Mix across two decks while Halo drives the lighting rig in sync with your set. Lighting cues can be prepared per track, +then shaped and overridden live from a console-style programmer—so one performer can control the music, the lights, and +the energy of the room from a single app, without relying on a dedicated lighting operator. + +Halo is designed to integrate seamlessly with the Ableton Push 2, providing hands-on real-time control and visual +feedback without forcing you to perform through a mouse and keyboard. + +Built for macOS in Rust, Halo uses the [`timestretch`](https://github.com/robmorgan/timestretch-rs) engine for +real-time tempo and pitch control.

- Halo Screenshot + Halo Screenshot

> [!WARNING] > This project is still in heavy development and unsuitable for production use (even though I'm using it for shows). - ## Features -* **Intuitive UI.** Featuring a Dashboard, Programmer, Cue Editor, Patch Panel, Show Manager, and Settings panels. -* **Show File System.** Save and load complete shows with cues, effects, and fixture assignments. -* **Programmer.** Control lighting fixtures using a professional programmer interface with real-time feedback. -* **Cue System.** Create, save, and recall lighting scenes using cue lists with timecode synchronization. -* **Effect Engine.** Beat synchronized effects engine with sine, sawtooth and square patterns plus customizable parameters. -* **Pixel Engine.** Dedicated pixel engine for displaying various effects and colors on pixel bar fixtures. -* **Multi-destination Art-Net.** Route different fixture types (traditional lighting, pixels) to separate Art-Net nodes. -* **Audio Playback.** Integrated audio file playback synchronized with cues and Ableton Link. -* **SMPTE Timecode.** Both internal and external timecode synchronization for precise show timing and automation. -* **MIDI Integration.** Control your show with MIDI devices (currently supports Akai MPK49) with override system. -* **Configuration System.** Persistent settings with UI-based configuration panel for audio, MIDI, and DMX preferences. -* **Fixture Library.** Built-in support for various lighting fixtures with extensible fixture definitions. -* **Async Module Architecture.** Separate modules for DMX (44Hz output), Audio, MIDI, and Timecode running concurrently. +### DJ + +* **Dual decks** powered by one `timestretch` engine each: warm-start seeks, gapless loop wrap, and keylocked + tempo control. +* **Full mixer chain** per deck: trim → 3-band isolator EQ (full-kill) → resonant LP/HP filter → channel fader, + into a constant-power crossfader, master fader, and soft limiter. +* **CDJ-style transport**: play/pause and cue (set while paused, hold to preview, release to return). +* **Hot cues** — 8 per deck, with Normal and Gated modes and optional quantize to the beat grid. +* **Loops** — manual in/out, 4-beat quantized autoloop, and halve/double controls from 1/16 up to 16 beats. +* **Tempo & sync** — tempo slider with ±8/±16/±50% ranges, keylock, pitch-bend nudges, and one-button sync that + locks BPM and beat phase to a master deck. +* **Audible scrub** with momentum — grab the waveform and hear it, like dragging a platter. +* **Rich waveforms** — 3-band RGB overview strip and a zoomed, centered-playhead view with beat/bar marks, + rendered from background track analysis. +* **Track library** — SQLite-backed browser with playlists, search, sortable columns (BPM, key, duration, …), + folder import, and background analysis. BPM comes from analysis; musical key is read from file tags. +* **Prepare & Perform views** — audition tracks on an independent third channel and edit cues in Prepare, then + play the show in Perform. +* **Meters everywhere** — per-deck and master levels plus an audio-callback CPU meter. + +### Lighting + +* **Per-track cue lanes** (Lighting / Pixels / FX) under the waveforms, edited directly and persisted in the + library alongside the track. +* **Console-style programmer** — fixture grid, group selects, and Intensity/Color/Position/Beam/Pixel FX views + with beat-synced effects; latch or flash overrides sit above track cues, with STORE-from-live. +* **Real fixture engine** — a default rig patched from real fixture profiles, editable live in the PATCH tab + (profile, universe, address, grid position) and persisted to the library. +* **Art-Net output** — a dedicated 44 Hz engine thread resolves cues + programmer state into per-universe DMX + frames and sends them over Art-Net (broadcast or unicast to a node), independent of the UI. + +See [ROADMAP.md](ROADMAP.md) for the full feature arc and what's next. ## Requirements -* **macOS** (required for Core Audio and MIDI dependencies) -* **Rust toolchain** (cargo, rustc) - MSRV: 1.90.0 -* **Network interface for Art-Net output** (e.g., [Enttec ODE MK2](https://support.enttec.com/support/solutions/articles/101000438016-ode-mk2-70405-70406-)) -* **Optional:** MIDI controller (e.g., Akai MPK49, Novation Launch Control XL) -* **Optional:** Ableton Link compatible device/software for beat synchronization +* **macOS 12+** (Core Audio via cpal; the UI uses the wgpu backend) +* **Rust toolchain** (stable for building/testing; nightly only for `cargo +nightly fmt`) +* **[`timestretch-rs`](https://github.com/robmorgan/timestretch-rs)** cloned alongside this repo (path dependency) +* **Optional:** an Art-Net node and DMX fixtures for the lighting rig ## Installation +Halo depends on the `timestretch` crate by local path, so clone the two repos side by side: + ```bash git clone https://github.com/robmorgan/halo.git +git clone https://github.com/robmorgan/timestretch-rs.git cd halo cargo build --release ``` -## Usage - -### Basic Usage - -Start with Art-Net broadcast mode: +To build a macOS app bundle (`Halo.app`): ```bash -cargo run --release -- --source-ip +cargo install cargo-bundle +cargo bundle --release ``` -Load a show file: +## Usage ```bash -cargo run --release -- --source-ip --show-file shows/myshow.json +cargo run --release ``` -### Multi-Destination Setup - -Route lighting and pixel fixtures to separate Art-Net nodes: +Load tracks through the in-app library or file dialog, or pass a file directly to load it on launch: ```bash -cargo run --release -- --source-ip 192.168.1.100 \ - --lighting-dest-ip 192.168.1.200 \ - --pixel-dest-ip 192.168.1.201 \ - --enable-midi +cargo run --release -- path/to/track.mp3 ``` -See [docs/multi-destination-artnet.md](docs/multi-destination-artnet.md) for detailed multi-destination configuration. - -### Command Line Options - -```bash -USAGE: - halo [OPTIONS] - -OPTIONS: - --source-ip Art-Net source IP address (required) - --dest-ip Single destination IP (legacy, optional) - --lighting-dest-ip Lighting fixtures destination IP - --pixel-dest-ip Pixel fixtures destination IP - --lighting-universe Universe for lighting fixtures (default: 1) - --pixel-start-universe Starting universe for pixel fixtures (default: 2) - --artnet-port Art-Net port (default: 6454) - --broadcast Force broadcast mode - -m, --enable-midi Enable MIDI support - --show-file Path to show JSON file -``` +* **Prepare view** — import folders into the library, build playlists, audition tracks, and edit per-track + lighting cues. +* **Perform view** — two decks, mixer, and the lighting programmer. +* **Art-Net** — configure broadcast or unicast-to-node output in the in-app settings window; the choice is + persisted with the library. +* **Logs** — set `RUST_LOG=debug` (or another filter) when launching from a terminal. ## Documentation -For detailed documentation, see the [docs/](docs/) directory: - -* [Architecture Overview](docs/architecture.md) - System design and component structure -* [CLI Reference](docs/cli-reference.md) - Complete command-line interface documentation -* [Multi-Destination Art-Net](docs/multi-destination-artnet.md) - Setting up multiple Art-Net destinations -* [Troubleshooting Guide](docs/troubleshooting.md) - Common issues and solutions +* [ROADMAP.md](ROADMAP.md) — architecture overview, phased feature plan, and current status. ## License diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 189ddb1..0000000 --- a/TODO.md +++ /dev/null @@ -1,22 +0,0 @@ -# TODO - -## Input - -- [x] Use [crossterm](https://github.com/crossterm-rs/crossterm) for instant keyboard input. -- [ ] OSC - -## UI - -- [ ] Programmer - - [ ] Audio Playback - -## Engine - -- [ ] Cues - - [ ] Cue Master - - [ ] Cue Lists -- [ ] Presets (gobos, position, color) -- [ ] Color - - [ ] Interpolated Fades (note: your cheap PARs cant blend colors) -- [ ] Fixtures - - [ ] Groups diff --git a/_docs/screenshot.png b/_docs/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..abe710586284aaf92feecc5d11f42500a4946279 GIT binary patch literal 592079 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 PT-8mp)E*~h`^umb_lXSZ2=-gxkA3qa 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