wip: p.58, e=57

This commit is contained in:
🪞👃🪞 2024-11-19 00:30:03 +01:00
parent 0c94c2af8f
commit e95230a340
5 changed files with 173 additions and 174 deletions

View file

@ -25,8 +25,9 @@ impl PhraseTui {
entered: false, entered: false,
mode: false, mode: false,
now: Arc::new(0.into()), now: Arc::new(0.into()),
width: 0.into(), size: Measure::default(),
height: 0.into(), //width: 0.into(),
//height: 0.into(),
note_axis: RwLock::new(FixedAxis { note_axis: RwLock::new(FixedAxis {
start: 12, start: 12,
point: Some(36), point: Some(36),

View file

@ -38,17 +38,21 @@ pub enum SequencerCommand {
impl<T> Command<T> for SequencerCommand impl<T> Command<T> for SequencerCommand
where where
T: FocusGrid + PhrasesControl + PhraseControl + ClockApi + PlayheadApi T: PhrasesControl + PhraseControl + ClockApi + PlayheadApi
+ FocusGrid<Item = SequencerFocus> + FocusEnter<Item = SequencerFocus>
{ {
fn execute (self, state: T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use SequencerCommand::*; use SequencerCommand::*;
match self { Ok(match self {
Focus(cmd) => delegate(cmd, Focus, state), Focus(cmd) => cmd.execute(state)?.map(Focus),
Phrases(cmd) => delegate(cmd, Phrases, state), Phrases(cmd) => cmd.execute(state)?.map(Phrases),
Editor(cmd) => delegate(cmd, Editor, state), Editor(cmd) => cmd.execute(state)?.map(Editor),
Clock(cmd) => delegate(cmd, Clock, state), Clock(cmd) => cmd.execute(state)?.map(Clock),
Playhead(cmd) => delegate(cmd, Playhead, state) Playhead(cmd) => cmd.execute(state)?.map(Playhead),
} Undo => { todo!() },
Redo => { todo!() },
Clear => { todo!() },
})
} }
} }
@ -72,28 +76,32 @@ pub enum ArrangerCommand {
impl<T> Command<T> for ArrangerCommand impl<T> Command<T> for ArrangerCommand
where where
T: FocusGrid + ArrangerControl + HasPhrases + PhraseControl + ClockApi + PlayheadApi T: ArrangerControl + HasPhrases + PhraseControl + ClockApi + PlayheadApi
+ FocusGrid<Item = ArrangerFocus> + FocusEnter<Item = ArrangerFocus>
{ {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use ArrangerCommand::*; use ArrangerCommand::*;
match self { Ok(match self {
Focus(cmd) => { delegate(cmd, Focus, &mut state) }, Focus(cmd) => cmd.execute(state)?.map(Focus),
Scene(cmd) => { delegate(cmd, Scene, &mut state) }, Scene(cmd) => cmd.execute(state)?.map(Scene),
Track(cmd) => { delegate(cmd, Track, &mut state) }, Track(cmd) => cmd.execute(state)?.map(Track),
Clip(cmd) => { delegate(cmd, Clip, &mut state) }, Clip(cmd) => cmd.execute(state)?.map(Clip),
Phrases(cmd) => { delegate(cmd, Phrases, &mut state) }, Phrases(cmd) => cmd.execute(state)?.map(Phrases),
Editor(cmd) => { delegate(cmd, Editor, &mut state) }, Editor(cmd) => cmd.execute(state)?.map(Editor),
Clock(cmd) => { delegate(cmd, Clock, &mut state) }, Clock(cmd) => cmd.execute(state)?.map(Clock),
Playhead(cmd) => { delegate(cmd, Playhead, &mut state) }, Playhead(cmd) => cmd.execute(state)?.map(Playhead),
Zoom(zoom) => { todo!(); }, Zoom(zoom) => { todo!(); },
Select(selected) => { state.selected = selected; Ok(None) }, Select(selected) => {
state.selected = selected;
None
},
EditPhrase(phrase) => { EditPhrase(phrase) => {
state.editor.phrase = phrase.clone(); state.editor.phrase = phrase.clone();
state.focus(ArrangerFocus::PhraseEditor); state.focus(ArrangerFocus::PhraseEditor);
state.focus_enter(); state.focus_enter();
Ok(None) None
} }
} })
} }
} }
@ -124,31 +132,21 @@ pub enum PhrasesCommand {
Phrase(PhrasePoolCommand), Phrase(PhrasePoolCommand),
Rename(PhraseRenameCommand), Rename(PhraseRenameCommand),
Length(PhraseLengthCommand), Length(PhraseLengthCommand),
MoveUp,
MoveDown,
} }
impl<T: PhrasesControl> Command<T> for PhrasesCommand { impl<T: PhrasesControl> Command<T> for PhrasesCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use PhraseRenameCommand as Rename; use PhraseRenameCommand as Rename;
use PhraseLengthCommand as Length; use PhraseLengthCommand as Length;
match self { Ok(match self {
Self::Phrase(command) => command.execute(state)?.map(Self::Phrase),
Self::Rename(command) => command.execute(state)?.map(Self::Rename),
Self::Length(command) => command.execute(state)?.map(Self::Length),
Self::Select(phrase) => { Self::Select(phrase) => {
state.phrase = phrase state.phrase = phrase;
None
}, },
Self::Phrase(command) => { })
return Ok(command.execute(&mut state)?.map(Self::Phrase))
}
Self::Rename(command) => match command {
Rename::Begin => self.phrases_rename_begin(),
_ => return Ok(command.execute(state)?.map(Self::Rename)),
},
Self::Length(command) => match command {
Length::Begin => self.phrases_length_begin(),
_ => return Ok(command.execute(state)?.map(Self::Length)),
},
}
Ok(None)
} }
} }
@ -199,7 +197,7 @@ impl<T: PhrasesControl> Command<T> for PhraseLengthCommand {
} }
Ok(None) Ok(None)
} else if self == Begin { } else if self == Begin {
self.phrases_length_begin(); state.phrase_length_begin();
Ok(None) Ok(None)
} else { } else {
unreachable!() unreachable!()
@ -239,7 +237,7 @@ where
}; };
Ok(None) Ok(None)
} else if self == Begin { } else if self == Begin {
self.phrase_rename_begin(); state.phrase_rename_begin();
Ok(None) Ok(None)
} else { } else {
unreachable!() unreachable!()
@ -279,81 +277,107 @@ where
//} //}
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use PhraseCommand::*; use PhraseCommand::*;
match self.translate(state) { Ok(match self {
ToggleDirection => { ToggleDirection => {
state.mode = !state.mode; state.mode = !state.mode;
None
}, },
EnterEditMode => { EnterEditMode => {
state.focus_enter(); state.focus_enter();
None
}, },
ExitEditMode => { ExitEditMode => {
state.focus_exit(); state.focus_exit();
None
}, },
TimeZoomOut => { TimeZoomOut => {
let scale = state.time_axis().read().unwrap().scale; let scale = state.time_axis().read().unwrap().scale;
state.time_axis().write().unwrap().scale = next_note_length(scale) state.time_axis().write().unwrap().scale = next_note_length(scale);
None
}, },
TimeZoomIn => { TimeZoomIn => {
let scale = state.time_axis().read().unwrap().scale; let scale = state.time_axis().read().unwrap().scale;
state.time_axis().write().unwrap().scale = prev_note_length(scale) state.time_axis().write().unwrap().scale = prev_note_length(scale);
None
}, },
TimeCursorDec => { TimeCursorDec => {
let scale = state.time_axis().read().unwrap().scale; let scale = state.time_axis().read().unwrap().scale;
state.time_axis().write().unwrap().point_dec(scale); state.time_axis().write().unwrap().point_dec(scale);
None
}, },
TimeCursorInc => { TimeCursorInc => {
let scale = state.time_axis().read().unwrap().scale; let scale = state.time_axis().read().unwrap().scale;
state.time_axis().write().unwrap().point_inc(scale); state.time_axis().write().unwrap().point_inc(scale);
None
}, },
TimeScrollDec => { TimeScrollDec => {
let scale = state.time_axis().read().unwrap().scale; let scale = state.time_axis().read().unwrap().scale;
state.time_axis().write().unwrap().start_dec(scale); state.time_axis().write().unwrap().start_dec(scale);
None
}, },
TimeScrollInc => { TimeScrollInc => {
let scale = state.time_axis().read().unwrap().scale; let scale = state.time_axis().read().unwrap().scale;
state.time_axis().write().unwrap().start_inc(scale); state.time_axis().write().unwrap().start_inc(scale);
None
}, },
NoteCursorDec => { NoteCursorDec => {
let mut axis = state.note_axis().write().unwrap(); let mut axis = state.note_axis().write().unwrap();
axis.point_inc(1); axis.point_inc(1);
if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } } if let Some(point) = axis.point {
if point > 73 { axis.point = Some(73); }
}
None
}, },
NoteCursorInc => { NoteCursorInc => {
let mut axis = state.note_axis().write().unwrap(); let mut axis = state.note_axis().write().unwrap();
axis.point_dec(1); axis.point_dec(1);
if let Some(point) = axis.point { if point < axis.start { axis.start = (point / 2) * 2; } } if let Some(point) = axis.point {
if point < axis.start { axis.start = (point / 2) * 2; }
}
None
}, },
NoteScrollDec => { NoteScrollDec => {
state.note_axis().write().unwrap().start_inc(1); state.note_axis().write().unwrap().start_inc(1);
None
}, },
NoteScrollInc => { NoteScrollInc => {
state.note_axis().write().unwrap().start_dec(1); state.note_axis().write().unwrap().start_dec(1);
None
}, },
NoteLengthDec => { NoteLengthDec => {
*state.note_len_mut() = prev_note_length(state.note_len()) *state.note_len_mut() = prev_note_length(state.note_len());
None
}, },
NoteLengthInc => { NoteLengthInc => {
*state.note_len_mut() = next_note_length(state.note_len()) *state.note_len_mut() = next_note_length(state.note_len());
None
}, },
NotePageUp => { NotePageUp => {
let mut axis = state.note_axis().write().unwrap(); let mut axis = state.note_axis().write().unwrap();
axis.start_dec(3); axis.start_dec(3);
axis.point_dec(3); axis.point_dec(3);
None
}, },
NotePageDown => { NotePageDown => {
let mut axis = state.note_axis().write().unwrap(); let mut axis = state.note_axis().write().unwrap();
axis.start_inc(3); axis.start_inc(3);
axis.point_inc(3); axis.point_inc(3);
None
}, },
NoteAppend => if state.focus_entered() { NoteAppend => if state.focus_entered() {
state.put(); state.put();
state.time_cursor_advance(); state.time_cursor_advance();
None
} else {
None
}, },
NoteSet => if state.focus_entered() { NoteSet => if state.focus_entered() {
state.put(); state.put();
None
} else {
None
}, },
_ => unreachable!() _ => unreachable!()
} })
Ok(None)
} }
} }

View file

@ -52,7 +52,8 @@ where
impl<T> InputToCommand<Tui, T> for SequencerCommand impl<T> InputToCommand<Tui, T> for SequencerCommand
where where
T: SequencerControl + TransportControl + PhrasesControl + PhraseControl + HasFocus<Item = SequencerFocus> T: SequencerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi
+ HasFocus<Item = SequencerFocus> + FocusGrid<Item = SequencerFocus>
{ {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> { fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use FocusCommand::*; use FocusCommand::*;
@ -92,7 +93,8 @@ where
impl<T> InputToCommand<Tui, T> for ArrangerCommand impl<T> InputToCommand<Tui, T> for ArrangerCommand
where where
T: ArrangerControl + TransportControl + PhrasesControl + PhraseControl + HasFocus<Item = ArrangerFocus> T: ArrangerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi
+ HasFocus<Item = ArrangerFocus> + FocusGrid<Item = ArrangerFocus>
{ {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> { fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use FocusCommand::*; use FocusCommand::*;

View file

@ -176,6 +176,29 @@ impl HasScenes<ArrangerScene> for ArrangerTui {
} }
} }
/// Display mode of arranger
#[derive(Clone, PartialEq)]
pub enum ArrangerMode {
/// Tracks are rows
Horizontal,
/// Tracks are columns
Vertical(usize),
}
/// Arranger display mode can be cycled
impl ArrangerMode {
/// Cycle arranger display mode
pub fn to_next (&mut self) {
*self = match self {
Self::Horizontal => Self::Vertical(1),
Self::Vertical(1) => Self::Vertical(2),
Self::Vertical(2) => Self::Vertical(2),
Self::Vertical(0) => Self::Horizontal,
Self::Vertical(_) => Self::Vertical(0),
}
}
}
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct ArrangerScene { pub struct ArrangerScene {
/// Name of scene /// Name of scene
@ -425,6 +448,42 @@ pub enum PhrasesMode {
Length(usize, usize, PhraseLengthFocus), Length(usize, usize, PhraseLengthFocus),
} }
/// Displays and edits phrase length.
pub struct PhraseLength {
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of phrase in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<PhraseLengthFocus>,
}
impl PhraseLength {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus }
}
pub fn bars (&self) -> usize {
self.pulses / (self.bpb * self.ppq)
}
pub fn beats (&self) -> usize {
(self.pulses % (self.bpb * self.ppq)) / self.ppq
}
pub fn ticks (&self) -> usize {
self.pulses % self.ppq
}
pub fn bars_string (&self) -> String {
format!("{}", self.bars())
}
pub fn beats_string (&self) -> String {
format!("{}", self.beats())
}
pub fn ticks_string (&self) -> String {
format!("{:>02}", self.ticks())
}
}
/// Contains state for viewing and editing a phrase /// Contains state for viewing and editing a phrase
pub struct PhraseTui { pub struct PhraseTui {
/// Phrase being played /// Phrase being played

View file

@ -2,26 +2,10 @@ use crate::*;
pub struct TransportView<'a, T: TransportViewState>(pub &'a T); pub struct TransportView<'a, T: TransportViewState>(pub &'a T);
pub trait TransportViewState: Send + Sync { pub trait TransportViewState: ClockApi + PlayheadApi + Send + Sync {
fn transport_selected (&self) -> TransportFocus; fn transport_selected (&self) -> TransportFocus;
fn transport_focused (&self) -> bool; fn transport_focused (&self) -> bool;
fn transport_state (&self) -> Option<TransportState>; fn transport_state (&self) -> Option<TransportState>;
fn bpm_value (&self) -> f64;
fn sync_value (&self) -> f64;
fn format_beat (&self) -> String;
fn format_msu (&self) -> String;
}
impl TransportViewState for TransportTui {
fn transport_selected (&self) -> TransportFocus {
self.focus
}
fn transport_focused (&self) -> bool {
true
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
fn bpm_value (&self) -> f64 { fn bpm_value (&self) -> f64 {
self.bpm().get() self.bpm().get()
} }
@ -36,6 +20,18 @@ impl TransportViewState for TransportTui {
} }
} }
impl TransportViewState for TransportTui {
fn transport_selected (&self) -> TransportFocus {
self.focus
}
fn transport_focused (&self) -> bool {
true
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
}
impl TransportViewState for SequencerTui { impl TransportViewState for SequencerTui {
fn transport_selected (&self) -> TransportFocus { fn transport_selected (&self) -> TransportFocus {
self.focus self.focus
@ -46,18 +42,6 @@ impl TransportViewState for SequencerTui {
fn transport_state (&self) -> Option<TransportState> { fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap() *self.playing().read().unwrap()
} }
fn bpm_value (&self) -> f64 {
self.bpm().get()
}
fn sync_value (&self) -> f64 {
self.sync().get()
}
fn format_beat (&self) -> String {
self.current().format_beat()
}
fn format_msu (&self) -> String {
self.current().usec.format_msu()
}
} }
impl TransportViewState for ArrangerTui { impl TransportViewState for ArrangerTui {
@ -70,18 +54,6 @@ impl TransportViewState for ArrangerTui {
fn transport_state (&self) -> Option<TransportState> { fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap() *self.playing().read().unwrap()
} }
fn bpm_value (&self) -> f64 {
self.bpm().get()
}
fn sync_value (&self) -> f64 {
self.sync().get()
}
fn format_beat (&self) -> String {
self.current().format_beat()
}
fn format_msu (&self) -> String {
self.current().usec.format_msu()
}
} }
pub trait ArrangerViewState { pub trait ArrangerViewState {
@ -161,31 +133,34 @@ impl PhrasesViewState for ArrangerTui {
pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T); pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T);
pub trait PhraseViewState: Send + Sync { pub trait PhraseViewState: Send + Sync {
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
fn phrase_focused (&self) -> bool; fn phrase_focused (&self) -> bool;
fn phrase_editor_size (&self) -> &Measure<Tui>;
fn entered (&self) -> bool; fn entered (&self) -> bool;
fn keys (&self) -> &Buffer; fn keys (&self) -> &Buffer;
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
fn buffer (&self) -> &BigBuffer; fn buffer (&self) -> &BigBuffer;
fn note_len (&self) -> usize; fn note_len (&self) -> usize;
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>; fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>; fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
fn size (&self) -> &Measure<Tui>;
fn now (&self) -> &Arc<Pulse>; fn now (&self) -> &Arc<Pulse>;
} }
impl PhraseViewState for PhraseTui { impl PhraseViewState for PhraseTui {
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
&self.phrase
}
fn phrase_focused (&self) -> bool { fn phrase_focused (&self) -> bool {
self.focused self.focused
} }
fn phrase_editor_size (&self) -> &Measure<Tui> {
todo!()
}
fn entered (&self) -> bool { fn entered (&self) -> bool {
self.entered self.entered
} }
fn keys (&self) -> &Buffer { fn keys (&self) -> &Buffer {
&self.keys &self.keys
} }
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
&self.phrase
}
fn buffer (&self) -> &BigBuffer { fn buffer (&self) -> &BigBuffer {
&self.buffer &self.buffer
} }
@ -198,27 +173,27 @@ impl PhraseViewState for PhraseTui {
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> { fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
&self.time_axis &self.time_axis
} }
fn size (&self) -> &Measure<Tui> {
&self.size
}
fn now (&self) -> &Arc<Pulse> { fn now (&self) -> &Arc<Pulse> {
&self.now &self.now
} }
} }
impl PhraseViewState for SequencerTui { impl PhraseViewState for SequencerTui {
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
todo!()
}
fn phrase_focused (&self) -> bool { fn phrase_focused (&self) -> bool {
todo!() todo!()
} }
fn phrase_editor_size (&self) -> &Measure<Tui> {
todo!()
}
fn entered (&self) -> bool { fn entered (&self) -> bool {
todo!() todo!()
} }
fn keys (&self) -> &Buffer { fn keys (&self) -> &Buffer {
todo!() todo!()
} }
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
todo!()
}
fn buffer (&self) -> &BigBuffer { fn buffer (&self) -> &BigBuffer {
todo!() todo!()
} }
@ -231,27 +206,27 @@ impl PhraseViewState for SequencerTui {
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> { fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
todo!() todo!()
} }
fn size (&self) -> &Measure<Tui> {
todo!()
}
fn now (&self) -> &Arc<Pulse> { fn now (&self) -> &Arc<Pulse> {
todo!() todo!()
} }
} }
impl PhraseViewState for ArrangerTui { impl PhraseViewState for ArrangerTui {
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
todo!()
}
fn phrase_focused (&self) -> bool { fn phrase_focused (&self) -> bool {
todo!() todo!()
} }
fn phrase_editor_size (&self) -> &Measure<Tui> {
todo!()
}
fn entered (&self) -> bool { fn entered (&self) -> bool {
todo!() todo!()
} }
fn keys (&self) -> &Buffer { fn keys (&self) -> &Buffer {
todo!() todo!()
} }
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
todo!()
}
fn buffer (&self) -> &BigBuffer { fn buffer (&self) -> &BigBuffer {
todo!() todo!()
} }
@ -264,73 +239,11 @@ impl PhraseViewState for ArrangerTui {
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> { fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
todo!() todo!()
} }
fn size (&self) -> &Measure<Tui> {
todo!()
}
fn now (&self) -> &Arc<Pulse> { fn now (&self) -> &Arc<Pulse> {
todo!() todo!()
} }
} }
/// Displays and edits phrase length.
pub struct PhraseLength {
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of phrase in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<PhraseLengthFocus>,
}
impl PhraseLength {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus }
}
pub fn bars (&self) -> usize {
self.pulses / (self.bpb * self.ppq)
}
pub fn beats (&self) -> usize {
(self.pulses % (self.bpb * self.ppq)) / self.ppq
}
pub fn ticks (&self) -> usize {
self.pulses % self.ppq
}
pub fn bars_string (&self) -> String {
format!("{}", self.bars())
}
pub fn beats_string (&self) -> String {
format!("{}", self.beats())
}
pub fn ticks_string (&self) -> String {
format!("{:>02}", self.ticks())
}
}
/// Display mode of arranger
#[derive(Clone, PartialEq)]
pub enum ArrangerMode {
/// Tracks are rows
Horizontal,
/// Tracks are columns
Vertical(usize),
}
/// Arranger display mode can be cycled
impl ArrangerMode {
/// Cycle arranger display mode
pub fn to_next (&mut self) {
*self = match self {
Self::Horizontal => Self::Vertical(1),
Self::Vertical(1) => Self::Vertical(2),
Self::Vertical(2) => Self::Vertical(2),
Self::Vertical(0) => Self::Horizontal,
Self::Vertical(_) => Self::Vertical(0),
}
}
}
fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> { fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> {
let mut widths = vec![]; let mut widths = vec![];
let mut total = 0; let mut total = 0;