Skip to content

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 20 Mar 04:33
· 60 commits to main since this release
fd878a5

Added

  • Zero-config start: mtrack start mtrack.yaml now works even when the config file doesn't
    exist. A default config is created automatically, the songs directory is created if missing,
    and the player starts idle with the web UI and gRPC available. No playlist file is required —
    the player falls back to an alphabetized all-songs playlist (which may be empty).
  • Empty profile fallback: When no hardware profiles match the current hostname, the player
    starts with a synthetic empty profile (no audio/MIDI/DMX) instead of exiting with an error.
  • config::Player Default impl: Produces a minimal config (songs: songs) suitable for
    bootstrapping a new installation.
  • Project directory mode: mtrack start now accepts a project directory instead of a config
    file path. When given a directory, it looks for mtrack.yaml inside it. When given a file
    (or a path with a .yaml/.yml extension), it uses it directly for backwards compatibility.
    The default is . (current directory), so bare mtrack start works as a zero-config entry point.
  • Song upload API: New REST endpoints for uploading track files to songs:
    • PUT /api/songs/{name}/tracks/{filename} — upload a single file (binary body)
    • POST /api/songs/{name}/tracks — upload multiple files via multipart form
    • Uploading to a new song name auto-creates the song directory and generates song.yaml
    • Subsequent uploads preserve existing song.yaml (track names, lighting config, etc.)
  • Song creation API: POST /api/songs/{name} creates a new song with a user-provided
    config YAML. This allows setting track names, lighting shows, and MIDI config before
    uploading any audio files. Returns 409 Conflict if the song already exists.
  • Broader audio format support: Song auto-discovery and track uploads now accept all
    audio formats supported by symphonia (FLAC, MP3, OGG, AAC, M4A, AIFF) in addition to WAV.
  • Lighting file uploads: .light DSL files can be uploaded alongside audio and MIDI
    files. Song::initialize() now auto-discovers .light files and includes them in the
    generated song.yaml.
  • Hardware profile editor UI: The web UI config page now includes a full hardware profile
    editor. Profiles can be created, edited, and deleted with dedicated sections for audio
    (device, sample rate, format, buffer size, track mappings), MIDI (device, beat clock),
    DMX (OLA host/port, universe mappings), and controllers (gRPC, OSC). Changes are saved
    with optimistic concurrency and trigger automatic hardware reinitialization.
  • Song browser UI: A new song management page in the web UI provides:
    • Song list with create/delete
    • Song detail view with track editor (rename tracks, assign lighting shows)
    • Per-track waveform visualization
    • File upload (drag-and-drop or file picker) for audio, MIDI, and lighting files
    • Server-side file browser for importing songs from the local filesystem
    • Bulk song import from a directory
  • Non-blocking hardware initialization: Audio, MIDI, and DMX devices are now discovered
    asynchronously in the background at startup. The player, web UI, and gRPC server become
    available immediately while hardware init retries perpetually until devices are found.
    This eliminates startup delays when hardware is slow to enumerate or temporarily
    unavailable (e.g. USB devices not yet plugged in).
  • Hardware hot-reload on profile save: Saving a hardware profile through the config
    editor web UI now automatically reinitializes all hardware from the updated configuration.
    The old hardware is torn down and new devices are discovered asynchronously, just like at
    startup. Hardware reload is rejected during active playback (returns 409 Conflict).
  • Samples configuration UI: The config editor now includes a Samples section for managing
    the samples map in the player configuration. Each sample can be configured with file path,
    output channels, output track, release behavior, retrigger mode, max voices, fade time, and
    velocity settings (ignore/scale/layers with per-layer configuration). Samples are saved with
    the same optimistic concurrency as hardware profiles.
  • Sample file upload and browse: Sample audio files can be uploaded via drag-and-drop or
    file picker, or imported from the server filesystem using the file browser. Uploaded files
    are stored in a samples/ directory alongside the config file. Supports WAV, FLAC, MP3,
    OGG, AAC, M4A, AIFF formats. New REST endpoints: PUT /api/config/samples for updating
    sample definitions, PUT /api/samples/upload/{filename} for uploading sample files.
  • Trigger configuration UI: The profile editor now includes a Triggers tab for configuring
    audio and MIDI trigger inputs. Each input can be configured with device, channel, threshold,
    gain, scan/retrigger timing, velocity curve, sample assignment, and release groups. Audio
    inputs support calibration directly from the UI.
  • Lighting configuration UI: Fixture types and venues can now be created, edited, renamed,
    and deleted directly from the web UI without a text editor. The profile editor's Lighting tab
    provides three sub-views:
    • Fixture Types: Visual editor for channel maps (name + DMX offset) and strobe settings
    • Venues: Visual editor for fixtures (type, universe, channel, tags) with fixture type
      dropdowns populated from loaded definitions
    • Profile Settings: Directory overrides, current venue selection, inline fixtures, and
      logical group configuration with constraint editors (AllOf, AnyOf, Prefer, MinCount,
      MaxCount, FallbackTo, AllowEmpty)
  • Lighting REST API: Ten new endpoints for fixture type and venue CRUD:
    GET/PUT/DELETE /api/lighting/fixture-types/{name},
    GET /api/lighting/fixture-types,
    GET/PUT/DELETE /api/lighting/venues/{name},
    GET /api/lighting/venues. All endpoints accept an optional ?dir= parameter for
    directory overrides. PUT endpoints accept structured JSON (converted to .light DSL) or
    raw DSL text, and validate by parsing before writing.
  • Tag input component: A reusable chip-style tag editor used in venue fixture tags and
    lighting group constraints. Supports typing (Enter/comma to add), backspace to remove,
    paste with auto-split, and sanitizes input to [a-z0-9_-] to prevent syntax errors.
  • Profile editor tab layout: The hardware profile editor now uses horizontal tabs
    (Audio, MIDI, DMX, Lighting, Triggers, Controllers) instead of stacked collapsible
    sections, reducing visual clutter. Each tab shows a green dot when that section is enabled.
  • Lighting timeline editor: A DAW-style visual editor for lighting cue authoring,
    replacing the previous form-based editor. The editor is song-driven — the left panel lists
    songs, and selecting one loads its waveform and all associated .light files into a
    horizontal scrollable timeline. Features include:
    • Time ruler with absolute timestamps and measure/beat grid (when tempo is defined)
    • Song waveform reference track
    • Per-show cue lanes with color-coded draggable cue blocks
    • Click to select, double-click to add, drag to reposition with snap-to-grid
    • Compact single-line effect display that expands for full editing
    • Sequence editing in a dedicated modal with its own timeline
    • Legacy MIDI DMX file management (upload and import from server filesystem)
    • Zoom, fit-to-view, and beat/measure snap controls
  • Song file import API: New POST /api/songs/{name}/import endpoint copies a file from
    the server filesystem into a song directory. Source paths are validated against the project
    root to prevent path traversal.
  • Song creation UI: Songs can now be created from the web UI song browser with a name
    and optional configuration.
  • Multiple playlist support: The player now supports multiple user-defined playlists
    stored as individual YAML files in a playlists/ directory (default: {config_dir}/playlists/).
    Playlists are named after their filename stem. The all_songs playlist remains always
    present and auto-generated. Switching to all_songs is session-only (not persisted across
    restarts), acting as a temporary escape hatch. The persisted active playlist is stored in
    mtrack.yaml via the active_playlist field (defaults to "playlist" for backward
    compatibility). MIDI/OSC Playlist action returns to the persisted active playlist,
    regardless of its name.
  • Playlist REST API: Five new endpoints for playlist CRUD:
    GET /api/playlists (list all with song count and active status),
    GET /api/playlists/{name} (songs + available songs),
    PUT /api/playlists/{name} (create or update),
    DELETE /api/playlists/{name} (delete, refuses all_songs),
    POST /api/playlists/{name}/activate (switch active playlist).
    Legacy /api/playlist GET/PUT endpoints remain as backward-compatible aliases.
  • Playlist editor UI: The web UI playlist page is now a full editor with a left panel
    for browsing, creating, and deleting playlists, and a right panel for editing song order
    (reorder, add, remove) with a searchable available-songs list. Playlists can be activated
    directly from the editor.
  • Dashboard playlist selector: The dashboard playlist card now shows a dropdown of all
    available playlists instead of a hardcoded Playlist/All Songs toggle. The available
    playlists and active playlist name are broadcast via WebSocket.
  • Timeline playback from editor: The lighting timeline editor now supports full audio
    playback with synchronized lighting preview. Users can click the ruler to set a play
    position, then play from that point to hear audio and see lighting effects update in
    real time. Features include:
    • Full transport controls: skip to start, stop (reset), play/pause toggle, skip to end
    • Spacebar play/pause toggle, Home/End keyboard shortcuts for skip
    • Green playhead line across ruler and all show lanes during playback
    • Dashed green cursor marker showing where playback will start
    • Pause remembers playhead position for resume; Stop resets to beginning
    • Auto-save of dirty lighting files before playback starts
    • Client-side requestAnimationFrame interpolation for smooth playhead movement
    • Toolbar time display: green during playback, dimmed at play cursor position
  • Stage preview in timeline editor: A compact stage visualization is displayed alongside
    the cue properties panel at the bottom of the timeline editor. Shows real-time fixture
    RGB output with glow and strobe animation, plus active effect names. Fixtures can be
    rearranged by dragging, same as the dashboard stage view.
  • PlaySongFrom gRPC endpoint: New PlaySongFrom RPC accepts a song name and start
    time, switches to the all_songs playlist, navigates to the song, and begins playback.
    This enables the timeline editor to play any song from any position in a single call.
  • Playlist::navigate_to(name): New method sets the playlist position to the song
    matching the given name, returning the song if found.
  • Bulk song import: New POST /api/browse/bulk-import endpoint recursively scans
    subdirectories of a given path and creates song.yaml in each one that contains
    audio files and doesn't already have a song.yaml. Nested structures (artist/album/song)
    are handled automatically. The song browser's import UI shows an "Import All
    Subdirectories" button with a results summary showing created, skipped, and failed imports.
  • Song deletion: New DELETE /api/songs/{name} endpoint removes a song by deleting its
    song.yaml. Audio and other files are preserved. The song is automatically removed from
    any playlists that reference it. Cannot delete a song that is currently playing.
  • Nested song creation: POST /api/songs/{name} now accepts paths with slashes
    (e.g. Artist/Album/Song), creating nested directory structures automatically.
  • Lock mode: The player starts in locked mode by default, blocking all state-altering
    operations (song edits, playlist changes, config updates, file uploads) via a middleware
    layer. Playback controls always work. Toggle via PUT /api/lock or the lock icon in
    the web UI nav bar. Lock state is broadcast via WebSocket.
  • SafePath module: Centralized path verification (src/webui/safe_path.rs) with
    SafePath, VerifiedRoot, and SafePathError types. All REST API handlers that touch
    the filesystem now use SafePath for canonicalize + starts_with containment verification,
    replacing bespoke per-handler implementations.

Changed

  • Binary uses library crate: main.rs now imports from mtrack:: instead of
    re-declaring all modules. This eliminates double-compilation and ensures the binary
    runs the same code that tests verify.

  • Song path resolution via registry: put_song, upload_track_single,
    upload_tracks_multipart, and import_file_to_song now look up the song's actual
    directory from the player registry before falling back to a flat songs_path/name join.
    This correctly handles songs in nested subdirectories.

  • Zero-config songs path: Fresh mtrack.yaml files created during zero-config startup
    now default to songs: . (project root) instead of conditionally choosing between
    songs and .. This ensures bulk-imported songs are always discoverable.

  • Playlist::current(), next(), and prev() now return Option<Arc<Song>> instead of
    Arc<Song>, returning None when the playlist is empty. All callers (player, controllers,
    web UI, TUI) handle the empty case gracefully — controllers return appropriate error statuses,
    fire-and-forget handlers skip silently, and the web UI sends minimal "no song" state.

  • The start command no longer requires a playlist file to be specified. When absent, it falls
    back to an all-songs playlist.

  • The start command's positional argument has been renamed from player_path to path and
    now defaults to . (current directory). Existing usage with a config file path continues to
    work unchanged.

  • PUT /api/songs/{name} now locates songs by directory name rather than requiring audio files
    to be present. Songs created via POST /api/songs/{name} (config-only, no audio yet) can
    be updated immediately.

  • The systemd service template now uses $MTRACK_PATH instead of $MTRACK_CONFIG to reflect
    the new directory-or-file semantics.

  • The Player struct now uses a HashMap<String, Arc<Playlist>> internally instead of
    separate playlist / all_songs / use_all_songs fields. switch_to_playlist() accepts
    any playlist name (not just "playlist" or "all_songs"). The gRPC SwitchToPlaylist RPC
    accepts arbitrary playlist names — invalid names return NOT_FOUND instead of
    UNIMPLEMENTED.

  • Mutable configuration store: A new ConfigStore wraps the player configuration in a
    tokio::sync::RwLock, enabling runtime config mutations with optimistic concurrency control
    via whole-config checksums. Mutations are persisted atomically to the YAML config file on
    every change. The store is accessible from the Player via player.config_store().

  • Config gRPC RPCs: Eight new RPCs on PlayerService for reading and mutating configuration
    at runtime: GetConfig, UpdateAudio, UpdateMidi, UpdateDmx, UpdateControllers,
    AddProfile, UpdateProfile, RemoveProfile. Stale checksums return FAILED_PRECONDITION.

  • Config REST endpoints: New REST API endpoints for config mutations:
    GET /api/config/store, PUT /api/config/{audio,midi,dmx,controllers},
    POST /api/config/profiles, PUT /api/config/profiles/:index,
    DELETE /api/config/profiles/:index. Stale checksums return 409 Conflict.

  • Config change notifications: WebSocket clients receive config_changed messages when
    the configuration is mutated, enabling real-time UI updates without polling.

  • Config round-trip test: A serialize/deserialize round-trip test validates that
    config::Player survives YAML serialization via yaml-rust2 without data loss.

  • Config store checksums use SHA-256 (via sha2 crate) instead of hand-rolled FNV-1a for
    clarity and industry-standard collision resistance.

  • config::Player and config::TrackMappings now derive Clone, enabling the config store
    to snapshot the full configuration.

  • config::Player gained setter methods (set_audio, set_midi, set_dmx,
    set_controllers, profiles_mut) for structured config mutations.

Fixed

  • Playback elapsed time accuracy: play_start_time is now set inside play_files
    immediately after clock.start(), rather than in play_from before subsystem setup.
    Previously, the variable setup time (loading audio buffers, seeking, DMX timeline
    reconstruction) was included in the reported elapsed time, causing lighting and audio
    to drift apart when seeking to mid-song positions.
  • Timeline zoom stability: Toolbar zoom (+/- buttons) now correctly anchors on the
    center of the content area, accounting for the 80px label column. Previously, each zoom
    step shifted the center by a fraction of 40px, causing compounding drift that could move
    the view by many measures when zooming deeply.
  • Timeline zoom during rapid scrolling: Zoom operations now read scrollLeft directly
    from the DOM and suppress the RAF-debounced scroll handler during zoom, preventing stale
    values from corrupting the anchor point during rapid zoom-in/out.
  • Playback status reporting: OSC broadcast and gRPC Status RPC now use
    player.is_playing() (join handle check) instead of elapsed().is_some() to determine
    playing state. This prevents a brief false "Stopped" report during the startup window
    between play_from returning and clock.start() firing.
  • Play rejected during initialization: play_from() now returns an error if hardware
    hasn't finished initializing, preventing silent no-output playback.
  • PlaySongFrom playlist switch is session-only: The editor's play-from-position switches
    to all_songs for the duration of playback (required for correct WebSocket state
    broadcasting) but the switch is not persisted — the user's real playlist is restored
    on restart.
  • Playhead interpolation drift cap: Client-side playhead interpolation is capped at 2
    seconds of drift, preventing large backward jumps when the WebSocket reconnects after a
    long disconnect.
  • Deleting playing song blocked: DELETE /api/songs/{name} returns 409 Conflict if the
    song is currently playing.
  • Playlist cleanup on song deletion: When a song is deleted, all playlist YAML files are
    updated to remove the deleted song, preventing broken references on restart.
  • DSL serializer trailing commas: The lighting DSL serializer no longer emits trailing
    commas in tempo change lists, which caused parse errors on re-read.
  • DSL serializer empty groups: Effects with empty group names are skipped during
    serialization instead of producing invalid DSL like ": static".
  • DSL serializer dimmer separator: All effect types now use comma-separated parameters.
    Previously, dimmer effects incorrectly used space separation.
  • Default effect group: New effects created from the timeline editor default to group
    "all" instead of an empty string.
  • Upload error visibility: MIDI upload error messages are now displayed on the MIDI tab
    (previously only shown on the Tracks tab).
  • Upload body size limit removed: The axum default 2MB body limit is disabled for API
    routes, allowing upload of large audio files.
  • File replacement confirmation: Uploading a file that already exists prompts for
    confirmation and shows "Replaced" instead of "Uploaded" in the success message.
  • WebSocket disconnect indicator: The lighting editor shows a yellow warning banner when
    the WebSocket connection is lost.
  • Chase direction/pattern dropdowns: Chase effect direction uses the correct spatial
    directions (not "forward"/"backward"), and pattern is now a dropdown instead of free text.
  • Effect group dropdown: The effect group field is now a text input with a datalist
    populated from the venue's fixture groups, supporting both selection and free-text entry.
  • Sample upload path injection: The sample file upload endpoint now canonicalizes the
    project root before constructing filesystem paths, preventing path traversal via crafted
    filenames.
  • Web UI asset tests now discover embedded filenames dynamically instead of hardcoding
    hashed filenames that change with every Svelte build.