use async_channel::{self, Receiver, Sender}; use async_std::prelude::*; use async_std::task::{self, JoinHandle}; use rosc::OscMessage; use std::collections::VecDeque; use thiserror::Error; use async_osc::prelude::*; #[derive(Error, Debug)] pub enum SwitcherError { #[error("Channel for switcher events full")] CannotSendEvent(#[from] async_channel::SendError), #[error("Channel for OSC messages full")] CannotSendMessage(#[from] async_channel::SendError), #[error("Decode OSC packet failed")] Osc(rosc::OscError), #[error("IO error")] Io(#[from] std::io::Error), } impl From for SwitcherError { fn from(error: rosc::OscError) -> Self { Self::Osc(error) } } pub type Result = std::result::Result; pub type ChannelId = usize; #[derive(Debug)] pub enum SwitcherEvent { Enable(ChannelId), Disable(ChannelId), } #[derive(Default)] pub struct SwitcherState { // osc_tx: Sender, channels: Vec, active_channel: usize, messages: Messages, } #[derive(Default)] pub struct ChannelState { id: ChannelId, pub name: String, pub locked: bool, pub enabled: bool, } impl ChannelState { pub fn new(id: ChannelId) -> Self { Self { id, ..Default::default() } } } pub struct SwitcherHandle { events_tx: Sender, osc_rx: Option>, task: JoinHandle>, } impl SwitcherHandle { pub fn run() -> Self { let (osc_tx, osc_rx) = async_channel::unbounded(); let (events_tx, events_rx) = async_channel::unbounded(); let state = SwitcherState::new(); let task = task::spawn(run_loop(state, events_rx, osc_tx)); SwitcherHandle { events_tx, osc_rx: Some(osc_rx), task, } } pub async fn join(self) -> Result<()> { self.task.await } pub async fn send(&self, event: SwitcherEvent) -> Result<()> { log::debug!("switcher got event: {:?}", event); self.events_tx.send(event).await?; Ok(()) } pub fn sender(&self) -> Sender { self.events_tx.clone() } pub fn take_receiver(&mut self) -> Option> { self.osc_rx.take() } } pub async fn run_loop( mut state: SwitcherState, mut events_rx: Receiver, osc_tx: Sender, ) -> Result<()> { state.init(); loop { while let Some(message) = state.pop_message() { osc_tx.send(message).await?; } if let Some(event) = events_rx.next().await { state.on_event(event); } } } impl SwitcherState { pub fn new() -> Self { Default::default() } pub fn init(&mut self) { let num_channels = 4; for i in 0..num_channels { let channel = ChannelState::new(i); self.channels.push(channel); } self.messages.push("/*", ("xmit", 1)); self.messages.push("/mixer/out/0/bus/0/on", (1.0,)); self.messages.push("/mixer/bus/0/ch/0/on", (1.0,)); self.update_state(); } pub fn on_event(&mut self, event: SwitcherEvent) { match event { SwitcherEvent::Enable(channel) => self.enable_channel(channel), SwitcherEvent::Disable(channel) => self.disable_channel(channel), } self.update_state(); } pub fn enable_channel(&mut self, channel_id: ChannelId) { if let Some(channel) = self.channels.iter_mut().find(|c| c.id == channel_id) { if !channel.locked { channel.enabled = true; } } } pub fn disable_channel(&mut self, channel_id: ChannelId) { if let Some(channel) = self.channels.iter_mut().find(|c| c.id == channel_id) { if !channel.locked { channel.enabled = false; } } } fn update_state(&mut self) { let next_active_channel = self .channels .iter() .rev() .find(|c| c.enabled) .or(self.channels.get(0)) .unwrap() .id; if next_active_channel != self.active_channel { self.active_channel = next_active_channel; self.on_channel_change(); } } fn on_channel_change(&mut self) { for channel in self.channels.iter() { if channel.id == self.active_channel { self.messages .push(format!("/mixer/bus/0/ch/{}/on", channel.id), (1.0,)); } else { self.messages .push(format!("/mixer/bus/0/ch/{}/on", channel.id), (0.0,)); } } } fn pop_message(&mut self) -> Option { self.messages.pop() } } pub struct Messages { inner: VecDeque, } impl Default for Messages { fn default() -> Self { Self::new() } } impl Messages { pub fn new() -> Self { Self { inner: VecDeque::new(), } } fn push(&mut self, addr: impl ToString, args: T) where T: IntoOscArgs, { self.inner.push_back(OscMessage::new(addr, args)); } fn pop(&mut self) -> Option { self.inner.pop_front() } }