mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
298 lines
12 KiB
Rust
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();
|
|
}
|
|
}
|
|
}
|
|
}
|