gled-core 2.34.0

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! Evaluating input events (keys, gamepad, Art-Net) against the current input
//! state, for the project's trigger events and the scene instances' inputs.

use std::sync::Arc;

use crate::{
    input::{Input, event::InputEvent},
    storage::asset::{
        curve::multiplied_curve::{MultipliedCurve, RangePercentage},
        project::Project,
        scene::instance::SceneInstance,
    },
};

/// The state of an input event right now.
pub(crate) trait InputEventState {
    /// Started this tick.
    fn is_new(&self) -> bool;
    /// Held down, or above zero for a fader.
    fn is_live(&self) -> bool;
    /// Let go this tick, or back to zero for a fader.
    fn is_released(&self) -> bool;
    /// The event as a dimmer value in 0..=1.
    fn dimmer(&self) -> f32;
}

impl InputEventState for InputEvent {
    fn is_new(&self) -> bool {
        Input::is_event_new(self)
    }

    fn is_live(&self) -> bool {
        Input::event_live_value(self).is_some()
    }

    fn is_released(&self) -> bool {
        Input::is_event_released(self)
    }

    fn dimmer(&self) -> f32 {
        Input::event_live_value(self)
            .map(|value| f32::from(value) / 255.0)
            .unwrap_or(0.0)
    }
}

/// What a scene instance's activation, flash and dimmer inputs set.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SceneInputState {
    active: bool,
    flash: bool,
    beat_progression_offset: MultipliedCurve<RangePercentage>,
    input_dimmer: f32,
}

/// The activation, flash and dimmer inputs of a scene instance.
pub(crate) trait SceneInstanceInputs {
    /// What the input events make of the instance this tick, without
    /// changing it. Runs on the engine thread, once per `Input::tick`, so
    /// one-tick "new" events are seen exactly once.
    fn next_input_state(&self, beat_progression: f32) -> SceneInputState;
    fn input_state(&self) -> SceneInputState;
    fn set_input_state(&mut self, state: SceneInputState);
}

impl SceneInstanceInputs for SceneInstance {
    fn next_input_state(&self, beat_progression: f32) -> SceneInputState {
        let mut next = self.input_state();
        if let Some(event) = self.activation_input.as_ref()
            && event.is_new()
        {
            next.active = !next.active;
        }

        if let Some(event) = self.flash_input.as_ref() {
            let pressed = event.is_new() && event.is_live();
            if !self.flash && pressed && self.set_offset_on_flash {
                next.beat_progression_offset =
                    MultipliedCurve::new_multiplier(4.0 - beat_progression % 4.0);
            }
            next.flash = flash_after_input(self.flash, pressed, event.is_released());
        }

        if let Some(event) = self.dimmer_input.as_ref() {
            next.input_dimmer = event.dimmer()
        }
        next
    }

    fn input_state(&self) -> SceneInputState {
        SceneInputState {
            active: self.active,
            flash: self.flash,
            beat_progression_offset: self.beat_progression_offset.clone(),
            input_dimmer: self.input_dimmer,
        }
    }

    fn set_input_state(&mut self, state: SceneInputState) {
        let SceneInputState {
            active,
            flash,
            beat_progression_offset,
            input_dimmer,
        } = state;
        self.active = active;
        self.flash = flash;
        self.beat_progression_offset = beat_progression_offset;
        self.input_dimmer = input_dimmer;
    }
}

/// The flash after its input: a press starts it and a release ends it.
/// In between it is left alone, so a flash from MIDI, OSC or the interface
/// stays while the input rests.
fn flash_after_input(flash: bool, pressed: bool, released: bool) -> bool {
    if pressed {
        true
    } else if released {
        false
    } else {
        flash
    }
}

/// Applies the activation, flash and dimmer input events of every scene
/// instance. The project is shared with the render thread and the
/// interfaces, so it is copied (`Arc::make_mut`) only on a tick an input
/// actually changes it: held faders and unused inputs leave it shared.
pub(crate) fn update_input_state(project: &mut Arc<Project>, beat_progression: f32) {
    let changes: Vec<_> = project
        .scenes_instances_grid
        .iter()
        .filter_map(|(location, instance)| {
            let next = instance.next_input_state(beat_progression);
            (next != instance.input_state()).then_some((*location, next))
        })
        .collect();
    if changes.is_empty() {
        return;
    }
    let project = Arc::make_mut(project);
    for (location, state) in changes {
        if let Some(instance) = project.scenes_instances_grid.get_mut(&location) {
            instance.set_input_state(state);
        }
    }
}

/// The project's trigger inputs.
pub(crate) trait ProjectInputs {
    fn tap_input_is_new(&self) -> bool;
    fn blackout_input_is_new(&self) -> bool;
    fn blackout_hold_input_is_live(&self) -> bool;
    fn half_input_is_new(&self) -> bool;
    fn double_input_is_new(&self) -> bool;
}

impl ProjectInputs for Project {
    fn tap_input_is_new(&self) -> bool {
        self.tap_input_events.iter().any(|event| event.is_new())
    }

    fn blackout_input_is_new(&self) -> bool {
        self.blackout_input_events
            .iter()
            .any(|event| event.is_new())
    }

    fn blackout_hold_input_is_live(&self) -> bool {
        self.blackout_hold_input_events
            .iter()
            .any(|event| event.is_live())
    }

    fn half_input_is_new(&self) -> bool {
        self.half_input_events.iter().any(|event| event.is_new())
    }

    fn double_input_is_new(&self) -> bool {
        self.double_input_events.iter().any(|event| event.is_new())
    }
}

#[cfg(test)]
mod tests {
    use super::flash_after_input;

    #[test]
    fn a_resting_flash_input_keeps_a_flash_from_elsewhere() {
        assert!(flash_after_input(true, false, false));
        assert!(!flash_after_input(false, false, false));
    }

    #[test]
    fn the_flash_input_starts_and_ends_the_flash() {
        assert!(flash_after_input(false, true, false));
        assert!(!flash_after_input(true, false, true));
    }
}