diff --git a/bin/cli_arranger.rs b/bin/cli_arranger.rs index 5de516ab..54f22c35 100644 --- a/bin/cli_arranger.rs +++ b/bin/cli_arranger.rs @@ -55,7 +55,7 @@ fn add_tracks (jack: &JackConnection, app: &mut Arranger, cli: &ArrangerCli) -> track_color_1.mix(track_color_2, i as f32 / n as f32).into() ))?; track.width = cli.track_width; - let name = track.name.read().unwrap(); + let name = track.name.clone(); track.player.midi_ins.push( jack.register_port(&format!("{}I", &name), MidiIn::default())? ); diff --git a/edn/Cargo.lock b/edn/Cargo.lock index 943a03dd..73059751 100644 --- a/edn/Cargo.lock +++ b/edn/Cargo.lock @@ -770,19 +770,18 @@ dependencies = [ "clojure-reader", "itertools 0.14.0", "konst", - "tek_layout", "tek_tui", ] [[package]] -name = "tek_engine" +name = "tek_input" version = "0.2.0" [[package]] -name = "tek_layout" +name = "tek_output" version = "0.2.0" dependencies = [ - "tek_engine", + "tek_edn", ] [[package]] @@ -795,8 +794,8 @@ dependencies = [ "rand", "ratatui", "tek_edn", - "tek_engine", - "tek_layout", + "tek_input", + "tek_output", ] [[package]] diff --git a/edn/src/edn_token.rs b/edn/src/edn_token.rs index 4428c0b3..723614c7 100644 --- a/edn/src/edn_token.rs +++ b/edn/src/edn_token.rs @@ -12,7 +12,8 @@ pub enum Token<'a> { impl<'a> Token<'a> { pub fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> { use Token::*; - let mut state = Self::default(); + use konst::string::{split_at, char_indices}; + let mut state = Self::Nil; for (index, c) in source.char_indices() { state = match state { // must begin expression @@ -29,21 +30,33 @@ impl<'a> Token<'a> { Key(_, _, 0) => unreachable!(), Num(source, index, length) => match c { '0'..='9' => Num(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))), + ' '|'\n'|'\r'|'\t' => return Ok(( + split_at(source, index+length).1, + Num(source, index, length) + )), _ => return Err(ParseError::Unexpected(c)) }, Sym(source, index, length) => match c { 'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))), + ' '|'\n'|'\r'|'\t' => return Ok(( + split_at(source, index+length).1, + Sym(source, index, length) + )), _ => return Err(ParseError::Unexpected(c)) }, Key(source, index, length) => match c { 'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))), + ' '|'\n'|'\r'|'\t' => return Ok(( + split_at(source, index+length).1, + Key(source, index, length) + )), _ => return Err(ParseError::Unexpected(c)) }, Exp(source, index, length, 0) => match c { - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))), + ' '|'\n'|'\r'|'\t' => return Ok(( + split_at(source, index+length).1, + Exp(source, index, length, 0) + )), _ => return Err(ParseError::Unexpected(c)) }, Exp(source, index, length, depth) => match c { diff --git a/output/src/measure.rs b/output/src/measure.rs index 33eb07ea..1db15cca 100644 --- a/output/src/measure.rs +++ b/output/src/measure.rs @@ -57,7 +57,7 @@ impl Measure { pub fn set_w (&self, w: impl Into) { self.x.store(w.into(), Relaxed) } pub fn set_h (&self, h: impl Into) { self.y.store(h.into(), Relaxed) } pub fn set_wh (&self, w: impl Into, h: impl Into) { self.set_w(w); self.set_h(h); } - pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) } + pub fn format (&self) -> Arc { format!("{}x{}", self.w(), self.h()).into() } pub fn new () -> Self { Self { _engine: PhantomData::default(), diff --git a/src/arranger.rs b/src/arranger.rs index 5f46b04f..bb020184 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -94,6 +94,11 @@ from_jack!(|jack| Arranger { compact: true, } }); +has_clock!(|self: Arranger|&self.clock); +has_phrases!(|self: Arranger|self.pool.phrases); +has_editor!(|self: Arranger|self.editor); +handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); + //render!(TuiOut: (self: Arranger) => { //let pool_w = if self.pool.visible { self.splits[1] } else { 0 }; //let color = self.color; @@ -108,58 +113,54 @@ from_jack!(|jack| Arranger { //Self::render_mode(self))))), Fill::y(&"fixme: self.editor")))))); //self.size.of(layout) //}); -has_clock!(|self: Arranger|&self.clock); -has_phrases!(|self: Arranger|self.pool.phrases); -has_editor!(|self: Arranger|self.editor); -handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); -/// Status bar for arranger app -#[derive(Clone)] -pub struct ArrangerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) playing: bool, -} -from!(|state:&Arranger|ArrangerStatus = { - let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get(); - let buffer = samples as f64 / rate; - let width = state.size.w(); - Self { - width, - playing: state.clock.is_rolling(), - cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), - size: format!("{}x{}│", width, state.size.h()), - } -}); -render!(TuiOut: (self: ArrangerStatus) => Fixed::y(2, lay!( - Self::help(), - Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -))); -impl ArrangerStatus { - fn help () -> impl Content { - let single = |binding, command|row!(" ", col!( - Tui::fg(TuiTheme::yellow(), binding), - command - )); - let double = |(b1, c1), (b2, c2)|col!( - row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,), - row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,), - ); - Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!( - single("SPACE", "play/pause"), - single(" Ctrl", " scroll"), - single(" ▲▼▶◀", " cell"), - double(("p", "put"), ("g", "get")), - double(("q", "enqueue"), ("e", "edit")), - single(" wsad", " note"), - double(("a", "append"), ("s", "set"),), - double((",.", "length"), ("<>", "triplet"),), - double(("[]", "phrase"), ("{}", "order"),), - )) - } - fn stats (&self) -> impl Content + use<'_> { - row!(&self.cpu, &self.size) - } -} +///// Status bar for arranger app +//#[derive(Clone)] +//pub struct ArrangerStatus { + //pub(crate) width: usize, + //pub(crate) cpu: Option>, + //pub(crate) size: Arc, + //pub(crate) playing: bool, +//} +//from!(|state:&Arranger|ArrangerStatus = { + //let samples = state.clock.chunk.load(Relaxed); + //let rate = state.clock.timebase.sr.get(); + //let buffer = samples as f64 / rate; + //let width = state.size.w(); + //Self { + //width, + //playing: state.clock.is_rolling(), + //cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()), + //size: format!("{}x{}│", width, state.size.h()).into(), + //} +//}); +//render!(TuiOut: (self: ArrangerStatus) => Fixed::y(2, lay!( + //Self::help(), + //Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +//))); +//impl ArrangerStatus { + //fn help () -> impl Content { + //let single = |binding, command|row!(" ", col!( + //Tui::fg(TuiTheme::yellow(), binding), + //command + //)); + //let double = |(b1, c1), (b2, c2)|col!( + //row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,), + //row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,), + //); + //Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!( + //single("SPACE", "play/pause"), + //single(" Ctrl", " scroll"), + //single(" ▲▼▶◀", " cell"), + //double(("p", "put"), ("g", "get")), + //double(("q", "enqueue"), ("e", "edit")), + //single(" wsad", " note"), + //double(("a", "append"), ("s", "set"),), + //double((",.", "length"), ("<>", "triplet"),), + //double(("[]", "phrase"), ("{}", "order"),), + //)) + //} + //fn stats (&self) -> impl Content + use<'_> { + //row!(&self.cpu, &self.size) + //} +//} diff --git a/src/arranger/arranger_scene.rs b/src/arranger/arranger_scene.rs index 100332b0..f0716f9f 100644 --- a/src/arranger/arranger_scene.rs +++ b/src/arranger/arranger_scene.rs @@ -3,9 +3,8 @@ impl Arranger { pub fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerScene> { - let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); let scene = ArrangerScene { - name: Arc::new(name.into()), + name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), clips: vec![None;self.tracks.len()], color: color.unwrap_or_else(ItemPalette::random), }; @@ -16,8 +15,8 @@ impl Arranger { pub fn scene_del (&mut self, index: usize) { todo!("delete scene"); } - fn scene_default_name (&self) -> String { - format!("Sc{:3>}", self.scenes.len() + 1) + fn scene_default_name (&self) -> Arc { + format!("Sc{:3>}", self.scenes.len() + 1).into() } pub fn selected_scene (&self) -> Option<&ArrangerScene> { self.selected.scene().and_then(|s|self.scenes.get(s)) @@ -28,14 +27,14 @@ impl Arranger { } #[derive(Default, Debug, Clone)] pub struct ArrangerScene { /// Name of scene - pub(crate) name: Arc>, + pub(crate) name: Arc, /// Clips in scene, one per track pub(crate) clips: Vec>>>, /// Identifying color of scene pub(crate) color: ItemPalette, } impl ArrangerScene { - pub fn name (&self) -> &Arc> { + pub fn name (&self) -> &Arc { &self.name } pub fn clips (&self) -> &Vec>>> { @@ -45,7 +44,7 @@ impl ArrangerScene { self.color } pub fn longest_name (scenes: &[Self]) -> usize { - scenes.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) + scenes.iter().map(|s|s.name.len()).fold(0, usize::max) } /// Returns the pulse length of the longest phrase in the scene pub fn pulses (&self) -> usize { diff --git a/src/arranger/arranger_select.rs b/src/arranger/arranger_select.rs index e6bd0f22..c8526295 100644 --- a/src/arranger/arranger_select.rs +++ b/src/arranger/arranger_select.rs @@ -21,17 +21,13 @@ impl ArrangerSelection { &self, tracks: &[ArrangerTrack], scenes: &[ArrangerScene], - ) -> String { + ) -> Arc { format!("Selected: {}", match self { Self::Mix => "Everything".to_string(), - Self::Track(t) => match tracks.get(*t) { - Some(track) => format!("T{t}: {}", &track.name.read().unwrap()), - None => "T??".into(), - }, - Self::Scene(s) => match scenes.get(*s) { - Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()), - None => "S??".into(), - }, + 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), @@ -39,7 +35,7 @@ impl ArrangerSelection { }, _ => format!("T{t} S{s}: Empty"), } - }) + }).into() } pub fn track (&self) -> Option { use ArrangerSelection::*; diff --git a/src/arranger/arranger_track.rs b/src/arranger/arranger_track.rs index 77b1497d..ce5cc329 100644 --- a/src/arranger/arranger_track.rs +++ b/src/arranger/arranger_track.rs @@ -1,17 +1,17 @@ use crate::*; impl Arranger { - pub fn track_next_name (&self) -> String { - format!("Tr{}", self.tracks.len() + 1) + pub fn track_next_name (&self) -> Arc { + format!("Tr{:02}", self.tracks.len() + 1).into() } pub fn track_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerTrack> { - let name = name.map_or_else(||self.track_next_name(), |x|x.to_string()); + let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let track = ArrangerTrack { width: name.len() + 2, - name: Arc::new(name.into()), color: color.unwrap_or_else(ItemPalette::random), player: MidiPlayer::from(&self.clock), + name, }; self.tracks.push(track); let index = self.tracks.len() - 1; @@ -26,7 +26,7 @@ impl Arranger { } #[derive(Debug)] pub struct ArrangerTrack { /// Name of track - pub name: Arc>, + pub name: Arc, /// Preferred width of track column pub width: usize, /// Identifying color of track @@ -38,7 +38,7 @@ has_clock!(|self:ArrangerTrack|self.player.clock()); has_player!(|self:ArrangerTrack|self.player); impl ArrangerTrack { /// Name of track - pub fn name (&self) -> &Arc> { + pub fn name (&self) -> &Arc { &self.name } /// Preferred width of track column @@ -54,7 +54,7 @@ impl ArrangerTrack { self.color } fn longest_name (tracks: &[Self]) -> usize { - tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) + tracks.iter().map(|s|s.name.len()).fold(0, usize::max) } fn width_inc (&mut self) { *self.width_mut() += 1; diff --git a/src/arranger/arranger_tui.rs b/src/arranger/arranger_tui.rs index 98a353fa..9f65c659 100644 --- a/src/arranger/arranger_tui.rs +++ b/src/arranger/arranger_tui.rs @@ -3,27 +3,25 @@ pub(crate) const HEADER_H: u16 = 0; // 5 pub(crate) const SCENES_W_OFFSET: u16 = 0; render!(TuiOut: (self: Arranger) => { let toolbar = |x|Bsp::s(self.toolbar_view(), x); - let status = |x|Bsp::n(self.status_view(), x); let pool = |x|Bsp::w(self.pool_view(), x); - let editing = |x|Bsp::n(self.editor_status_view(), x); + let editing = |x|Bsp::n(Bsp::e(self.editor.clip_status(), self.editor.edit_status()), x); let enclosed = |x|Outer(Style::default().fg(Color::Rgb(72,72,72))).enclose(x); - let editor = Fixed::y(20, enclosed(&self.editor)); let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16); - let arrrrrr = Fixed::y(32, Map::new( + let arrrrrr = Fixed::y(27, Map::new( move||[ (0, 2, self.output_row_header(), self.output_row_cells()), - (2, 2, self.elapsed_row_header(), self.elapsed_row_cells()), - (4, 2, self.next_row_header(), self.next_row_cells()), + (2, 3, self.elapsed_row_header(), self.elapsed_row_cells()), + (4, 3, self.next_row_header(), self.next_row_cells()), (6, 3, self.track_row_header(), self.track_row_cells()), - (9, 20, self.scene_row_headers(), self.scene_row_cells()), - (29, 2, self.input_row_header(), self.input_row_cells()), + (8, 20, self.scene_row_headers(), self.scene_row_cells()), + (25, 2, self.input_row_header(), self.input_row_cells()), ].into_iter(), move|(y, h, header, cells), index|map_south(y, h, Fill::x(Align::w(Bsp::e( Fixed::xy(scenes_w, h, header), Fixed::xy(self.tracks.len() as u16*6, h, cells) )))))); - self.size.of(toolbar(status(pool(editing(Bsp::n(editor, arrrrrr)))))) + self.size.of(toolbar(pool(editing(Bsp::s(arrrrrr, enclosed(&self.editor)))))) }); impl Arranger { @@ -63,7 +61,7 @@ impl Arranger { (move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { let w = (x2 - x1) as u16; let color: ItemPalette = track.color().dark.into(); - let cell = Bsp::s(format!("MutSol"), Self::phat_hi(color.dark.rgb, color.darker.rgb)); + let cell = Bsp::s(format!(" M S "), Self::phat_hi(color.dark.rgb, color.darker.rgb)); map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell))) })).boxed()).into() } @@ -73,19 +71,17 @@ impl Arranger { } fn elapsed_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { - let color = track.color(); + //let color = track.color(); let color: ItemPalette = track.color().dark.into(); let timebase = self.clock().timebase(); - let elapsed = { - let mut result = String::new(); + let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb, if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { let length = phrase.read().unwrap().length; let elapsed = track.player.pulses_since_start().unwrap() as usize; - result = format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64)) - } - result - }; - let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb, elapsed); + format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64)) + } else { + String::new() + }); let cell = Bsp::s(value, Self::phat_hi(color.dark.rgb, color.darker.rgb)); Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell)) })).boxed()).into() @@ -109,12 +105,14 @@ impl Arranger { (||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into() } fn track_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { - (move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| { + let iter = ||self.tracks_with_widths(); + (move||Fixed::y(2, Map::new(iter, move|(_, track, x1, x2), i| { let color = track.color(); + let name = format!(" {}", &track.name); Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, Tui::fg_bg(color.lightest.rgb, color.base.rgb, - Self::phat_cell(color, color.darkest.rgb.into(), Tui::bold(true, format!("{}", track.name.read().unwrap())))))) - })).boxed()).into() + Self::phat_cell(color, color.darkest.rgb.into(), + Tui::bold(true, name))))) })).boxed()).into() } fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { @@ -138,7 +136,7 @@ impl Arranger { move|(_, scene, y1, y2), i| { let h = (y2 - y1) as u16; let color = scene.color(); - let name = format!(" {}", scene.name.read().unwrap()); + let name = format!("🭬{}", &scene.name); let cell = Self::phat_cell(color, *last_color.read().unwrap(), name); *last_color.write().unwrap() = color; map_south(y1 as u16, 2, Fill::x(cell)) @@ -147,7 +145,7 @@ impl Arranger { }).into() } fn scene_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { - (||Tui::bg(Color::Rgb(20,40,60), "").boxed()).into() + (||Tui::bg(self.color.darkest.rgb, "").boxed()).into() } pub fn tracks_with_widths (&self) @@ -175,8 +173,7 @@ impl Arranger { } /// name and width of track fn cell_name (track: &ArrangerTrack, _w: usize) -> impl Content { - let name = track.name().read().unwrap().clone(); - Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) + Tui::bold(true, Tui::fg(track.color.lightest.rgb, track.name().clone())) } /// beats until switchover fn cell_until_next (track: &ArrangerTrack, current: &Arc) @@ -207,7 +204,7 @@ impl Arranger { Map::new(||self.scenes_with_heights(1), move|(_, scene, y1, y2), i| { let h = (y2 - y1) as u16; let color = scene.color(); - let cell = Fixed::y(h, Fixed::x(scenes_w, Self::cell(color, scene.name.read().unwrap().clone()))); + let cell = Fixed::y(h, Fixed::x(scenes_w, Self::cell(color, scene.name.clone()))); map_south(y1 as u16, 1, cell) }) } @@ -235,7 +232,7 @@ impl Arranger { scene.color.base.rgb, if playing { "▶ " } else { " " } ); let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, - Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone())) + Expand::x(1, Tui::bold(true, scene.name.clone())) ); let clips = Map::new(||Arranger::tracks_with_widths_static(tracks), move|(index, track, x1, x2), _| Push::x((x2 - x1) as u16, Self::cell_clip(scene, index, track, (x2 - x1) as u16, height)) @@ -264,20 +261,6 @@ impl Arranger { fn toolbar_view (&self) -> impl Content + use<'_> { Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock)))) } - fn status_view (&self) -> impl Content + use<'_> { - ArrangerStatus::from(self) - //let edit_clip = MidiEditClip(&self.editor); - ////let selectors = When(false, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); - //row!([>selectors,<] edit_clip, MidiEditStatus(&self.editor)) - } - fn editor_status_view (&self) -> impl Content + use<'_> { - Bsp::e( - //ClipSelected::play_phrase(&self.player), - //ClipSelected::next_phrase(&self.player), - MidiEditClip(&self.editor), - MidiEditStatus(&self.editor), - ) - } fn pool_view (&self) -> impl Content + use<'_> { let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; diff --git a/src/clock/clock_tui.rs b/src/clock/clock_tui.rs index ec8d002e..781c4cbb 100644 --- a/src/clock/clock_tui.rs +++ b/src/clock/clock_tui.rs @@ -46,17 +46,20 @@ render!(TuiOut: (self: PlayPause) => Tui::bg( Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))))))); -pub struct BeatStats { compact: bool, bpm: String, beat: String, time: String, } +pub struct BeatStats { compact: bool, bpm: Arc, beat: Arc, time: Arc, } impl BeatStats { fn new (compact: bool, clock: &Clock) -> Self { - let (beat, time) = clock.started.read().unwrap().as_ref().map(|started|{ - let current_usec = clock.global.usec.get() - started.usec.get(); + let bpm = format!("{:.3}", clock.timebase.bpm.get()).into(); + let (beat, time) = if let Some(started) = clock.started.read().unwrap().as_ref() { + let now = clock.global.usec.get() - started.usec.get(); ( - clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(current_usec)), - format!("{:.3}s", current_usec/1000000.) + clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)).into(), + format!("{:.3}s", now/1000000.).into() ) - }).unwrap_or_else(||("-.-.--".to_string(), "-.---s".to_string())); - Self { compact, bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time } + } else { + ("-.-.--".to_string().into(), "-.---s".to_string().into()) + }; + Self { compact, bpm, beat, time } } } render!(TuiOut: (self: BeatStats) => Either(self.compact, @@ -71,7 +74,7 @@ render!(TuiOut: (self: BeatStats) => Either(self.compact, Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)), ))); -pub struct OutputStats { compact: bool, sample_rate: String, buffer_size: String, latency: String, } +pub struct OutputStats { compact: bool, sample_rate: Arc, buffer_size: Arc, latency: Arc, } impl OutputStats { fn new (compact: bool, clock: &Clock) -> Self { let rate = clock.timebase.sr.get(); @@ -82,9 +85,9 @@ impl OutputStats { format!("{:.1}kHz", rate / 1000.) } else { format!("{:.0}Hz", rate) - }, - buffer_size: format!("{chunk}"), - latency: format!("{:.1}ms", chunk as f64 / rate * 1000.), + }.into(), + buffer_size: format!("{chunk}").into(), + latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(), } } } diff --git a/src/clock/microsecond.rs b/src/clock/microsecond.rs index 8f69548b..3fc868f7 100644 --- a/src/clock/microsecond.rs +++ b/src/clock/microsecond.rs @@ -6,10 +6,10 @@ use crate::*; impl_time_unit!(Microsecond); impl Microsecond { - #[inline] pub fn format_msu (&self) -> String { + #[inline] pub fn format_msu (&self) -> Arc { let usecs = self.get() as usize; let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); let (minutes, seconds) = (seconds / 60, seconds % 60); - format!("{minutes}:{seconds:02}:{msecs:03}") + format!("{minutes}:{seconds:02}:{msecs:03}").into() } } diff --git a/src/clock/moment.rs b/src/clock/moment.rs index 34f53997..000696a3 100644 --- a/src/clock/moment.rs +++ b/src/clock/moment.rs @@ -64,7 +64,7 @@ impl Moment { self.pulse.set(pulse); self.sample.set(self.timebase.pulses_to_sample(pulse)); } - #[inline] pub fn format_beat (&self) -> String { - self.timebase.format_beats_1(self.pulse.get()) + #[inline] pub fn format_beat (&self) -> Arc { + self.timebase.format_beats_1(self.pulse.get()).into() } } diff --git a/src/clock/timebase.rs b/src/clock/timebase.rs index cc898b92..45895868 100644 --- a/src/clock/timebase.rs +++ b/src/clock/timebase.rs @@ -78,32 +78,32 @@ impl Timebase { events.map(|(time, event)|(self.quantize(step, time).0, event)).collect() } /// Format a number of pulses into Beat.Bar.Pulse starting from 0 - #[inline] pub fn format_beats_0 (&self, pulse: f64) -> String { + #[inline] pub fn format_beats_0 (&self, pulse: f64) -> Arc { let pulse = pulse as usize; let ppq = self.ppq.get() as usize; let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; - format!("{}.{}.{pulses:02}", beats / 4, beats % 4) + format!("{}.{}.{pulses:02}", beats / 4, beats % 4).into() } /// Format a number of pulses into Beat.Bar starting from 0 - #[inline] pub fn format_beats_0_short (&self, pulse: f64) -> String { + #[inline] pub fn format_beats_0_short (&self, pulse: f64) -> Arc { let pulse = pulse as usize; let ppq = self.ppq.get() as usize; let beats = if ppq > 0 { pulse / ppq } else { 0 }; - format!("{}.{}", beats / 4, beats % 4) + format!("{}.{}", beats / 4, beats % 4).into() } /// Format a number of pulses into Beat.Bar.Pulse starting from 1 - #[inline] pub fn format_beats_1 (&self, pulse: f64) -> String { + #[inline] pub fn format_beats_1 (&self, pulse: f64) -> Arc { let pulse = pulse as usize; let ppq = self.ppq.get() as usize; let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; - format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1) + format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1).into() } /// Format a number of pulses into Beat.Bar.Pulse starting from 1 - #[inline] pub fn format_beats_1_short (&self, pulse: f64) -> String { + #[inline] pub fn format_beats_1_short (&self, pulse: f64) -> Arc { let pulse = pulse as usize; let ppq = self.ppq.get() as usize; let beats = if ppq > 0 { pulse / ppq } else { 0 }; - format!("{}.{}", beats / 4 + 1, beats % 4 + 1) + format!("{}.{}", beats / 4 + 1, beats % 4 + 1).into() } } diff --git a/src/file.rs b/src/file.rs index 942f011b..2c1a0154 100644 --- a/src/file.rs +++ b/src/file.rs @@ -18,7 +18,7 @@ pub enum FileBrowserCommand { Confirm, Select(usize), Chdir(PathBuf), - Filter(String), + Filter(Arc), } render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{ let mut i = 0; diff --git a/src/groovebox/groovebox_edn.rs b/src/groovebox/groovebox_edn.rs index 2e7bc79c..8f3c0b8e 100644 --- a/src/groovebox/groovebox_edn.rs +++ b/src/groovebox/groovebox_edn.rs @@ -23,10 +23,10 @@ impl EdnViewData for &Groovebox { ":input-meter-r" => Box::new(Meter("R/", self.sampler.input_meter[1])), ":transport" => Box::new(TransportView::new(true, &self.player.clock)), - ":clip-play" => Box::new(ClipSelected::play_phrase(&self.player)), - ":clip-next" => Box::new(ClipSelected::next_phrase(&self.player)), - ":clip-edit" => Box::new(MidiEditClip(&self.editor)), - ":edit-stat" => Box::new(MidiEditStatus(&self.editor)), + ":clip-play" => Box::new(self.player.play_status()), + ":clip-next" => Box::new(self.player.next_status()), + ":clip-edit" => Box::new(self.editor.clip_status()), + ":edit-stat" => Box::new(self.editor.edit_status()), ":pool-view" => Box::new(PoolView(self.compact, &self.pool)), ":midi-view" => Box::new(&self.editor), diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 296194bc..da7788ca 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -30,10 +30,10 @@ impl Groovebox { } fn selector_view (&self) -> impl Content + use<'_> { row!( - ClipSelected::play_phrase(&self.player), - ClipSelected::next_phrase(&self.player), - MidiEditClip(&self.editor), - MidiEditStatus(&self.editor), + self.player.play_status(), + self.player.next_status(), + self.editor.clip_status(), + self.editor.edit_status(), ) } fn sample_view (&self) -> impl Content + use<'_> { @@ -53,7 +53,7 @@ impl Groovebox { PoolView(self.compact, &self.pool)) } fn sampler_view (&self) -> impl Content + use<'_> { - let sampler_w = if self.compact { 4 } else { 11 }; + let sampler_w = if self.compact { 4 } else { 40 }; let sampler_y = if self.compact { 1 } else { 0 }; Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( SampleList::new(self.compact, &self.sampler, &self.editor)))) diff --git a/src/jack.rs b/src/jack.rs index 5500892e..4fe9a1fb 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -242,12 +242,12 @@ impl RegisterPort for Arc> { /// Event enum for JACK events. pub enum JackEvent { ThreadInit, - Shutdown(ClientStatus, String), + Shutdown(ClientStatus, Arc), Freewheel(bool), SampleRate(Frames), - ClientRegistration(String, bool), + ClientRegistration(Arc, bool), PortRegistration(PortId, bool), - PortRename(PortId, String, String), + PortRename(PortId, Arc, Arc), PortsConnected(PortId, PortId, bool), GraphReorder, XRun, diff --git a/src/menu.rs b/src/menu.rs index 3ce55383..3f5ee25e 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -11,7 +11,7 @@ impl> MenuBar { } } pub struct Menu> { - pub title: String, + pub title: Arc, pub items: Vec>, pub index: Option, } diff --git a/src/midi.rs b/src/midi.rs index c433e6cd..be478f11 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -11,9 +11,7 @@ pub(crate) mod midi_note; pub(crate) use midi_note::*; pub(crate) mod midi_range; pub(crate) use midi_range::*; pub(crate) mod midi_point; pub(crate) use midi_point::*; pub(crate) mod midi_view; pub(crate) use midi_view::*; - pub(crate) mod midi_editor; pub(crate) use midi_editor::*; -pub(crate) mod midi_status; pub(crate) use midi_status::*; /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut [Vec>]) { diff --git a/src/midi/midi_clip.rs b/src/midi/midi_clip.rs index 3960dc0c..fe8438c1 100644 --- a/src/midi/midi_clip.rs +++ b/src/midi/midi_clip.rs @@ -17,7 +17,7 @@ pub trait HasMidiClip { pub struct MidiClip { pub uuid: uuid::Uuid, /// Name of phrase - pub name: String, + pub name: Arc, /// Temporal resolution in pulses per quarter note pub ppq: usize, /// Length of phrase in pulses @@ -49,7 +49,7 @@ impl MidiClip { ) -> Self { Self { uuid: uuid::Uuid::new_v4(), - name: name.as_ref().to_string(), + name: name.as_ref().into(), ppq: PPQ, length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), diff --git a/src/midi/midi_editor.rs b/src/midi/midi_editor.rs index 7cabd096..e7c59c7a 100644 --- a/src/midi/midi_editor.rs +++ b/src/midi/midi_editor.rs @@ -122,6 +122,39 @@ impl MidiEditor { self.mode.redraw(); } } + + pub fn clip_status (&self) -> impl Content + '_ { + let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) { + (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) + } else { + (ItemPalette::from(TuiTheme::g(64)), String::new().into(), 0, false) + }; + row!( + FieldV(color, "Edit", format!("{name} ({length})")), + FieldV(color, "Loop", looped.to_string()) + ) + } + + pub fn edit_status (&self) -> impl Content + '_ { + let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) { + (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) + } else { + (ItemPalette::from(TuiTheme::g(64)), String::new().into(), 0, false) + }; + let time_point = self.time_point(); + let time_start = self.time_start(); + let time_end = self.time_end(); + let time_axis = self.time_axis().get(); + let time_zoom = self.time_zoom().get(); + let time_lock = if self.time_lock().get() { "[lock]" } else { " " }; + let time_field = FieldV(color, "Time", format!("{length}/{time_zoom}+{time_point} {time_lock}")); + + let note_point = format!("{:>3}", self.note_point()); + let note_name = format!("{:4}", Note::pitch_to_name(self.note_point())); + let note_len = format!("{:>4}", self.note_len());;;; + let note_field = FieldV(color, "Note", format!("{note_name} {note_point} {note_len}")); + Bsp::e(time_field, note_field,) + } } impl std::fmt::Debug for MidiEditor { diff --git a/src/midi/midi_player.rs b/src/midi/midi_player.rs index e1ba9159..10ff95ed 100644 --- a/src/midi/midi_player.rs +++ b/src/midi/midi_player.rs @@ -52,7 +52,9 @@ impl MidiPlayer { midi_from: &[impl AsRef], midi_to: &[impl AsRef], ) -> Usually { - let name = name.as_ref(); + let name = name.as_ref(); + let midi_in = jack.midi_in(&format!("M/{name}"), midi_from)?; + let midi_out = jack.midi_out(&format!("{name}/M"), midi_to)?; Ok(Self { clock: Clock::from(jack), play_phrase: None, @@ -62,19 +64,20 @@ impl MidiPlayer { overdub: false, notes_in: RwLock::new([false;128]).into(), - midi_ins: vec![ - jack.midi_in(&format!("M/{name}"), midi_from)?, - ], - - midi_outs: vec![ - jack.midi_out(&format!("{name}/M"), midi_to)?, - ], + midi_ins: vec![midi_in], + midi_outs: vec![midi_out], notes_out: RwLock::new([false;128]).into(), reset: true, note_buf: vec![0;8], }) } + pub fn play_status (&self) -> impl Content { + ClipSelected::play_phrase(self) + } + pub fn next_status (&self) -> impl Content { + ClipSelected::next_phrase(self) + } } impl std::fmt::Debug for MidiPlayer { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { diff --git a/src/midi/midi_pool.rs b/src/midi/midi_pool.rs index f69d0027..23a04833 100644 --- a/src/midi/midi_pool.rs +++ b/src/midi/midi_pool.rs @@ -21,7 +21,7 @@ pub enum PhrasePoolCommand { Swap(usize, usize), Import(usize, PathBuf), Export(usize, PathBuf), - SetName(usize, String), + SetName(usize, Arc), SetLength(usize, usize), SetColor(usize, ItemColor), } diff --git a/src/midi/midi_status.rs b/src/midi/midi_status.rs deleted file mode 100644 index 35ceca4a..00000000 --- a/src/midi/midi_status.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::*; - -pub struct MidiEditClip<'a>(pub &'a MidiEditor); -render!(TuiOut: (self: MidiEditClip<'a>) => { - let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { - (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) - } else { - (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) - }; - row!( - FieldV(color, "Edit", format!("{name} ({length})")), - FieldV(color, "Loop", looped.to_string()) - ) -}); - -pub struct MidiEditStatus<'a>(pub &'a MidiEditor); -render!(TuiOut: (self: MidiEditStatus<'a>) => { - let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { - (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) - } else { - (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) - }; - let time_point = self.0.time_point(); - let time_start = self.0.time_start(); - let time_end = self.0.time_end(); - let time_axis = self.0.time_axis().get(); - let time_zoom = self.0.time_zoom().get(); - let time_lock = if self.0.time_lock().get() { "[lock]" } else { " " }; - let time_field = FieldV(color, "Time", format!("{length}/{time_zoom}+{time_point} {time_lock}")); - - let note_point = format!("{:>3}", self.0.note_point()); - let note_name = format!("{:4}", Note::pitch_to_name(self.0.note_point())); - let note_len = format!("{:>4}", self.0.note_len());;;; - let note_field = FieldV(color, "Note", format!("{note_name} {note_point} {note_len}")); - Bsp::e(time_field, note_field,) -}); diff --git a/src/mixer.rs b/src/mixer.rs index 48772111..bae4b4a2 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -4,7 +4,7 @@ use crate::*; pub struct Mixer { /// JACK client handle (needs to not be dropped for standalone mode to work). pub jack: Arc>, - pub name: String, + pub name: Arc, pub tracks: Vec, pub selected_track: usize, pub selected_column: usize, @@ -13,7 +13,7 @@ pub struct Mixer { /// A mixer track. #[derive(Debug)] pub struct MixerTrack { - pub name: String, + pub name: Arc, /// Inputs of 1st device pub audio_ins: Vec>, /// Outputs of last device @@ -47,7 +47,7 @@ impl Mixer { impl MixerTrack { pub fn new (name: &str) -> Usually { Ok(Self { - name: name.to_string(), + name: name.to_string().into(), audio_ins: vec![], audio_outs: vec![], devices: vec![], @@ -267,7 +267,7 @@ const SYM_LV2: &str = "lv2"; from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { let mut _gain = 0.0f64; let mut track = MixerTrack { - name: String::new(), + name: "".into(), audio_ins: vec![], audio_outs: vec![], devices: vec![], diff --git a/src/plugin.rs b/src/plugin.rs index 6c05aadc..fe058038 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -8,8 +8,8 @@ pub use self::lv2::LV2Plugin; pub struct Plugin { /// JACK client handle (needs to not be dropped for standalone mode to work). pub jack: Arc>, - pub name: String, - pub path: Option, + pub name: Arc, + pub path: Option>, pub plugin: Option, pub selected: usize, pub mapping: bool, @@ -47,7 +47,7 @@ impl Plugin { Ok(Self { jack: jack.clone(), name: name.into(), - path: Some(String::from(path)), + path: Some(String::from(path).into()), plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)), selected: 0, mapping: false, diff --git a/src/pool.rs b/src/pool.rs index 237019c4..2a885762 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -24,7 +24,7 @@ pub struct PoolModel { #[derive(Debug, Clone)] pub enum PoolMode { /// Renaming a pattern - Rename(usize, String), + Rename(usize, Arc), /// Editing the length of a pattern Length(usize, usize, PhraseLengthFocus), /// Load phrase from disk @@ -147,10 +147,10 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option return None }, kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Phrase(PhrasePoolCommand::Add(count, MidiClip::new( - String::from("Clip"), true, 4 * PPQ, None, Some(ItemPalette::random()) + "Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) ))), kpat!(Char('i')) => Cmd::Phrase(PhrasePoolCommand::Add(index + 1, MidiClip::new( - String::from("Clip"), true, 4 * PPQ, None, Some(ItemPalette::random()) + "Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) ))), kpat!(Char('d')) | kpat!(Shift-Char('D')) => { let mut phrase = state.phrases()[index].read().unwrap().duplicate(); diff --git a/src/pool/clip_length.rs b/src/pool/clip_length.rs index 34d5a0fc..d9e54258 100644 --- a/src/pool/clip_length.rs +++ b/src/pool/clip_length.rs @@ -30,14 +30,14 @@ impl PhraseLength { pub fn ticks (&self) -> usize { self.pulses % self.ppq } - pub fn bars_string (&self) -> String { - format!("{}", self.bars()) + pub fn bars_string (&self) -> Arc { + format!("{}", self.bars()).into() } - pub fn beats_string (&self) -> String { - format!("{}", self.beats()) + pub fn beats_string (&self) -> Arc { + format!("{}", self.beats()).into() } - pub fn ticks_string (&self) -> String { - format!("{:>02}", self.ticks()) + pub fn ticks_string (&self) -> Arc { + format!("{:>02}", self.ticks()).into() } } diff --git a/src/pool/clip_rename.rs b/src/pool/clip_rename.rs index af9c2339..ba027565 100644 --- a/src/pool/clip_rename.rs +++ b/src/pool/clip_rename.rs @@ -6,7 +6,7 @@ pub enum PhraseRenameCommand { Begin, Cancel, Confirm, - Set(String), + Set(Arc), } impl Command for PhraseRenameCommand { @@ -16,7 +16,7 @@ impl Command for PhraseRenameCommand { Some(PoolMode::Rename(phrase, ref mut old_name)) => match self { Set(s) => { state.phrases()[phrase].write().unwrap().name = s; - return Ok(Some(Self::Set(old_name.clone()))) + return Ok(Some(Self::Set(old_name.clone().into()))) }, Confirm => { let old_name = old_name.clone(); @@ -24,7 +24,7 @@ impl Command for PhraseRenameCommand { return Ok(Some(Self::Set(old_name))) }, Cancel => { - state.phrases()[phrase].write().unwrap().name = old_name.clone(); + state.phrases()[phrase].write().unwrap().name = old_name.clone().into(); }, _ => unreachable!() }, @@ -40,14 +40,14 @@ impl InputToCommand for PhraseRenameCommand { if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() { Some(match input { kpat!(Char(c)) => { - let mut new_name = old_name.clone(); + let mut new_name = old_name.clone().to_string(); new_name.push(*c); - Self::Set(new_name) + Self::Set(new_name.into()) }, kpat!(Backspace) => { - let mut new_name = old_name.clone(); + let mut new_name = old_name.clone().to_string(); new_name.pop(); - Self::Set(new_name) + Self::Set(new_name.into()) }, kpat!(Enter) => Self::Confirm, kpat!(Esc) => Self::Cancel, diff --git a/src/pool/clip_select.rs b/src/pool/clip_select.rs index 9dd7d991..0f79add0 100644 --- a/src/pool/clip_select.rs +++ b/src/pool/clip_select.rs @@ -2,9 +2,9 @@ use crate::*; pub struct ClipSelected { pub(crate) title: &'static str, - pub(crate) name: String, + pub(crate) name: Arc, pub(crate) color: ItemPalette, - pub(crate) time: String, + pub(crate) time: Arc, } render!(TuiOut: (self: ClipSelected) => @@ -16,9 +16,9 @@ impl ClipSelected { pub fn play_phrase (state: &T) -> Self { let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); - (name.clone(), color) + (name.clone().into(), color) } else { - ("".to_string(), TuiTheme::g(64).into()) + ("".to_string().into(), TuiTheme::g(64).into()) }; Self { title: "Now", @@ -28,14 +28,14 @@ impl ClipSelected { .map(|(times, time)|format!("{:>3}x {:>}", times+1.0, state.clock().timebase.format_beats_1(time))) - .unwrap_or_else(||String::from(" ")) + .unwrap_or_else(||String::from(" ")).into() } } /// Shows next phrase with beats remaining until switchover pub fn next_phrase (state: &T) -> Self { - let mut time = String::from("--.-.--"); - let mut name = String::from(""); + let mut time: Arc = String::from("--.-.--").into(); + let mut name: Arc = String::from("").into(); let mut color = ItemPalette::from(TuiTheme::g(64)); if let Some((t, Some(phrase))) = state.next_phrase() { let phrase = phrase.read().unwrap(); @@ -50,7 +50,7 @@ impl ClipSelected { } else { String::new() } - } + }.into() } else if let Some((t, Some(phrase))) = state.play_phrase() { let phrase = phrase.read().unwrap(); if phrase.looped { @@ -59,10 +59,12 @@ impl ClipSelected { let target = t.pulse.get() + phrase.length as f64; let current = state.clock().playhead.pulse.get(); if target > current { - time = format!("-{:>}", state.clock().timebase.format_beats_0(target - current)) + time = format!("-{:>}", state.clock().timebase.format_beats_0( + target - current + )).into() } } else { - name = "Stop".to_string(); + name = "Stop".to_string().into(); } }; Self { title: "Next", time, name, color, } diff --git a/src/pool/pool_tui.rs b/src/pool/pool_tui.rs index 2ddda0ea..4ed2a028 100644 --- a/src/pool/pool_tui.rs +++ b/src/pool/pool_tui.rs @@ -5,7 +5,7 @@ render!(TuiOut: (self: PoolView<'a>) => { let Self(compact, model) = self; let PoolModel { phrases, mode, .. } = self.1; let color = self.1.phrase().read().unwrap().color; - Outer( + Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, Outer( Style::default().fg(color.dark.rgb).bg(color.darkest.rgb) ).enclose(Map::new(||model.phrases().iter(), |clip, i|{ let item_height = 1; @@ -20,5 +20,5 @@ render!(TuiOut: (self: PoolView<'a>) => { Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))), Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))), ))) - })) + })))) }); diff --git a/src/sampler/sample.rs b/src/sampler/sample.rs index 08ece69b..c0b9103e 100644 --- a/src/sampler/sample.rs +++ b/src/sampler/sample.rs @@ -4,7 +4,7 @@ use super::*; /// A sound sample. #[derive(Default, Debug)] pub struct Sample { - pub name: String, + pub name: Arc, pub start: usize, pub end: usize, pub channels: Vec>, @@ -24,8 +24,8 @@ pub struct Sample { } impl Sample { - pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { - Self { name: name.to_string(), start, end, channels, rate: None, gain: 1.0 } + pub fn new (name: impl AsRef, start: usize, end: usize, channels: Vec>) -> Self { + Self { name: name.as_ref().into(), start, end, channels, rate: None, gain: 1.0 } } pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { Voice { diff --git a/src/sampler/sample_list.rs b/src/sampler/sample_list.rs index 96804061..51227409 100644 --- a/src/sampler/sample_list.rs +++ b/src/sampler/sample_list.rs @@ -18,9 +18,7 @@ render!(TuiOut: (self: SampleList<'a>) => { let note_pt = editor.note_point(); let note_hi = editor.note_hi(); Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| { - let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a)))); - let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset }; let mut fg = TuiTheme::g(160); if sampler.mapped[note].is_some() { @@ -33,13 +31,20 @@ render!(TuiOut: (self: SampleList<'a>) => { fg = Color::Rgb(224,64,32) } } - - offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", if *compact { + let label = if *compact { String::default() } else if let Some(sample) = &sampler.mapped[note] { - sample.read().unwrap().name.clone() + let sample = sample.read().unwrap(); + format!("{:8} {:3} {:6}-{:6}/{:6}", + sample.name, + sample.gain, + sample.start, + sample.end, + sample.channels[0].len() + ) } else { String::from("(none)") - }))) + }; + offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", label))) })) }); diff --git a/src/sequencer.rs b/src/sequencer.rs index 2e7ea1de..d5af1db7 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -57,16 +57,21 @@ impl Sequencer { Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock)))) } fn status_view (&self) -> impl Content + use<'_> { - let edit_clip = MidiEditClip(&self.editor); - let selectors = When(self.selectors, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); - row!(selectors, edit_clip, MidiEditStatus(&self.editor)) + row!( + When(self.selectors, Bsp::e( + self.player.play_status(), + self.player.next_status(), + )), + self.editor.clip_status(), + self.editor.edit_status(), + ) } fn selector_view (&self) -> impl Content + use<'_> { row!( - ClipSelected::play_phrase(&self.player), - ClipSelected::next_phrase(&self.player), - MidiEditClip(&self.editor), - MidiEditStatus(&self.editor), + self.player.play_status(), + self.player.next_status(), + self.editor.clip_status(), + self.editor.edit_status(), ) } fn pool_view (&self) -> impl Content + use<'_> { @@ -180,8 +185,8 @@ command!(|self: SequencerCommand, state: Sequencer|match self { #[derive(Clone)] pub struct SequencerStatus { pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, + pub(crate) cpu: Option>, + pub(crate) size: Arc, pub(crate) playing: bool, } from!(|state:&Sequencer|SequencerStatus = { @@ -192,8 +197,8 @@ from!(|state:&Sequencer|SequencerStatus = { Self { width, playing: state.clock.is_rolling(), - cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), - size: format!("{}x{}│", width, state.size.h()), + cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()), + size: format!("{}x{}│", width, state.size.h()).into(), } }); render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!( diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 404cdb03..51cf7b3f 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -18,6 +18,15 @@ impl Content for String { } } +impl Content for Arc { + fn layout (&self, to: [u16;4]) -> [u16;4] { + to.center_xy([self.chars().count() as u16, 1]) + } + fn render (&self, to: &mut TuiOut) { + to.blit(self, to.area.x(), to.area.y(), None) + } +} + pub struct Repeat<'a>(pub &'a str); impl Content for Repeat<'_> {