gled-core 2.32.1

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! The engine's MIDI: the host of the controllers plugged into this machine,
//! fed with the engine's state every tick, and the tools interfaces use
//! while configuring controllers: the monitor of incoming traffic, MIDI
//! learn and test outputs. The host streams traffic only while an interface
//! shows the monitor, and captures learn input only while one waits for it.

use std::{collections::BTreeSet, sync::Arc};

use gled_midi::{
    HostEvent, MidiRuntimeSnapshot, MidiSource, SnapshotCache, live_values, native::NativeHost,
};
use gled_protocol::midi::TestCommand;
use kanal::Receiver;

use super::{ClientId, Engine, LearnLog, MidiLog};
use crate::action::UiAction;

/// What the controller mappings were built from: the project's `Arc`, which
/// the engine replaces only when it changes, and the assets' generation.
type SnapshotKey = (Option<usize>, u64);

#[derive(Debug)]
pub(super) struct MidiTools {
    host: NativeHost,
    events: Receiver<HostEvent>,
    snapshots: SnapshotCache<SnapshotKey>,
    output_ports: Vec<String>,
    input_ports: Vec<String>,
    watchers: BTreeSet<ClientId>,
    learners: BTreeSet<ClientId>,
    pub(super) monitor_log: Arc<MidiLog>,
    pub(super) learn_log: Arc<LearnLog>,
}

impl MidiTools {
    pub(super) fn new(host: NativeHost, events: Receiver<HostEvent>) -> Self {
        Self {
            host,
            events,
            snapshots: SnapshotCache::default(),
            output_ports: Vec::new(),
            input_ports: Vec::new(),
            watchers: BTreeSet::new(),
            learners: BTreeSet::new(),
            monitor_log: Arc::default(),
            learn_log: Arc::default(),
        }
    }

    pub(super) fn watch(&mut self, id: ClientId, watch: bool) {
        set(&mut self.watchers, id, watch);
        self.update_gates();
    }

    pub(super) fn learning(&mut self, id: ClientId, learning: bool) {
        set(&mut self.learners, id, learning);
        self.update_gates();
    }

    pub(super) fn disconnect(&mut self, id: ClientId) {
        self.watchers.remove(&id);
        self.learners.remove(&id);
        self.update_gates();
    }

    fn update_gates(&self) {
        self.host
            .gates()
            .set(!self.watchers.is_empty(), !self.learners.is_empty());
    }

    pub(super) fn test(&self, command: TestCommand) {
        self.host.test(command);
    }

    /// The output ports of this machine's controllers.
    pub(super) fn output_ports(&self) -> &[String] {
        &self.output_ports
    }

    /// The input ports of this machine's controllers.
    pub(super) fn input_ports(&self) -> &[String] {
        &self.input_ports
    }

    /// Applies what the controllers did and moves what the monitor and
    /// learn saw into the logs interfaces read.
    pub(super) fn tick(&mut self) {
        while let Ok(Some(event)) = self.events.try_recv() {
            match event {
                HostEvent::Actions(actions) => actions.into_iter().for_each(UiAction::enqueue),
                HostEvent::Monitor(event) => Arc::make_mut(&mut self.monitor_log).push(event),
                HostEvent::Learn(message) => Arc::make_mut(&mut self.learn_log).push(message),
                HostEvent::Error(message) => UiAction::Error(message).enqueue(),
                HostEvent::InputPorts(ports) => self.input_ports = ports,
                HostEvent::OutputPorts(ports) => {
                    if ports.is_empty() != self.output_ports.is_empty() {
                        UiAction::MidiOutputActive(!ports.is_empty()).enqueue();
                    }
                    self.output_ports = ports;
                }
            }
        }
    }
}

impl Engine {
    /// Hands the controllers this tick's state.
    pub(super) fn publish_midi(&mut self) {
        let project = self.project.as_deref();
        let key = (
            self.project
                .as_ref()
                .map(|project| Arc::as_ptr(project) as usize),
            self.collections.generation(),
        );
        let collections = &self.collections;
        let snapshot = self
            .midi
            .snapshots
            .get(key, || MidiRuntimeSnapshot::build(project, collections));
        let beat_progression = self.timing.beat_progression();
        let live = live_values::selected_scene_live_values(
            project,
            self.selected_scene_instance,
            collections,
            &self.sound_data,
            beat_progression,
        );
        let source = MidiSource {
            project: self.project.clone(),
            selected_scene_instance: self.selected_scene_instance,
            selected_scene_effect: self
                .effect_selection
                .effect_in(self.selected_scene_instance),
            selected_scene_live: Arc::new(live),
            blackout: self.blackout || self.blackout_hold,
            beats_per_minute: self.timing.beats_per_minute(),
            beat_progression,
            midi_modes: self.controllers.modes().clone(),
        };
        self.midi.host.publish(source, snapshot);
    }
}

fn set(clients: &mut BTreeSet<ClientId>, id: ClientId, member: bool) {
    if member {
        clients.insert(id);
    } else {
        clients.remove(&id);
    }
}