diff --git a/.old/tek.rs.old b/.old/tek.rs.old index cabd6d5a..044e55da 100644 --- a/.old/tek.rs.old +++ b/.old/tek.rs.old @@ -1520,3 +1520,304 @@ //self.perf.update(t0, scope); //return Control::Continue //}); + //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) + //} + ///// Add a device to the end of the chain. + //pub fn append_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 add_device (&mut self, device: JackDevice) { + //self.devices.push(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: &Track) -> 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 => () + //}) + //} +use crate::*; +impl MixerTrack { + pub fn new (name: &str) -> Usually { + Ok(Self { + name: name.to_string().into(), + audio_ins: vec![], + audio_outs: vec![], + devices: vec![], + //ports: JackPorts::default(), + //devices: vec![], + //device: 0, + }) + } +} + +pub struct TrackView<'a> { + pub chain: Option<&'a MixerTrack>, + pub direction: Direction, + pub focused: bool, + pub entered: bool, +} + +impl<'a> Content for TrackView<'a> { + fn render (&self, to: &mut TuiOut) { + todo!(); + //let mut area = to.area(); + //if let Some(chain) = self.chain { + //match self.direction { + //Direction::Down => area.width = area.width.min(40), + //Direction::Right => area.width = area.width.min(10), + //_ => { unimplemented!() }, + //} + //to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered)); + //let mut split = Stack::new(self.direction); + //for device in chain.devices.as_slice().iter() { + //split = split.add_ref(device); + //} + //let (area, areas) = split.render_areas(to)?; + //if self.focused && self.entered && areas.len() > 0 { + //Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?; + //} + //Ok(Some(area)) + //} else { + //let [x, y, width, height] = area; + //let label = "No chain selected"; + //let x = x + (width - label.len() as u16) / 2; + //let y = y + height / 2; + //to.blit(&label, x, y, Some(Style::default().dim().bold()))?; + //Ok(Some(area)) + //} + } +} + +//impl Content for Mixer { + //fn content (&self) -> impl Content { + //Stack::right(|add| { + //for channel in self.tracks.iter() { + //add(channel)?; + //} + //Ok(()) + //}) + //} +//} + +//impl Content for Track { + //fn content (&self) -> impl Content { + //TrackView { + //chain: Some(&self), + //direction: tek_core::Direction::Right, + //focused: true, + //entered: true, + ////pub channels: u8, + ////pub input_ports: Vec>, + ////pub pre_gain_meter: f64, + ////pub gain: f64, + ////pub insert_ports: Vec>, + ////pub return_ports: Vec>, + ////pub post_gain_meter: f64, + ////pub post_insert_meter: f64, + ////pub level: f64, + ////pub pan: f64, + ////pub output_ports: Vec>, + ////pub post_fader_meter: f64, + ////pub route: String, + //} + //} +//} + +handle!(TuiIn: |self: Mixer, engine|{ + if let crossterm::event::Event::Key(event) = engine.event() { + + match event.code { + //KeyCode::Char('c') => { + //if event.modifiers == KeyModifiers::CONTROL { + //self.exit(); + //} + //}, + KeyCode::Down => { + self.selected_track = (self.selected_track + 1) % self.tracks.len(); + println!("{}", self.selected_track); + return Ok(Some(true)) + }, + KeyCode::Up => { + if self.selected_track == 0 { + self.selected_track = self.tracks.len() - 1; + } else { + self.selected_track -= 1; + } + println!("{}", self.selected_track); + return Ok(Some(true)) + }, + KeyCode::Left => { + if self.selected_column == 0 { + self.selected_column = 6 + } else { + self.selected_column -= 1; + } + return Ok(Some(true)) + }, + KeyCode::Right => { + if self.selected_column == 6 { + self.selected_column = 0 + } else { + self.selected_column += 1; + } + return Ok(Some(true)) + }, + _ => { + println!("\n{event:?}"); + } + } + + } + Ok(None) +}); + +handle!(TuiIn: |self:MixerTrack,from|{ + match from.event() { + //, NONE, "chain_cursor_up", "move cursor up", || { + kpat!(KeyCode::Up) => { + Ok(Some(true)) + }, + // , NONE, "chain_cursor_down", "move cursor down", || { + kpat!(KeyCode::Down) => { + Ok(Some(true)) + }, + // Left, NONE, "chain_cursor_left", "move cursor left", || { + kpat!(KeyCode::Left) => { + //if let Some(track) = app.arranger.track_mut() { + //track.device = track.device.saturating_sub(1); + //return Ok(true) + //} + Ok(Some(true)) + }, + // , NONE, "chain_cursor_right", "move cursor right", || { + kpat!(KeyCode::Right) => { + //if let Some(track) = app.arranger.track_mut() { + //track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); + //return Ok(true) + //} + Ok(Some(true)) + }, + // , NONE, "chain_mode_switch", "switch the display mode", || { + kpat!(KeyCode::Char('`')) => { + //app.chain_mode = !app.chain_mode; + Ok(Some(true)) + }, + _ => Ok(None) + } +}); + +pub enum MixerTrackCommand {} + +//impl MixerTrackDevice for LV2Plugin {} + +pub trait MixerTrackDevice: Debug + Send + Sync { + fn boxed (self) -> Box where Self: Sized + 'static { + Box::new(self) + } +} + +impl MixerTrackDevice for Sampler {} + +impl MixerTrackDevice for Plugin {} + +const SYM_NAME: &str = ":name"; +const SYM_GAIN: &str = ":gain"; +const SYM_SAMPLER: &str = "sampler"; +const SYM_LV2: &str = "lv2"; + +from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { + let mut _gain = 0.0f64; + let mut track = MixerTrack { + name: "".into(), + audio_ins: vec![], + audio_outs: vec![], + devices: vec![], + }; + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) { + track.name = n.to_string(); + } + if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) { + _gain = f64::from(*g); + } + }, + Edn::List(args) => match args.first() { + // Add a sampler device to the track + Some(Edn::Symbol(SYM_SAMPLER)) => { + track.devices.push( + Box::new(Sampler::from_edn(jack, &args[1..])?) as Box + ); + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", + &track.name, + args.first().unwrap() + ) + }, + // Add a LV2 plugin to the track. + Some(Edn::Symbol(SYM_LV2)) => { + track.devices.push( + Box::new(Plugin::from_edn(jack, &args[1..])?) as Box + ); + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", + &track.name, + args.first().unwrap() + ) + }, + None => + panic!("empty list track {}", &track.name), + _ => + panic!("unexpected in track {}: {:?}", &track.name, args.first().unwrap()) + }, + _ => {} + }); + Ok(track) +}); + +//impl ArrangerScene { + + ////TODO + ////pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { + ////let mut name = None; + ////let mut clips = vec![]; + ////edn!(edn in args { + ////Edn::Map(map) => { + ////let key = map.get(&Edn::Key(":name")); + ////if let Some(Edn::Str(n)) = key { + ////name = Some(*n); + ////} else { + ////panic!("unexpected key in scene '{name:?}': {key:?}") + ////} + ////}, + ////Edn::Symbol("_") => { + ////clips.push(None); + ////}, + ////Edn::Int(i) => { + ////clips.push(Some(*i as usize)); + ////}, + ////_ => panic!("unexpected in scene '{name:?}': {edn:?}") + ////}); + ////Ok(ArrangerScene { + ////name: Arc::new(name.unwrap_or("").to_string().into()), + ////color: ItemColor::random(), + ////clips, + ////}) + ////} +//} diff --git a/tek/src/lib.rs b/tek/src/lib.rs index e5c2c0e9..3e7a0af1 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -9,19 +9,13 @@ pub type Usually = std::result::Result>; /// Standard optional result type. pub type Perhaps = std::result::Result, Box>; -pub mod mixer; pub use self::mixer::*; pub use ::tek_tui::{ - *, - tek_edn::*, - tek_input::*, - tek_output::*, - crossterm, - crossterm::event::{ + *, tek_edn::*, tek_input::*, tek_output::*, + crossterm, crossterm::event::{ Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyCode::{self, *}, }, - ratatui, - ratatui::{ + ratatui, ratatui::{ prelude::{Color, Style, Stylize, Buffer, Modifier}, buffer::Cell, } @@ -31,25 +25,9 @@ pub use ::tek_jack::{self, *, jack::{*, contrib::*}}; pub use ::tek_midi::{self, *, midly::{*, num::*, live::*}}; pub use ::tek_sampler::{self, *}; pub use ::tek_plugin::{self, *}; -use EdnItem::*; -pub(crate) use ClockCommand::{Play, Pause}; -pub(crate) use KeyCode::{Tab, Char}; -pub(crate) use SamplerCommand as SmplCmd; -pub(crate) use MidiEditCommand as EditCmd; -pub(crate) use PoolClipCommand as PoolCmd; -pub(crate) use std::cmp::{Ord, Eq, PartialEq}; -pub(crate) use std::collections::BTreeMap; pub(crate) use std::error::Error; -pub(crate) use std::fmt::{Debug, Display, Formatter}; -pub(crate) use std::io::{Stdout, stdout}; -pub(crate) use std::marker::PhantomData; -pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; -pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::{self, *}}; -pub(crate) use std::sync::{Arc, Mutex, RwLock}; -pub(crate) use std::thread::{spawn, JoinHandle}; -pub(crate) use std::time::Duration; -pub(crate) use std::path::PathBuf; -pub(crate) use std::ffi::OsString; +pub(crate) use std::sync::atomic::{AtomicBool, Ordering::{self, *}}; +pub(crate) use std::sync::{Arc, RwLock}; #[derive(Default)] pub struct App { pub jack: Arc>, pub edn: String, @@ -138,15 +116,75 @@ render!(TuiOut: (self: Meter<'a>) => col!( render!(TuiOut: (self: Meters<'a>) => col!( format!("L/{:>+9.3}", self.0[0]), format!("R/{:>+9.3}", self.0[1]))); +/// Represents the current user selection in the arranger +#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { + /// The whole mix is selected + #[default] Mix, + /// A track is selected. + Track(usize), + /// A scene is selected. + Scene(usize), + /// A clip (track × scene) is selected. + Clip(usize, usize), +} +/// Focus identification methods +impl Selection { + pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) } + pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) } + pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) } + pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) } + pub fn track (&self) -> Option { + use Selection::*; + match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None } + } + pub fn scene (&self) -> Option { + use Selection::*; + match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None } + } + pub fn description ( + &self, + tracks: &[Track], + scenes: &[Scene], + ) -> Arc { + format!("Selected: {}", match self { + Self::Mix => "Everything".to_string(), + Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) + .unwrap_or_else(||"T??".into()), + Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) + .unwrap_or_else(||"S??".into()), + Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { + (Some(_), Some(scene)) => match scene.clip(*t) { + Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name), + None => format!("T{t} S{s}: Empty") + }, + _ => format!("T{t} S{s}: Empty"), + } + }).into() + } +} +impl HasSelection for App { + fn selected (&self) -> &Selection { &self.selected } + fn selected_mut (&mut self) -> &mut Selection { &mut self.selected } +} +pub trait HasSelection { + fn selected (&self) -> &Selection; + fn selected_mut (&mut self) -> &mut Selection; +} #[derive(Debug, Default)] struct Track { /// Name of track - name: Arc, + name: Arc, /// Preferred width of track column - width: usize, + width: usize, /// Identifying color of track - color: ItemPalette, + color: ItemPalette, /// MIDI player state - player: MidiPlayer, + player: MidiPlayer, + /// Inputs of 1st device + audio_ins: Vec>, + /// Outputs of last device + audio_outs: Vec>, + /// Device chain + devices: Vec, } impl Track { const MIN_WIDTH: usize = 9; @@ -162,6 +200,65 @@ impl Track { } } } +impl HasTracks for App { + fn tracks (&self) -> &Vec { &self.tracks } + fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } +} +pub trait HasTracks: HasSelection + HasClock + HasJack { + fn tracks (&self) -> &Vec; + fn tracks_mut (&mut self) -> &mut Vec; + fn tracks_sizes ( + &self, + editing: bool, + bigger: usize + ) -> impl Iterator { + let mut x = 0; + let active = match self.selected() { + Selection::Track(t) if editing => Some(t), + Selection::Clip(t, _) if editing => Some(t), + _ => None + }; + self.tracks().iter().enumerate().map(move |(index, track)|{ + let width = if Some(&index) == active { bigger } else { track.width.max(8) }; + let data = (index, track, x, x + width); + x += width; + data + }) + } + fn track_next_name (&self) -> Arc { + format!("Trk{:02}", self.tracks().len() + 1).into() + } + fn track (&self) -> Option<&Track> { + self.selected().track().and_then(|s|self.tracks().get(s)) + } + fn track_mut (&mut self) -> Option<&mut Track> { + self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) + } + fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||Tui::bg(TuiTheme::g(32), Bsp::s( + help_tag("add ", "t", "rack"), + help_tag("", "a", "dd scene"), + )).boxed()).into() + } + fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + let iter = ||self.tracks_with_sizes(); + (move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { + let name = Push::x(1, &track.name); + let color = track.color; + let fg = color.lightest.rgb; + let bg = color.base.rgb; + let active = self.selected().track() == Some(i); + let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) }; + let border = Style::default().fg(bfg).bg(bg); + Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16, + Outer(border).enclose(Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::x(name))))) + )) + })).boxed()).into() + } +} +trait Device {} +impl Device for Sampler {} +impl Device for Plugin {} #[derive(Debug, Default)] struct Scene { /// Name of scene name: Arc, @@ -202,6 +299,87 @@ impl Scene { match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } } } +#[derive(Clone, Debug)] pub enum SceneCommand { + Add, + Del(usize), + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), + SetColor(usize, ItemPalette), + Enqueue(usize), +} +edn_command!(SceneCommand: |state: App| { + ("add" [] Self::Add) + ("del" [a: usize] Self::Del(0)) + ("zoom" [a: usize] Self::SetZoom(a.unwrap())) + ("color" [a: usize] Self::SetColor(a.unwrap(), ItemPalette::random())) + ("enqueue" [a: usize] Self::Enqueue(a.unwrap())) + ("swap" [a: usize, b: usize] Self::Swap(a.unwrap(), b.unwrap())) +}); +command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") }); +impl HasScenes for App { + fn scenes (&self) -> &Vec { &self.scenes } + fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } +} +pub trait HasScenes: HasSelection { + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + fn scenes_sizes( + &self, + editing: bool, + scene_height: usize, + scene_larger: usize, + ) -> impl Iterator { + let mut y = 0; + let (selected_track, selected_scene) = match self.selected() { + Selection::Clip(t, s) => (Some(t), Some(s)), + _ => (None, None) + }; + self.scenes().iter().enumerate().map(move|(s, scene)|{ + let active = editing && selected_track.is_some() && selected_scene == Some(&s); + let height = if active { scene_larger } else { scene_height }; + let data = (s, scene, y, y + height); + y += height; + data + }) + } + fn scene_default_name (&self) -> Arc { + format!("Sc{:3>}", self.scenes().len() + 1).into() + } + fn scene (&self) -> Option<&Scene> { + self.selected().scene().and_then(|s|self.scenes().get(s)) + } + fn scene_mut (&mut self) -> Option<&mut Scene> { + self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s)) + } + fn scene_del (&mut self, index: usize) { + todo!("delete scene"); + } + fn scene_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { + (||{ + let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); + let selected = self.selected().scene(); + Fill::y(Align::c(Map::new(||self.scenes_with_sizes(2), move|(_, scene, y1, y2), i| { + let h = (y2 - y1) as u16; + let name = format!("🭬{}", &scene.name); + let color = scene.color; + let active = selected == Some(i); + let mid = if active { color.light } else { color.base }; + let top = Some(last_color.read().unwrap().base.rgb); + let cell = phat_sel_3( + active, + Tui::bold(true, name.clone()), + Tui::bold(true, name), + top, + mid.rgb, + Color::Rgb(0, 0, 0) + ); + *last_color.write().unwrap() = color; + map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell)) + }))).boxed() + }).into() + } +} impl App { pub fn sequencer ( jack: &Arc>, pool: MidiPool, editor: MidiEditor, @@ -405,7 +583,7 @@ command!(|self: AppCommand, state: App|match self { match cmd { // autoselect: automatically load selected clip in editor // autocolor: update color in all places simultaneously - PoolCommand::Select(_) | PoolCommand::Clip(PoolCmd::SetColor(_, _)) => + PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) => editor.set_clip(pool.clip().as_ref()), _ => {} } @@ -458,24 +636,6 @@ edn_command!(ClipCommand: |state: App| { ,b: usize] Self::SetColor(a.unwrap(), b.unwrap(), ItemPalette::random())) }); command!(|self: ClipCommand, state: App|match self { _ => todo!("clip command") }); -#[derive(Clone, Debug)] pub enum SceneCommand { - Add, - Del(usize), - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), - SetColor(usize, ItemPalette), - Enqueue(usize), -} -edn_command!(SceneCommand: |state: App| { - ("add" [] Self::Add) - ("del" [a: usize] Self::Del(0)) - ("zoom" [a: usize] Self::SetZoom(a.unwrap())) - ("color" [a: usize] Self::SetColor(a.unwrap(), ItemPalette::random())) - ("enqueue" [a: usize] Self::Enqueue(a.unwrap())) - ("swap" [a: usize, b: usize] Self::Swap(a.unwrap(), b.unwrap())) -}); -command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") }); #[derive(Clone, Debug)] pub enum TrackCommand { Add, Del(usize), @@ -692,100 +852,8 @@ fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content { fn cell > (color: ItemPalette, field: T) -> impl Content { Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) } -impl HasSelection for App { - fn selected (&self) -> &Selection { &self.selected } - fn selected_mut (&mut self) -> &mut Selection { &mut self.selected } -} -pub trait HasSelection { - fn selected (&self) -> &Selection; - fn selected_mut (&mut self) -> &mut Selection; -} -/// Represents the current user selection in the arranger -#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { - /// The whole mix is selected - #[default] Mix, - /// A track is selected. - Track(usize), - /// A scene is selected. - Scene(usize), - /// A clip (track × scene) is selected. - Clip(usize, usize), -} -/// Focus identification methods -impl Selection { - pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) } - pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) } - pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) } - pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) } - pub fn track (&self) -> Option { - use Selection::*; - match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None } - } - pub fn scene (&self) -> Option { - use Selection::*; - match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None } - } - pub fn description ( - &self, - tracks: &[Track], - scenes: &[Scene], - ) -> Arc { - format!("Selected: {}", match self { - Self::Mix => "Everything".to_string(), - Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) - .unwrap_or_else(||"T??".into()), - Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) - .unwrap_or_else(||"S??".into()), - Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { - (Some(_), Some(scene)) => match scene.clip(*t) { - Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name), - None => format!("T{t} S{s}: Empty") - }, - _ => format!("T{t} S{s}: Empty"), - } - }).into() - } -} -impl HasTracks for App { - fn tracks (&self) -> &Vec { &self.tracks } - fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } -} -pub trait HasTracks: HasSelection + HasScenes + HasClock + HasJack { - fn tracks (&self) -> &Vec; - fn tracks_mut (&mut self) -> &mut Vec; - fn tracks_sizes ( - &self, - editing: bool, - bigger: usize - ) -> impl Iterator { - let mut x = 0; - let active = match self.selected() { - Selection::Track(t) if editing => Some(t), - Selection::Clip(t, _) if editing => Some(t), - _ => None - }; - self.tracks().iter().enumerate().map(move |(index, track)|{ - let width = if Some(&index) == active { bigger } else { track.width.max(8) }; - let data = (index, track, x, x + width); - x += width; - data - }) - } - fn track_next_name (&self) -> Arc { - format!("Trk{:02}", self.tracks().len() + 1).into() - } - fn track (&self) -> Option<&Track> { - self.selected().track().and_then(|s|self.tracks().get(s)) - } - fn track_mut (&mut self) -> Option<&mut Track> { - self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) - } - fn track_del (&mut self, index: usize) { - self.tracks_mut().remove(index); - for scene in self.scenes_mut().iter_mut() { - scene.clips.remove(index); - } - } +impl Arrangement for App {} +pub trait Arrangement: HasTracks + HasScenes + HasSelection + HasClock + HasJack { fn track_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut Track> { @@ -827,65 +895,11 @@ pub trait HasTracks: HasSelection + HasScenes + HasClock + HasJack { } Ok(()) } - fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { - (||Tui::bg(TuiTheme::g(32), Bsp::s( - help_tag("add ", "t", "rack"), - help_tag("", "a", "dd scene"), - )).boxed()).into() - } - fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { - let iter = ||self.tracks_with_sizes(); - (move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { - let name = Push::x(1, &track.name); - let color = track.color; - let fg = color.lightest.rgb; - let bg = color.base.rgb; - let active = self.selected.track() == Some(i); - let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) }; - let border = Style::default().fg(bfg).bg(bg); - Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16, - Outer(border).enclose(Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::x(name))))) - )) - })).boxed()).into() - } -} -impl HasScenes for App { - fn scenes (&self) -> &Vec { &self.scenes } - fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } -} -pub trait HasScenes: HasSelection { - fn scenes (&self) -> &Vec; - fn scenes_mut (&mut self) -> &mut Vec; - fn scenes_sizes( - &self, - editing: bool, - scene_height: usize, - scene_larger: usize, - ) -> impl Iterator { - let mut y = 0; - let (selected_track, selected_scene) = match self.selected() { - Selection::Clip(t, s) => (Some(t), Some(s)), - _ => (None, None) - }; - self.scenes().iter().enumerate().map(move|(s, scene)|{ - let active = editing && selected_track.is_some() && selected_scene == Some(&s); - let height = if active { scene_larger } else { scene_height }; - let data = (s, scene, y, y + height); - y += height; - data - }) - } - fn scene_default_name (&self) -> Arc { - format!("Sc{:3>}", self.scenes().len() + 1).into() - } - fn scene (&self) -> Option<&Scene> { - self.selected().scene().and_then(|s|self.scenes().get(s)) - } - fn scene_mut (&mut self) -> Option<&mut Scene> { - self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s)) - } - fn scene_del (&mut self, index: usize) { - todo!("delete scene"); + fn track_del (&mut self, index: usize) { + self.tracks_mut().remove(index); + for scene in self.scenes_mut().iter_mut() { + scene.clips.remove(index); + } } fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut Scene> @@ -909,30 +923,6 @@ pub trait HasScenes: HasSelection { } Ok(()) } - fn scene_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { - (||{ - let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); - let selected = self.selected.scene(); - Fill::y(Align::c(Map::new(||self.scenes_with_sizes(2), move|(_, scene, y1, y2), i| { - let h = (y2 - y1) as u16; - let name = format!("🭬{}", &scene.name); - let color = scene.color; - let active = selected == Some(i); - let mid = if active { color.light } else { color.base }; - let top = Some(last_color.read().unwrap().base.rgb); - let cell = phat_sel_3( - active, - Tui::bold(true, name.clone()), - Tui::bold(true, name), - top, - mid.rgb, - Color::Rgb(0, 0, 0) - ); - *last_color.write().unwrap() = color; - map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell)) - }))).boxed() - }).into() - } fn scene_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { let editing = self.is_editing(); let tracks = move||self.tracks_with_sizes(); @@ -979,9 +969,6 @@ pub trait HasScenes: HasSelection { Fixed::x(w, map_east(x1 as u16, w, column)) }))).boxed()).into() } -} -impl Arrangement for App {} -pub trait Arrangement: HasTracks + HasScenes + HasSelection + HasClock + HasJack { fn activate (&mut self) -> Usually<()> { let selected = self.selected().clone(); match selected { diff --git a/tek/src/mixer.rs b/tek/src/mixer.rs deleted file mode 100644 index bae4b4a2..00000000 --- a/tek/src/mixer.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::*; - -#[derive(Debug)] -pub struct Mixer { - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, - pub name: Arc, - pub tracks: Vec, - pub selected_track: usize, - pub selected_column: usize, -} - -/// A mixer track. -#[derive(Debug)] -pub struct MixerTrack { - pub name: Arc, - /// Inputs of 1st device - pub audio_ins: Vec>, - /// Outputs of last device - pub audio_outs: Vec>, - /// Device chain - pub devices: Vec>, -} - -audio!(|self: Mixer, _client, _scope|Control::Continue); - -impl Mixer { - pub fn new (jack: &Arc>, name: &str) -> Usually { - Ok(Self { - jack: jack.clone(), - name: name.into(), - selected_column: 0, - selected_track: 1, - tracks: vec![], - }) - } - pub fn track_add (&mut self, name: &str, channels: usize) -> Usually<&mut Self> { - let track = MixerTrack::new(name)?; - self.tracks.push(track); - Ok(self) - } - pub fn track (&self) -> Option<&MixerTrack> { - self.tracks.get(self.selected_track) - } -} - -impl MixerTrack { - pub fn new (name: &str) -> Usually { - Ok(Self { - name: name.to_string().into(), - audio_ins: vec![], - audio_outs: vec![], - devices: vec![], - //ports: JackPorts::default(), - //devices: vec![], - //device: 0, - }) - } - //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) - //} - ///// Add a device to the end of the chain. - //pub fn append_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 add_device (&mut self, device: JackDevice) { - //self.devices.push(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: &Track) -> 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 struct TrackView<'a> { - pub chain: Option<&'a MixerTrack>, - pub direction: Direction, - pub focused: bool, - pub entered: bool, -} - -impl<'a> Content for TrackView<'a> { - fn render (&self, to: &mut TuiOut) { - todo!(); - //let mut area = to.area(); - //if let Some(chain) = self.chain { - //match self.direction { - //Direction::Down => area.width = area.width.min(40), - //Direction::Right => area.width = area.width.min(10), - //_ => { unimplemented!() }, - //} - //to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered)); - //let mut split = Stack::new(self.direction); - //for device in chain.devices.as_slice().iter() { - //split = split.add_ref(device); - //} - //let (area, areas) = split.render_areas(to)?; - //if self.focused && self.entered && areas.len() > 0 { - //Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?; - //} - //Ok(Some(area)) - //} else { - //let [x, y, width, height] = area; - //let label = "No chain selected"; - //let x = x + (width - label.len() as u16) / 2; - //let y = y + height / 2; - //to.blit(&label, x, y, Some(Style::default().dim().bold()))?; - //Ok(Some(area)) - //} - } -} - -//impl Content for Mixer { - //fn content (&self) -> impl Content { - //Stack::right(|add| { - //for channel in self.tracks.iter() { - //add(channel)?; - //} - //Ok(()) - //}) - //} -//} - -//impl Content for Track { - //fn content (&self) -> impl Content { - //TrackView { - //chain: Some(&self), - //direction: tek_core::Direction::Right, - //focused: true, - //entered: true, - ////pub channels: u8, - ////pub input_ports: Vec>, - ////pub pre_gain_meter: f64, - ////pub gain: f64, - ////pub insert_ports: Vec>, - ////pub return_ports: Vec>, - ////pub post_gain_meter: f64, - ////pub post_insert_meter: f64, - ////pub level: f64, - ////pub pan: f64, - ////pub output_ports: Vec>, - ////pub post_fader_meter: f64, - ////pub route: String, - //} - //} -//} - -handle!(TuiIn: |self: Mixer, engine|{ - if let crossterm::event::Event::Key(event) = engine.event() { - - match event.code { - //KeyCode::Char('c') => { - //if event.modifiers == KeyModifiers::CONTROL { - //self.exit(); - //} - //}, - KeyCode::Down => { - self.selected_track = (self.selected_track + 1) % self.tracks.len(); - println!("{}", self.selected_track); - return Ok(Some(true)) - }, - KeyCode::Up => { - if self.selected_track == 0 { - self.selected_track = self.tracks.len() - 1; - } else { - self.selected_track -= 1; - } - println!("{}", self.selected_track); - return Ok(Some(true)) - }, - KeyCode::Left => { - if self.selected_column == 0 { - self.selected_column = 6 - } else { - self.selected_column -= 1; - } - return Ok(Some(true)) - }, - KeyCode::Right => { - if self.selected_column == 6 { - self.selected_column = 0 - } else { - self.selected_column += 1; - } - return Ok(Some(true)) - }, - _ => { - println!("\n{event:?}"); - } - } - - } - Ok(None) -}); - -handle!(TuiIn: |self:MixerTrack,from|{ - match from.event() { - //, NONE, "chain_cursor_up", "move cursor up", || { - kpat!(KeyCode::Up) => { - Ok(Some(true)) - }, - // , NONE, "chain_cursor_down", "move cursor down", || { - kpat!(KeyCode::Down) => { - Ok(Some(true)) - }, - // Left, NONE, "chain_cursor_left", "move cursor left", || { - kpat!(KeyCode::Left) => { - //if let Some(track) = app.arranger.track_mut() { - //track.device = track.device.saturating_sub(1); - //return Ok(true) - //} - Ok(Some(true)) - }, - // , NONE, "chain_cursor_right", "move cursor right", || { - kpat!(KeyCode::Right) => { - //if let Some(track) = app.arranger.track_mut() { - //track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); - //return Ok(true) - //} - Ok(Some(true)) - }, - // , NONE, "chain_mode_switch", "switch the display mode", || { - kpat!(KeyCode::Char('`')) => { - //app.chain_mode = !app.chain_mode; - Ok(Some(true)) - }, - _ => Ok(None) - } -}); - -pub enum MixerTrackCommand {} - -//impl MixerTrackDevice for LV2Plugin {} - -pub trait MixerTrackDevice: Debug + Send + Sync { - fn boxed (self) -> Box where Self: Sized + 'static { - Box::new(self) - } -} - -impl MixerTrackDevice for Sampler {} - -impl MixerTrackDevice for Plugin {} - -const SYM_NAME: &str = ":name"; -const SYM_GAIN: &str = ":gain"; -const SYM_SAMPLER: &str = "sampler"; -const SYM_LV2: &str = "lv2"; - -from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { - let mut _gain = 0.0f64; - let mut track = MixerTrack { - name: "".into(), - audio_ins: vec![], - audio_outs: vec![], - devices: vec![], - }; - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) { - track.name = n.to_string(); - } - if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) { - _gain = f64::from(*g); - } - }, - Edn::List(args) => match args.first() { - // Add a sampler device to the track - Some(Edn::Symbol(SYM_SAMPLER)) => { - track.devices.push( - Box::new(Sampler::from_edn(jack, &args[1..])?) as Box - ); - panic!( - "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", - &track.name, - args.first().unwrap() - ) - }, - // Add a LV2 plugin to the track. - Some(Edn::Symbol(SYM_LV2)) => { - track.devices.push( - Box::new(Plugin::from_edn(jack, &args[1..])?) as Box - ); - panic!( - "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", - &track.name, - args.first().unwrap() - ) - }, - None => - panic!("empty list track {}", &track.name), - _ => - panic!("unexpected in track {}: {:?}", &track.name, args.first().unwrap()) - }, - _ => {} - }); - Ok(track) -}); - -//impl ArrangerScene { - - ////TODO - ////pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { - ////let mut name = None; - ////let mut clips = vec![]; - ////edn!(edn in args { - ////Edn::Map(map) => { - ////let key = map.get(&Edn::Key(":name")); - ////if let Some(Edn::Str(n)) = key { - ////name = Some(*n); - ////} else { - ////panic!("unexpected key in scene '{name:?}': {key:?}") - ////} - ////}, - ////Edn::Symbol("_") => { - ////clips.push(None); - ////}, - ////Edn::Int(i) => { - ////clips.push(Some(*i as usize)); - ////}, - ////_ => panic!("unexpected in scene '{name:?}': {edn:?}") - ////}); - ////Ok(ArrangerScene { - ////name: Arc::new(name.unwrap_or("").to_string().into()), - ////color: ItemColor::random(), - ////clips, - ////}) - ////} -//}