mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
refactor arranger back into modules
This commit is contained in:
parent
e1eaa51d26
commit
088b5a5127
7 changed files with 710 additions and 721 deletions
|
|
@ -1,8 +1,10 @@
|
||||||
//! Clip launcher and arrangement editor.
|
//! Clip launcher and arrangement editor.
|
||||||
|
|
||||||
use crate::{core::*, model::*};
|
use crate::{core::*, model::*};
|
||||||
use self::focus::ArrangerFocus;
|
use self::arr_focus::ArrangerFocus;
|
||||||
pub use self::scene::Scene;
|
pub use self::arr_scene::Scene;
|
||||||
|
|
||||||
|
submod! { arr_draw_h arr_draw_v arr_focus arr_phrase arr_scene arr_track }
|
||||||
|
|
||||||
/// Key bindings for arranger section.
|
/// Key bindings for arranger section.
|
||||||
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
|
|
@ -124,725 +126,9 @@ impl Arranger {
|
||||||
|
|
||||||
render!(Arranger |self, buf, area| match self.mode {
|
render!(Arranger |self, buf, area| match self.mode {
|
||||||
ArrangerViewMode::Horizontal =>
|
ArrangerViewMode::Horizontal =>
|
||||||
self::draw_horizontal::draw(self, buf, area),
|
self::arr_draw_h::draw(self, buf, area),
|
||||||
ArrangerViewMode::Vertical =>
|
ArrangerViewMode::Vertical =>
|
||||||
self::draw_vertical::draw_expanded(self, buf, area),
|
self::arr_draw_v::draw_expanded(self, buf, area),
|
||||||
ArrangerViewMode::VerticalCompact =>
|
ArrangerViewMode::VerticalCompact =>
|
||||||
self::draw_vertical::draw_compact(self, buf, area),
|
self::arr_draw_v::draw_compact(self, buf, area),
|
||||||
});
|
});
|
||||||
|
|
||||||
mod draw_vertical {
|
|
||||||
use crate::{core::*, view::*};
|
|
||||||
use super::{Arranger, focus::ArrangerFocus, track::track_clip_name_lengths, scene::{Scene, scene_ppqs, scene_name_max_len}};
|
|
||||||
|
|
||||||
pub fn draw_expanded (state: &Arranger, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
|
||||||
let scene_rows = scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice());
|
|
||||||
draw(state, buf, area, track_cols.as_slice(), scene_rows.as_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_compact (state: &Arranger, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
|
||||||
let scene_rows = (0..=state.scenes.len()+3).map(|i|(96, 96*i)).collect::<Vec<_>>();
|
|
||||||
draw(state, buf, area, track_cols.as_slice(), scene_rows.as_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw (
|
|
||||||
state: &Arranger,
|
|
||||||
buf: &mut Buffer,
|
|
||||||
mut area: Rect,
|
|
||||||
cols: &[(usize, usize)],
|
|
||||||
rows: &[(usize, usize)],
|
|
||||||
) -> Usually<Rect> {
|
|
||||||
area.height = 2 + (rows[rows.len() - 1].1 / 96) as u16;
|
|
||||||
let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16;
|
|
||||||
Layered([
|
|
||||||
&to_fill_bg(Nord::bg_lo(state.focused, state.entered)),
|
|
||||||
&column_separators(offset, cols),
|
|
||||||
&cursor_focus(state, offset, cols, rows),
|
|
||||||
&Split::down([
|
|
||||||
&tracks_header(state, cols, offset),
|
|
||||||
&scene_rows(state, cols, rows, offset),
|
|
||||||
]),
|
|
||||||
&row_separators(rows),
|
|
||||||
]).render(buf, area)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column_separators <'a> (offset: u16, cols: &'a [(usize, usize)]) -> impl Render + 'a {
|
|
||||||
move |buf: &mut Buffer, area: Rect|{
|
|
||||||
let style = Some(Style::default().fg(Color::Rgb(0,0,0)));
|
|
||||||
for (_, x) in cols.iter() {
|
|
||||||
let x = offset + area.x + *x as u16 - 1;
|
|
||||||
for y in area.y..area.height+area.y {
|
|
||||||
"▎".blit(buf, x, y, style)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn row_separators <'a> (rows: &'a [(usize, usize)]) -> impl Render + 'a {
|
|
||||||
move |buf: &mut Buffer, area: Rect| {
|
|
||||||
for (_, y) in rows.iter() {
|
|
||||||
let y = area.y + (*y / 96) as u16 + 1;
|
|
||||||
if y >= buf.area.height {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for x in area.x..area.width+area.y-2 {
|
|
||||||
let cell = buf.get_mut(x, y);
|
|
||||||
cell.modifier = Modifier::UNDERLINED;
|
|
||||||
cell.underline_color = Color::Rgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_focus <'a> (
|
|
||||||
state: &'a Arranger, offset: u16, cols: &'a [(usize, usize)], rows: &'a [(usize, usize)],
|
|
||||||
) -> impl Render + 'a {
|
|
||||||
move |buf: &mut Buffer, area: Rect| {
|
|
||||||
match state.selected {
|
|
||||||
ArrangerFocus::Mix => if state.focused
|
|
||||||
&& state.entered
|
|
||||||
&& state.selected == ArrangerFocus::Mix
|
|
||||||
{
|
|
||||||
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
|
||||||
Corners(Style::default().green().not_dim()).draw(buf, area)
|
|
||||||
} else {
|
|
||||||
Ok(area)
|
|
||||||
},
|
|
||||||
ArrangerFocus::Track(t) => {
|
|
||||||
let area = Rect {
|
|
||||||
x: offset + area.x + cols[t].1 as u16 - 1,
|
|
||||||
y: area.y,
|
|
||||||
width: cols[t].0 as u16,
|
|
||||||
height: area.height
|
|
||||||
};
|
|
||||||
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
|
||||||
Corners(Style::default().green().not_dim()).draw(buf, area)
|
|
||||||
},
|
|
||||||
ArrangerFocus::Scene(s) => {
|
|
||||||
let area = Rect {
|
|
||||||
x: area.x,
|
|
||||||
y: 2 + area.y + (rows[s].1 / 96) as u16,
|
|
||||||
width: area.width,
|
|
||||||
height: (rows[s].0 / 96) as u16
|
|
||||||
};
|
|
||||||
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
|
||||||
Corners(Style::default().green().not_dim()).draw(buf, area)
|
|
||||||
},
|
|
||||||
ArrangerFocus::Clip(t, s) => {
|
|
||||||
let track_area = Rect {
|
|
||||||
x: offset + area.x + cols[t].1 as u16 - 1,
|
|
||||||
y: area.y,
|
|
||||||
width: cols[t].0 as u16,
|
|
||||||
height: area.height
|
|
||||||
};
|
|
||||||
let scene_area = Rect {
|
|
||||||
x: area.x,
|
|
||||||
y: 2 + area.y + (rows[s].1 / 96) as u16,
|
|
||||||
width: area.width,
|
|
||||||
height: (rows[s].0 / 96) as u16
|
|
||||||
};
|
|
||||||
let area = Rect {
|
|
||||||
x: offset + area.x + cols[t].1 as u16 - 1,
|
|
||||||
y: 2 + area.y + (rows[s].1 / 96) as u16,
|
|
||||||
width: cols[t].0 as u16,
|
|
||||||
height: (rows[s].0 / 96) as u16
|
|
||||||
};
|
|
||||||
let lo = Nord::bg_hi(state.focused, state.entered);
|
|
||||||
let hi = Nord::bg_hier(state.focused, state.entered);
|
|
||||||
fill_bg(buf, track_area, lo);
|
|
||||||
fill_bg(buf, scene_area, lo);
|
|
||||||
fill_bg(buf, area, hi);
|
|
||||||
Corners(Style::default().green().not_dim()).draw(buf, area)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tracks_header <'a> (
|
|
||||||
state: &'a Arranger,
|
|
||||||
track_cols: &'a [(usize, usize)],
|
|
||||||
offset: u16,
|
|
||||||
) -> impl Render + 'a {
|
|
||||||
move |buf: &mut Buffer, area: Rect| {
|
|
||||||
let Rect { y, width, .. } = area;
|
|
||||||
for (track, (_, x)) in state.tracks.iter().zip(track_cols) {
|
|
||||||
let x = *x as u16;
|
|
||||||
if x > width {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
track.name.blit(buf, offset + x, y, Some(Style::default()))?;
|
|
||||||
}
|
|
||||||
Ok(Rect { x: area.x, y, width, height: 2 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scene_rows <'a> (
|
|
||||||
state: &'a Arranger,
|
|
||||||
track_cols: &'a [(usize, usize)],
|
|
||||||
scene_rows: &'a [(usize, usize)],
|
|
||||||
offset: u16,
|
|
||||||
) -> impl Render + 'a {
|
|
||||||
move |buf: &mut Buffer, area: Rect| {
|
|
||||||
let black = Some(Style::default().fg(Color::Rgb(0, 0, 0)));
|
|
||||||
let Rect { mut y, height, .. } = area;
|
|
||||||
for (_, x) in track_cols.iter() {
|
|
||||||
let x = *x as u16;
|
|
||||||
if x > 0 {
|
|
||||||
for y in area.y-2..y-2 {
|
|
||||||
"▎".blit(buf, x - 1, y, black)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (scene, (pulses, _)) in state.scenes.iter().zip(scene_rows) {
|
|
||||||
if y > height {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let h = 1.max((pulses / 96) as u16);
|
|
||||||
let area = Rect { x: area.x, y, width: area.width, height: h.min(area.height - y) };
|
|
||||||
scene_row(state, buf, area, scene, track_cols, offset)?;
|
|
||||||
y = y + h
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scene_row (
|
|
||||||
state: &Arranger,
|
|
||||||
buf: &mut Buffer,
|
|
||||||
area: Rect,
|
|
||||||
scene: &Scene,
|
|
||||||
track_cols: &[(usize, usize)],
|
|
||||||
offset: u16
|
|
||||||
) -> Usually<u16> {
|
|
||||||
let Rect { y, width, .. } = area;
|
|
||||||
let tracks = state.tracks.as_ref();
|
|
||||||
let playing = scene.is_playing(tracks);
|
|
||||||
(if playing { "▶" } else { " " }).blit(buf, area.x, y, None)?;
|
|
||||||
scene.name.blit(buf, area.x + 1, y, None)?;
|
|
||||||
let style = Some(Style::default().white());
|
|
||||||
for (track, (w, x)) in track_cols.iter().enumerate() {
|
|
||||||
let x = *x as u16 + offset;
|
|
||||||
if x > width {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if let (Some(track), Some(Some(clip))) = (
|
|
||||||
tracks.get(track), scene.clips.get(track)
|
|
||||||
) {
|
|
||||||
if let Some(phrase) = track.phrases.get(*clip) {
|
|
||||||
let phrase = phrase.read().unwrap();
|
|
||||||
phrase.name.blit(buf, x, y, style)?;
|
|
||||||
if track.sequence == Some(*clip) {
|
|
||||||
fill_bg(buf, Rect {
|
|
||||||
x: x - 1,
|
|
||||||
y,
|
|
||||||
width: *w as u16,
|
|
||||||
height: area.height,
|
|
||||||
}, Color::Rgb(60,100,50));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((scene.pulses(tracks) / 96) as u16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod draw_horizontal {
|
|
||||||
use crate::{core::*, view::*};
|
|
||||||
use super::{Arranger, track::*};
|
|
||||||
|
|
||||||
pub fn draw (state: &Arranger, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
|
||||||
area.height = area.height.min((2 + state.tracks.len() * 2) as u16);
|
|
||||||
Layered([
|
|
||||||
&to_fill_bg(Nord::bg_lo(state.focused, state.entered)),
|
|
||||||
&Split::right([
|
|
||||||
&track_name_column(state),
|
|
||||||
&track_mon_column(state),
|
|
||||||
&track_rec_column(state),
|
|
||||||
&track_ovr_column(state),
|
|
||||||
&track_del_column(state),
|
|
||||||
&track_gain_column(state),
|
|
||||||
&track_scenes_column(state),
|
|
||||||
]),
|
|
||||||
]).render(buf, area)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn track_name_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
|
||||||
let dim = Some(Style::default().dim());
|
|
||||||
let yellow = Some(Style::default().yellow().bold().not_dim());
|
|
||||||
let white = Some(Style::default().white().bold().not_dim());
|
|
||||||
move |buf: &mut Buffer, mut area: Rect| {
|
|
||||||
area.width = 3 + 5.max(track_name_max_len(state.tracks.as_slice())) as u16;
|
|
||||||
let offset = 0; // track scroll offset
|
|
||||||
for y in 0..area.height {
|
|
||||||
if y == 0 {
|
|
||||||
"Mixer".blit(buf, area.x + 1, area.y + y, dim)?;
|
|
||||||
} else if y % 2 == 0 {
|
|
||||||
let index = (y as usize - 2) / 2 + offset;
|
|
||||||
if let Some(track) = state.tracks.get(index) {
|
|
||||||
let selected = state.selected.track() == Some(index);
|
|
||||||
let style = if selected { yellow } else { white };
|
|
||||||
format!(" {index:>02} ").blit(buf, area.x, area.y + y, style)?;
|
|
||||||
track.name.blit(buf, area.x + 4, area.y + y, style)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn track_mon_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
|
||||||
let on = Some(Style::default().not_dim().green().bold());
|
|
||||||
let off = Some(Style::default().dim());
|
|
||||||
move |buf: &mut Buffer, mut area: Rect| {
|
|
||||||
area.x = area.x + 1;
|
|
||||||
for y in 0..area.height {
|
|
||||||
if y == 0 {
|
|
||||||
//" MON ".blit(buf, area.x, area.y + y, style2)?;
|
|
||||||
} else if y % 2 == 0 {
|
|
||||||
let index = (y as usize - 2) / 2;
|
|
||||||
if let Some(track) = state.tracks.get(index) {
|
|
||||||
let style = if track.monitoring { on } else { off };
|
|
||||||
" MON ".blit(buf, area.x, area.y + y, style)?;
|
|
||||||
} else {
|
|
||||||
area.height = y;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
area.width = 4;
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn track_rec_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
|
||||||
let on = Some(Style::default().not_dim().red().bold());
|
|
||||||
let off = Some(Style::default().dim());
|
|
||||||
move |buf: &mut Buffer, mut area: Rect| {
|
|
||||||
area.x = area.x + 1;
|
|
||||||
for y in 0..area.height {
|
|
||||||
if y == 0 {
|
|
||||||
//" REC ".blit(buf, area.x, area.y + y, style2)?;
|
|
||||||
} else if y % 2 == 0 {
|
|
||||||
let index = (y as usize - 2) / 2;
|
|
||||||
if let Some(track) = state.tracks.get(index) {
|
|
||||||
let style = if track.recording { on } else { off };
|
|
||||||
" REC ".blit(buf, area.x, area.y + y, style)?;
|
|
||||||
} else {
|
|
||||||
area.height = y;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
area.width = 4;
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn track_ovr_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
|
||||||
let on = Some(Style::default().not_dim().yellow().bold());
|
|
||||||
let off = Some(Style::default().dim());
|
|
||||||
move |buf: &mut Buffer, mut area: Rect| {
|
|
||||||
area.x = area.x + 1;
|
|
||||||
for y in 0..area.height {
|
|
||||||
if y == 0 {
|
|
||||||
//" OVR ".blit(buf, area.x, area.y + y, style2)?;
|
|
||||||
} else if y % 2 == 0 {
|
|
||||||
let index = (y as usize - 2) / 2;
|
|
||||||
if let Some(track) = state.tracks.get(index) {
|
|
||||||
" OVR ".blit(buf, area.x, area.y + y, if track.overdub {
|
|
||||||
on
|
|
||||||
} else {
|
|
||||||
off
|
|
||||||
})?;
|
|
||||||
} else {
|
|
||||||
area.height = y;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
area.width = 4;
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn track_del_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
|
||||||
let off = Some(Style::default().dim());
|
|
||||||
move |buf: &mut Buffer, mut area: Rect| {
|
|
||||||
area.x = area.x + 1;
|
|
||||||
for y in 0..area.height {
|
|
||||||
if y == 0 {
|
|
||||||
//" DEL ".blit(buf, area.x, area.y + y, style2)?;
|
|
||||||
} else if y % 2 == 0 {
|
|
||||||
let index = (y as usize - 2) / 2;
|
|
||||||
if let Some(_) = state.tracks.get(index) {
|
|
||||||
" DEL ".blit(buf, area.x, area.y + y, off)?;
|
|
||||||
} else {
|
|
||||||
area.height = y;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
area.width = 4;
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn track_gain_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
|
||||||
let off = Some(Style::default().dim());
|
|
||||||
move |buf: &mut Buffer, mut area: Rect| {
|
|
||||||
area.x = area.x + 1;
|
|
||||||
for y in 0..area.height {
|
|
||||||
if y == 0 {
|
|
||||||
//" GAIN ".blit(buf, area.x, area.y + y, style2)?;
|
|
||||||
} else if y % 2 == 0 {
|
|
||||||
let index = (y as usize - 2) / 2;
|
|
||||||
if let Some(_) = state.tracks.get(index) {
|
|
||||||
" +0.0 ".blit(buf, area.x, area.y + y, off)?;
|
|
||||||
} else {
|
|
||||||
area.height = y;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
area.width = 7;
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn track_scenes_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
|
||||||
|buf: &mut Buffer, area: Rect| {
|
|
||||||
let mut x2 = 0;
|
|
||||||
let Rect { x, y, height, .. } = area;
|
|
||||||
for (scene_index, scene) in state.scenes.iter().enumerate() {
|
|
||||||
let active_scene = state.selected.scene() == Some(scene_index);
|
|
||||||
let sep = Some(if active_scene {
|
|
||||||
Style::default().yellow().not_dim()
|
|
||||||
} else {
|
|
||||||
Style::default().dim()
|
|
||||||
});
|
|
||||||
for y in y+1..y+height {
|
|
||||||
"│".blit(buf, x + x2, y, sep)?;
|
|
||||||
}
|
|
||||||
let mut x3 = scene.name.len() as u16;
|
|
||||||
scene.name.blit(buf, x + x2, y, sep)?;
|
|
||||||
for (i, clip) in scene.clips.iter().enumerate() {
|
|
||||||
let active_track = state.selected.track() == Some(i);
|
|
||||||
if let Some(clip) = clip {
|
|
||||||
let y2 = y + 2 + i as u16 * 2;
|
|
||||||
let label = match state.tracks[i].phrases.get(*clip) {
|
|
||||||
Some(phrase) => &format!("{}", phrase.read().unwrap().name),
|
|
||||||
None => "...."
|
|
||||||
};
|
|
||||||
label.blit(buf, x + x2, y2, Some(if active_track && active_scene {
|
|
||||||
Style::default().not_dim().yellow().bold()
|
|
||||||
} else {
|
|
||||||
Style::default().not_dim()
|
|
||||||
}))?;
|
|
||||||
x3 = x3.max(label.len() as u16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
x2 = x2 + x3 + 1;
|
|
||||||
}
|
|
||||||
Ok(Rect { x, y, height, width: x2 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod focus {
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
/// Represents the current user selection in the arranger
|
|
||||||
pub enum ArrangerFocus {
|
|
||||||
/** The whole mix is selected */
|
|
||||||
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 ArrangerFocus {
|
|
||||||
pub fn is_track (&self) -> bool {
|
|
||||||
match self { Self::Track(_) => true, _ => false }
|
|
||||||
}
|
|
||||||
pub fn is_scene (&self) -> bool {
|
|
||||||
match self { Self::Scene(_) => true, _ => false }
|
|
||||||
}
|
|
||||||
pub fn is_clip (&self) -> bool {
|
|
||||||
match self { Self::Clip(_, _) => true, _ => false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Track focus methods
|
|
||||||
impl ArrangerFocus {
|
|
||||||
pub fn track (&self) -> Option<usize> {
|
|
||||||
match self {
|
|
||||||
Self::Clip(t, _) => Some(*t),
|
|
||||||
Self::Track(t) => Some(*t),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn track_next (&mut self, last_track: usize) {
|
|
||||||
*self = match self {
|
|
||||||
Self::Mix => Self::Track(0),
|
|
||||||
Self::Track(t) => Self::Track(last_track.min(*t + 1)),
|
|
||||||
Self::Scene(s) => Self::Clip(0, *s),
|
|
||||||
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn track_prev (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::Mix => Self::Mix,
|
|
||||||
Self::Scene(s) => Self::Scene(*s),
|
|
||||||
Self::Track(t) => if *t == 0 {
|
|
||||||
Self::Mix
|
|
||||||
} else {
|
|
||||||
Self::Track(*t - 1)
|
|
||||||
},
|
|
||||||
Self::Clip(t, s) => if *t == 0 {
|
|
||||||
Self::Scene(*s)
|
|
||||||
} else {
|
|
||||||
Self::Clip(t.saturating_sub(1), *s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scene focus methods
|
|
||||||
impl ArrangerFocus {
|
|
||||||
pub fn scene (&self) -> Option<usize> {
|
|
||||||
match self {
|
|
||||||
Self::Clip(_, s) => Some(*s),
|
|
||||||
Self::Scene(s) => Some(*s),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn scene_next (&mut self, last_scene: usize) {
|
|
||||||
*self = match self {
|
|
||||||
Self::Mix => Self::Scene(0),
|
|
||||||
Self::Track(t) => Self::Clip(*t, 0),
|
|
||||||
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)),
|
|
||||||
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn scene_prev (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::Mix => Self::Mix,
|
|
||||||
Self::Track(t) => Self::Track(*t),
|
|
||||||
Self::Scene(s) => if *s == 0 {
|
|
||||||
Self::Mix
|
|
||||||
} else {
|
|
||||||
Self::Scene(*s - 1)
|
|
||||||
},
|
|
||||||
Self::Clip(t, s) => if *s == 0 {
|
|
||||||
Self::Track(*t)
|
|
||||||
} else {
|
|
||||||
Self::Clip(*t, s.saturating_sub(1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod scene {
|
|
||||||
use crate::{core::*, model::Track};
|
|
||||||
use super::Arranger;
|
|
||||||
|
|
||||||
/// A collection of phrases to play on each track.
|
|
||||||
pub struct Scene {
|
|
||||||
pub name: String,
|
|
||||||
pub clips: Vec<Option<usize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scene {
|
|
||||||
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
|
|
||||||
let name = name.as_ref().into();
|
|
||||||
let clips = clips.as_ref().iter().map(|x|x.clone()).collect();
|
|
||||||
Self { name, clips, }
|
|
||||||
}
|
|
||||||
/// Returns the pulse length of the longest phrase in the scene
|
|
||||||
pub fn pulses (&self, tracks: &[Track]) -> usize {
|
|
||||||
self.clips.iter().enumerate()
|
|
||||||
.filter_map(|(i, c)|c.map(|c|tracks[i].phrases.get(c)))
|
|
||||||
.filter_map(|p|p)
|
|
||||||
.fold(0, |a, p|a.max(p.read().unwrap().length))
|
|
||||||
}
|
|
||||||
/// Returns true if all phrases in the scene are currently playing
|
|
||||||
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
|
||||||
self.clips.iter().enumerate()
|
|
||||||
.all(|(track_index, phrase_index)|match phrase_index {
|
|
||||||
Some(i) => tracks[track_index].sequence == Some(*i),
|
|
||||||
None => true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scene_name_max_len (scenes: &[Scene]) -> usize {
|
|
||||||
scenes.iter()
|
|
||||||
.map(|s|s.name.len())
|
|
||||||
.fold(0, usize::max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scene_ppqs (tracks: &[Track], scenes: &[Scene]) -> Vec<(usize, usize)> {
|
|
||||||
let mut total = 0;
|
|
||||||
let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{
|
|
||||||
let pulses = scene.pulses(tracks);
|
|
||||||
total = total + pulses;
|
|
||||||
(pulses, total - pulses)
|
|
||||||
}).collect();
|
|
||||||
scenes.push((0, total));
|
|
||||||
scenes
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scene management methods
|
|
||||||
impl Arranger {
|
|
||||||
pub fn scene (&self) -> Option<&Scene> {
|
|
||||||
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
|
|
||||||
}
|
|
||||||
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
|
|
||||||
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
|
|
||||||
}
|
|
||||||
pub fn scene_next (&mut self) {
|
|
||||||
self.selected.scene_next(self.scenes.len() - 1)
|
|
||||||
}
|
|
||||||
pub fn scene_prev (&mut self) {
|
|
||||||
self.selected.scene_prev()
|
|
||||||
}
|
|
||||||
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
|
||||||
let clips = vec![None;self.tracks.len()];
|
|
||||||
self.scenes.push(match name {
|
|
||||||
Some(name) => Scene::new(name, clips),
|
|
||||||
None => Scene::new(&self.scene_default_name(), clips),
|
|
||||||
});
|
|
||||||
let index = self.scenes.len() - 1;
|
|
||||||
Ok(&mut self.scenes[index])
|
|
||||||
}
|
|
||||||
pub fn scene_del (&mut self) {
|
|
||||||
unimplemented!("Arranger::scene_del");
|
|
||||||
}
|
|
||||||
pub fn scene_default_name (&self) -> String {
|
|
||||||
format!("Scene {}", self.scenes.len() + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod track {
|
|
||||||
use crate::{core::*, model::Track};
|
|
||||||
use super::Arranger;
|
|
||||||
|
|
||||||
/// Track management methods
|
|
||||||
impl Arranger {
|
|
||||||
pub fn track (&self) -> Option<&Track> {
|
|
||||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
|
||||||
}
|
|
||||||
pub fn track_mut (&mut self) -> Option<&mut Track> {
|
|
||||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
|
||||||
}
|
|
||||||
pub fn track_next (&mut self) {
|
|
||||||
self.selected.track_next(self.tracks.len() - 1)
|
|
||||||
}
|
|
||||||
pub fn track_prev (&mut self) {
|
|
||||||
self.selected.track_prev()
|
|
||||||
}
|
|
||||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
|
||||||
self.tracks.push(name.map_or_else(
|
|
||||||
|| Track::new(&self.track_default_name()),
|
|
||||||
|name| Track::new(name),
|
|
||||||
)?);
|
|
||||||
let index = self.tracks.len() - 1;
|
|
||||||
Ok(&mut self.tracks[index])
|
|
||||||
}
|
|
||||||
pub fn track_del (&mut self) {
|
|
||||||
unimplemented!("Arranger::track_del");
|
|
||||||
}
|
|
||||||
pub fn track_default_name (&self) -> String {
|
|
||||||
format!("Track {}", self.tracks.len() + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn track_name_max_len (tracks: &[Track]) -> usize {
|
|
||||||
tracks.iter()
|
|
||||||
.map(|s|s.name.len())
|
|
||||||
.fold(0, usize::max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn track_clip_name_lengths (tracks: &[Track]) -> Vec<(usize, usize)> {
|
|
||||||
let mut total = 0;
|
|
||||||
let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{
|
|
||||||
let len = 2 + track.phrases
|
|
||||||
.iter()
|
|
||||||
.fold(track.name.len(), |len, phrase|{
|
|
||||||
len.max(phrase.read().unwrap().name.len())
|
|
||||||
});
|
|
||||||
total = total + len;
|
|
||||||
(len, total - len)
|
|
||||||
}).collect();
|
|
||||||
lengths.push((0, total));
|
|
||||||
lengths
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod phrase {
|
|
||||||
use crate::{core::*, devices::sequencer::Phrase};
|
|
||||||
use super::Arranger;
|
|
||||||
|
|
||||||
/// Phrase management methods
|
|
||||||
impl Arranger {
|
|
||||||
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
|
|
||||||
let track_id = self.selected.track()?;
|
|
||||||
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
|
|
||||||
}
|
|
||||||
pub fn phrase_next (&mut self) {
|
|
||||||
let track_index = self.selected.track();
|
|
||||||
let scene_index = self.selected.scene();
|
|
||||||
track_index
|
|
||||||
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
|
|
||||||
.and_then(|(track_index, track)|{
|
|
||||||
let phrases = track.phrases.len();
|
|
||||||
scene_index
|
|
||||||
.and_then(|index|self.scenes.get_mut(index))
|
|
||||||
.and_then(|scene|{
|
|
||||||
if let Some(phrase_index) = scene.clips[track_index] {
|
|
||||||
if phrase_index >= phrases - 1 {
|
|
||||||
scene.clips[track_index] = None;
|
|
||||||
} else {
|
|
||||||
scene.clips[track_index] = Some(phrase_index + 1);
|
|
||||||
}
|
|
||||||
} else if phrases > 0 {
|
|
||||||
scene.clips[track_index] = Some(0);
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn phrase_prev (&mut self) {
|
|
||||||
let track_index = self.selected.track();
|
|
||||||
let scene_index = self.selected.scene();
|
|
||||||
track_index
|
|
||||||
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
|
|
||||||
.and_then(|(track_index, track)|{
|
|
||||||
let phrases = track.phrases.len();
|
|
||||||
scene_index
|
|
||||||
.and_then(|index|self.scenes.get_mut(index))
|
|
||||||
.and_then(|scene|{
|
|
||||||
if let Some(phrase_index) = scene.clips[track_index] {
|
|
||||||
scene.clips[track_index] = if phrase_index == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(phrase_index - 1)
|
|
||||||
};
|
|
||||||
} else if phrases > 0 {
|
|
||||||
scene.clips[track_index] = Some(phrases - 1);
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
199
src/devices/arranger/arr_draw_h.rs
Normal file
199
src/devices/arranger/arr_draw_h.rs
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
use crate::{core::*, view::*};
|
||||||
|
use super::{Arranger, arr_track::*};
|
||||||
|
|
||||||
|
pub fn draw (state: &Arranger, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||||
|
area.height = area.height.min((2 + state.tracks.len() * 2) as u16);
|
||||||
|
Layered([
|
||||||
|
&to_fill_bg(Nord::bg_lo(state.focused, state.entered)),
|
||||||
|
&Split::right([
|
||||||
|
&track_name_column(state),
|
||||||
|
&track_mon_column(state),
|
||||||
|
&track_rec_column(state),
|
||||||
|
&track_ovr_column(state),
|
||||||
|
&track_del_column(state),
|
||||||
|
&track_gain_column(state),
|
||||||
|
&track_scenes_column(state),
|
||||||
|
]),
|
||||||
|
]).render(buf, area)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_name_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
||||||
|
let dim = Some(Style::default().dim());
|
||||||
|
let yellow = Some(Style::default().yellow().bold().not_dim());
|
||||||
|
let white = Some(Style::default().white().bold().not_dim());
|
||||||
|
move |buf: &mut Buffer, mut area: Rect| {
|
||||||
|
area.width = 3 + 5.max(track_name_max_len(state.tracks.as_slice())) as u16;
|
||||||
|
let offset = 0; // track scroll offset
|
||||||
|
for y in 0..area.height {
|
||||||
|
if y == 0 {
|
||||||
|
"Mixer".blit(buf, area.x + 1, area.y + y, dim)?;
|
||||||
|
} else if y % 2 == 0 {
|
||||||
|
let index = (y as usize - 2) / 2 + offset;
|
||||||
|
if let Some(track) = state.tracks.get(index) {
|
||||||
|
let selected = state.selected.track() == Some(index);
|
||||||
|
let style = if selected { yellow } else { white };
|
||||||
|
format!(" {index:>02} ").blit(buf, area.x, area.y + y, style)?;
|
||||||
|
track.name.blit(buf, area.x + 4, area.y + y, style)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_mon_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
||||||
|
let on = Some(Style::default().not_dim().green().bold());
|
||||||
|
let off = Some(Style::default().dim());
|
||||||
|
move |buf: &mut Buffer, mut area: Rect| {
|
||||||
|
area.x = area.x + 1;
|
||||||
|
for y in 0..area.height {
|
||||||
|
if y == 0 {
|
||||||
|
//" MON ".blit(buf, area.x, area.y + y, style2)?;
|
||||||
|
} else if y % 2 == 0 {
|
||||||
|
let index = (y as usize - 2) / 2;
|
||||||
|
if let Some(track) = state.tracks.get(index) {
|
||||||
|
let style = if track.monitoring { on } else { off };
|
||||||
|
" MON ".blit(buf, area.x, area.y + y, style)?;
|
||||||
|
} else {
|
||||||
|
area.height = y;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
area.width = 4;
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_rec_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
||||||
|
let on = Some(Style::default().not_dim().red().bold());
|
||||||
|
let off = Some(Style::default().dim());
|
||||||
|
move |buf: &mut Buffer, mut area: Rect| {
|
||||||
|
area.x = area.x + 1;
|
||||||
|
for y in 0..area.height {
|
||||||
|
if y == 0 {
|
||||||
|
//" REC ".blit(buf, area.x, area.y + y, style2)?;
|
||||||
|
} else if y % 2 == 0 {
|
||||||
|
let index = (y as usize - 2) / 2;
|
||||||
|
if let Some(track) = state.tracks.get(index) {
|
||||||
|
let style = if track.recording { on } else { off };
|
||||||
|
" REC ".blit(buf, area.x, area.y + y, style)?;
|
||||||
|
} else {
|
||||||
|
area.height = y;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
area.width = 4;
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_ovr_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
||||||
|
let on = Some(Style::default().not_dim().yellow().bold());
|
||||||
|
let off = Some(Style::default().dim());
|
||||||
|
move |buf: &mut Buffer, mut area: Rect| {
|
||||||
|
area.x = area.x + 1;
|
||||||
|
for y in 0..area.height {
|
||||||
|
if y == 0 {
|
||||||
|
//" OVR ".blit(buf, area.x, area.y + y, style2)?;
|
||||||
|
} else if y % 2 == 0 {
|
||||||
|
let index = (y as usize - 2) / 2;
|
||||||
|
if let Some(track) = state.tracks.get(index) {
|
||||||
|
" OVR ".blit(buf, area.x, area.y + y, if track.overdub {
|
||||||
|
on
|
||||||
|
} else {
|
||||||
|
off
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
area.height = y;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
area.width = 4;
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_del_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
||||||
|
let off = Some(Style::default().dim());
|
||||||
|
move |buf: &mut Buffer, mut area: Rect| {
|
||||||
|
area.x = area.x + 1;
|
||||||
|
for y in 0..area.height {
|
||||||
|
if y == 0 {
|
||||||
|
//" DEL ".blit(buf, area.x, area.y + y, style2)?;
|
||||||
|
} else if y % 2 == 0 {
|
||||||
|
let index = (y as usize - 2) / 2;
|
||||||
|
if let Some(_) = state.tracks.get(index) {
|
||||||
|
" DEL ".blit(buf, area.x, area.y + y, off)?;
|
||||||
|
} else {
|
||||||
|
area.height = y;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
area.width = 4;
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_gain_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
||||||
|
let off = Some(Style::default().dim());
|
||||||
|
move |buf: &mut Buffer, mut area: Rect| {
|
||||||
|
area.x = area.x + 1;
|
||||||
|
for y in 0..area.height {
|
||||||
|
if y == 0 {
|
||||||
|
//" GAIN ".blit(buf, area.x, area.y + y, style2)?;
|
||||||
|
} else if y % 2 == 0 {
|
||||||
|
let index = (y as usize - 2) / 2;
|
||||||
|
if let Some(_) = state.tracks.get(index) {
|
||||||
|
" +0.0 ".blit(buf, area.x, area.y + y, off)?;
|
||||||
|
} else {
|
||||||
|
area.height = y;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
area.width = 7;
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_scenes_column <'a> (state: &'a Arranger) -> impl Render + 'a {
|
||||||
|
|buf: &mut Buffer, area: Rect| {
|
||||||
|
let mut x2 = 0;
|
||||||
|
let Rect { x, y, height, .. } = area;
|
||||||
|
for (scene_index, scene) in state.scenes.iter().enumerate() {
|
||||||
|
let active_scene = state.selected.scene() == Some(scene_index);
|
||||||
|
let sep = Some(if active_scene {
|
||||||
|
Style::default().yellow().not_dim()
|
||||||
|
} else {
|
||||||
|
Style::default().dim()
|
||||||
|
});
|
||||||
|
for y in y+1..y+height {
|
||||||
|
"│".blit(buf, x + x2, y, sep)?;
|
||||||
|
}
|
||||||
|
let mut x3 = scene.name.len() as u16;
|
||||||
|
scene.name.blit(buf, x + x2, y, sep)?;
|
||||||
|
for (i, clip) in scene.clips.iter().enumerate() {
|
||||||
|
let active_track = state.selected.track() == Some(i);
|
||||||
|
if let Some(clip) = clip {
|
||||||
|
let y2 = y + 2 + i as u16 * 2;
|
||||||
|
let label = match state.tracks[i].phrases.get(*clip) {
|
||||||
|
Some(phrase) => &format!("{}", phrase.read().unwrap().name),
|
||||||
|
None => "...."
|
||||||
|
};
|
||||||
|
label.blit(buf, x + x2, y2, Some(if active_track && active_scene {
|
||||||
|
Style::default().not_dim().yellow().bold()
|
||||||
|
} else {
|
||||||
|
Style::default().not_dim()
|
||||||
|
}))?;
|
||||||
|
x3 = x3.max(label.len() as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x2 = x2 + x3 + 1;
|
||||||
|
}
|
||||||
|
Ok(Rect { x, y, height, width: x2 })
|
||||||
|
}
|
||||||
|
}
|
||||||
221
src/devices/arranger/arr_draw_v.rs
Normal file
221
src/devices/arranger/arr_draw_v.rs
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
use crate::{core::*, view::*};
|
||||||
|
use super::{
|
||||||
|
Arranger,
|
||||||
|
arr_focus::ArrangerFocus,
|
||||||
|
arr_track::track_clip_name_lengths,
|
||||||
|
arr_scene::{Scene, scene_ppqs, scene_name_max_len}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn draw_expanded (state: &Arranger, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
||||||
|
let scene_rows = scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice());
|
||||||
|
draw(state, buf, area, track_cols.as_slice(), scene_rows.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_compact (state: &Arranger, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
||||||
|
let scene_rows = (0..=state.scenes.len()+3).map(|i|(96, 96*i)).collect::<Vec<_>>();
|
||||||
|
draw(state, buf, area, track_cols.as_slice(), scene_rows.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw (
|
||||||
|
state: &Arranger,
|
||||||
|
buf: &mut Buffer,
|
||||||
|
mut area: Rect,
|
||||||
|
cols: &[(usize, usize)],
|
||||||
|
rows: &[(usize, usize)],
|
||||||
|
) -> Usually<Rect> {
|
||||||
|
area.height = 2 + (rows[rows.len() - 1].1 / 96) as u16;
|
||||||
|
let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16;
|
||||||
|
Layered([
|
||||||
|
&to_fill_bg(Nord::bg_lo(state.focused, state.entered)),
|
||||||
|
&column_separators(offset, cols),
|
||||||
|
&cursor_focus(state, offset, cols, rows),
|
||||||
|
&Split::down([
|
||||||
|
&tracks_header(state, cols, offset),
|
||||||
|
&scene_rows(state, cols, rows, offset),
|
||||||
|
]),
|
||||||
|
&row_separators(rows),
|
||||||
|
]).render(buf, area)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_separators <'a> (offset: u16, cols: &'a [(usize, usize)]) -> impl Render + 'a {
|
||||||
|
move |buf: &mut Buffer, area: Rect|{
|
||||||
|
let style = Some(Style::default().fg(Color::Rgb(0,0,0)));
|
||||||
|
for (_, x) in cols.iter() {
|
||||||
|
let x = offset + area.x + *x as u16 - 1;
|
||||||
|
for y in area.y..area.height+area.y {
|
||||||
|
"▎".blit(buf, x, y, style)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_separators <'a> (rows: &'a [(usize, usize)]) -> impl Render + 'a {
|
||||||
|
move |buf: &mut Buffer, area: Rect| {
|
||||||
|
for (_, y) in rows.iter() {
|
||||||
|
let y = area.y + (*y / 96) as u16 + 1;
|
||||||
|
if y >= buf.area.height {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for x in area.x..area.width+area.y-2 {
|
||||||
|
let cell = buf.get_mut(x, y);
|
||||||
|
cell.modifier = Modifier::UNDERLINED;
|
||||||
|
cell.underline_color = Color::Rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_focus <'a> (
|
||||||
|
state: &'a Arranger, offset: u16, cols: &'a [(usize, usize)], rows: &'a [(usize, usize)],
|
||||||
|
) -> impl Render + 'a {
|
||||||
|
move |buf: &mut Buffer, area: Rect| {
|
||||||
|
match state.selected {
|
||||||
|
ArrangerFocus::Mix => if state.focused
|
||||||
|
&& state.entered
|
||||||
|
&& state.selected == ArrangerFocus::Mix
|
||||||
|
{
|
||||||
|
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buf, area)
|
||||||
|
} else {
|
||||||
|
Ok(area)
|
||||||
|
},
|
||||||
|
ArrangerFocus::Track(t) => {
|
||||||
|
let area = Rect {
|
||||||
|
x: offset + area.x + cols[t].1 as u16 - 1,
|
||||||
|
y: area.y,
|
||||||
|
width: cols[t].0 as u16,
|
||||||
|
height: area.height
|
||||||
|
};
|
||||||
|
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buf, area)
|
||||||
|
},
|
||||||
|
ArrangerFocus::Scene(s) => {
|
||||||
|
let area = Rect {
|
||||||
|
x: area.x,
|
||||||
|
y: 2 + area.y + (rows[s].1 / 96) as u16,
|
||||||
|
width: area.width,
|
||||||
|
height: (rows[s].0 / 96) as u16
|
||||||
|
};
|
||||||
|
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buf, area)
|
||||||
|
},
|
||||||
|
ArrangerFocus::Clip(t, s) => {
|
||||||
|
let track_area = Rect {
|
||||||
|
x: offset + area.x + cols[t].1 as u16 - 1,
|
||||||
|
y: area.y,
|
||||||
|
width: cols[t].0 as u16,
|
||||||
|
height: area.height
|
||||||
|
};
|
||||||
|
let scene_area = Rect {
|
||||||
|
x: area.x,
|
||||||
|
y: 2 + area.y + (rows[s].1 / 96) as u16,
|
||||||
|
width: area.width,
|
||||||
|
height: (rows[s].0 / 96) as u16
|
||||||
|
};
|
||||||
|
let area = Rect {
|
||||||
|
x: offset + area.x + cols[t].1 as u16 - 1,
|
||||||
|
y: 2 + area.y + (rows[s].1 / 96) as u16,
|
||||||
|
width: cols[t].0 as u16,
|
||||||
|
height: (rows[s].0 / 96) as u16
|
||||||
|
};
|
||||||
|
let lo = Nord::bg_hi(state.focused, state.entered);
|
||||||
|
let hi = Nord::bg_hier(state.focused, state.entered);
|
||||||
|
fill_bg(buf, track_area, lo);
|
||||||
|
fill_bg(buf, scene_area, lo);
|
||||||
|
fill_bg(buf, area, hi);
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buf, area)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tracks_header <'a> (
|
||||||
|
state: &'a Arranger,
|
||||||
|
track_cols: &'a [(usize, usize)],
|
||||||
|
offset: u16,
|
||||||
|
) -> impl Render + 'a {
|
||||||
|
move |buf: &mut Buffer, area: Rect| {
|
||||||
|
let Rect { y, width, .. } = area;
|
||||||
|
for (track, (_, x)) in state.tracks.iter().zip(track_cols) {
|
||||||
|
let x = *x as u16;
|
||||||
|
if x > width {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
track.name.blit(buf, offset + x, y, Some(Style::default()))?;
|
||||||
|
}
|
||||||
|
Ok(Rect { x: area.x, y, width, height: 2 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scene_rows <'a> (
|
||||||
|
state: &'a Arranger,
|
||||||
|
track_cols: &'a [(usize, usize)],
|
||||||
|
scene_rows: &'a [(usize, usize)],
|
||||||
|
offset: u16,
|
||||||
|
) -> impl Render + 'a {
|
||||||
|
move |buf: &mut Buffer, area: Rect| {
|
||||||
|
let black = Some(Style::default().fg(Color::Rgb(0, 0, 0)));
|
||||||
|
let Rect { mut y, height, .. } = area;
|
||||||
|
for (_, x) in track_cols.iter() {
|
||||||
|
let x = *x as u16;
|
||||||
|
if x > 0 {
|
||||||
|
for y in area.y-2..y-2 {
|
||||||
|
"▎".blit(buf, x - 1, y, black)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (scene, (pulses, _)) in state.scenes.iter().zip(scene_rows) {
|
||||||
|
if y > height {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let h = 1.max((pulses / 96) as u16);
|
||||||
|
let area = Rect { x: area.x, y, width: area.width, height: h.min(area.height - y) };
|
||||||
|
scene_row(state, buf, area, scene, track_cols, offset)?;
|
||||||
|
y = y + h
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scene_row (
|
||||||
|
state: &Arranger,
|
||||||
|
buf: &mut Buffer,
|
||||||
|
area: Rect,
|
||||||
|
scene: &Scene,
|
||||||
|
track_cols: &[(usize, usize)],
|
||||||
|
offset: u16
|
||||||
|
) -> Usually<u16> {
|
||||||
|
let Rect { y, width, .. } = area;
|
||||||
|
let tracks = state.tracks.as_ref();
|
||||||
|
let playing = scene.is_playing(tracks);
|
||||||
|
(if playing { "▶" } else { " " }).blit(buf, area.x, y, None)?;
|
||||||
|
scene.name.blit(buf, area.x + 1, y, None)?;
|
||||||
|
let style = Some(Style::default().white());
|
||||||
|
for (track, (w, x)) in track_cols.iter().enumerate() {
|
||||||
|
let x = *x as u16 + offset;
|
||||||
|
if x > width {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let (Some(track), Some(Some(clip))) = (
|
||||||
|
tracks.get(track), scene.clips.get(track)
|
||||||
|
) {
|
||||||
|
if let Some(phrase) = track.phrases.get(*clip) {
|
||||||
|
let phrase = phrase.read().unwrap();
|
||||||
|
phrase.name.blit(buf, x, y, style)?;
|
||||||
|
if track.sequence == Some(*clip) {
|
||||||
|
fill_bg(buf, Rect {
|
||||||
|
x: x - 1,
|
||||||
|
y,
|
||||||
|
width: *w as u16,
|
||||||
|
height: area.height,
|
||||||
|
}, Color::Rgb(60,100,50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((scene.pulses(tracks) / 96) as u16)
|
||||||
|
}
|
||||||
95
src/devices/arranger/arr_focus.rs
Normal file
95
src/devices/arranger/arr_focus.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
/// Represents the current user selection in the arranger
|
||||||
|
pub enum ArrangerFocus {
|
||||||
|
/** The whole mix is selected */
|
||||||
|
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 ArrangerFocus {
|
||||||
|
pub fn is_track (&self) -> bool {
|
||||||
|
match self { Self::Track(_) => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn is_scene (&self) -> bool {
|
||||||
|
match self { Self::Scene(_) => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn is_clip (&self) -> bool {
|
||||||
|
match self { Self::Clip(_, _) => true, _ => false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track focus methods
|
||||||
|
impl ArrangerFocus {
|
||||||
|
pub fn track (&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
Self::Clip(t, _) => Some(*t),
|
||||||
|
Self::Track(t) => Some(*t),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn track_next (&mut self, last_track: usize) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Mix => Self::Track(0),
|
||||||
|
Self::Track(t) => Self::Track(last_track.min(*t + 1)),
|
||||||
|
Self::Scene(s) => Self::Clip(0, *s),
|
||||||
|
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn track_prev (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Mix => Self::Mix,
|
||||||
|
Self::Scene(s) => Self::Scene(*s),
|
||||||
|
Self::Track(t) => if *t == 0 {
|
||||||
|
Self::Mix
|
||||||
|
} else {
|
||||||
|
Self::Track(*t - 1)
|
||||||
|
},
|
||||||
|
Self::Clip(t, s) => if *t == 0 {
|
||||||
|
Self::Scene(*s)
|
||||||
|
} else {
|
||||||
|
Self::Clip(t.saturating_sub(1), *s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scene focus methods
|
||||||
|
impl ArrangerFocus {
|
||||||
|
pub fn scene (&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
Self::Clip(_, s) => Some(*s),
|
||||||
|
Self::Scene(s) => Some(*s),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn scene_next (&mut self, last_scene: usize) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Mix => Self::Scene(0),
|
||||||
|
Self::Track(t) => Self::Clip(*t, 0),
|
||||||
|
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)),
|
||||||
|
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn scene_prev (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Mix => Self::Mix,
|
||||||
|
Self::Track(t) => Self::Track(*t),
|
||||||
|
Self::Scene(s) => if *s == 0 {
|
||||||
|
Self::Mix
|
||||||
|
} else {
|
||||||
|
Self::Scene(*s - 1)
|
||||||
|
},
|
||||||
|
Self::Clip(t, s) => if *s == 0 {
|
||||||
|
Self::Track(*t)
|
||||||
|
} else {
|
||||||
|
Self::Clip(*t, s.saturating_sub(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/devices/arranger/arr_phrase.rs
Normal file
56
src/devices/arranger/arr_phrase.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::{core::*, devices::sequencer::Phrase};
|
||||||
|
use super::Arranger;
|
||||||
|
|
||||||
|
/// Phrase management methods
|
||||||
|
impl Arranger {
|
||||||
|
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
|
||||||
|
let track_id = self.selected.track()?;
|
||||||
|
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
|
||||||
|
}
|
||||||
|
pub fn phrase_next (&mut self) {
|
||||||
|
let track_index = self.selected.track();
|
||||||
|
let scene_index = self.selected.scene();
|
||||||
|
track_index
|
||||||
|
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
|
||||||
|
.and_then(|(track_index, track)|{
|
||||||
|
let phrases = track.phrases.len();
|
||||||
|
scene_index
|
||||||
|
.and_then(|index|self.scenes.get_mut(index))
|
||||||
|
.and_then(|scene|{
|
||||||
|
if let Some(phrase_index) = scene.clips[track_index] {
|
||||||
|
if phrase_index >= phrases - 1 {
|
||||||
|
scene.clips[track_index] = None;
|
||||||
|
} else {
|
||||||
|
scene.clips[track_index] = Some(phrase_index + 1);
|
||||||
|
}
|
||||||
|
} else if phrases > 0 {
|
||||||
|
scene.clips[track_index] = Some(0);
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pub fn phrase_prev (&mut self) {
|
||||||
|
let track_index = self.selected.track();
|
||||||
|
let scene_index = self.selected.scene();
|
||||||
|
track_index
|
||||||
|
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
|
||||||
|
.and_then(|(track_index, track)|{
|
||||||
|
let phrases = track.phrases.len();
|
||||||
|
scene_index
|
||||||
|
.and_then(|index|self.scenes.get_mut(index))
|
||||||
|
.and_then(|scene|{
|
||||||
|
if let Some(phrase_index) = scene.clips[track_index] {
|
||||||
|
scene.clips[track_index] = if phrase_index == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(phrase_index - 1)
|
||||||
|
};
|
||||||
|
} else if phrases > 0 {
|
||||||
|
scene.clips[track_index] = Some(phrases - 1);
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/devices/arranger/arr_scene.rs
Normal file
79
src/devices/arranger/arr_scene.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
use crate::{core::*, model::Track};
|
||||||
|
use super::Arranger;
|
||||||
|
|
||||||
|
/// A collection of phrases to play on each track.
|
||||||
|
pub struct Scene {
|
||||||
|
pub name: String,
|
||||||
|
pub clips: Vec<Option<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
|
||||||
|
let name = name.as_ref().into();
|
||||||
|
let clips = clips.as_ref().iter().map(|x|x.clone()).collect();
|
||||||
|
Self { name, clips, }
|
||||||
|
}
|
||||||
|
/// Returns the pulse length of the longest phrase in the scene
|
||||||
|
pub fn pulses (&self, tracks: &[Track]) -> usize {
|
||||||
|
self.clips.iter().enumerate()
|
||||||
|
.filter_map(|(i, c)|c.map(|c|tracks[i].phrases.get(c)))
|
||||||
|
.filter_map(|p|p)
|
||||||
|
.fold(0, |a, p|a.max(p.read().unwrap().length))
|
||||||
|
}
|
||||||
|
/// Returns true if all phrases in the scene are currently playing
|
||||||
|
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
||||||
|
self.clips.iter().enumerate()
|
||||||
|
.all(|(track_index, phrase_index)|match phrase_index {
|
||||||
|
Some(i) => tracks[track_index].sequence == Some(*i),
|
||||||
|
None => true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scene_name_max_len (scenes: &[Scene]) -> usize {
|
||||||
|
scenes.iter()
|
||||||
|
.map(|s|s.name.len())
|
||||||
|
.fold(0, usize::max)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scene_ppqs (tracks: &[Track], scenes: &[Scene]) -> Vec<(usize, usize)> {
|
||||||
|
let mut total = 0;
|
||||||
|
let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{
|
||||||
|
let pulses = scene.pulses(tracks);
|
||||||
|
total = total + pulses;
|
||||||
|
(pulses, total - pulses)
|
||||||
|
}).collect();
|
||||||
|
scenes.push((0, total));
|
||||||
|
scenes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scene management methods
|
||||||
|
impl Arranger {
|
||||||
|
pub fn scene (&self) -> Option<&Scene> {
|
||||||
|
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
|
||||||
|
}
|
||||||
|
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||||
|
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
|
||||||
|
}
|
||||||
|
pub fn scene_next (&mut self) {
|
||||||
|
self.selected.scene_next(self.scenes.len() - 1)
|
||||||
|
}
|
||||||
|
pub fn scene_prev (&mut self) {
|
||||||
|
self.selected.scene_prev()
|
||||||
|
}
|
||||||
|
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||||
|
let clips = vec![None;self.tracks.len()];
|
||||||
|
self.scenes.push(match name {
|
||||||
|
Some(name) => Scene::new(name, clips),
|
||||||
|
None => Scene::new(&self.scene_default_name(), clips),
|
||||||
|
});
|
||||||
|
let index = self.scenes.len() - 1;
|
||||||
|
Ok(&mut self.scenes[index])
|
||||||
|
}
|
||||||
|
pub fn scene_del (&mut self) {
|
||||||
|
unimplemented!("Arranger::scene_del");
|
||||||
|
}
|
||||||
|
pub fn scene_default_name (&self) -> String {
|
||||||
|
format!("Scene {}", self.scenes.len() + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/devices/arranger/arr_track.rs
Normal file
53
src/devices/arranger/arr_track.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use crate::{core::*, model::Track};
|
||||||
|
use super::Arranger;
|
||||||
|
|
||||||
|
/// Track management methods
|
||||||
|
impl Arranger {
|
||||||
|
pub fn track (&self) -> Option<&Track> {
|
||||||
|
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||||
|
}
|
||||||
|
pub fn track_mut (&mut self) -> Option<&mut Track> {
|
||||||
|
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||||
|
}
|
||||||
|
pub fn track_next (&mut self) {
|
||||||
|
self.selected.track_next(self.tracks.len() - 1)
|
||||||
|
}
|
||||||
|
pub fn track_prev (&mut self) {
|
||||||
|
self.selected.track_prev()
|
||||||
|
}
|
||||||
|
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||||
|
self.tracks.push(name.map_or_else(
|
||||||
|
|| Track::new(&self.track_default_name()),
|
||||||
|
|name| Track::new(name),
|
||||||
|
)?);
|
||||||
|
let index = self.tracks.len() - 1;
|
||||||
|
Ok(&mut self.tracks[index])
|
||||||
|
}
|
||||||
|
pub fn track_del (&mut self) {
|
||||||
|
unimplemented!("Arranger::track_del");
|
||||||
|
}
|
||||||
|
pub fn track_default_name (&self) -> String {
|
||||||
|
format!("Track {}", self.tracks.len() + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track_name_max_len (tracks: &[Track]) -> usize {
|
||||||
|
tracks.iter()
|
||||||
|
.map(|s|s.name.len())
|
||||||
|
.fold(0, usize::max)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track_clip_name_lengths (tracks: &[Track]) -> Vec<(usize, usize)> {
|
||||||
|
let mut total = 0;
|
||||||
|
let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{
|
||||||
|
let len = 2 + track.phrases
|
||||||
|
.iter()
|
||||||
|
.fold(track.name.len(), |len, phrase|{
|
||||||
|
len.max(phrase.read().unwrap().name.len())
|
||||||
|
});
|
||||||
|
total = total + len;
|
||||||
|
(len, total - len)
|
||||||
|
}).collect();
|
||||||
|
lengths.push((0, total));
|
||||||
|
lengths
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue