//! Application state. use crate::{core::*, devices::{arranger::*, sequencer::*, transport::*}}; submod! { axis phrase scene track } /// Root of application state. pub struct App { /// Optional modal dialog pub modal: Option>, /// Whether the currently focused section has input priority pub entered: bool, /// Currently focused section pub section: AppFocus, /// Transport model and view. pub transport: TransportToolbar, /// Arranger model and view. pub arranger: Arranger, /// Phrase editor pub sequencer: Sequencer, /// Main JACK client. pub jack: Option, /// Map of external MIDI outs in the jack graph /// to internal MIDI ins of this app. pub midi_in: Option>>, /// Names of ports to connect to main MIDI IN. pub midi_ins: Vec, /// Display mode of chain section pub chain_mode: bool, /// Paths to user directories _xdg: Option>, /// Main audio outputs. pub audio_outs: Vec>>, /// Number of frames requested by process callback chunk_size: usize, } impl App { pub fn new () -> Usually { let xdg = Arc::new(microxdg::XdgApp::new("tek")?); let first_run = crate::config::AppPaths::new(&xdg)?.should_create(); let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0); Ok(Self { modal: first_run.then(||{ Exit::boxed(crate::config::SetupModal(Some(xdg.clone()), false)) }), entered: true, section: AppFocus::default(), transport: TransportToolbar::new(Some(jack.transport())), arranger: Arranger::new(), sequencer: Sequencer::new(), jack: Some(jack), audio_outs: vec![], chain_mode: false, chunk_size: 0, midi_in: None, midi_ins: vec![], _xdg: Some(xdg), }) } } process!(App |self, _client, scope| { let ( reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs ) = self.transport.update(&scope); self.chunk_size = chunk_size; for track in self.arranger.tracks.iter_mut() { track.process( self.midi_in.as_ref().map(|p|p.iter(&scope)), &self.transport.timebase, self.transport.playing, self.transport.started, self.transport.quant as usize, reset, &scope, (current_frames as usize, self.chunk_size), (current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize), period_usecs as f64 ); } Control::Continue }); impl App { pub fn client (&self) -> &Client { self.jack.as_ref().unwrap().client() } pub fn audio_out (&self, index: usize) -> Option>> { self.audio_outs.get(index).map(|x|x.clone()) } pub fn with_midi_ins (mut self, names: &[&str]) -> Usually { self.midi_ins = names.iter().map(|x|x.to_string()).collect(); Ok(self) } pub fn with_audio_outs (mut self, names: &[&str]) -> Usually { let client = self.client(); self.audio_outs = names .iter() .map(|name|client .ports(Some(name), None, PortFlags::empty()) .get(0) .map(|name|client.port_by_name(name))) .flatten() .filter_map(|x|x) .map(Arc::new) .collect(); Ok(self) } pub fn activate (mut self, init: Option>)->Usually<()>>) -> Usually>> { let jack = self.jack.take().expect("no jack client"); let app = Arc::new(RwLock::new(self)); app.write().unwrap().jack = Some(jack.activate(&app.clone(), |state, client, scope|{ state.write().unwrap().process(client, scope) })?); if let Some(init) = init { init(&app)?; } Ok(app) } } /// Different sections of the UI that may be focused. #[derive(PartialEq, Clone, Copy)] pub enum AppFocus { /// The transport is selected. Transport, /// The arranger is selected. Arranger, /// The sequencer is selected. Sequencer, /// The device chain is selected. Chain, } impl Default for AppFocus { fn default () -> Self { Self::Arranger } } impl AppFocus { pub fn prev (&mut self) { *self = match self { Self::Transport => Self::Chain, Self::Arranger => Self::Transport, Self::Sequencer => Self::Arranger, Self::Chain => Self::Sequencer, } } pub fn next (&mut self) { *self = match self { Self::Transport => Self::Arranger, Self::Arranger => Self::Sequencer, Self::Sequencer => Self::Chain, Self::Chain => Self::Transport, } } }