mtrack 0.12.0

A multitrack audio and MIDI player for live performances.
Documentation
// Copyright (C) 2026 Michael Wilson <mike@mdwn.dev>
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, version 3.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/>.
//
syntax = "proto3";

package player.v1;

import "google/protobuf/duration.proto";

// BeatGrid contains beat positions and measure boundaries detected from
// a click track. Used for snapping loop points to musically meaningful positions.
message BeatGrid {
    // Absolute time in seconds of each detected beat.
    repeated double beats = 1;
    // Indices into beats that mark measure boundaries (accented beats).
    repeated uint32 measure_starts = 2;
}

// Section defines a named region of a song by measure boundaries.
message Section {
    // Display name for this section.
    string name = 1;
    // Start measure (1-indexed, inclusive).
    uint32 start_measure = 2;
    // End measure (1-indexed, exclusive).
    uint32 end_measure = 3;
}

// Song is a message that contains information about a song.
message Song {
    reserved 4;
    reserved "is_transcoded";

    // Name is the name of the song.
    string name = 1;

    // Duration is the length of the song.
    google.protobuf.Duration duration = 2;

    // Tracks are the tracks available for the song.
    repeated string tracks = 3;

    // Beat grid derived from click track analysis.
    BeatGrid beat_grid = 5;

    // Named sections defined by measure boundaries.
    repeated Section sections = 6;
}

// PlayRequest is the message for requesting the player to play the current song.
message PlayRequest {}

// PlayResponse is the response message after playing a song.
message PlayResponse {
    // Song is the song that is now playing.
    Song song = 1;
}

// PreviousRequest is the message for requesting the player to move to the previous
// song.
message PreviousRequest {}

// PreviousResponse is the response message after moving to the previous song.
message PreviousResponse {
    // Song is the song the playlist is now pointing at.
    Song song = 1;
}

// NextRequest is the message for requesting the player to move to the next
// song.
message NextRequest {}

// NextResponse is the response message after moving to the next song.
message NextResponse {
    // Song is the song the playlist is now pointing at.
    Song song = 1;
}

// StopRequest is the message for requesting the player to stop the currently
// playing song.
message StopRequest {}

// StopResponse is the response message after requesting the player to stop.
message StopResponse {
    // Song is the song that was stopped.
    Song song = 1;
}

// SwitchToPlaylistRequest is the message for requesting the player to switch
// to a different playlist.
message SwitchToPlaylistRequest {
    // PlaylistName is the name of the playlist to switch to.
    string playlist_name = 1;
}

// SwitchToPlaylistResponse is the response message after requesting the player
// to switch to a different playlist.
message SwitchToPlaylistResponse {}

// StatusRequest is the message for requesting a streaming status from the player.
message StatusRequest {}

// StatusResponse is the response message after requesting the status from the player.
message StatusResponse {
    // PlaylistName is the name of the current playlist.
    string playlist_name = 1;

    // CurrentSong is the current song that the current playlist is pointing to.
    Song current_song = 2;

    // Playing is true while a song is playing.
    bool playing = 3;

    // Elapsed is the amount of time that has elapsed while playing the song.
    google.protobuf.Duration elapsed = 4;
}

// PlayFromRequest is the message for requesting the player to play from a specific time.
message PlayFromRequest {
    // StartTime is the time to start playback from (e.g., "1:23.456" or "45.5s").
    // If not provided or zero, plays from the beginning.
    google.protobuf.Duration start_time = 1;
}

// PlaySongFromRequest is the message for requesting the player to play a specific song
// from a specific time.
message PlaySongFromRequest {
    // SongName is the name of the song to play.
    string song_name = 1;
    // StartTime is the time to start playback from.
    google.protobuf.Duration start_time = 2;
}

// GetCuesRequest is the message for requesting the list of cues in the current song.
message GetCuesRequest {}

// Cue represents a single cue in the lighting timeline.
message Cue {
    // Time is the time at which this cue triggers.
    google.protobuf.Duration time = 1;
    // Index is the index of this cue in the timeline.
    uint32 index = 2;
}

// GetCuesResponse is the response containing the list of cues.
message GetCuesResponse {
    // Cues is the list of cues in the current song's lighting timeline.
    repeated Cue cues = 1;
}

// GetActiveEffectsRequest is the message for requesting active lighting effects.
message GetActiveEffectsRequest {}

// GetActiveEffectsResponse is the response containing active lighting effects information.
message GetActiveEffectsResponse {
    // ActiveEffects is a formatted string listing all active lighting effects.
    string active_effects = 1;
}

// StopSamplesRequest is the message for requesting the player to stop all triggered samples.
message StopSamplesRequest {}

// StopSamplesResponse is the response message after stopping triggered samples.
message StopSamplesResponse {}

// GetConfigRequest is the message for requesting the current configuration.
message GetConfigRequest {}

// GetConfigResponse is the response containing the current configuration.
message GetConfigResponse {
    // The full configuration as a YAML string.
    string yaml = 1;
    // Checksum of the current configuration for optimistic concurrency.
    string checksum = 2;
}

// UpdateAudioRequest is the message for updating the audio configuration.
message UpdateAudioRequest {
    // The audio configuration as a JSON string.
    string audio_json = 1;
    // Expected checksum for optimistic concurrency.
    string expected_checksum = 2;
}

// UpdateMidiRequest is the message for updating the MIDI configuration.
message UpdateMidiRequest {
    // The MIDI configuration as a JSON string.
    string midi_json = 1;
    // Expected checksum for optimistic concurrency.
    string expected_checksum = 2;
}

// UpdateDmxRequest is the message for updating the DMX configuration.
message UpdateDmxRequest {
    // The DMX configuration as a JSON string.
    string dmx_json = 1;
    // Expected checksum for optimistic concurrency.
    string expected_checksum = 2;
}

// UpdateControllersRequest is the message for updating the controllers configuration.
message UpdateControllersRequest {
    // The controllers configuration as a JSON string (array).
    string controllers_json = 1;
    // Expected checksum for optimistic concurrency.
    string expected_checksum = 2;
}

// AddProfileRequest is the message for adding a new profile.
message AddProfileRequest {
    // The profile as a JSON string.
    string profile_json = 1;
    // Expected checksum for optimistic concurrency.
    string expected_checksum = 2;
}

// UpdateProfileRequest is the message for updating a profile at a specific index.
message UpdateProfileRequest {
    // The index of the profile to update.
    uint32 index = 1;
    // The profile as a JSON string.
    string profile_json = 2;
    // Expected checksum for optimistic concurrency.
    string expected_checksum = 3;
}

// RemoveProfileRequest is the message for removing a profile at a specific index.
message RemoveProfileRequest {
    // The index of the profile to remove.
    uint32 index = 1;
    // Expected checksum for optimistic concurrency.
    string expected_checksum = 2;
}

// UpdateConfigResponse is the response after a configuration mutation.
message UpdateConfigResponse {
    // The full configuration as a YAML string after the update.
    string yaml = 1;
    // New checksum after the update.
    string checksum = 2;
}

// LoopSectionRequest activates section looping for a named section.
message LoopSectionRequest {
    // The name of the section to loop.
    string section_name = 1;
}

// LoopSectionResponse is returned after activating a section loop.
message LoopSectionResponse {}

// StopSectionLoopRequest deactivates section looping.
message StopSectionLoopRequest {}

// StopSectionLoopResponse is returned after stopping a section loop.
message StopSectionLoopResponse {}

// SectionAckRequest acknowledges the current section in reactive looping mode.
message SectionAckRequest {}

// SectionAckResponse is returned after acknowledging a section.
message SectionAckResponse {}

// PlayerService is a service for controlling the mtrack player.
service PlayerService {
    // Play will play the current song in the playlist if no other songs
    // are playing.
    rpc Play(PlayRequest) returns (PlayResponse);

    // PlayFrom will play the current song starting from a specific time.
    rpc PlayFrom(PlayFromRequest) returns (PlayResponse);

    // PlaySongFrom will play a specific song starting from a specific time.
    rpc PlaySongFrom(PlaySongFromRequest) returns (PlayResponse);

    // Previous will move the playlist to the previous song.
    rpc Previous(PreviousRequest) returns (PreviousResponse);

    // Next will move the playlist to the next song.
    rpc Next(NextRequest) returns (NextResponse);

    // Stop will stop the currently playing song.
    rpc Stop(StopRequest) returns (StopResponse);

    // SwitchToPlaylist will switch the player to a different playlist.
    rpc SwitchToPlaylist(SwitchToPlaylistRequest) returns (SwitchToPlaylistResponse);

    // Status will return the current status of the player.
    rpc Status(StatusRequest) returns (StatusResponse);


    // GetCues returns the list of cues in the current song's lighting timeline.
    rpc GetCues(GetCuesRequest) returns (GetCuesResponse);

    // GetActiveEffects returns a formatted string listing all active lighting effects.
    rpc GetActiveEffects(GetActiveEffectsRequest) returns (GetActiveEffectsResponse);

    // StopSamples will stop all triggered samples that are currently playing.
    rpc StopSamples(StopSamplesRequest) returns (StopSamplesResponse);

    // GetConfig returns the current configuration as YAML with a checksum.
    rpc GetConfig(GetConfigRequest) returns (GetConfigResponse);

    // UpdateAudio updates the audio configuration section.
    rpc UpdateAudio(UpdateAudioRequest) returns (UpdateConfigResponse);

    // UpdateMidi updates the MIDI configuration section.
    rpc UpdateMidi(UpdateMidiRequest) returns (UpdateConfigResponse);

    // UpdateDmx updates the DMX configuration section.
    rpc UpdateDmx(UpdateDmxRequest) returns (UpdateConfigResponse);

    // UpdateControllers updates the controllers configuration.
    rpc UpdateControllers(UpdateControllersRequest) returns (UpdateConfigResponse);

    // AddProfile adds a new hardware profile.
    rpc AddProfile(AddProfileRequest) returns (UpdateConfigResponse);

    // UpdateProfile updates a profile at a specific index.
    rpc UpdateProfile(UpdateProfileRequest) returns (UpdateConfigResponse);

    // RemoveProfile removes a profile at a specific index.
    rpc RemoveProfile(RemoveProfileRequest) returns (UpdateConfigResponse);

    // LoopSection activates section looping for the named section.
    rpc LoopSection(LoopSectionRequest) returns (LoopSectionResponse);

    // StopSectionLoop deactivates section looping. The current iteration
    // finishes and the song continues from the section end.
    rpc StopSectionLoop(StopSectionLoopRequest) returns (StopSectionLoopResponse);

    // SectionAck acknowledges the current section in reactive looping mode,
    // arming the loop so it engages at the section end.
    rpc SectionAck(SectionAckRequest) returns (SectionAckResponse);
}