v0.11.0
Added
- Zero-config start:
mtrack start mtrack.yamlnow 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::PlayerDefault impl: Produces a minimal config (songs: songs) suitable for
bootstrapping a new installation.- Project directory mode:
mtrack startnow accepts a project directory instead of a config
file path. When given a directory, it looks formtrack.yamlinside it. When given a file
(or a path with a.yaml/.ymlextension), it uses it directly for backwards compatibility.
The default is.(current directory), so baremtrack startworks 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:
.lightDSL files can be uploaded alongside audio and MIDI
files.Song::initialize()now auto-discovers.lightfiles and includes them in the
generatedsong.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
thesamplesmap 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 asamples/directory alongside the config file. Supports WAV, FLAC, MP3,
OGG, AAC, M4A, AIFF formats. New REST endpoints:PUT /api/config/samplesfor 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.lightDSL) 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.lightfiles 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}/importendpoint 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 aplaylists/directory (default:{config_dir}/playlists/).
Playlists are named after their filename stem. Theall_songsplaylist remains always
present and auto-generated. Switching toall_songsis session-only (not persisted across
restarts), acting as a temporary escape hatch. The persisted active playlist is stored in
mtrack.yamlvia theactive_playlistfield (defaults to"playlist"for backward
compatibility). MIDI/OSCPlaylistaction 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, refusesall_songs),
POST /api/playlists/{name}/activate(switch active playlist).
Legacy/api/playlistGET/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
requestAnimationFrameinterpolation 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. PlaySongFromgRPC endpoint: NewPlaySongFromRPC accepts a song name and start
time, switches to theall_songsplaylist, 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-importendpoint recursively scans
subdirectories of a given path and createssong.yamlin each one that contains
audio files and doesn't already have asong.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 viaPUT /api/lockor 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, andSafePathErrortypes. 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.rsnow imports frommtrack::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, andimport_file_to_songnow look up the song's actual
directory from the player registry before falling back to a flatsongs_path/namejoin.
This correctly handles songs in nested subdirectories. -
Zero-config songs path: Fresh
mtrack.yamlfiles created during zero-config startup
now default tosongs: .(project root) instead of conditionally choosing between
songsand.. This ensures bulk-imported songs are always discoverable. -
Playlist::current(),next(), andprev()now returnOption<Arc<Song>>instead of
Arc<Song>, returningNonewhen 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
startcommand no longer requires a playlist file to be specified. When absent, it falls
back to an all-songs playlist. -
The
startcommand's positional argument has been renamed fromplayer_pathtopathand
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 viaPOST /api/songs/{name}(config-only, no audio yet) can
be updated immediately. -
The systemd service template now uses
$MTRACK_PATHinstead of$MTRACK_CONFIGto reflect
the new directory-or-file semantics. -
The
Playerstruct now uses aHashMap<String, Arc<Playlist>>internally instead of
separateplaylist/all_songs/use_all_songsfields.switch_to_playlist()accepts
any playlist name (not just"playlist"or"all_songs"). The gRPCSwitchToPlaylistRPC
accepts arbitrary playlist names — invalid names returnNOT_FOUNDinstead of
UNIMPLEMENTED. -
Mutable configuration store: A new
ConfigStorewraps 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 thePlayerviaplayer.config_store(). -
Config gRPC RPCs: Eight new RPCs on
PlayerServicefor reading and mutating configuration
at runtime:GetConfig,UpdateAudio,UpdateMidi,UpdateDmx,UpdateControllers,
AddProfile,UpdateProfile,RemoveProfile. Stale checksums returnFAILED_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_changedmessages 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::Playersurvives YAML serialization viayaml-rust2without data loss. -
Config store checksums use SHA-256 (via
sha2crate) instead of hand-rolled FNV-1a for
clarity and industry-standard collision resistance. -
config::Playerandconfig::TrackMappingsnow deriveClone, enabling the config store
to snapshot the full configuration. -
config::Playergained setter methods (set_audio,set_midi,set_dmx,
set_controllers,profiles_mut) for structured config mutations.
Fixed
- Playback elapsed time accuracy:
play_start_timeis now set insideplay_files
immediately afterclock.start(), rather than inplay_frombefore 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
scrollLeftdirectly
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 ofelapsed().is_some()to determine
playing state. This prevents a brief false "Stopped" report during the startup window
betweenplay_fromreturning andclock.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
toall_songsfor 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.