//! Application state. use crate::{core::*, devices::{arranger::*, sequencer::*, transport::*}}; /// 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::devices::setup::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, } } } /// A sequencer track. pub struct Track { pub name: String, /// Play input through output. pub monitoring: bool, /// Write input to sequence. pub recording: bool, /// Overdub input to sequence. pub overdub: bool, /// Map: tick -> MIDI events at tick pub phrases: Vec>>, /// Phrase selector pub sequence: Option, /// Output from current sequence. pub midi_out: Option>, /// MIDI output buffer midi_out_buf: Vec>>, /// Device chain pub devices: Vec, /// Device selector pub device: usize, /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) /// Highlight keys on piano roll. pub notes_in: [bool;128], /// Highlight keys on piano roll. pub notes_out: [bool;128], } impl Track { pub fn new (name: &str) -> Usually { Ok(Self { name: name.to_string(), midi_out: None, midi_out_buf: vec![Vec::with_capacity(16);16384], notes_in: [false;128], notes_out: [false;128], monitoring: false, recording: false, overdub: true, sequence: None, phrases: vec![], devices: vec![], device: 0, reset: true, }) } fn get_device_mut (&self, i: usize) -> Option>> { self.devices.get(i).map(|d|d.state.write().unwrap()) } pub fn device_mut (&self) -> Option>> { self.get_device_mut(self.device) } pub fn connect_first_device (&self) -> Usually<()> { if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) { device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?; } Ok(()) } pub fn connect_last_device (&self, app: &App) -> Usually<()> { Ok(match self.devices.get(self.devices.len().saturating_sub(1)) { Some(device) => { app.audio_out(0).map(|left|device.connect_audio_out(0, &left)).transpose()?; app.audio_out(1).map(|right|device.connect_audio_out(1, &right)).transpose()?; () }, None => () }) } pub fn add_device (&mut self, device: JackDevice) -> Usually<&mut JackDevice> { self.devices.push(device); let index = self.devices.len() - 1; Ok(&mut self.devices[index]) } pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; } pub fn toggle_record (&mut self) { self.recording = !self.recording; } pub fn toggle_overdub (&mut self) { self.overdub = !self.overdub; } pub fn process ( &mut self, input: Option, timebase: &Arc, playing: Option, started: Option<(usize, usize)>, quant: usize, reset: bool, scope: &ProcessScope, (frame0, frames): (usize, usize), (_usec0, _usecs): (usize, usize), period: f64, ) { if self.midi_out.is_some() { // Clear the section of the output buffer that we will be using for frame in &mut self.midi_out_buf[0..frames] { frame.clear(); } // Emit "all notes off" at start of buffer if requested if self.reset { all_notes_off(&mut self.midi_out_buf); self.reset = false; } else if reset { all_notes_off(&mut self.midi_out_buf); } } if let ( Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase) ) = ( playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id)) ) { phrase.read().map(|phrase|{ if self.midi_out.is_some() { phrase.process_out( &mut self.midi_out_buf, &mut self.notes_out, timebase, (frame0.saturating_sub(start_frame), frames, period) ); } }).unwrap(); let mut phrase = phrase.write().unwrap(); let length = phrase.length; // Monitor and record input if input.is_some() && (self.recording || self.monitoring) { // For highlighting keys and note repeat for (frame, event, bytes) in parse_midi_input(input.unwrap()) { match event { LiveEvent::Midi { message, .. } => { if self.monitoring { self.midi_out_buf[frame].push(bytes.to_vec()) } if self.recording { phrase.record_event({ let pulse = timebase.frame_to_pulse( (frame0 + frame - start_frame) as f64 ); let quantized = ( pulse / quant as f64 ).round() as usize * quant; let looped = quantized % length; looped }, message); } match message { MidiMessage::NoteOn { key, .. } => { self.notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { self.notes_in[key.as_int() as usize] = false; }, _ => {} } }, _ => {} } } } } else if input.is_some() && self.midi_out.is_some() && self.monitoring { for (frame, event, bytes) in parse_midi_input(input.unwrap()) { self.process_monitor_event(frame, &event, bytes) } } if let Some(out) = &mut self.midi_out { write_midi_output(&mut out.writer(scope), &self.midi_out_buf, frames); } } #[inline] fn process_monitor_event (&mut self, frame: usize, event: &LiveEvent, bytes: &[u8]) { match event { LiveEvent::Midi { message, .. } => { self.write_to_output_buffer(frame, bytes); self.process_monitor_message(&message); }, _ => {} } } #[inline] fn write_to_output_buffer (&mut self, frame: usize, bytes: &[u8]) { self.midi_out_buf[frame].push(bytes.to_vec()); } #[inline] fn process_monitor_message (&mut self, message: &MidiMessage) { match message { MidiMessage::NoteOn { key, .. } => { self.notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { self.notes_in[key.as_int() as usize] = false; }, _ => {} } } } /// Define a MIDI phrase. #[macro_export] macro_rules! phrase { ($($t:expr => $msg:expr),* $(,)?) => {{ #[allow(unused_mut)] let mut phrase = BTreeMap::new(); $(phrase.insert($t, vec![]);)* $(phrase.get_mut(&$t).unwrap().push($msg);)* phrase }} } pub type PhraseData = Vec>; #[derive(Debug)] /// A MIDI sequence. pub struct Phrase { pub name: String, pub length: usize, pub notes: PhraseData, pub looped: Option<(usize, usize)>, /// Immediate note-offs in view pub percussive: bool } impl Default for Phrase { fn default () -> Self { Self::new("", 0, None) } } impl Phrase { pub fn new (name: &str, length: usize, notes: Option) -> Self { Self { name: name.to_string(), length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), looped: Some((0, length)), percussive: true, } } pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { if pulse >= self.length { panic!("extend phrase first") } self.notes[pulse].push(message); } /// Check if a range `start..end` contains MIDI Note On `k` pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { //panic!("{:?} {start} {end}", &self); for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { for event in events.iter() { match event { MidiMessage::NoteOn {key,..} => { if *key == k { return true } } _ => {} } } } return false } /// Write a chunk of MIDI events to an output port. pub fn process_out ( &self, output: &mut MIDIChunk, notes_on: &mut [bool;128], timebase: &Arc, (frame0, frames, _): (usize, usize, f64), ) { let mut buf = Vec::with_capacity(8); for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames( frame0, frame0 + frames ) { let tick = tick % self.length; for message in self.notes[tick].iter() { buf.clear(); let channel = 0.into(); let message = *message; LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); output[time as usize].push(buf.clone()); match message { MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, _ => {} } } } } } /// A collection of phrases to play on each track. pub struct Scene { pub name: String, pub clips: Vec>, } impl Scene { pub fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { Self { name: name.as_ref().into(), clips: clips.as_ref().iter().map(|x|x.clone()).collect() } } } macro_rules! impl_axis_common { ($A:ident $T:ty) => { impl $A<$T> { pub fn start_inc (&mut self) -> $T { self.start = self.start + 1; self.start } pub fn start_dec (&mut self) -> $T { self.start = self.start.saturating_sub(1); self.start } pub fn point_inc (&mut self) -> Option<$T> { self.point = self.point.map(|p|p + 1); self.point } pub fn point_dec (&mut self) -> Option<$T> { self.point = self.point.map(|p|p.saturating_sub(1)); self.point } } } } pub struct FixedAxis { pub start: T, pub point: Option } impl_axis_common!(FixedAxis u16); impl_axis_common!(FixedAxis usize); pub struct ScaledAxis { pub start: T, pub scale: T, pub point: Option } impl_axis_common!(ScaledAxis u16); impl_axis_common!(ScaledAxis usize); impl ScaledAxis { pub fn scale_mut (&mut self, cb: &impl Fn(T)->T) { self.scale = cb(self.scale) } }