tek/src/arranger.rs

162 lines
6.4 KiB
Rust

use crate::*;
mod arranger_command; pub(crate) use self::arranger_command::*;
mod arranger_scene; pub(crate) use self::arranger_scene::*;
mod arranger_select; pub(crate) use self::arranger_select::*;
mod arranger_track; pub(crate) use self::arranger_track::*;
mod arranger_mode; pub(crate) use self::arranger_mode::*;
mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*;
mod arranger_h;
/// Root view for standalone `tek_arranger`
pub struct ArrangerTui {
jack: Arc<RwLock<JackConnection>>,
pub clock: ClockModel,
pub phrases: PoolModel,
pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>,
pub splits: [u16;2],
pub selected: ArrangerSelection,
pub mode: ArrangerMode,
pub color: ItemPalette,
pub size: Measure<Tui>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub editor: MidiEditor,
pub perf: PerfModel,
}
impl ArrangerTui {
pub fn selected (&self) -> ArrangerSelection {
self.selected
}
pub fn selected_mut (&mut self) -> &mut ArrangerSelection {
&mut self.selected
}
pub fn activate (&mut self) -> Usually<()> {
if let ArrangerSelection::Scene(s) = self.selected {
for (t, track) in self.tracks.iter_mut().enumerate() {
let phrase = self.scenes[s].clips[t].clone();
if track.player.play_phrase.is_some() || phrase.is_some() {
track.player.enqueue_next(phrase.as_ref());
}
}
if self.clock().is_stopped() {
self.clock().play_from(Some(0))?;
}
} else if let ArrangerSelection::Clip(t, s) = self.selected {
let phrase = self.scenes[s].clips[t].clone();
self.tracks[t].player.enqueue_next(phrase.as_ref());
};
Ok(())
}
pub fn selected_phrase (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
}
pub fn toggle_loop (&mut self) {
if let Some(phrase) = self.selected_phrase() {
phrase.write().unwrap().toggle_loop()
}
}
pub fn randomize_color (&mut self) {
match self.selected {
ArrangerSelection::Mix => { self.color = ItemPalette::random() },
ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() },
ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() },
ArrangerSelection::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] {
phrase.write().unwrap().color = ItemPalette::random();
}
}
}
}
from_jack!(|jack| ArrangerTui {
let clock = ClockModel::from(jack);
let phrase = Arc::new(RwLock::new(MidiClip::new(
"New", true, 4 * clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into())
)));
Self {
clock,
phrases: (&phrase).into(),
editor: (&phrase).into(),
selected: ArrangerSelection::Clip(0, 0),
scenes: vec![],
tracks: vec![],
color: TuiTheme::bg().into(),
mode: ArrangerMode::V(1),
size: Measure::new(),
splits: [12, 20],
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
jack: jack.clone(),
}
});
impl ArrangerTui {
fn render_mode (state: &Self) -> impl Render<Tui> + use<'_> {
match state.mode {
ArrangerMode::H => todo!("horizontal arranger"),
ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
}
}
}
render!(<Tui>|self: ArrangerTui|{
let play = PlayPause(self.clock.is_rolling());
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self);
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
let with_status = |x|Split::n(false, 2, status, x);
let with_size = |x|lay!([&self.size, x]);
let arranger = ||lay!(|add|{
let color = self.color;
add(&Fill::xy(Tui::bg(color.darkest.rgb, "")))?;
add(&Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
add(&Self::render_mode(self))
});
Some(with_size(with_status(with_editbar(with_pool(with_transport(col!([
Fill::x(Fixed::y(20, arranger())),
Fill::xy(&self.editor),
])))))))
});
audio!(|self: ArrangerTui, client, scope|{
// Start profiling cycle
let t0 = self.perf.get_t0();
// Update transport clock
if Control::Quit == ClockAudio(self).process(client, scope) {
return Control::Quit
}
// Update MIDI sequencers
let tracks = &mut self.tracks;
let note_buf = &mut self.note_buf;
let midi_buf = &mut self.midi_buf;
if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) {
return Control::Quit
}
// FIXME: one of these per playing track
//self.now.set(0.);
//if let ArrangerSelection::Clip(t, s) = self.selected {
//let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t));
//if let Some(Some(Some(phrase))) = phrase {
//if let Some(track) = self.tracks().get(t) {
//if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
//let phrase = phrase.read().unwrap();
//if *playing.read().unwrap() == *phrase {
//let pulse = self.current().pulse.get();
//let start = started_at.pulse.get();
//let now = (pulse - start) % phrase.length as f64;
//self.now.set(now);
//}
//}
//}
//}
//}
// End profiling cycle
self.perf.update(t0, scope);
return Control::Continue
});
has_clock!(|self: ArrangerTui|&self.clock);
has_phrases!(|self: ArrangerTui|self.phrases.phrases);
has_editor!(|self: ArrangerTui|self.editor);
handle!(<Tui>|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));