fcb_looper/gui.md

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.