5.7 KiB
GUI Architecture
This document defines the high-level architectural decisions for the real-time display system. The display must provide smooth visual feedback for metronome position, column progress, and track states while maintaining responsive updates from OSC messages without blocking the rendering thread.
Overview
The GUI architecture uses two threads to maintain responsive visual updates. The OSC receiver thread handles incoming state messages from the audio engine and updates shared state. The GUI thread runs the egui rendering loop and interpolates time-based animations independently.
These subsystems communicate through Tokio watch channels for efficient state synchronization. The audio engine acts as an OSC server, with the display connecting as a client and automatically reconnecting on failures.
graph LR
AudioEngine[Audio Engine<br/>OSC Server<br/>Unix Socket]
OSCThread[OSC Receiver Thread<br/>tokio + rosc<br/>State Updates]
GUIThread[GUI Thread<br/>egui Rendering<br/>Animation Interpolation]
AudioEngine -->|OSC Messages| OSCThread
OSCThread -->|Watch Channel| GUIThread
subgraph "Display Process"
OSCThread
GUIThread
end
The system receives state updates through standardized OSC messages and renders them using a hierarchical state structure. Time-critical animations like metronome movement and progress bars use local interpolation to maintain smooth 60fps updates independent of the OSC message frequency.
Technology Stack
The display uses egui as the immediate mode GUI framework, providing excellent performance for real-time updates and straightforward integration with Rust's async ecosystem.
Core Libraries:
- egui: Immediate mode GUI framework for rendering
- tokio: Async runtime for OSC message handling
- rosc: OSC message parsing and Unix socket communication
- tokio::sync::watch: Lock-free state sharing between threads
Threading Architecture
OSC Receiver Thread
The OSC receiver thread manages the Unix socket connection to the audio engine and processes incoming state messages. This thread runs a tokio async loop that handles connection establishment, message parsing, and state updates.
GUI Thread
The GUI thread owns the egui context and handles all rendering operations. This thread polls the watch channel receiver for state changes and triggers repaints when updates arrive. Time interpolation for smooth animations happens during the render loop using stored timestamps and elapsed time calculations.
State Management
Hierarchical State Structure
The display state mirrors the audio engine's organization to maintain consistency and simplify message parsing.
DisplayState
├── global
│ ├── mode: OperationMode (Menu | Performance)
│ ├── tempo: f32 (BPM)
│ ├── selected_cell: (usize, usize)
│ ├── click_enabled: bool
│ ├── click_volume: f32
│ └── master_volume: f32
├── columns: [ColumnState; 5]
│ ├── beats: usize (Total beats, 0 = not set)
│ ├── current_beat: usize (1-based position)
│ └── tracks: [TrackState; 5]
│ ├── state: TrackDisplayState (Empty | Ready | Recording | Playing | Solo)
│ └── volume: f32
└── metronome
├── position: f32 (0.0-1.0 within current beat)
└── timestamp: Instant (When received)
State Updates
The OSC receiver thread modifies the state directly based on incoming messages and sends the complete updated state through the watch channel. This approach ensures atomic updates and eliminates complex synchronization logic.
OSC Message Processing
Message Parsing Flow
Incoming OSC messages map directly to state field updates using a straightforward parsing pipeline. Initial connection triggers a complete state dump from the audio engine using the same message format as regular updates.
Time-Sensitive Messages
Metronome position messages include timestamp capture to enable smooth interpolation in the GUI thread. This allows the display to show fluid metronome movement despite receiving position updates at MIDI PPQN rate rather than display refresh rate.
Time Interpolation Strategy
Metronome Animation
The metronome requires smooth movement between received position updates to maintain visual continuity. Time interpolation happens during the render loop by calculating elapsed time since the last position update and advancing the visual position accordingly. This maintains frame-rate independence and ensures smooth animations regardless of system load.
Column Progress Animation
Column progress bars use similar interpolation to show smooth advancement between beat boundaries. The current beat position provides the discrete timing reference, with interpolation filling the gaps for fluid visual feedback.
Connection Management
Client Connection Pattern
The display connects to the audio engine's Unix socket server and handles connection lifecycle automatically. Connection failures trigger retry attempts to avoid overwhelming the system during startup sequences.
Error States and Recovery
The GUI displays connection status to provide user feedback during system startup or audio engine restarts. Connection recovery happens transparently in the background, with the GUI resuming normal operation once the OSC stream resumes.
Process Lifecycle
The display process operates independently of the audio engine, connecting as needed and handling temporary disconnections gracefully. External process management tools handle startup coordination and service lifecycle, keeping the display implementation focused on its core visualization responsibilities.