wip: refactor pt.15: 107 errors

This commit is contained in:
🪞👃🪞 2024-11-11 00:39:45 +01:00
parent 8b68a993bc
commit 5823d0b746
8 changed files with 423 additions and 455 deletions

View file

@ -113,12 +113,12 @@ impl ArrangementScene {
/// Returns true if all phrases in the scene are /// Returns true if all phrases in the scene are
/// currently playing on the given collection of tracks. /// currently playing on the given collection of tracks.
pub fn is_playing (&self, tracks: &[MIDIPlayer]) -> bool { pub fn is_playing (&self, tracks: &[ArrangementTrack]) -> bool {
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
.all(|(track_index, clip)|match clip { .all(|(track_index, clip)|match clip {
Some(clip) => tracks Some(clip) => tracks
.get(track_index) .get(track_index)
.map(|track|if let Some((_, Some(phrase))) = &track.phrase { .map(|track|if let Some((_, Some(phrase))) = &track.player.phrase {
*phrase.read().unwrap() == *clip.read().unwrap() *phrase.read().unwrap() == *clip.read().unwrap()
} else { } else {
false false

View file

@ -14,9 +14,9 @@ pub enum ArrangementCommand {
#[derive(Clone)] #[derive(Clone)]
pub enum ArrangementSceneCommand { pub enum ArrangementSceneCommand {
Add, Add,
Delete, Delete(usize),
RandomColor, RandomColor,
Play, Play(usize),
Swap(usize), Swap(usize),
SetSize(usize), SetSize(usize),
SetZoom(usize), SetZoom(usize),
@ -25,7 +25,7 @@ pub enum ArrangementSceneCommand {
#[derive(Clone)] #[derive(Clone)]
pub enum ArrangementTrackCommand { pub enum ArrangementTrackCommand {
Add, Add,
Delete, Delete(usize),
RandomColor, RandomColor,
Stop, Stop,
Swap(usize), Swap(usize),

View file

@ -45,9 +45,9 @@ impl Content for ArrangementEditor<Tui> {
Layers::new(move |add|{ Layers::new(move |add|{
match self.mode { match self.mode {
ArrangementEditorMode::Horizontal => ArrangementEditorMode::Horizontal =>
add(&HorizontalArranger(&self))?, add(&arranger_content_horizontal(self))?,
ArrangementEditorMode::Vertical(factor) => ArrangementEditorMode::Vertical(factor) =>
add(&VerticalArranger(&self, factor))? add(&arranger_content_vertical(self, factor))?
}; };
add(&self.size) add(&self.size)
}) })

View file

@ -94,7 +94,7 @@ impl InputToCommand<Tui, ArrangementEditor<Tui>> for ArrangementEditorCommand {
key!(KeyCode::Enter) => match state.selected { key!(KeyCode::Enter) => match state.selected {
ArrangementEditorFocus::Mix => return None, ArrangementEditorFocus::Mix => return None,
ArrangementEditorFocus::Track(t) => return None, ArrangementEditorFocus::Track(t) => return None,
ArrangementEditorFocus::Scene(s) => return None, ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Play(s))),
ArrangementEditorFocus::Clip(t, s) => return None, ArrangementEditorFocus::Clip(t, s) => return None,
}, },
@ -129,16 +129,16 @@ impl InputToCommand<Tui, ArrangementEditor<Tui>> for ArrangementEditorCommand {
} }
impl<E: Engine> Command<ArrangementEditor<E>> for ArrangementEditorCommand { impl<E: Engine> Command<ArrangementEditor<E>> for ArrangementEditorCommand {
fn execute (self, state: &mut ArrangementEditor<E>) -> Perhaps<Self> { fn execute (self, view: &mut ArrangementEditor<E>) -> Perhaps<Self> {
match self { match self {
Self::Zoom(zoom) => { Self::Zoom(zoom) => {
todo!(); todo!();
}, },
Self::Select(selected) => { Self::Select(selected) => {
state.selected = selected; view.selected = selected;
}, },
Self::Edit(command) => { Self::Edit(command) => {
return command.execute(state.state).map(Self::Edit) return Ok(command.execute(&mut view.model)?.map(Self::Edit))
}, },
} }
Ok(None) Ok(None)

View file

@ -88,12 +88,8 @@ impl<E: Engine> ArrangerView<E> {
} }
/// Toggle global play/pause /// Toggle global play/pause
pub fn toggle_play (&mut self) -> Perhaps<bool> { pub fn toggle_play (&mut self) -> Usually<()> {
match self.transport { self.sequencer.transport.model.toggle_play()
Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; },
None => { return Ok(None) }
}
Ok(Some(true))
} }
pub fn next_color (&self) -> ItemColor { pub fn next_color (&self) -> ItemColor {
if let ArrangementEditorFocus::Clip(track, scene) = self.arrangement.selected { if let ArrangementEditorFocus::Clip(track, scene) = self.arrangement.selected {
@ -225,30 +221,6 @@ impl<E: Engine> ArrangerView<E> {
phrase.write().unwrap().toggle_loop() phrase.write().unwrap().toggle_loop()
} }
} }
pub fn go_up (&mut self) {
match self.mode {
ArrangementEditorMode::Horizontal => self.track_prev(),
_ => self.scene_prev(),
};
}
pub fn go_down (&mut self) {
match self.mode {
ArrangementEditorMode::Horizontal => self.track_next(),
_ => self.scene_next(),
};
}
pub fn go_left (&mut self) {
match self.mode {
ArrangementEditorMode::Horizontal => self.scene_prev(),
_ => self.track_prev(),
};
}
pub fn go_right (&mut self) {
match self.mode {
ArrangementEditorMode::Horizontal => self.scene_next(),
_ => self.track_next(),
};
}
pub fn move_back (&mut self) { pub fn move_back (&mut self) {
match self.selected { match self.selected {
ArrangementEditorFocus::Scene(s) => { ArrangementEditorFocus::Scene(s) => {

View file

@ -4,8 +4,8 @@ use crate::*;
pub enum ArrangerViewCommand { pub enum ArrangerViewCommand {
Focus(FocusCommand), Focus(FocusCommand),
Arrangement(ArrangementEditorCommand), Arrangement(ArrangementEditorCommand),
Transport(TransportCommand), Transport(TransportViewCommand),
Phrases(PhrasePoolCommand), Phrases(PhrasePoolViewCommand),
Editor(PhraseEditorCommand), Editor(PhraseEditorCommand),
EditPhrase(Option<Arc<RwLock<Phrase>>>), EditPhrase(Option<Arc<RwLock<Phrase>>>),
} }
@ -18,44 +18,48 @@ impl Handle<Tui> for ArrangerView<Tui> {
} }
impl InputToCommand<Tui, ArrangerView<Tui>> for ArrangerViewCommand { impl InputToCommand<Tui, ArrangerView<Tui>> for ArrangerViewCommand {
fn input_to_command (state: &ArrangerView<Tui>, input: &TuiInput) -> Option<Self> { fn input_to_command (view: &ArrangerView<Tui>, input: &TuiInput) -> Option<Self> {
use FocusCommand::*; use FocusCommand::*;
use ArrangerViewCommand::*; use ArrangerViewCommand::*;
match input.event() { Some(match input.event() {
key!(KeyCode::Tab) => Some(Focus(Next)), key!(KeyCode::Tab) => Focus(Next),
key!(Shift-KeyCode::Tab) => Some(Focus(Prev)), key!(Shift-KeyCode::Tab) => Focus(Prev),
key!(KeyCode::BackTab) => Some(Focus(Prev)), key!(KeyCode::BackTab) => Focus(Prev),
key!(Shift-KeyCode::BackTab) => Some(Focus(Prev)), key!(Shift-KeyCode::BackTab) => Focus(Prev),
key!(KeyCode::Up) => Some(Focus(Up)), key!(KeyCode::Up) => Focus(Up),
key!(KeyCode::Down) => Some(Focus(Down)), key!(KeyCode::Down) => Focus(Down),
key!(KeyCode::Left) => Some(Focus(Left)), key!(KeyCode::Left) => Focus(Left),
key!(KeyCode::Right) => Some(Focus(Right)), key!(KeyCode::Right) => Focus(Right),
key!(KeyCode::Enter) => Some(Focus(Enter)), key!(KeyCode::Enter) => Focus(Enter),
key!(KeyCode::Esc) => Some(Focus(Exit)), key!(KeyCode::Esc) => Focus(Exit),
//key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)), key!(KeyCode::Char(' ')) => {
_ => match state.focused() { Transport(TransportViewCommand::Transport(TransportCommand::Play(None)))
ArrangerViewFocus::Transport => state.transport.as_ref() },
.map(|t|TransportCommand::input_to_command(&*t.read().unwrap(), input) _ => match view.focused() {
.map(Transport)) ArrangerViewFocus::Transport => Transport(
.flatten(), TransportViewCommand::input_to_command(&view.sequencer.transport, input)?
ArrangerViewFocus::PhrasePool => { ),
let phrases = state.phrases.read().unwrap(); ArrangerViewFocus::PhraseEditor => Editor(
match input.event() { PhraseEditorCommand::input_to_command(&view.sequencer.editor, input)?
key!(KeyCode::Char('e')) => Some(EditPhrase(Some(phrases.phrase().clone()))), ),
_ => PhrasePoolCommand::input_to_command(&*phrases, input) ArrangerViewFocus::PhrasePool => match input.event() {
.map(Phrases) key!(KeyCode::Char('e')) => EditPhrase(
} Some(view.sequencer.phrases.phrase().clone())
),
_ => Phrases(
PhrasePoolViewCommand::input_to_command(&view.sequencer.phrases, input)?
)
}, },
ArrangerViewFocus::PhraseEditor =>
PhraseEditorCommand::input_to_command(&state.editor, input)
.map(Editor),
ArrangerViewFocus::Arrangement => match input.event() { ArrangerViewFocus::Arrangement => match input.event() {
key!(KeyCode::Char('e')) => Some(EditPhrase(state.arrangement.phrase())), key!(KeyCode::Char('e')) => EditPhrase(
_ => ArrangementCommand::input_to_command(&state.arrangement, &input) view.arrangement.phrase()
.map(Arrangement) ),
_ => Arrangement(
ArrangementEditorCommand::input_to_command(&view.arrangement, &input)?
)
} }
} }
} })
} }
} }
@ -69,7 +73,7 @@ impl<E: Engine> Command<ArrangerView<E>> for ArrangerViewCommand {
Self::Editor(cmd) => Self::Editor(cmd) =>
delegate(cmd, Self::Editor, &mut view.sequencer.editor), delegate(cmd, Self::Editor, &mut view.sequencer.editor),
Self::Transport(cmd) => Self::Transport(cmd) =>
delegate(cmd, Self::Transport, &mut view.sequencer.transport.state), delegate(cmd, Self::Transport, &mut view.sequencer.transport),
Self::Arrangement(cmd) => Self::Arrangement(cmd) =>
delegate(cmd, Self::Arrangement, &mut view.arrangement), delegate(cmd, Self::Arrangement, &mut view.arrangement),
Self::EditPhrase(phrase) => { Self::EditPhrase(phrase) => {
@ -79,8 +83,8 @@ impl<E: Engine> Command<ArrangerView<E>> for ArrangerViewCommand {
Ok(None) Ok(None)
} }
}?; }?;
view.sequencer.show_phrase(); view.show_phrase();
view.sequencer.update_status(); view.update_status();
return Ok(undo); return Ok(undo);
} }
} }

View file

@ -1,200 +1,197 @@
use crate::*; use crate::*;
/// Arrangement, rendered horizontally (arrangement/track mode). pub fn arranger_content_horizontal (
pub struct HorizontalArranger<'a, E: Engine>(pub &'a Arrangement<E>); state: &ArrangementEditor<Tui>,
) -> impl Widget<Engine = Tui> + use<'_> {
impl<'a> Content for HorizontalArranger<'a, Tui> { let focused = state.focused;
type Engine = Tui; let _tracks = state.model.tracks.as_slice();
fn content (&self) -> impl Widget<Engine = Tui> { lay!(
let Arrangement { tracks, focused, .. } = self.0; focused.then_some(Background(TuiTheme::border_bg())),
let _tracks = tracks.as_slice(); row!(
lay!( // name
focused.then_some(Background(Arranger::<Tui>::border_bg())), CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
row!( todo!()
// name //let Self(tracks, selected) = self;
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ //let yellow = Some(Style::default().yellow().bold().not_dim());
todo!() //let white = Some(Style::default().white().bold().not_dim());
//let Self(tracks, selected) = self; //let area = to.area();
//let yellow = Some(Style::default().yellow().bold().not_dim()); //let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
//let white = Some(Style::default().white().bold().not_dim()); //let offset = 0; // track scroll offset
//let area = to.area(); //for y in 0..area.h() {
//let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()]; //if y == 0 {
//let offset = 0; // track scroll offset //to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
//for y in 0..area.h() { //} else if y % 2 == 0 {
//if y == 0 { //let index = (y as usize - 2) / 2 + offset;
//to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?; //if let Some(track) = tracks.get(index) {
//} else if y % 2 == 0 { //let selected = selected.track() == Some(index);
//let index = (y as usize - 2) / 2 + offset; //let style = if selected { yellow } else { white };
//if let Some(track) = tracks.get(index) { //to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
//let selected = selected.track() == Some(index); //to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
//let style = if selected { yellow } else { white };
//to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
//to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
//}
//} //}
//} //}
//Ok(Some(area)) //}
}), //Ok(Some(area))
// monitor }),
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ // monitor
todo!() CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//let Self(tracks) = self; todo!()
//let mut area = to.area(); //let Self(tracks) = self;
//let on = Some(Style::default().not_dim().green().bold()); //let mut area = to.area();
//let off = Some(DIM); //let on = Some(Style::default().not_dim().green().bold());
//area.x += 1; //let off = Some(DIM);
//for y in 0..area.h() { //area.x += 1;
//if y == 0 { //for y in 0..area.h() {
////" MON ".blit(to.buffer, area.x, area.y + y, style2)?; //if y == 0 {
//} else if y % 2 == 0 { ////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
//let index = (y as usize - 2) / 2; //} else if y % 2 == 0 {
//if let Some(track) = tracks.get(index) { //let index = (y as usize - 2) / 2;
//let style = if track.monitoring { on } else { off }; //if let Some(track) = tracks.get(index) {
//to.blit(&" MON ", area.x(), area.y() + y, style)?; //let style = if track.monitoring { on } else { off };
//to.blit(&" MON ", area.x(), area.y() + y, style)?;
//} else {
//area.height = y;
//break
//}
//}
//}
//area.width = 4;
//Ok(Some(area))
}),
// record
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks) = self;
//let mut area = to.area();
//let on = Some(Style::default().not_dim().red().bold());
//let off = Some(Style::default().dim());
//area.x += 1;
//for y in 0..area.h() {
//if y == 0 {
////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(track) = tracks.get(index) {
//let style = if track.recording { on } else { off };
//to.blit(&" REC ", area.x(), area.y() + y, style)?;
//} else {
//area.height = y;
//break
//}
//}
//}
//area.width = 4;
//Ok(Some(area))
}),
// overdub
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
todo!()
//let Self(tracks) = self;
//let mut area = to.area();
//let on = Some(Style::default().not_dim().yellow().bold());
//let off = Some(Style::default().dim());
//area.x = area.x + 1;
//for y in 0..area.h() {
//if y == 0 {
////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(track) = tracks.get(index) {
//to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
//on
//} else { //} else {
//area.height = y; //off
//break //})?;
//} //} else {
//area.height = y;
//break
//} //}
//} //}
//area.width = 4; //}
//Ok(Some(area)) //area.width = 4;
}), //Ok(Some(area))
// record }),
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ // erase
todo!() CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//let Self(tracks) = self; todo!()
//let mut area = to.area(); //let Self(tracks) = self;
//let on = Some(Style::default().not_dim().red().bold()); //let mut area = to.area();
//let off = Some(Style::default().dim()); //let off = Some(Style::default().dim());
//area.x += 1; //area.x = area.x + 1;
//for y in 0..area.h() { //for y in 0..area.h() {
//if y == 0 { //if y == 0 {
////" REC ".blit(to.buffer, area.x, area.y + y, style2)?; ////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 { //} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2; //let index = (y as usize - 2) / 2;
//if let Some(track) = tracks.get(index) { //if let Some(_) = tracks.get(index) {
//let style = if track.recording { on } else { off }; //to.blit(&" DEL ", area.x(), area.y() + y, off)?;
//to.blit(&" REC ", area.x(), area.y() + y, style)?; //} else {
//} else { //area.height = y;
//area.height = y; //break
//break
//}
//} //}
//} //}
//area.width = 4; //}
//Ok(Some(area)) //area.width = 4;
}), //Ok(Some(area))
// overdub }),
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ // gain
todo!() CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
//let Self(tracks) = self; todo!()
//let mut area = to.area(); //let Self(tracks) = self;
//let on = Some(Style::default().not_dim().yellow().bold()); //let mut area = to.area();
//let off = Some(Style::default().dim()); //let off = Some(Style::default().dim());
//area.x = area.x + 1; //area.x = area.x() + 1;
//for y in 0..area.h() { //for y in 0..area.h() {
//if y == 0 { //if y == 0 {
////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?; ////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 { //} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2; //let index = (y as usize - 2) / 2;
//if let Some(track) = tracks.get(index) { //if let Some(_) = tracks.get(index) {
//to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub { //to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
//on //} else {
//} else { //area.height = y;
//off //break
//})?;
//} else {
//area.height = y;
//break
//}
//} //}
//} //}
//area.width = 4; //}
//Ok(Some(area)) //area.width = 7;
}), //Ok(Some(area))
// erase }),
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ // scenes
todo!() CustomWidget::new(|_|{todo!()}, |to: &mut TuiOutput|{
//let Self(tracks) = self; let selected = &state.selected;
//let mut area = to.area(); let scenes = &state.model.scenes;
//let off = Some(Style::default().dim()); let area = to.area();
//area.x = area.x + 1; let mut x2 = 0;
//for y in 0..area.h() { let [x, y, _, height] = area;
//if y == 0 { Ok(for (scene_index, scene) in scenes.iter().enumerate() {
////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?; let active_scene = selected.scene() == Some(scene_index);
//} else if y % 2 == 0 { let sep = Some(if active_scene {
//let index = (y as usize - 2) / 2; Style::default().yellow().not_dim()
//if let Some(_) = tracks.get(index) { } else {
//to.blit(&" DEL ", area.x(), area.y() + y, off)?; Style::default().dim()
//} else { });
//area.height = y; for y in y+1..y+height {
//break to.blit(&"", x + x2, y, sep);
//} }
//} let name = scene.name.read().unwrap();
//} let mut x3 = name.len() as u16;
//area.width = 4; to.blit(&*name, x + x2, y, sep);
//Ok(Some(area)) for (i, clip) in scene.clips.iter().enumerate() {
}), let active_track = selected.track() == Some(i);
// gain if let Some(clip) = clip {
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ let y2 = y + 2 + i as u16 * 2;
todo!() let label = format!("{}", clip.read().unwrap().name);
//let Self(tracks) = self; to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
//let mut area = to.area(); Style::default().not_dim().yellow().bold()
//let off = Some(Style::default().dim()); } else {
//area.x = area.x() + 1; Style::default().not_dim()
//for y in 0..area.h() { }));
//if y == 0 { x3 = x3.max(label.len() as u16)
////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
//} else if y % 2 == 0 {
//let index = (y as usize - 2) / 2;
//if let Some(_) = tracks.get(index) {
//to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
//} else {
//area.height = y;
//break
//}
//}
//}
//area.width = 7;
//Ok(Some(area))
}),
// scenes
CustomWidget::new(|_|{todo!()}, |to: &mut TuiOutput|{
let Arrangement { scenes, selected, .. } = self.0;
let area = to.area();
let mut x2 = 0;
let [x, y, _, height] = area;
Ok(for (scene_index, scene) in scenes.iter().enumerate() {
let active_scene = 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 {
to.blit(&"", x + x2, y, sep);
} }
let name = scene.name.read().unwrap(); }
let mut x3 = name.len() as u16; x2 = x2 + x3 + 1;
to.blit(&*name, x + x2, y, sep); })
for (i, clip) in scene.clips.iter().enumerate() { }),
let active_track = selected.track() == Some(i);
if let Some(clip) = clip {
let y2 = y + 2 + i as u16 * 2;
let label = format!("{}", clip.read().unwrap().name);
to.blit(&label, 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;
})
}),
)
) )
} )
} }

View file

@ -1,197 +1,192 @@
use crate::*; use crate::*;
/// Arrangement, rendered vertically (session/grid mode). pub fn arranger_content_vertical (
pub struct VerticalArranger<'a, E: Engine>(pub &'a Arrangement<E>, pub usize); view: &ArrangerView<Tui>,
factor: usize
impl<'a> Content for VerticalArranger<'a, Tui> { ) -> impl Widget<Engine = Tui> + use<'_> {
type Engine = Tui; let tracks = view.arrangement.model.tracks.as_ref() as &[ArrangementTrack];
fn content (&self) -> impl Widget<Engine = Tui> { let scenes = view.arrangement.model.scenes.as_ref();
let Self(state, factor) = self; let cols = view.arrangement.track_widths();
let tracks = state.tracks.as_ref() as &[ArrangementTrack]; let rows = ArrangementScene::ppqs(scenes, factor);
let scenes = state.scenes.as_ref(); let bg = view.arrangement.color;
let cols = state.track_widths(); let clip_bg = TuiTheme::border_bg();
let rows = ArrangementScene::ppqs(scenes, *factor); let sep_fg = TuiTheme::separator_fg(false);
let bg = state.color; let header_h = 3u16;//5u16;
let clip_bg = TuiTheme::border_bg(); let scenes_w = 3 + ArrangementScene::longest_name(scenes) as u16; // x of 1st track
let sep_fg = TuiTheme::separator_fg(false); let clock = &view.sequencer.transport.model.clock;
let header_h = 3u16;//5u16; let arrangement = Layers::new(move |add|{
let scenes_w = 3 + ArrangementScene::longest_name(scenes) as u16; // x of 1st track let rows: &[(usize, usize)] = rows.as_ref();
let clock = &self.0.clock; let cols: &[(usize, usize)] = cols.as_ref();
let arrangement = Layers::new(move |add|{ let any_size = |_|Ok(Some([0,0]));
let rows: &[(usize, usize)] = rows.as_ref(); // column separators
let cols: &[(usize, usize)] = cols.as_ref(); add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{
let any_size = |_|Ok(Some([0,0])); let style = Some(Style::default().fg(sep_fg));
// column separators Ok(for x in cols.iter().map(|col|col.1) {
add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ let x = scenes_w + to.area().x() + x as u16;
let style = Some(Style::default().fg(sep_fg)); for y in to.area().y()..to.area().y2() { to.blit(&"", x, y, style); }
Ok(for x in cols.iter().map(|col|col.1) { })
let x = scenes_w + to.area().x() + x as u16; }))?;
for y in to.area().y()..to.area().y2() { to.blit(&"", x, y, style); } // row separators
}) add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{
}))?; Ok(for y in rows.iter().map(|row|row.1) {
// row separators let y = to.area().y() + (y / PPQ) as u16 + 1;
add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ if y >= to.buffer.area.height { break }
Ok(for y in rows.iter().map(|row|row.1) { for x in to.area().x()..to.area().x2().saturating_sub(2) {
let y = to.area().y() + (y / PPQ) as u16 + 1; if x < to.buffer.area.x && y < to.buffer.area.y {
if y >= to.buffer.area.height { break } let cell = to.buffer.get_mut(x, y);
for x in to.area().x()..to.area().x2().saturating_sub(2) { cell.modifier = Modifier::UNDERLINED;
if x < to.buffer.area.x && y < to.buffer.area.y { cell.underline_color = sep_fg;
let cell = to.buffer.get_mut(x, y);
cell.modifier = Modifier::UNDERLINED;
cell.underline_color = sep_fg;
}
} }
}) }
}))?; })
// track titles }))?;
let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ // track titles
// name and width of track let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{
let name = track.name.read().unwrap(); // name and width of track
let player = &track.player; let name = track.name.read().unwrap();
let max_w = w.saturating_sub(1).min(name.len()).max(2); let player = &track.player;
let name = format!("{}", &name[0..max_w]); let max_w = w.saturating_sub(1).min(name.len()).max(2);
let name = TuiStyle::bold(name, true); let name = format!("{}", &name[0..max_w]);
// beats elapsed let name = TuiStyle::bold(name, true);
let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() { // beats elapsed
let length = phrase.read().unwrap().length; let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() {
let elapsed = player.pulses_since_start().unwrap(); let length = phrase.read().unwrap().length;
let elapsed = clock.timebase().format_beats_1_short( let elapsed = player.pulses_since_start().unwrap();
(elapsed as usize % length) as f64 let elapsed = clock.timebase().format_beats_1_short(
); (elapsed as usize % length) as f64
format!("▎+{elapsed:>}") );
format!("▎+{elapsed:>}")
} else {
String::from("")
};
// beats until switchover
let until_next = player.next_phrase.as_ref().map(|(t, _)|{
let target = t.pulse.get();
let current = clock.current.pulse.get();
if target > current {
let remaining = target - current;
format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining))
} else { } else {
String::from("") String::new()
};
// beats until switchover
let until_next = player.next_phrase.as_ref().map(|(t, _)|{
let target = t.pulse.get();
let current = clock.current.pulse.get();
if target > current {
let remaining = target - current;
format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining))
} else {
String::new()
}
}).unwrap_or(String::from(""));
// name of active MIDI input
let input = format!("▎>{}", track.player.midi_inputs.get(0)
.map(|port|port.short_name())
.transpose()?
.unwrap_or("(none)".into()));
// name of active MIDI output
let output = format!("▎<{}", track.player.midi_outputs.get(0)
.map(|port|port.short_name())
.transpose()?
.unwrap_or("(none)".into()));
col!(name, /*input, output,*/ until_next, elapsed)
.min_xy(w as u16, header_h)
.bg(track.color.rgb)
.push_x(scenes_w)
});
// scene titles
let scene_name = |scene: &ArrangementScene, playing: bool, height|row!(
if playing { "" } else { " " },
TuiStyle::bold(scene.name.read().unwrap().as_str(), true),
).fixed_xy(scenes_w, height);
// scene clips
let scene_clip = |scene: &ArrangementScene, track: usize, w: u16, h: u16|Layers::new(move |add|{
let mut bg = clip_bg;
match (tracks.get(track), scene.clips.get(track)) {
(Some(track), Some(Some(phrase))) => {
let name = &(phrase as &Arc<RwLock<Phrase>>).read().unwrap().name;
let name = format!("{}", name);
let max_w = name.len().min((w as usize).saturating_sub(2));
let color = phrase.read().unwrap().color;
add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?;
bg = color.dark.rgb;
if let Some((_, Some(ref playing))) = track.player.phrase {
if *playing.read().unwrap() == *phrase.read().unwrap() {
bg = color.light.rgb
}
};
},
_ => {}
};
add(&Background(bg))
}).fixed_xy(w, h);
// tracks and scenes
let content = col!(
// scenes:
(scene, pulses) in scenes.iter().zip(rows.iter().map(|row|row.0)) => {
let height = 1.max((pulses / PPQ) as u16);
let playing = scene.is_playing(tracks);
Stack::right(move |add| {
// scene title:
add(&scene_name(scene, playing, height).bg(scene.color.rgb))?;
// clip per track:
Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() {
add(&scene_clip(scene, track, w as u16, height))?;
})
}).fixed_y(height)
} }
).fixed_y((self.0.size.h() as u16).saturating_sub(header_h)); }).unwrap_or(String::from(""));
// full grid with header and footer // name of active MIDI input
add(&col!(header, content))?; let input = format!("▎>{}", track.player.midi_inputs.get(0)
// cursor .map(|port|port.short_name())
add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ .transpose()?
let area = to.area(); .unwrap_or("(none)".into()));
let focused = state.focused; // name of active MIDI output
let selected = state.selected; let output = format!("▎<{}", track.player.midi_outputs.get(0)
let get_track_area = |t: usize| [ .map(|port|port.short_name())
scenes_w + area.x() + cols[t].1 as u16, area.y(), .transpose()?
cols[t].0 as u16, area.h(), .unwrap_or("(none)".into()));
]; col!(name, /*input, output,*/ until_next, elapsed)
let get_scene_area = |s: usize| [ .min_xy(w as u16, header_h)
area.x(), header_h + area.y() + (rows[s].1 / PPQ) as u16, .bg(track.color.rgb)
area.w(), (rows[s].0 / PPQ) as u16 .push_x(scenes_w)
]; });
let get_clip_area = |t: usize, s: usize| [ // scene titles
scenes_w + area.x() + cols[t].1 as u16, let scene_name = |scene: &ArrangementScene, playing: bool, height|row!(
header_h + area.y() + (rows[s].1/PPQ) as u16, if playing { "" } else { " " },
cols[t].0 as u16, TuiStyle::bold(scene.name.read().unwrap().as_str(), true),
(rows[s].0 / PPQ) as u16 ).fixed_xy(scenes_w, height);
]; // scene clips
let mut track_area: Option<[u16;4]> = None; let scene_clip = |scene: &ArrangementScene, track: usize, w: u16, h: u16|Layers::new(move |add|{
let mut scene_area: Option<[u16;4]> = None; let mut bg = clip_bg;
let mut clip_area: Option<[u16;4]> = None; match (tracks.get(track), scene.clips.get(track)) {
let area = match selected { (Some(track), Some(Some(phrase))) => {
ArrangementEditorFocus::Mix => area, let name = &(phrase as &Arc<RwLock<Phrase>>).read().unwrap().name;
ArrangementEditorFocus::Track(t) => { let name = format!("{}", name);
track_area = Some(get_track_area(t)); let max_w = name.len().min((w as usize).saturating_sub(2));
area let color = phrase.read().unwrap().color;
}, add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?;
ArrangementEditorFocus::Scene(s) => { bg = color.dark.rgb;
scene_area = Some(get_scene_area(s)); if let Some((_, Some(ref playing))) = track.player.phrase {
area if *playing.read().unwrap() == *phrase.read().unwrap() {
}, bg = color.light.rgb
ArrangementEditorFocus::Clip(t, s) => { }
track_area = Some(get_track_area(t)); };
scene_area = Some(get_scene_area(s)); },
clip_area = Some(get_clip_area(t, s)); _ => {}
area };
}, add(&Background(bg))
}; }).fixed_xy(w, h);
let bg = TuiTheme::border_bg(); // tracks and scenes
if let Some([x, y, width, height]) = track_area { let content = col!(
to.fill_fg([x, y, 1, height], bg); // scenes:
to.fill_fg([x + width, y, 1, height], bg); (scene, pulses) in scenes.iter().zip(rows.iter().map(|row|row.0)) => {
} let height = 1.max((pulses / PPQ) as u16);
if let Some([_, y, _, height]) = scene_area { let playing = scene.is_playing(tracks);
to.fill_ul([area.x(), y - 1, area.w(), 1], bg); Stack::right(move |add| {
to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); // scene title:
} add(&scene_name(scene, playing, height).bg(scene.color.rgb))?;
Ok(if focused { // clip per track:
to.render_in(if let Some(clip_area) = clip_area { clip_area } Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() {
else if let Some(track_area) = track_area { track_area.clip_h(header_h) } add(&scene_clip(scene, track, w as u16, height))?;
else if let Some(scene_area) = scene_area { scene_area.clip_w(scenes_w) } })
else { area.clip_w(scenes_w).clip_h(header_h) }, &CORNERS)? }).fixed_y(height)
}) }
})) ).fixed_y((view.size.h() as u16).saturating_sub(header_h));
}).bg(bg.rgb); // full grid with header and footer
let color = TuiTheme::title_fg(self.0.focused); add(&col!(header, content))?;
let size = format!("{}x{}", self.0.size.w(), self.0.size.h()); // cursor
let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy(); add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{
lay!(arrangement, lower_right) let area = to.area();
} let focused = view.arrangement.focused;
let selected = view.arrangement.selected;
let get_track_area = |t: usize| [
scenes_w + area.x() + cols[t].1 as u16, area.y(),
cols[t].0 as u16, area.h(),
];
let get_scene_area = |s: usize| [
area.x(), header_h + area.y() + (rows[s].1 / PPQ) as u16,
area.w(), (rows[s].0 / PPQ) as u16
];
let get_clip_area = |t: usize, s: usize| [
scenes_w + area.x() + cols[t].1 as u16,
header_h + area.y() + (rows[s].1/PPQ) as u16,
cols[t].0 as u16,
(rows[s].0 / PPQ) as u16
];
let mut track_area: Option<[u16;4]> = None;
let mut scene_area: Option<[u16;4]> = None;
let mut clip_area: Option<[u16;4]> = None;
let area = match selected {
ArrangementEditorFocus::Mix => area,
ArrangementEditorFocus::Track(t) => {
track_area = Some(get_track_area(t));
area
},
ArrangementEditorFocus::Scene(s) => {
scene_area = Some(get_scene_area(s));
area
},
ArrangementEditorFocus::Clip(t, s) => {
track_area = Some(get_track_area(t));
scene_area = Some(get_scene_area(s));
clip_area = Some(get_clip_area(t, s));
area
},
};
let bg = TuiTheme::border_bg();
if let Some([x, y, width, height]) = track_area {
to.fill_fg([x, y, 1, height], bg);
to.fill_fg([x + width, y, 1, height], bg);
}
if let Some([_, y, _, height]) = scene_area {
to.fill_ul([area.x(), y - 1, area.w(), 1], bg);
to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
}
Ok(if focused {
to.render_in(if let Some(clip_area) = clip_area { clip_area }
else if let Some(track_area) = track_area { track_area.clip_h(header_h) }
else if let Some(scene_area) = scene_area { scene_area.clip_w(scenes_w) }
else { area.clip_w(scenes_w).clip_h(header_h) }, &CORNERS)?
})
}))
}).bg(bg.rgb);
let color = TuiTheme::title_fg(view.arrangement.focused);
let size = format!("{}x{}", view.size.w(), view.size.h());
let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy();
lay!(arrangement, lower_right)
} }