mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
162 lines
6.4 KiB
Rust
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));
|