tek/src/main.rs

433 lines
16 KiB
Rust

//#![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<Mutex<App>>|{
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::<Usually<()>>())
.collect::<Usually<()>>()?;
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<Arc<XdgApp>>,
/// Main JACK client.
pub jack: Option<DynamicAsyncClient>,
/// Main MIDI controller.
pub midi_in: Option<Port<MidiIn>>,
/// Main audio outputs.
pub audio_outs: Option<Vec<Port<AudioOut>>>,
/// JACK transport handle.
pub transport: Option<Transport>,
/// Transport status
pub playing: Option<TransportState>,
/// Current transport position
pub playhead: usize,
/// Current sample rate and tempo.
pub timebase: Arc<Timebase>,
/// 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<Box<dyn Component>>,
/// 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<Scene>,
/// Focused track+1, 0 is scene list
pub track_cursor: usize,
/// Collection of tracks
pub tracks: Vec<Track>,
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 <A: ::_jack::PortSpec, B: ::_jack::PortSpec> (
&self, a: &Port<A>, b: &Port<B>
) -> 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<usize> {
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<usize>,
pub track: Option<&'a Track>,
pub scene_id: Option<usize>,
pub scene: Option<&'a Scene>,
pub phrase_id: Option<usize>,
pub phrase: Option<&'a Phrase>,
}
struct SelectionMut<'a> {
pub track_id: Option<usize>,
pub track: Option<&'a mut Track>,
pub scene_id: Option<usize>,
pub scene: Option<&'a mut Scene>,
pub phrase_id: Option<usize>,
pub phrase: Option<&'a mut Phrase>,
}