tek/crates/tek_tui/src/tui_arranger.rs

402 lines
16 KiB
Rust

use crate::*;
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerApp<Tui> {
type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
Ok(Self::new(ArrangerView {
name: Arc::new(RwLock::new(String::new())),
phrases: vec![],
phrase: 0,
scenes: vec![],
tracks: vec![],
metronome: false,
playing: None.into(),
started: None.into(),
transport: jack.read().unwrap().transport(),
current: Instant::default(),
jack: jack.clone(),
selected: ArrangerSelection::Clip(0, 0),
mode: ArrangerMode::Vertical(2),
color: Color::Rgb(28, 35, 25).into(),
size: Measure::new(),
entered: false,
quant: Default::default(),
sync: Default::default(),
splits: [20, 20],
note_buf: vec![],
midi_buf: vec![],
}.into(), None, None))
}
}
pub type ArrangerApp<E: Engine> = AppView<
E,
ArrangerView<E>,
ArrangerAppCommand,
ArrangerStatusBar
>;
/// Root view for standalone `tek_arranger`
pub struct ArrangerView<E: Engine> {
pub(crate) jack: Arc<RwLock<JackClient>>,
pub(crate) playing: RwLock<Option<TransportState>>,
pub(crate) started: RwLock<Option<(usize, usize)>>,
pub(crate) current: Instant,
pub(crate) quant: Quantize,
pub(crate) sync: LaunchSync,
pub(crate) transport: jack::Transport,
pub(crate) metronome: bool,
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
pub(crate) phrase: usize,
pub(crate) tracks: Vec<ArrangerTrack>,
pub(crate) scenes: Vec<ArrangerScene>,
pub(crate) name: Arc<RwLock<String>>,
pub(crate) splits: [u16;2],
pub(crate) selected: ArrangerSelection,
pub(crate) mode: ArrangerMode,
pub(crate) color: ItemColor,
pub(crate) entered: bool,
pub(crate) size: Measure<E>,
pub(crate) note_buf: Vec<u8>,
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
}
impl HasJack for ArrangerView<Tui> {
fn jack (&self) -> &Arc<RwLock<JackClient>> {
&self.transport.jack()
}
}
impl Audio for ArrangerApp<Tui> {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
TracksAudio(
&mut self.app.tracks,
&mut self.app.note_buf,
&mut self.app.midi_buf,
Default::default(),
).process(client, scope)
}
}
impl ClockApi for ArrangerView<Tui> {
fn timebase (&self) -> &Arc<Timebase> {
&self.current.timebase
}
fn quant (&self) -> &Quantize {
&self.quant
}
fn sync (&self) -> &LaunchSync {
&self.sync
}
}
impl PlayheadApi for ArrangerView<Tui> {
fn current (&self) -> &Instant {
&self.current
}
fn transport (&self) -> &jack::Transport {
&self.transport
}
fn playing (&self) -> &RwLock<Option<TransportState>> {
&self.playing
}
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
&self.started
}
}
impl HasPhrases for ArrangerView<Tui> {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.phrases
}
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
&mut self.phrases
}
}
/// General methods for arranger
impl ArrangerView<Tui> {
pub fn selected_scene (&self) -> Option<&ArrangerScene> {
self.selected.scene().map(|s|self.scenes().get(s)).flatten()
}
pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten()
}
pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
}
/// Focus the editor with the current phrase
pub fn show_phrase (&mut self) {
self.editor.show(self.selected_phrase().as_ref());
}
pub fn activate (&mut self) {
let scenes = self.scenes();
let tracks = self.tracks_mut();
match self.selected {
ArrangerSelection::Scene(s) => {
for (t, track) in tracks.iter_mut().enumerate() {
let player = &mut track.player;
let clip = 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()
//}
},
ArrangerSelection::Clip(t, s) => {
tracks[t].player.enqueue_next(scenes[s].clips[t]);
},
_ => {}
}
}
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 {
ArrangerSelection::Scene(s) => s == self.scenes().len() - 1,
ArrangerSelection::Clip(_, s) => s == self.scenes().len() - 1,
_ => false
}
}
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 = ItemColor::random_dark()
},
ArrangerSelection::Track(t) => {
self.tracks_mut()[t].color = ItemColor::random()
},
ArrangerSelection::Scene(s) => {
self.scenes_mut()[s].color = ItemColor::random()
},
ArrangerSelection::Clip(t, s) => {
if let Some(phrase) = &self.scenes_mut()[s].clips[t] {
phrase.write().unwrap().color = ItemColorTriplet::random();
}
}
}
}
}
impl<E: Engine> Audio for ArrangerView<E> {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if self.process(client, scope) == Control::Quit {
return Control::Quit
}
// FIXME: one of these per playing track
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.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.editor.now.set(now);
return Control::Continue
}
}
}
}
}
self.editor.now.set(0.);
return Control::Continue
}
}
//pub fn track_next (&mut self, last_track: usize) {
//use ArrangerSelection::*;
//*self = match self {
//Mix => Track(0),
//Track(t) => Track(last_track.min(*t + 1)),
//Scene(s) => Clip(0, *s),
//Clip(t, s) => Clip(last_track.min(*t + 1), *s),
//}
//}
//pub fn track_prev (&mut self) {
//use ArrangerSelection::*;
//*self = match self {
//Mix => Mix,
//Scene(s) => Scene(*s),
//Track(t) => if *t == 0 { Mix } else { Track(*t - 1) },
//Clip(t, s) => if *t == 0 { Scene(*s) } else { Clip(t.saturating_sub(1), *s) }
//}
//}
//pub fn scene_next (&mut self, last_scene: usize) {
//use ArrangerSelection::*;
//*self = match self {
//Mix => Scene(0),
//Track(t) => Clip(*t, 0),
//Scene(s) => Scene(last_scene.min(*s + 1)),
//Clip(t, s) => Clip(*t, last_scene.min(*s + 1)),
//}
//}
//pub fn scene_prev (&mut self) {
//use ArrangerSelection::*;
//*self = match self {
//Mix => Mix,
//Track(t) => Track(*t),
//Scene(s) => if *s == 0 { Mix } else { Scene(*s - 1) },
//Clip(t, s) => if *s == 0 { Track(*t) } else { Clip(*t, s.saturating_sub(1)) }
//}
//}
//pub fn arranger_menu_bar () -> MenuBar {
//use ArrangerCommand as Cmd;
//use ArrangerCommand as Edit;
//use ArrangerSelection as Focus;
//use ArrangerTrackCommand as Track;
//use ArrangerClipCommand as Clip;
//use ArrangerSceneCommand as Scene;
//use TransportCommand as Transport;
//MenuBar::new()
//.add({
//use ArrangerCommand::*;
//Menu::new("File")
//.cmd("n", "New project", ArrangerViewCommand::Arranger(New))
//.cmd("l", "Load project", ArrangerViewCommand::Arranger(Load))
//.cmd("s", "Save project", ArrangerViewCommand::Arranger(Save))
//})
//.add({
//Menu::new("Transport")
//.cmd("p", "Play", TransportCommand::Transport(Play(None)))
//.cmd("P", "Play from start", TransportCommand::Transport(Play(Some(0))))
//.cmd("s", "Pause", TransportCommand::Transport(Stop(None)))
//.cmd("S", "Stop and rewind", TransportCommand::Transport(Stop(Some(0))))
//})
//.add({
//use ArrangerCommand::*;
//Menu::new("Track")
//.cmd("a", "Append new", ArrangerViewCommand::Arranger(AddTrack))
//.cmd("i", "Insert new", ArrangerViewCommand::Arranger(AddTrack))
//.cmd("n", "Rename", ArrangerViewCommand::Arranger(AddTrack))
//.cmd("d", "Delete", ArrangerViewCommand::Arranger(AddTrack))
//.cmd(">", "Move up", ArrangerViewCommand::Arranger(AddTrack))
//.cmd("<", "Move down", ArrangerViewCommand::Arranger(AddTrack))
//})
//.add({
//use ArrangerCommand::*;
//Menu::new("Scene")
//.cmd("a", "Append new", ArrangerViewCommand::Arranger(AddScene))
//.cmd("i", "Insert new", ArrangerViewCommand::Arranger(AddTrack))
//.cmd("n", "Rename", ArrangerViewCommand::Arranger(AddTrack))
//.cmd("d", "Delete", ArrangerViewCommand::Arranger(AddTrack))
//.cmd(">", "Move up", ArrangerViewCommand::Arranger(AddTrack))
//.cmd("<", "Move down", ArrangerViewCommand::Arranger(AddTrack))
//})
//.add({
//use PhraseRenameCommand as Rename;
//use PhraseLengthCommand as Length;
//Menu::new("Phrase")
//.cmd("a", "Append new", PhrasePoolCommand::Phrases(Append))
//.cmd("i", "Insert new", PhrasePoolCommand::Phrases(Insert))
//.cmd("n", "Rename", PhrasePoolCommand::Phrases(Rename(Rename::Begin)))
//.cmd("t", "Set length", PhrasePoolCommand::Phrases(Length(Length::Begin)))
//.cmd("d", "Delete", PhrasePoolCommand::Phrases(Delete))
//.cmd("l", "Load from MIDI...", PhrasePoolCommand::Phrases(Import))
//.cmd("s", "Save to MIDI...", PhrasePoolCommand::Phrases(Export))
//.cmd(">", "Move up", PhrasePoolCommand::Phrases(MoveUp))
//.cmd("<", "Move down", PhrasePoolCommand::Phrases(MoveDown))
//})
//}
//pub fn phrase_next (&mut self) {
//if let ArrangerSelection::Clip(track, scene) = self.selected {
//if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] {
//let phrases = self.model.phrases.read().unwrap();
//let index = phrases.index_of(&*phrase.read().unwrap());
//if let Some(index) = index {
//if index < phrases.len().saturating_sub(1) {
//*phrase = phrases[index + 1].clone();
//}
//}
//}
//}
//}
//pub fn phrase_prev (&mut self) {
//if let ArrangerSelection::Clip(track, scene) = self.selected {
//if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] {
//let phrases = self.model.phrases.read().unwrap();
//let index = phrases.index_of(&*phrase.read().unwrap());
//if let Some(index) = index {
//if index > 0 {
//*phrase = phrases[index - 1].clone();
//}
//}
//}
//}
//}
//pub fn phrase_get (&mut self) {
//if let ArrangerSelection::Clip(track, scene) = self.selected {
//if let Some(phrase) = &self.model.scenes[scene].clips[track] {
//let mut phrases = self.model.phrases.write().unwrap();
//if let Some(index) = &*phrases.index_of(&*phrase.read().unwrap()) {
//self.model.phrase = index;
//}
//}
//}
//}
///// Focus the editor with the current phrase
//pub fn edit_phrase (&mut self) {
//if self.arrangement.selected.is_clip() && self.arrangement.phrase().is_none() {
//self.phrases.append_new(None, Some(self.next_color().into()));
//self.arrangement.phrase_put();
//}
//self.show_phrase();
//self.focus(ArrangerFocus::PhraseEditor);
//self.editor.entered = true;
//}
//pub fn next_color (&self) -> ItemColor {
//if let ArrangerSelection::Clip(track, scene) = self.arrangement.selected {
//let track_color = self.arrangement.model.tracks[track].color;
//let scene_color = self.arrangement.model.scenes[scene].color;
//track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25)
//} else {
//panic!("could not compute next color")
//}
//}
//pub fn phrase_del (&mut self) {
//let track_index = self.selected.track();
//let scene_index = self.selected.scene();
//track_index
//.and_then(|index|self.model.tracks.get_mut(index).map(|track|(index, track)))
//.map(|(track_index, _)|scene_index
//.and_then(|index|self.model.scenes.get_mut(index))
//.map(|scene|scene.clips[track_index] = None));
//}
//pub fn phrase_put (&mut self) {
//if let ArrangerSelection::Clip(track, scene) = self.selected {
//self.model.scenes[scene].clips[track] = self.selected_phrase().clone();
//}
//}
//pub fn selected_scene (&self) -> Option<&ArrangerScene> {
//self.selected.scene().map(|s|self.model.scenes.get(s)).flatten()
//}
//pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
//self.selected.scene().map(|s|self.model.scenes.get_mut(s)).flatten()
//}
//pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
//}