use std::{collections::BTreeSet, sync::Arc};
use gled_midi::{
HostEvent, MidiRuntimeSnapshot, MidiSource, SnapshotCache,
client::{ActionSink, ClientHost, ClientMidi, Frame},
live_values, port_matches,
};
use gled_protocol::{
action::UiAction,
message::ClientMessage,
midi::TestCommand,
state::{ClientInput, EngineEvent},
};
use super::App;
use crate::ui::frame_requests::LocalMidiStatus;
#[derive(Debug)]
pub(crate) struct LocalMidi {
host: ClientHost,
snapshots: SnapshotCache<(u64, u64)>,
input_ports: Vec<String>,
output_ports: Vec<String>,
test_device_here: bool,
}
impl LocalMidi {
pub(super) fn status(&self) -> LocalMidiStatus {
LocalMidiStatus {
availability: Some(self.host.availability()),
ports: self
.input_ports
.iter()
.chain(&self.output_ports)
.cloned()
.collect::<BTreeSet<_>>(),
}
}
}
impl App {
pub(super) fn drive_local_midi(&mut self, ctx: &egui::Context) {
if self.local_midi.is_none() && self.connection.is_remote() {
let ctx = ctx.clone();
let direct = self.connection.background_sender().map(|send| {
Box::new(move |actions: Vec<UiAction>| {
for action in actions {
send(ClientMessage::Input(ClientInput::Action {
sequence: 0,
action,
}));
}
}) as ActionSink
});
let mut host = ClientHost::new(move || ctx.request_repaint(), direct);
if self.settings.midi_access_wanted() {
host.request_access();
}
self.local_midi = Some(LocalMidi {
host,
snapshots: SnapshotCache::default(),
input_ports: Vec::new(),
output_ports: Vec::new(),
test_device_here: false,
});
}
let Some(local) = &mut self.local_midi else {
return;
};
let replica = &self.model.replica;
let state = &replica.state;
let project = state.project.as_deref();
let snapshot = local
.snapshots
.get((replica.project_changes, replica.assets_changes), || {
MidiRuntimeSnapshot::build(project, &replica.assets)
});
let beat_progression = self.timing.beat_progression_now();
let live = live_values::selected_scene_live_values(
project,
state.selected_scene_instance,
&replica.assets,
&*self.model.sound,
beat_progression,
);
let source = MidiSource {
project: state.project.clone(),
selected_scene_instance: state.selected_scene_instance,
selected_scene_effect: state.selected_scene_effect,
selected_scene_live: Arc::new(live),
blackout: state.blackout || state.blackout_hold,
beats_per_minute: self.timing.beats_per_minute(),
beat_progression,
midi_modes: state.midi_modes.clone(),
};
for event in local.host.tick(Frame { source, snapshot }) {
match event {
HostEvent::Actions(actions) => {
for action in actions {
ClientInput::Action {
sequence: 0,
action,
}
.enqueue();
}
}
HostEvent::Monitor(event) => {
let _ = self.midi_monitor_sender.send(event);
}
HostEvent::Learn(message) => self.midi_learn_state.feed(message),
HostEvent::Error(message) => self.model.events.push(EngineEvent::Error(message)),
HostEvent::InputPorts(ports) => local.input_ports = ports,
HostEvent::OutputPorts(ports) => local.output_ports = ports,
}
}
self.midi_output_active |= !local.output_ports.is_empty();
}
pub(super) fn set_local_midi_gates(&self, watching: bool, learning: bool) {
if let Some(local) = &self.local_midi {
local.host.gates().set(watching, learning);
}
}
pub(super) fn request_local_midi_access(&mut self) {
if let Some(local) = &mut self.local_midi {
local.host.request_access();
self.settings.want_midi_access();
}
}
pub(super) fn route_test_command(&mut self, command: TestCommand) {
let Some(local) = &mut self.local_midi else {
ClientInput::MidiTest(command).enqueue();
return;
};
match &command {
TestCommand::SetTestDevice { port_name } => {
local.test_device_here = local
.output_ports
.iter()
.any(|port| port_matches(port, port_name));
let other = TestCommand::UnsetTestDevice;
if local.test_device_here {
ClientInput::MidiTest(other).enqueue();
} else {
local.host.test(other);
}
}
TestCommand::UnsetTestDevice => {
local.host.test(TestCommand::UnsetTestDevice);
ClientInput::MidiTest(command).enqueue();
return;
}
TestCommand::SendOutput { .. } | TestCommand::ClearOutput { .. } => {}
}
if local.test_device_here {
local.host.test(command);
} else {
ClientInput::MidiTest(command).enqueue();
}
}
}