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