tek/crates/tek_tui/src/tui_arranger.rs

298 lines
12 KiB
Rust

use crate::*;
/// Root level object for standalone `tek_arranger`
pub struct ArrangerView<E: Engine> {
/// Sequencer component
pub sequencer: SequencerView<E>,
/// Contains all the sequencers.
pub arrangement: ArrangementEditor<E>,
/// Height of arrangement
pub split: u16,
/// Width and height of app at last render
pub size: Measure<E>,
}
impl<E: Engine> Audio for ArrangerView<E> {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if self.sequencer.transport.process(client, scope) == Control::Quit {
return Control::Quit
}
if self.arrangement.process(client, scope) == Control::Quit {
return Control::Quit
}
if let ArrangementEditorFocus::Clip(t, s) = self.arrangement.selected {
let phrase = self.arrangement.state.scenes.get(s).map(|scene|scene.clips.get(t));
if let Some(Some(Some(phrase))) = phrase {
if let Some(track) = self.arrangement.state.tracks.get(t) {
if let Some((ref started_at, Some(ref playing))) = track.player.phrase {
let phrase = phrase.read().unwrap();
if *playing.read().unwrap() == *phrase {
let pulse = self.sequencer.transport.state.clock.current.pulse.get();
let start = started_at.pulse.get();
let now = (pulse - start) % phrase.length as f64;
self.sequencer.editor.now.set(now);
return Control::Continue
}
}
}
}
}
self.sequencer.editor.now.set(0.);
return Control::Continue
}
}
/// Layout for standalone arranger app.
impl Content for ArrangerView<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Split::up(
1,
widget(&self.sequencer.transport),
Split::down(
self.split,
lay!(
widget(&self.arrangement)
.grow_y(1)
.border(Lozenge(Style::default()
.bg(TuiTheme::border_bg())
.fg(TuiTheme::border_fg(self.arrangement.focused)))),
widget(&self.arrangement.size),
widget(&format!("[{}] Arrangement", if self.arrangement.entered {
""
} else {
" "
}))
.fg(TuiTheme::title_fg(self.arrangement.focused))
.push_x(1),
),
Split::right(
self.sequencer.split,
widget(&self.sequencer.phrases),
widget(&self.sequencer.editor),
)
)
)
}
}
/// General methods for arranger
impl<E: Engine> ArrangerView<E> {
pub fn new (
sequencer: SequencerView<E>,
arrangement: ArrangementEditor<E>,
) -> Self {
let mut app = Self { sequencer, arrangement, split: 15, size: Default::default() };
app.update_focus();
app
}
/// Toggle global play/pause
pub fn toggle_play (&mut self) -> Perhaps<bool> {
match self.transport {
Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; },
None => { return Ok(None) }
}
Ok(Some(true))
}
pub fn next_color (&self) -> ItemColor {
if let ArrangementEditorFocus::Clip(track, scene) = self.arrangement.selected {
let track_color = self.arrangement.tracks[track].color;
let scene_color = self.arrangement.scenes[scene].color;
track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25)
} else {
panic!("could not compute next color")
}
}
/// Focus the editor with the current phrase
pub fn show_phrase (&mut self) {
self.editor.show(self.arrangement.state.phrase().as_ref());
}
/// Focus the editor with the current phrase
pub fn edit_phrase (&mut self) {
if self.arrangement.selected.is_clip() && self.arrangement.state.phrase().is_none() {
self.phrases.write().unwrap().append_new(None, Some(self.next_color().into()));
self.arrangement.phrase_put();
}
self.show_phrase();
self.focus(ArrangerViewFocus::PhraseEditor);
self.editor.entered = true;
}
/// Rename the selected track, scene, or clip
pub fn rename_selected (&mut self) {
let Arrangement { selected, ref scenes, .. } = self.arrangement;
match selected {
ArrangementEditorFocus::Mix => {},
ArrangementEditorFocus::Track(_) => { todo!("rename track"); },
ArrangementEditorFocus::Scene(_) => { todo!("rename scene"); },
ArrangementEditorFocus::Clip(t, s) => if let Some(ref phrase) = scenes[s].clips[t] {
let index = self.phrases.read().unwrap().index_of(&*phrase.read().unwrap());
if let Some(index) = index {
self.focus(ArrangerViewFocus::PhrasePool);
self.phrases.write().unwrap().phrase = index;
self.phrases.write().unwrap().begin_rename();
}
},
}
}
/// Update status bar
pub fn update_status (&mut self) {
self.status = match self.focused() {
ArrangerViewFocus::Transport => ArrangerStatusBar::Transport,
ArrangerViewFocus::Arrangement => match self.arrangement.selected {
ArrangementEditorFocus::Mix => ArrangerStatusBar::ArrangementMix,
ArrangementEditorFocus::Track(_) => ArrangerStatusBar::ArrangementTrack,
ArrangementEditorFocus::Scene(_) => ArrangerStatusBar::ArrangementScene,
ArrangementEditorFocus::Clip(_, _) => ArrangerStatusBar::ArrangementClip,
},
ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
ArrangerViewFocus::PhraseEditor => match self.editor.entered {
true => ArrangerStatusBar::PhraseEdit,
false => ArrangerStatusBar::PhraseView,
},
}
}
pub fn activate (&mut self) {
match self.selected {
ArrangementEditorFocus::Scene(s) => {
for (t, track) in self.tracks.iter_mut().enumerate() {
let player = &mut track.player;
let clip = self.scenes[s].clips[t].as_ref();
if player.phrase.is_some() || clip.is_some() {
player.enqueue_next(clip);
}
}
// TODO make transport available here, so that
// activating a scene when stopped starts playback
//if self.is_stopped() {
//self.transport.toggle_play()
//}
},
ArrangementEditorFocus::Clip(t, s) => {
self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref());
},
_ => {}
}
}
pub fn delete (&mut self) {
match self.arrangement.selected {
ArrangementEditorFocus::Track(_) => self.track_del(),
ArrangementEditorFocus::Scene(_) => self.scene_del(),
ArrangementEditorFocus::Clip(_, _) => self.phrase_del(),
_ => {}
}
}
pub fn increment (&mut self) {
match self.arrangement.selected {
ArrangementEditorFocus::Track(_) => self.track_mut().map(|t|t.width_inc()),
ArrangementEditorFocus::Scene(_) => self.scene_next(),
ArrangementEditorFocus::Clip(_, _) => self.phrase_next(),
ArrangementEditorFocus::Mix => self.zoom_in(),
}
}
pub fn decrement (&mut self) {
match self.arrangement.selected {
ArrangementEditorFocus::Track(_) => self.track_mut().map(|t|t.width_dec()),
ArrangementEditorFocus::Scene(_) => self.scene_prev(),
ArrangementEditorFocus::Clip(_, _) => self.phrase_prev(),
ArrangementEditorFocus::Mix => self.zoom_out(),
}
}
pub fn zoom_in (&mut self) {
if let ArrangementEditorMode::Vertical(factor) = self.mode {
self.mode = ArrangementEditorMode::Vertical(factor + 1)
}
}
pub fn zoom_out (&mut self) {
if let ArrangementEditorMode::Vertical(factor) = self.mode {
self.mode = ArrangementEditorMode::Vertical(factor.saturating_sub(1))
}
}
pub fn is_first_row (&self) -> bool {
let selected = self.selected;
selected.is_mix() || selected.is_track()
}
pub fn is_last_row (&self) -> bool {
let selected = self.selected;
(self.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected {
ArrangementEditorFocus::Scene(s) => s == self.scenes.len() - 1,
ArrangementEditorFocus::Clip(_, s) => s == self.scenes.len() - 1,
_ => false
}
}
pub fn toggle_loop (&mut self) {
if let Some(phrase) = self.phrase() {
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) {
match self.selected {
ArrangementEditorFocus::Scene(s) => {
if s > 0 {
self.scenes.swap(s, s - 1);
self.selected = ArrangementEditorFocus::Scene(s - 1);
}
},
ArrangementEditorFocus::Track(t) => {
if t > 0 {
self.tracks.swap(t, t - 1);
self.selected = ArrangementEditorFocus::Track(t - 1);
// FIXME: also swap clip order in scenes
}
},
_ => todo!("arrangement: move forward")
}
}
pub fn move_forward (&mut self) {
match self.selected {
ArrangementEditorFocus::Scene(s) => {
if s < self.scenes.len().saturating_sub(1) {
self.scenes.swap(s, s + 1);
self.selected = ArrangementEditorFocus::Scene(s + 1);
}
},
ArrangementEditorFocus::Track(t) => {
if t < self.tracks.len().saturating_sub(1) {
self.tracks.swap(t, t + 1);
self.selected = ArrangementEditorFocus::Track(t + 1);
// FIXME: also swap clip order in scenes
}
},
_ => todo!("arrangement: move forward")
}
}
pub fn randomize_color (&mut self) {
match self.selected {
ArrangementEditorFocus::Mix => { self.color = ItemColor::random_dark() },
ArrangementEditorFocus::Track(t) => { self.tracks[t].color = ItemColor::random() },
ArrangementEditorFocus::Scene(s) => { self.scenes[s].color = ItemColor::random() },
ArrangementEditorFocus::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] {
phrase.write().unwrap().color = ItemColorTriplet::random();
}
}
}
}