Skip to content

Conversation

@robmorgan
Copy link
Owner

Summary

  • Introduces new halo-dj crate with comprehensive DJ functionality for live performance
  • Adds dual-deck audio playback with separate stereo outputs for external mixer mode
  • Implements BPM detection and beat grid analysis using FFT for automatic tempo sync
  • Includes SQLite library for track management with waveform caching
  • Supports hot cues, cue points, and tempo sync between decks
  • Adds MIDI controller support with TRAKTOR Z1 MK1 mappings
  • Integrates with lighting via RhythmState sync for beat-synchronized effects

Changes

New halo-dj crate

  • deck/ - Deck state management and playback control
  • library/ - Track database, BPM analysis, and waveform generation
  • module/ - Async DJ module with audio engine and deck player
  • midi/ - MIDI controller mappings (Z1 support)
  • Examples for testing audio playback and beat detection

Core integration

  • Extended ConsoleCommand with DJ deck control commands
  • Added ModuleEvent::DjCommand for routing to DJ module
  • Added tempo source (Internal/DJ) to RhythmState for DJ-driven lighting

UI additions

  • New DJ panel with deck controls (play, pause, cue, hot cues)
  • Track library browser with import functionality
  • Waveform display placeholder

Test plan

  • Verify crate compiles: cargo check --workspace
  • Test track import and library browsing
  • Test dual-deck playback with separate outputs
  • Verify BPM detection accuracy on known tracks
  • Test MIDI controller integration with Z1
  • Confirm lighting sync with DJ tempo source

🤖 Generated with Claude Code

robmorgan and others added 30 commits December 30, 2025 08:45
Introduces a new halo-dj crate with comprehensive DJ functionality:

- Dual-deck audio playback with separate stereo outputs (external mixer mode)
- BPM detection and beat grid analysis using FFT
- SQLite library for track management with waveform caching
- Hot cues and cue points for live performance
- Tempo sync between decks with master deck selection
- MIDI controller support (TRAKTOR Z1 MK1 mappings)
- Lighting integration via RhythmState sync

Core changes:
- Add DJ commands to ConsoleCommand for deck control
- Extend ModuleEvent with DjCommand routing
- Add tempo source (Internal/DJ) to RhythmState
- New DJ panel in UI with deck controls and library browser

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The DJ module was not being registered with the module manager,
causing all DJ commands (including folder import) to be sent to
a non-existent module and silently ignored.

Changes:
- Add `register_module` method to LightingConsole to allow
  registering additional modules after construction
- Add halo-dj dependency to the main halo crate
- Register DjModule in main.rs before console initialization
- Fix TrackId to i64 conversion in get_all_tracks_for_ui

The database should now be created at ~/.halo/library.db when
the DJ module initializes, and folder imports should work.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add new halo-push2 crate with full Push 2 integration:

Display (USB):
- 960x160 LCD via USB bulk transfer protocol
- BGR565 frame buffer with XOR encoding
- Dual-deck layout showing waveforms, track info, BPM, transport

MIDI:
- 64-pad mapping: top 4 rows for DJ (hot cues, transport), bottom 4 for lighting
- 8 encoders for pitch control and fixture parameters
- LED feedback reflecting console state

Integration:
- Push2Module implementing AsyncModule trait
- ModuleId::Push2 added to core traits
- DjCommand event routing from modules to console
- push2_enabled setting in UI

New console commands: DjSeekBeats, DjNudgePitch, ToggleAbletonLink

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
DJ Module:
- Add streaming waveform analysis for progressive display during loading
- Add cue preview support (press-and-hold to preview from cue point)
- Improve track loading with async waveform events
- Send DjWaveformProgress and DjWaveformLoaded events to UI

UI Improvements:
- Redesign deck display with larger waveform visualization
- Add waveform rendering with position indicator and cue markers
- Improve library panel with better track selection
- Add DJ state management for waveform data per deck
- Enhanced transport controls and time display

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add << and >> buttons for track navigation with CD player-style behavior:
- << (Previous): First press seeks to track start, second press loads previous track
- >> (Next): Loads next track in library
- Both buttons pause playback before navigating

Implementation:
- Add get_adjacent_track() to database for finding next/previous tracks by title
- Add DjPreviousTrack and DjNextTrack console commands
- Add PreviousTrack and NextTrack DJ module commands with full handling
- Add UI buttons positioned after Play/Pause and CUE

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add beat grid markers on deck waveform display with downbeat highlighting
- Automatically set cue point to first detected beat when track loads
- Seek playhead to first beat position on track load
- Add DjBeatGridLoaded event to propagate beat grid data to UI
- Works for both cached tracks (instant) and newly analyzed tracks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Click anywhere on waveform to seek to that position
- Show subtle shadow playhead when hovering to preview seek position
- Playback continues from new position if track is playing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add CDJ-3000 style Master Tempo functionality using SoundTouch
time-stretching library. When enabled, tempo changes via pitch fader
don't affect audio pitch (key lock). Also fixes BPM not updating in
UI when pitch slider is adjusted.

- Add SoundTouch dependency for WSOLA-based time-stretching
- Create TimeStretcher component wrapping SoundTouch for real-time use
- Integrate time-stretching into DeckPlayer with mode switching
- Add MasterTempoMode enum (Off = varispeed, On = time-stretch)
- Add M.TEMPO button and tempo range selector to DJ deck UI
- Add DjToggleMasterTempo and DjSetTempoRange console commands
- Add bpm field to DjDeckStateChanged events for live BPM updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The import_folder function was using import_directory which only
extracts metadata without running BPM analysis. Changed to use
import_and_analyze_directory which performs full audio analysis
including BPM detection, beat grid, and waveform generation.

Also fixes missing frequency_bands field in TrackWaveform and
ModuleEvent initializers.

Note: Existing tracks in the database will still show 120 BPM.
Re-import the music folder or clear the database to re-analyze.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add a toggle button to switch between overview and zoomed waveform view.
The zoomed view shows ~8 seconds of audio with the playhead fixed at 1/3
from the left (like a CDJ-3000), creating a scrolling "driving" effect
as the track plays.

Features:
- Toggle button with zoom icon to switch views
- Playhead stays fixed while waveform scrolls past
- Beat grid markers with downbeat highlighting
- Cue point and hot cue markers visible in zoomed window
- Time markers at window edges
- Click to seek within visible window
- Hover preview shows seek target time

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Check waveform version before using cached data
- Old waveforms (v1) without frequency_bands trigger re-analysis
- Add background analysis queue for non-blocking import
- Show analysis progress in footer status bar
- Add StatusClear and DjAnalysisProgress events

When loading a track with an outdated waveform, the system now
automatically re-analyzes it to generate 3-band frequency data
for CDJ-3000 style colored waveform display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Shows "Analyzing Track (3/10 - 30%)" format for clearer progress indication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…acks

When loading a track without colored waveform data, the footer now displays
"Analyzing <track name> (1/1 - 100%)" while the background analysis runs.
The status is cleared once analysis completes (or fails).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The time stretcher was feeding exactly 1 source sample per output request
regardless of tempo, causing audio to play incorrectly (too slow/fast).

Changes:
- Use fractional accumulation based on playback_rate to feed the correct
  number of source samples (e.g., ~2 samples at tempo 2.0, ~0.5 at 0.5)
- Pre-fill ~100ms of audio when enabling Master Tempo to reduce latency
- Reset fractional position when toggling Master Tempo mode
- Revert from ssstretch back to SoundTouch (WSOLA) for better quality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Major improvements to DJ sync functionality:

Master/Sync buttons:
- Master button now sends DjSetMaster command to backend
- Sync button sends DjToggleSync and triggers immediate BPM sync
- Buttons maintain visual state (lit when active)

BPM loading fix:
- Beat grid now properly passed to DeckPlayer (was only on Deck state)
- Fixed in track loading, cached waveform loading, and analysis completion
- Tracks now display correct analyzed BPM instead of 120 default

Beat phase alignment:
- Sync now jumps to align bar phase with master deck
- Wraps phase difference to find shortest path to alignment

Continuous sync (drift prevention):
- Added sync_enabled, sync_correction, base_playback_rate to DeckPlayer
- Audio engine calculates phase diff and applies proportional correction
- update_sync_corrections() called 30Hz from rhythm interval
- Correction limited to ±2% to avoid audible pitch artifacts
- Decks stay locked together instead of gradually drifting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Implement beat-quantized loop functionality with 4-beat and 8-beat loop
buttons plus a Reloop/Exit toggle. Loop IN point quantizes to nearest
beat, loop OUT is calculated as IN + (beat_count × beat_interval).

Features:
- LoopState struct tracking loop_in, loop_out, active, and beat_count
- BeatGrid helpers: nearest_beat() and beat_position_after()
- Sample-accurate loop wrap detection in DeckPlayer
- Loop region visualization in both overview and zoomed waveforms
- Green tint when active, gray when inactive, with IN/OUT markers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…tion

- Add right-click context menu to library tracks with:
  - Re-analyze BPM (queues track for background analysis)
  - Edit BPM manually (opens dialog to enter custom value)
  - Delete from library
  - Show in Finder (macOS) / Show in File Manager (Linux)
- Switch BPM detection from rustfft autocorrelation to SoundTouch BPMDetect
- Fix library not updating BPM values after track analysis completes
- Add file_path to DjTrackInfo for "Show in Finder" feature

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Move track analysis (BPM detection, waveform generation) to background
thread pool using tokio::task::spawn_blocking() to keep UI responsive.

Changes:
- Add analysis_handle, analysis_progress_rx, current_analysis_track
  fields to DjModule for tracking background tasks
- Create start_next_analysis() that spawns analysis with streaming
  waveform callback via unbounded channel
- Refactor run loop to poll for progress (non-blocking try_recv) and
  completion (non-blocking is_finished)
- Fix batch counter logic in import_folder/reanalyze_track to properly
  accumulate when adding to existing queue
- Remove old blocking process_analysis_queue_item() function
- Improve beat grid detection with bass-frequency onset detection
  (40-200 Hz) for better kick drum/downbeat alignment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Reduce tempo range options from 6 to 4, matching CDJ workflow:
- ±6%
- ±10%
- ±16%
- Wide (±100%, was ±50%)

Remove Range25 and Range100 variants, change Wide from ±50% to ±100%
for full tempo control. Also fix library analysis waveform events
(deck 255) incorrectly updating deck B display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
When sync is enabled, the deck now automatically matches the master's
tempo range and calculates the correct pitch to match BPM. Moving the
master's pitch fader updates synced decks in real-time.

- Add DjPitchChanged event for pitch/tempo state updates
- Match tempo range when enabling sync on a deck
- Real-time pitch following when master pitch changes
- Sync UI pitch slider position from backend state

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Fix USB frame format with correct 2048-byte line stride (was 1920)
- Add USB device enumeration for debugging connection issues
- Improve error reporting with detailed diagnostic output
- Broadcast DJ events to Push 2 module (deck loaded, state changes, waveform)
- Add non-blocking try_broadcast_event() for high-frequency position updates
- Drain event queue to prevent backlog and delayed updates
- Pre-compute waveform data with downsampling (100 samples/sec) and BGR565 colors
- Implement position interpolation for buttery smooth waveform scrolling
- Increase display refresh rate from 20fps to 40fps
- Time display and playhead now use interpolated positions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove `bpm` field from BeatGrid struct - BPM now only stored in tracks table
- Add `bpm` field to AnalysisResult for analysis output
- Add `base_bpm` field to DeckPlayer for playback calculations
- Update BeatGrid methods to take bpm as parameter
- Beat positions recalculated on-the-fly from first_beat_offset and track.bpm

Also includes:
- Fix beat grid nudge only working once (sync condition bug)
- Add Set Downbeat button to set first beat at current position
- Add Beat Shift buttons (+/- 1 beat) for grid adjustment
- Fix BPM edit not refreshing library list or loaded deck
- Fix track loading at wrong BPM (pitch/playback_rate not reset)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Switch from HFC to Energy onset mode for better kick drum detection
- Add dance-tempo-aware octave correction (prefers 80-160 BPM range)
- Implement multi-method consensus using Energy, SpecFlux, and FFT
- Add beat grid validation to catch BPM/interval mismatches
- Widen transient refinement window from 30ms to 50ms
- Update "Edit BPM" to "Fix BPM" which triggers full reanalysis

The previous HFC onset mode emphasized high frequencies (hi-hats)
rather than kick drums, causing ~1 BPM errors on house tracks.
The new multi-method approach runs three detection algorithms
and selects the best result based on agreement and confidence.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The Edit BPM dialog now allows manual BPM entry and triggers
reanalysis when saved to regenerate the beat grid based on
the user-specified tempo.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Implement Queen Mary-style BPM detection algorithm based on Mixxx/QM DSP:
- Complex Domain onset detection function
- 6-second windowed analysis with autocorrelation
- Perceptually-weighted comb filterbank
- Viterbi algorithm for optimal tempo path
- Dynamic programming beat tracking (Ellis 2007)

This provides more accurate BPM detection especially for electronic music.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add testing framework for validating BPM detection accuracy:
- BPM accuracy tests against known tracks
- Download script for test audio files (GiantSteps dataset)
- Test fixtures directory structure
- Feature flag 'accuracy-tests' to enable tests requiring audio files

Co-Authored-By: Claude Opus 4.5 <[email protected]>
robmorgan and others added 8 commits January 9, 2026 17:05
Replace CPU-based per-pixel waveform drawing with GPU textures:
- Pre-render waveform to texture (up to 8000px resolution)
- O(1) CPU work per frame instead of O(n) pixel iterations
- Smooth scrolling via UV coordinate manipulation
- Automatic texture updates when waveform data changes

Significantly improves UI performance especially for zoomed waveforms.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add opt-level=3 for heavy computational crates in dev builds:
- halo-dj, rustfft, aubio-rs (BPM detection)
- symphonia-* (audio decoding)
- rubato (resampling)

This makes track analysis run in seconds instead of minutes during
development, while keeping the rest of the app in debug mode.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Reset all deck state when a new track is loaded:
- is_playing = false (fixes play/pause button not resetting)
- waiting_for_quantized_start = false
- cue_point = None
- loop_in/loop_out = None, loop_active = false

Previously, dropping a track onto a playing deck would leave the
play button showing "pause" even though the new track was stopped.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add vendored QM-DSP TempoTrackV2 library with Rust FFI bindings for
accurate BPM detection. This improves octave-tolerant accuracy from
53.6% to 84.5% on the giantsteps-tempo dataset.

Changes:
- Vendor minimal QM-DSP sources (TempoTrackV2, MathUtilities, kissfft)
- Add C wrapper for easy FFI (wrapper.h/cpp)
- Add build.rs to compile C++ when qm-native feature is enabled
- Add Rust FFI bindings (qm_native.rs)
- Integrate native detector into analysis pipeline
- Add qm-native feature flag to Cargo.toml

Usage: cargo build --features qm-native

Note: QM-DSP is GPL-2.0 licensed. The qm-native feature is optional
to allow building without C++ toolchain or GPL dependency.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Improve Rust QM-DSP reimplementation with transient alignment scoring
to help resolve tempo ambiguities like the 2/3 ratio problem.

Changes:
- Add alignment_score field to QmTempoResult
- Add validate_tempo_with_alignment() to check related tempos
- Add adaptive whitening and median filtering to autocorrelation
- Use transient alignment to validate and correct detected tempo

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Change waveform rendering from filled vertical bars to an outline
envelope style with smooth diagonal connections between adjacent peaks.

Changes:
- Two-pass rendering: calculate heights first, then draw connections
- Connect adjacent column peaks for smooth envelope outline
- Add subtle dimmed fill inside the waveform envelope
- Improve visual appearance similar to professional DJ software

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…entation

- Set qm-native as default feature in Cargo.toml
- Remove conditional compilation guards from analysis.rs
- Delete qm_tempo.rs (Rust QM-DSP reimplementation)
- Update mod.rs to remove qm_tempo module exports
- Remove unused helper functions (calculate_onset_envelope, find_first_beat)

BPM detection now always uses the vendored C++ QM-DSP library,
achieving 84.5% octave-tolerant accuracy on giantsteps-tempo.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants