//#![feature(fn_traits)] //#![feature(unboxed_closures)] #![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)] extern crate clap; extern crate jack as _jack; extern crate crossterm; pub mod cli; pub mod config; pub mod control; pub mod core; pub mod model; pub mod view; pub mod jack; use crate::{core::*, model::*}; pub fn main () -> Usually<()> { App::default().run(Some(|app: Arc>|{ let mut state = app.lock().unwrap(); let xdg = Arc::new(microxdg::XdgApp::new("tek")?); state.xdg = Some(xdg.clone()); if crate::config::AppPaths::new(&xdg)?.should_create() { state.modal = Some(Box::new(crate::config::SetupModal(Some(xdg.clone())))); } let jack = jack_run("tek", &app)?; let client = jack.as_client(); state.transport = Some(client.transport()); state.playing = Some(TransportState::Stopped); state.midi_in = Some(client.register_port("midi-in", MidiIn)?); let _ = ["nanoKEY Studio.*capture.*"] .iter() .map(|name|client .ports(Some(name), None, PortFlags::empty()) .iter() .map(|name|{ if let Some(port) = client.port_by_name(name) { client.connect_ports(&port, &state.midi_in.as_ref().unwrap())?; } Ok(()) }) .collect::>()) .collect::>()?; let timebase = &state.timebase; let ppq = timebase.ppq() as usize; state.track_cursor = 1; state.scene_cursor = 1; state.note_start = 12; state.time_zoom = 12; state.quant = 24; let outputs: Vec<_> = ["Komplete.+:playback_FL", "Komplete.+:playback_FR"] .iter() .map(|name|client .ports(Some(name), None, PortFlags::empty()) .get(0) .map(|name|client.port_by_name(name))) .flatten() .collect(); state.jack = Some(jack); state.add_track_with_cb(Some("Drums"), |_, track|{ track.add_device_with_cb(Sampler::new("Sampler", Some(BTreeMap::from([ sample!(34, "808", "/home/user/Lab/Music/pak/808.wav"), sample!(35, "Kick1", "/home/user/Lab/Music/pak/kik.wav"), sample!(36, "Kick2", "/home/user/Lab/Music/pak/kik2.wav"), sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"), sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"), ])))?, |track, device|{ device.connect_midi_in(0, &track.midi_out.clone_unowned())?; Ok(()) })?; track.add_device_with_cb(Plugin::lv2( "Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2" )?, |track, device|{ device.connect_audio_in(0, &track.devices[0].audio_outs()?[0])?; device.connect_audio_in(0, &track.devices[0].audio_outs()?[1])?; if let Some(Some(left)) = outputs.get(0) { device.connect_audio_out(0, left)?; } if let Some(Some(right)) = outputs.get(0) { device.connect_audio_out(1, right)?; } Ok(()) })?; track.sequence = Some(1); // FIXME track.add_phrase("4 kicks", ppq * 4, Some(phrase! { 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, })); track.add_phrase("D-Beat", ppq * 4, Some(phrase! { 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 10 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, })); track.add_phrase("Garage", ppq * 4, Some(phrase! { 00 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() }, 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 11 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, })); Ok(()) })?; state.add_track_with_cb(Some("Bass"), |_, track|{ track.add_device_with_cb(Plugin::lv2( "Odin2", "file:///home/user/.lv2/Odin2.lv2" )?, |track, device|{ device.connect_midi_in(0, &track.midi_out.clone_unowned())?; if let Some(Some(left)) = outputs.get(0) { device.connect_audio_out(0, left)?; } if let Some(Some(right)) = outputs.get(0) { device.connect_audio_out(1, right)?; } Ok(()) })?; track.sequence = Some(0); // FIXME track.add_phrase("Custom", ppq * 4, None); track.add_phrase("Offbeat", ppq * 4, Some(phrase! { 00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, 02 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 04 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, 06 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 08 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, 10 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() }, 14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, })); Ok(()) })?; state.add_track_with_cb(Some("Lead"), |_, track|{ track.add_device_with_cb(Plugin::lv2( "Helm", "file:///home/user/.lv2/Helm.lv2" )?, |track, device|{ device.connect_midi_in(0, &track.midi_out.clone_unowned())?; if let Some(Some(left)) = outputs.get(0) { device.connect_audio_out(0, left)?; } if let Some(Some(right)) = outputs.get(0) { device.connect_audio_out(1, right)?; } Ok(()) })?; track.sequence = Some(0); // FIXME track.add_phrase("Custom", ppq * 4, None); Ok(()) })?; state.scenes = vec![ Scene::new("Intro", vec![None, Some(0), None, None]), Scene::new("Hook", vec![Some(0), Some(1), None, None]), Scene::new("Verse", vec![Some(1), Some(0), Some(0), None]), Scene::new("Chorus", vec![Some(0), Some(1), None, None]), Scene::new("Bridge", vec![Some(2), Some(0), Some(0), None]), Scene::new("Outro", vec![None, Some(1), None, None]), ]; Ok(()) })) } #[derive(Default)] pub struct App { /// Paths to user directories pub xdg: Option>, /// Main JACK client. pub jack: Option, /// Main MIDI controller. pub midi_in: Option>, /// Main audio outputs. pub audio_outs: Option>>, /// JACK transport handle. pub transport: Option, /// Transport status pub playing: Option, /// Current transport position pub playhead: usize, /// Current sample rate and tempo. pub timebase: Arc, /// Display mode of grid section pub grid_mode: bool, /// Display mode of chain section pub chain_mode: bool, /// Display mode of sequencer seciton pub seq_mode: bool, /// Optional modal dialog pub modal: Option>, /// Currently focused section pub section: usize, /// Whether the section is focused pub entered: bool, /// Current frame pub metronome: bool, /// Display position of cursor within note range pub note_cursor: usize, /// Range of notes to display pub note_start: usize, /// Display position of cursor within time range pub time_cursor: usize, /// PPQ per display unit pub time_zoom: usize, /// Range of time steps to display pub time_start: usize, /// Focused scene+1, 0 is track list pub scene_cursor: usize, /// Collection of scenes pub scenes: Vec, /// Focused track+1, 0 is scene list pub track_cursor: usize, /// Collection of tracks pub tracks: Vec, pub chunk_size: usize, pub quant: usize, } process!(App |self, _client, scope| { let transport = self.transport.as_ref().unwrap().query().unwrap(); let mut panic = false; if Some(transport.state) != self.playing { panic = true; } self.playing = Some(transport.state); self.playhead = transport.pos.frame() as usize; self.chunk_size = scope.n_frames() as usize; let CycleTimes { current_frames, current_usecs, next_usecs, period_usecs } = scope.cycle_times().unwrap(); for track in self.tracks.iter_mut() { track.process( self.midi_in.as_ref().unwrap().iter(scope), &self.timebase, self.playing, self.quant, panic, &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().as_client() } pub fn connect_ports ( &self, a: &Port, b: &Port ) -> Usually<()> { Ok(self.client().connect_ports(a, b)?) } pub fn toggle_play (&mut self) -> Usually<()> { self.playing = match self.playing.expect("after jack init") { TransportState::Stopped => { self.transport.as_ref().unwrap().start()?; Some(TransportState::Starting) }, _ => { self.transport.as_ref().unwrap().stop()?; self.transport.as_ref().unwrap().locate(0)?; Some(TransportState::Stopped) }, }; Ok(()) } pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> { let name = name.ok_or_else(||format!("Scene {}", self.scenes.len() + 1))?; self.scenes.push(Scene::new(&name, vec![])); self.scene_cursor = self.scenes.len(); Ok(&mut self.scenes[self.scene_cursor - 1]) } pub fn add_track ( &mut self, name: Option<&str>, ) -> Usually<&mut Track> { let name = name.ok_or_else(||format!("Track {}", self.tracks.len() + 1))?; self.tracks.push(Track::new(&name, self.client(), None, None)?); self.track_cursor = self.tracks.len(); Ok(&mut self.tracks[self.track_cursor - 1]) } pub fn add_track_with_cb ( &mut self, name: Option<&str>, init: impl Fn(&Client, &mut Track)->Usually<()>, ) -> Usually<&mut Track> { let name = name.ok_or_else(||format!("Track {}", self.tracks.len() + 1))?; let mut track = Track::new(&name, self.client(), None, None)?; init(self.client(), &mut track)?; self.tracks.push(track); self.track_cursor = self.tracks.len(); Ok(&mut self.tracks[self.track_cursor - 1]) } pub fn track (&self) -> Option<(usize, &Track)> { match self.track_cursor { 0 => None, _ => { let id = self.track_cursor as usize - 1; self.tracks.get(id).map(|t|(id, t)) } } } pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> { match self.track_cursor { 0 => None, _ => { let id = self.track_cursor as usize - 1; self.tracks.get_mut(id).map(|t|(id, t)) } } } pub fn scene (&self) -> Option<(usize, &Scene)> { match self.scene_cursor { 0 => None, _ => { let id = self.scene_cursor as usize - 1; self.scenes.get(id).map(|t|(id, t)) } } } pub fn scene_mut (&mut self) -> Option<(usize, &mut Scene)> { match self.scene_cursor { 0 => None, _ => { let id = self.scene_cursor as usize - 1; self.scenes.get_mut(id).map(|t|(id, t)) } } } pub fn phrase (&self) -> Option<&Phrase> { let (track_id, track) = self.track()?; let (_, scene) = self.scene()?; track.phrases.get((*scene.clips.get(track_id)?)?) } pub fn phrase_mut (&mut self) -> Option<&mut Phrase> { let (track_id, _) = self.track()?; let (_, scene) = self.scene()?; let clip = (*scene.clips.get(track_id)?)?; self.track_mut()?.1.phrases.get_mut(clip) } pub fn phrase_id (&self) -> Option { let (track_id, _) = self.track()?; let (_, scene) = self.scene()?; *scene.clips.get(track_id)? } pub fn selection <'a> (&'a self) -> Selection<'a> { let track_id = if self.track_cursor == 0 { None } else { Some(self.track_cursor - 1) }; let track = (&track_id).map(|id|self.tracks.get(id)).flatten(); let scene_id = if self.scene_cursor == 0 { None } else { Some(self.scene_cursor - 1) }; let scene = (&scene_id).map(|id|self.scenes.get(id)).flatten(); let phrase_id = if let (Some(scene), Some(id)) = (scene, track_id) { if let Some(Some(id)) = scene.clips.get(id) { Some(*id) } else { None } } else { None }; let phrase = if let (Some(id), Some(track)) = (phrase_id, track) { track.phrases.get(id) } else { None }; Selection { track_id, track, scene_id, scene, phrase_id, phrase } } //pub fn selection_mut <'a> (&'a mut self) -> SelectionMut<'a> { //let track_id = if self.track_cursor == 0 { None } else { Some(self.track_cursor - 1) }; //let track = (&track_id).map(|id|self.tracks.get_mut(id)).flatten(); //let scene_id = if self.scene_cursor == 0 { None } else { Some(self.scene_cursor - 1) }; //let scene = (&scene_id).map(|id|self.scenes.get_mut(id)).flatten(); //let phrase_id = if let (Some(scene), Some(id)) = (scene, track_id) { //if let Some(Some(id)) = scene.clips.get_mut(id) { //Some(*id) //} else { //None //} //} else { //None //}; //let phrase = if let (Some(id), Some(track)) = (phrase_id, track) { //track.phrases.get_mut(id) //} else { //None //}; //SelectionMut { //track_id, //track, //scene_id, //scene, //phrase_id, //phrase //} //} } struct Selection<'a> { pub track_id: Option, pub track: Option<&'a Track>, pub scene_id: Option, pub scene: Option<&'a Scene>, pub phrase_id: Option, pub phrase: Option<&'a Phrase>, } struct SelectionMut<'a> { pub track_id: Option, pub track: Option<&'a mut Track>, pub scene_id: Option, pub scene: Option<&'a mut Scene>, pub phrase_id: Option, pub phrase: Option<&'a mut Phrase>, }