mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-02-01 00:36:40 +01:00
wip(p60,e90): impl macros
This commit is contained in:
parent
f4a4b08c8a
commit
9d4fcaa32b
17 changed files with 748 additions and 1083 deletions
|
|
@ -33,33 +33,49 @@ impl<T> Coordinate for T where T: Send + Sync + Copy
|
||||||
+ Into<f64>
|
+ Into<f64>
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct FixedAxis<T> {
|
pub struct FixedAxis<T> {
|
||||||
pub start: T,
|
pub start: T,
|
||||||
pub point: Option<T>,
|
pub point: Option<T>,
|
||||||
pub clamp: Option<T>,
|
pub clamp: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ScaledAxis<T> {
|
pub struct ScaledAxis<T> {
|
||||||
pub start: T,
|
pub start: T,
|
||||||
pub scale: T,
|
pub scale: T,
|
||||||
pub point: Option<T>,
|
pub point: Option<T>,
|
||||||
pub clamp: Option<T>,
|
pub clamp: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
||||||
impl $A<$T> {
|
impl $A<$T> {
|
||||||
|
#[inline] pub fn start_plus (&mut self, n: $T) -> $T {
|
||||||
|
(self.start + n).min(self.clamp.unwrap_or(<$T>::MAX))
|
||||||
|
}
|
||||||
#[inline] pub fn start_inc (&mut self, n: $T) -> $T {
|
#[inline] pub fn start_inc (&mut self, n: $T) -> $T {
|
||||||
self.start = (self.start + n).min(self.clamp.unwrap_or(<$T>::MAX));
|
self.start = self.start_plus(n);
|
||||||
self.start
|
self.start
|
||||||
}
|
}
|
||||||
|
#[inline] pub fn start_minus (&mut self, n: $T) -> $T {
|
||||||
|
self.start.saturating_sub(n)
|
||||||
|
}
|
||||||
#[inline] pub fn start_dec (&mut self, n: $T) -> $T {
|
#[inline] pub fn start_dec (&mut self, n: $T) -> $T {
|
||||||
self.start = self.start.saturating_sub(n);
|
self.start = self.start_minus(n);
|
||||||
self.start
|
self.start
|
||||||
}
|
}
|
||||||
|
#[inline] pub fn point_plus (&mut self, n: $T) -> Option<$T> {
|
||||||
|
self.point.map(|p|(p + n).min(self.clamp.unwrap_or(<$T>::MAX)))
|
||||||
|
}
|
||||||
#[inline] pub fn point_inc (&mut self, n: $T) -> Option<$T> {
|
#[inline] pub fn point_inc (&mut self, n: $T) -> Option<$T> {
|
||||||
self.point = self.point.map(|p|(p + n).min(self.clamp.unwrap_or(<$T>::MAX)));
|
self.point = self.point_plus(n);
|
||||||
self.point
|
self.point
|
||||||
}
|
}
|
||||||
|
#[inline] pub fn point_minus (&mut self, n: $T) -> Option<$T> {
|
||||||
|
self.point.map(|p|p.saturating_sub(n))
|
||||||
|
}
|
||||||
#[inline] pub fn point_dec (&mut self, n: $T) -> Option<$T> {
|
#[inline] pub fn point_dec (&mut self, n: $T) -> Option<$T> {
|
||||||
self.point = self.point.map(|p|p.saturating_sub(n));
|
self.point = self.point_minus(n);
|
||||||
self.point
|
self.point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -871,9 +887,17 @@ impl<E: Engine, A: Widget<Engine = E>, B: Widget<Engine = E>> Widget for Split<E
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A widget that tracks its render width and height
|
/// A widget that tracks its render width and height
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Measure<E: Engine>(PhantomData<E>, AtomicUsize, AtomicUsize);
|
pub struct Measure<E: Engine>(PhantomData<E>, AtomicUsize, AtomicUsize);
|
||||||
|
|
||||||
|
impl<E: Engine> std::fmt::Debug for Measure<E> {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("Measure")
|
||||||
|
.field("width", &self.0)
|
||||||
|
.field("height", &self.1)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<E: Engine> Measure<E> {
|
impl<E: Engine> Measure<E> {
|
||||||
pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) }
|
pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) }
|
||||||
pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) }
|
pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) }
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,16 @@ pub(crate) use std::fs::read_dir;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
tui_apis
|
tui_apps
|
||||||
tui_command
|
tui_command
|
||||||
tui_content
|
tui_content
|
||||||
tui_control
|
tui_control
|
||||||
|
tui_debug
|
||||||
tui_focus
|
tui_focus
|
||||||
tui_handle
|
tui_handle
|
||||||
tui_init
|
tui_init
|
||||||
tui_input
|
tui_input
|
||||||
|
tui_impls
|
||||||
tui_jack
|
tui_jack
|
||||||
tui_menu
|
tui_menu
|
||||||
tui_model
|
tui_model
|
||||||
|
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl PhrasesTui {
|
|
||||||
pub fn new (phrases: Vec<Arc<RwLock<Phrase>>>) -> Self {
|
|
||||||
Self {
|
|
||||||
scroll: 0,
|
|
||||||
phrase: 0,
|
|
||||||
mode: None,
|
|
||||||
focused: false,
|
|
||||||
entered: false,
|
|
||||||
phrases,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PhraseTui {
|
|
||||||
pub fn new () -> Self {
|
|
||||||
Self {
|
|
||||||
phrase: None,
|
|
||||||
note_len: 24,
|
|
||||||
notes_in: Arc::new(RwLock::new([false;128])),
|
|
||||||
notes_out: Arc::new(RwLock::new([false;128])),
|
|
||||||
keys: keys_vert(),
|
|
||||||
buffer: Default::default(),
|
|
||||||
focused: false,
|
|
||||||
entered: false,
|
|
||||||
mode: false,
|
|
||||||
now: Arc::new(0.into()),
|
|
||||||
size: Measure::new(),
|
|
||||||
//width: 0.into(),
|
|
||||||
//height: 0.into(),
|
|
||||||
note_axis: RwLock::new(FixedAxis {
|
|
||||||
start: 12,
|
|
||||||
point: Some(36),
|
|
||||||
clamp: Some(127)
|
|
||||||
}),
|
|
||||||
time_axis: RwLock::new(ScaledAxis {
|
|
||||||
start: 00,
|
|
||||||
point: Some(00),
|
|
||||||
clamp: Some(000),
|
|
||||||
scale: 24
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//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()
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//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 index_before (&self, index: usize) -> usize {
|
|
||||||
//index.overflowing_sub(1).0.min(self.len() - 1)
|
|
||||||
//}
|
|
||||||
//pub fn index_after (&self, index: usize) -> usize {
|
|
||||||
//(index + 1) % self.len()
|
|
||||||
//}
|
|
||||||
44
crates/tek_tui/src/tui_apps.rs
Normal file
44
crates/tek_tui/src/tui_apps.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Stores and displays time-related info.
|
||||||
|
pub struct TransportTui {
|
||||||
|
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub(crate) state: TransportModel,
|
||||||
|
pub(crate) size: Measure<Tui>,
|
||||||
|
pub(crate) cursor: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Root view for standalone `tek_sequencer`.
|
||||||
|
pub struct SequencerTui {
|
||||||
|
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub(crate) transport: TransportModel,
|
||||||
|
pub(crate) phrases: PhrasesModel,
|
||||||
|
pub(crate) player: PhrasePlayerModel,
|
||||||
|
pub(crate) editor: PhraseEditorModel,
|
||||||
|
pub(crate) size: Measure<Tui>,
|
||||||
|
pub(crate) cursor: (usize, usize),
|
||||||
|
pub(crate) split: u16,
|
||||||
|
pub(crate) entered: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Root view for standalone `tek_arranger`
|
||||||
|
pub struct ArrangerTui {
|
||||||
|
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub(crate) transport: TransportModel,
|
||||||
|
pub(crate) phrases: PhrasesModel,
|
||||||
|
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<Tui>,
|
||||||
|
pub(crate) note_buf: Vec<u8>,
|
||||||
|
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
|
pub(crate) cursor: (usize, usize),
|
||||||
|
pub(crate) menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
|
||||||
|
pub(crate) status_bar: Option<ArrangerStatus>,
|
||||||
|
pub(crate) history: Vec<ArrangerCommand>,
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ pub enum SequencerCommand {
|
||||||
|
|
||||||
impl<T> Command<T> for SequencerCommand
|
impl<T> Command<T> for SequencerCommand
|
||||||
where
|
where
|
||||||
T: PhrasesControl + PhraseControl + ClockApi + PlayheadApi
|
T: PhrasesControl + PhraseControl + PlayheadApi
|
||||||
+ FocusGrid<Item = SequencerFocus> + FocusEnter<Item = SequencerFocus>
|
+ FocusGrid<Item = SequencerFocus> + FocusEnter<Item = SequencerFocus>
|
||||||
{
|
{
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
|
|
@ -62,6 +62,7 @@ pub enum ArrangerCommand {
|
||||||
Undo,
|
Undo,
|
||||||
Redo,
|
Redo,
|
||||||
Clear,
|
Clear,
|
||||||
|
Color(ItemColor),
|
||||||
Clock(ClockCommand),
|
Clock(ClockCommand),
|
||||||
Playhead(PlayheadCommand),
|
Playhead(PlayheadCommand),
|
||||||
Scene(ArrangerSceneCommand),
|
Scene(ArrangerSceneCommand),
|
||||||
|
|
@ -74,12 +75,8 @@ pub enum ArrangerCommand {
|
||||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Command<T> for ArrangerCommand
|
impl Command<ArrangerTui> for ArrangerCommand {
|
||||||
where
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
T: ArrangerControl + HasPhrases + PhraseControl + ClockApi + PlayheadApi
|
|
||||||
+ FocusGrid<Item = ArrangerFocus> + FocusEnter<Item = ArrangerFocus>
|
|
||||||
{
|
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
||||||
use ArrangerCommand::*;
|
use ArrangerCommand::*;
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||||
|
|
@ -92,35 +89,33 @@ where
|
||||||
Playhead(cmd) => cmd.execute(state)?.map(Playhead),
|
Playhead(cmd) => cmd.execute(state)?.map(Playhead),
|
||||||
Zoom(zoom) => { todo!(); },
|
Zoom(zoom) => { todo!(); },
|
||||||
Select(selected) => {
|
Select(selected) => {
|
||||||
state.selected = selected;
|
*state.selected_mut() = selected;
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
EditPhrase(phrase) => {
|
EditPhrase(phrase) => {
|
||||||
state.editor.phrase = phrase.clone();
|
state.show_phrase(phrase);
|
||||||
state.focus(ArrangerFocus::PhraseEditor);
|
|
||||||
state.focus_enter();
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ArrangerControl> Command<T> for ArrangerSceneCommand {
|
impl Command<ArrangerTui> for ArrangerSceneCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
todo!();
|
todo!();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ArrangerControl> Command<T> for ArrangerTrackCommand {
|
impl Command<ArrangerTui> for ArrangerTrackCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
todo!();
|
todo!();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ArrangerControl> Command<T> for ArrangerClipCommand {
|
impl Command<ArrangerTui> for ArrangerClipCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
todo!();
|
todo!();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +136,7 @@ impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
||||||
Self::Rename(command) => command.execute(state)?.map(Self::Rename),
|
Self::Rename(command) => command.execute(state)?.map(Self::Rename),
|
||||||
Self::Length(command) => command.execute(state)?.map(Self::Length),
|
Self::Length(command) => command.execute(state)?.map(Self::Length),
|
||||||
Self::Select(phrase) => {
|
Self::Select(phrase) => {
|
||||||
*state.phrase_index_mut() = phrase;
|
state.set_phrase_index(phrase);
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -268,118 +263,34 @@ impl<T> Command<T> for PhraseCommand
|
||||||
where
|
where
|
||||||
T: PhraseControl + FocusEnter
|
T: PhraseControl + FocusEnter
|
||||||
{
|
{
|
||||||
//fn translate (self, state: &PhraseTui<E>) -> Self {
|
|
||||||
//use PhraseCommand::*;
|
|
||||||
//match self {
|
|
||||||
//GoUp => match state.entered { true => NoteCursorInc, false => NoteScrollInc, },
|
|
||||||
//GoDown => match state.entered { true => NoteCursorDec, false => NoteScrollDec, },
|
|
||||||
//GoLeft => match state.entered { true => TimeCursorDec, false => TimeScrollDec, },
|
|
||||||
//GoRight => match state.entered { true => TimeCursorInc, false => TimeScrollInc, },
|
|
||||||
//_ => self
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
ToggleDirection => {
|
ToggleDirection => { todo!() },
|
||||||
state.phrase_mode_mut() = !state.mode;
|
EnterEditMode => { state.focus_enter(); None },
|
||||||
|
ExitEditMode => { state.focus_exit(); None },
|
||||||
|
NoteAppend => {
|
||||||
|
if state.phrase_entered() {
|
||||||
|
state.put_note();
|
||||||
|
state.time_cursor_advance();
|
||||||
|
}
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
EnterEditMode => {
|
NoteSet => { if state.phrase_entered() { state.put_note(); } None },
|
||||||
state.focus_enter();
|
TimeCursorSet(time) => { state.time_axis().write().unwrap().point_set(time); None },
|
||||||
None
|
TimeScrollSet(time) => { state.time_axis().write().unwrap().start_set(time); None },
|
||||||
},
|
TimeZoomSet(zoom) => { state.time_axis().write().unwrap().scale_set(zoom); None },
|
||||||
ExitEditMode => {
|
NoteScrollSet(note) => { state.note_axis().write().unwrap().start_set(note); None },
|
||||||
state.focus_exit();
|
NoteLengthSet(time) => { *state.note_len_mut() = time; None },
|
||||||
None
|
NoteCursorSet(note) => {
|
||||||
},
|
let axis = state.note_axis().write().unwrap();
|
||||||
TimeZoomOut => {
|
axis.point_set(note);
|
||||||
let scale = state.time_axis().read().unwrap().scale;
|
|
||||||
state.time_axis().write().unwrap().scale = next_note_length(scale);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
TimeZoomIn => {
|
|
||||||
let scale = state.time_axis().read().unwrap().scale;
|
|
||||||
state.time_axis().write().unwrap().scale = prev_note_length(scale);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
TimeCursorDec => {
|
|
||||||
let scale = state.time_axis().read().unwrap().scale;
|
|
||||||
state.time_axis().write().unwrap().point_dec(scale);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
TimeCursorInc => {
|
|
||||||
let scale = state.time_axis().read().unwrap().scale;
|
|
||||||
state.time_axis().write().unwrap().point_inc(scale);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
TimeScrollDec => {
|
|
||||||
let scale = state.time_axis().read().unwrap().scale;
|
|
||||||
state.time_axis().write().unwrap().start_dec(scale);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
TimeScrollInc => {
|
|
||||||
let scale = state.time_axis().read().unwrap().scale;
|
|
||||||
state.time_axis().write().unwrap().start_inc(scale);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NoteCursorDec => {
|
|
||||||
let mut axis = state.note_axis().write().unwrap();
|
|
||||||
axis.point_inc(1);
|
|
||||||
if let Some(point) = axis.point {
|
if let Some(point) = axis.point {
|
||||||
if point > 73 { axis.point = Some(73); }
|
if point > 73 { axis.point = Some(73); }
|
||||||
}
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NoteCursorInc => {
|
|
||||||
let mut axis = state.note_axis().write().unwrap();
|
|
||||||
axis.point_dec(1);
|
|
||||||
if let Some(point) = axis.point {
|
|
||||||
if point < axis.start { axis.start = (point / 2) * 2; }
|
if point < axis.start { axis.start = (point / 2) * 2; }
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
NoteScrollDec => {
|
|
||||||
state.note_axis().write().unwrap().start_inc(1);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NoteScrollInc => {
|
|
||||||
state.note_axis().write().unwrap().start_dec(1);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NoteLengthDec => {
|
|
||||||
*state.note_len_mut() = prev_note_length(state.note_len());
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NoteLengthInc => {
|
|
||||||
*state.note_len_mut() = next_note_length(state.note_len());
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NotePageUp => {
|
|
||||||
let mut axis = state.note_axis().write().unwrap();
|
|
||||||
axis.start_dec(3);
|
|
||||||
axis.point_dec(3);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NotePageDown => {
|
|
||||||
let mut axis = state.note_axis().write().unwrap();
|
|
||||||
axis.start_inc(3);
|
|
||||||
axis.point_inc(3);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NoteAppend => if state.focus_entered() {
|
|
||||||
state.put();
|
|
||||||
state.time_cursor_advance();
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
NoteSet => if state.focus_entered() {
|
|
||||||
state.put();
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,29 @@ impl Content for SequencerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display mode of arranger
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum ArrangerMode {
|
||||||
|
/// Tracks are rows
|
||||||
|
Horizontal,
|
||||||
|
/// Tracks are columns
|
||||||
|
Vertical(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arranger display mode can be cycled
|
||||||
|
impl ArrangerMode {
|
||||||
|
/// Cycle arranger display mode
|
||||||
|
pub fn to_next (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Horizontal => Self::Vertical(1),
|
||||||
|
Self::Vertical(1) => Self::Vertical(2),
|
||||||
|
Self::Vertical(2) => Self::Vertical(2),
|
||||||
|
Self::Vertical(0) => Self::Horizontal,
|
||||||
|
Self::Vertical(_) => Self::Vertical(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Layout for standalone arranger app.
|
/// Layout for standalone arranger app.
|
||||||
impl Content for ArrangerTui {
|
impl Content for ArrangerTui {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
|
|
@ -59,7 +82,7 @@ impl Content for ArrangerTui {
|
||||||
let arranger_focused = self.arranger_focused();
|
let arranger_focused = self.arranger_focused();
|
||||||
Split::up(
|
Split::up(
|
||||||
1,
|
1,
|
||||||
widget(&TransportView(self)),
|
TransportView(self),
|
||||||
Split::down(
|
Split::down(
|
||||||
self.splits[0],
|
self.splits[0],
|
||||||
lay!(
|
lay!(
|
||||||
|
|
@ -87,8 +110,8 @@ impl Content for ArrangerTui {
|
||||||
),
|
),
|
||||||
Split::right(
|
Split::right(
|
||||||
self.splits[1],
|
self.splits[1],
|
||||||
widget(&PhrasesView(self)),
|
PhrasesView(self),
|
||||||
widget(&PhraseView(self)),
|
PhraseView(self),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,95 @@ use crate::*;
|
||||||
|
|
||||||
pub trait TransportControl: ClockApi {}
|
pub trait TransportControl: ClockApi {}
|
||||||
|
|
||||||
|
pub trait SequencerControl: TransportControl {}
|
||||||
|
|
||||||
|
pub trait ArrangerControl: TransportControl {
|
||||||
|
fn selected (&self) -> ArrangerSelection;
|
||||||
|
fn selected_mut (&mut self) -> &mut ArrangerSelection;
|
||||||
|
fn show_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>);
|
||||||
|
fn activate (&mut self);
|
||||||
|
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
|
||||||
|
fn toggle_loop (&mut self);
|
||||||
|
fn randomize_color (&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhrasesControl: HasPhrases {
|
||||||
|
fn phrase_index (&self) -> usize;
|
||||||
|
fn set_phrase_index (&self, index: usize);
|
||||||
|
fn phrases_mode (&self) -> &Option<PhrasesMode>;
|
||||||
|
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode>;
|
||||||
|
fn phrase_rename_begin (&mut self) {
|
||||||
|
let name = self.phrases()[self.phrase_index()].read().unwrap().name.clone();
|
||||||
|
*self.phrases_mode_mut() = Some(
|
||||||
|
PhrasesMode::Rename(self.phrase_index(), name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn phrase_length_begin (&mut self) {
|
||||||
|
let length = self.phrases()[self.phrase_index()].read().unwrap().length;
|
||||||
|
*self.phrases_mode_mut() = Some(
|
||||||
|
PhrasesMode::Length(self.phrase_index(), length, PhraseLengthFocus::Bar)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn new_phrase (name: Option<&str>, color: Option<ItemColorTriplet>) -> Arc<RwLock<Phrase>> {
|
||||||
|
Arc::new(RwLock::new(Phrase::new(
|
||||||
|
String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn index_of (&self, phrase: &Phrase) -> Option<usize> {
|
||||||
|
for i in 0..self.phrases().len() {
|
||||||
|
if *self.phrases()[i].read().unwrap() == *phrase { return Some(i) }
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
fn insert_dup (&mut self) {
|
||||||
|
let mut phrase = self.phrases()[self.phrase_index()].read().unwrap().duplicate();
|
||||||
|
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);
|
||||||
|
let index = self.phrase_index() + 1;
|
||||||
|
self.phrases_mut().insert(index, Arc::new(RwLock::new(phrase)));
|
||||||
|
self.set_phrase_index(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasPhrasesModel: HasPhrases {
|
||||||
|
fn phrases_model (&self) -> &PhrasesModel;
|
||||||
|
fn phrases_model_mut (&mut self) -> &mut PhrasesModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhraseControl {
|
||||||
|
fn phrase_entered (&self) -> bool;
|
||||||
|
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
||||||
|
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||||
|
fn note_len (&self) -> usize;
|
||||||
|
fn note_len_mut (&mut self) -> &mut usize;
|
||||||
|
fn put_note (&mut self);
|
||||||
|
fn time_cursor_advance (&self) {
|
||||||
|
let point = self.time_axis().read().unwrap().point;
|
||||||
|
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||||
|
let forward = |time|(time + self.note_len()) % length;
|
||||||
|
self.time_axis().write().unwrap().point = point.map(forward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TransportControl for TransportTui {}
|
impl TransportControl for TransportTui {}
|
||||||
|
|
||||||
impl TransportControl for SequencerTui {}
|
impl TransportControl for SequencerTui {}
|
||||||
|
|
||||||
impl TransportControl for ArrangerTui {}
|
impl TransportControl for ArrangerTui {}
|
||||||
|
|
||||||
pub trait SequencerControl {}
|
|
||||||
|
|
||||||
impl SequencerControl for SequencerTui {}
|
impl SequencerControl for SequencerTui {}
|
||||||
|
|
||||||
pub trait ArrangerControl {
|
|
||||||
fn selected (&self) -> ArrangerSelection;
|
|
||||||
fn show_phrase (&mut self);
|
|
||||||
fn activate (&mut self);
|
|
||||||
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
|
|
||||||
fn toggle_loop (&mut self);
|
|
||||||
fn randomize_color (&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerControl for ArrangerTui {
|
impl ArrangerControl for ArrangerTui {
|
||||||
fn selected (&self) -> ArrangerSelection {
|
fn selected (&self) -> ArrangerSelection {
|
||||||
self.selected
|
self.selected
|
||||||
}
|
}
|
||||||
fn show_phrase (&mut self) {
|
fn selected_mut (&mut self) -> &mut ArrangerSelection {
|
||||||
|
&mut self.selected
|
||||||
|
}
|
||||||
|
fn show_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||||
self.editor.show(self.selected_phrase().as_ref());
|
self.editor.show(self.selected_phrase().as_ref());
|
||||||
|
//state.editor.phrase = phrase.clone();
|
||||||
|
//state.focus(ArrangerFocus::PhraseEditor);
|
||||||
|
//state.focus_enter();
|
||||||
}
|
}
|
||||||
fn activate (&mut self) {
|
fn activate (&mut self) {
|
||||||
if let ArrangerSelection::Scene(s) = self.selected {
|
if let ArrangerSelection::Scene(s) = self.selected {
|
||||||
|
|
@ -73,99 +137,36 @@ impl ArrangerControl for ArrangerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PhrasesControl: HasPhrases {
|
impl HasPhrasesModel for ArrangerTui {
|
||||||
fn phrase_index (&self) -> usize;
|
fn phrases_model (&self) -> &PhrasesModel {
|
||||||
fn phrase_index_mut (&mut self) -> &mut usize;
|
&self.phrases
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode>;
|
|
||||||
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode>;
|
|
||||||
fn phrase_rename_begin (&mut self) {
|
|
||||||
let name = self.phrases()[self.phrase_index()].read().unwrap().name.clone();
|
|
||||||
*self.phrases_mode_mut() = Some(
|
|
||||||
PhrasesMode::Rename(self.phrase_index(), name)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
fn phrase_length_begin (&mut self) {
|
fn phrases_model_mut (&mut self) -> &mut PhrasesModel {
|
||||||
let length = self.phrases()[self.phrase_index()].read().unwrap().length;
|
&mut self.phrases
|
||||||
*self.phrases_mode_mut() = Some(
|
|
||||||
PhrasesMode::Length(self.phrase_index(), length, PhraseLengthFocus::Bar)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn new_phrase (name: Option<&str>, color: Option<ItemColorTriplet>) -> Arc<RwLock<Phrase>> {
|
|
||||||
Arc::new(RwLock::new(Phrase::new(
|
|
||||||
String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
fn index_of (&self, phrase: &Phrase) -> Option<usize> {
|
|
||||||
for i in 0..self.phrases().len() {
|
|
||||||
if *self.phrases()[i].read().unwrap() == *phrase { return Some(i) }
|
|
||||||
}
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
fn insert_dup (&mut self) {
|
|
||||||
let mut phrase = self.phrases()[self.phrase_index()].read().unwrap().duplicate();
|
|
||||||
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);
|
|
||||||
let index = self.phrase_index() + 1;
|
|
||||||
self.phrases_mut().insert(index, Arc::new(RwLock::new(phrase)));
|
|
||||||
*self.phrase_index_mut() += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhrasesControl for SequencerTui {
|
impl HasPhrasesModel for SequencerTui {
|
||||||
fn phrase_index (&self) -> usize {
|
fn phrases_model (&self) -> &PhrasesModel {
|
||||||
self.view_phrase
|
&self.phrases
|
||||||
}
|
}
|
||||||
fn phrase_index_mut (&mut self) -> &mut usize {
|
fn phrases_model_mut (&mut self) -> &mut PhrasesModel {
|
||||||
&mut self.view_phrase
|
&mut self.phrases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasPhrasesModel> PhrasesControl for T {
|
||||||
|
fn phrase_index (&self) -> usize {
|
||||||
|
self.phrases_model().phrase.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn set_phrase_index (&self, value: usize) {
|
||||||
|
self.phrases_model().phrase.store(value, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
||||||
&self.phrases_mode
|
&self.phrases_model().mode
|
||||||
}
|
}
|
||||||
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode> {
|
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode> {
|
||||||
&mut self.phrases_mode
|
&mut self.phrases_model_mut().mode
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhrasesControl for ArrangerTui {
|
|
||||||
fn phrase_index (&self) -> usize {
|
|
||||||
self.phrase
|
|
||||||
}
|
|
||||||
fn phrase_index_mut (&mut self) -> &mut usize {
|
|
||||||
&mut self.phrase
|
|
||||||
}
|
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
|
||||||
&self.phrases_mode
|
|
||||||
}
|
|
||||||
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode> {
|
|
||||||
&mut self.phrases_mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhrasesControl for PhrasesTui {
|
|
||||||
fn phrase_index (&self) -> usize {
|
|
||||||
self.phrase
|
|
||||||
}
|
|
||||||
fn phrase_index_mut (&mut self) -> &mut usize {
|
|
||||||
&mut self.phrase
|
|
||||||
}
|
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
|
||||||
&self.mode
|
|
||||||
}
|
|
||||||
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode> {
|
|
||||||
&mut self.mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PhraseControl {
|
|
||||||
fn phrase_entered (&self) -> bool;
|
|
||||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
|
||||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
|
||||||
fn note_len (&self) -> usize;
|
|
||||||
fn note_len_mut (&mut self) -> &mut usize;
|
|
||||||
fn time_cursor_advance (&self) {
|
|
||||||
let point = self.time_axis().read().unwrap().point;
|
|
||||||
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
|
||||||
let forward = |time|(time + self.note_len()) % length;
|
|
||||||
self.time_axis().write().unwrap().point = point.map(forward);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,6 +186,9 @@ impl PhraseControl for SequencerTui {
|
||||||
fn note_len_mut (&mut self) -> &mut usize {
|
fn note_len_mut (&mut self) -> &mut usize {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
fn put_note (&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseControl for ArrangerTui {
|
impl PhraseControl for ArrangerTui {
|
||||||
|
|
@ -203,4 +207,7 @@ impl PhraseControl for ArrangerTui {
|
||||||
fn note_len_mut (&mut self) -> &mut usize {
|
fn note_len_mut (&mut self) -> &mut usize {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
fn put_note (&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
33
crates/tek_tui/src/tui_debug.rs
Normal file
33
crates/tek_tui/src/tui_debug.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl std::fmt::Debug for TransportModel {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("TransportModel")
|
||||||
|
.field("playing", &self.playing)
|
||||||
|
.field("started", &self.started)
|
||||||
|
.field("current", &self.current)
|
||||||
|
.field("quant", &self.quant)
|
||||||
|
.field("sync", &self.sync)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for TransportTui {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("Measure")
|
||||||
|
.field("jack", &self.jack)
|
||||||
|
.field("state", &self.state)
|
||||||
|
.field("size", &self.size)
|
||||||
|
.field("cursor", &self.cursor)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for PhraseEditorModel {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("editor")
|
||||||
|
.field("note_axis", &self.time_axis)
|
||||||
|
.field("time_axis", &self.note_axis)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -217,7 +217,7 @@ impl FocusGrid for ArrangerTui {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Focused field of `PhraseLength`
|
/// Focused field of `PhraseLength`
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum PhraseLengthFocus {
|
pub enum PhraseLengthFocus {
|
||||||
/// Editing the number of bars
|
/// Editing the number of bars
|
||||||
Bar,
|
Bar,
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ impl Handle<Tui> for ArrangerTui {
|
||||||
ArrangerCommand::execute_with_state(self, i)
|
ArrangerCommand::execute_with_state(self, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Handle<Tui> for PhrasesTui {
|
impl Handle<Tui> for PhrasesModel {
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
PhrasesCommand::execute_with_state(self, from)
|
PhrasesCommand::execute_with_state(self, from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Handle<Tui> for PhraseTui {
|
impl Handle<Tui> for PhraseEditorModel {
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
PhraseCommand::execute_with_state(self, from)
|
PhraseCommand::execute_with_state(self, from)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
190
crates/tek_tui/src/tui_impls.rs
Normal file
190
crates/tek_tui/src/tui_impls.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
macro_rules! impl_jack_api {
|
||||||
|
($Struct:ident $(:: $field:ident)*) => {
|
||||||
|
impl JackApi for $Struct {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||||
|
&self$(.$field)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_clock_api {
|
||||||
|
($Struct:ident $(:: $field:ident)*) => {
|
||||||
|
impl ClockApi for $Struct {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
&self$(.$field)*.current.timebase
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
&self$(.$field)*.quant
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
&self$(.$field)*.sync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_playhead_api {
|
||||||
|
($Struct:ident $(:: $field:ident)*) => {
|
||||||
|
impl PlayheadApi for $Struct {
|
||||||
|
fn current (&self) -> &Instant {
|
||||||
|
&self$(.$field)*.current
|
||||||
|
}
|
||||||
|
fn transport (&self) -> &jack::Transport {
|
||||||
|
&self$(.$field)*.transport
|
||||||
|
}
|
||||||
|
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
&self$(.$field)*.playing
|
||||||
|
}
|
||||||
|
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
&self$(.$field)*.started
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_has_phrases {
|
||||||
|
($Struct:ident $(:: $field:ident)*) => {
|
||||||
|
impl HasPhrases for $Struct {
|
||||||
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&self$(.$field)*
|
||||||
|
}
|
||||||
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&mut self$(.$field)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_has_phrase {
|
||||||
|
($Struct:ident $(:: $field:ident)*) => {
|
||||||
|
impl HasPhrase for $Struct {
|
||||||
|
fn reset (&self) -> bool {
|
||||||
|
self$(.$field)*.reset
|
||||||
|
}
|
||||||
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
|
&mut self$(.$field)*.reset
|
||||||
|
}
|
||||||
|
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_midi_player {
|
||||||
|
($Struct:ident $(:: $field:ident)*) => {
|
||||||
|
impl MidiInputApi for $Struct {
|
||||||
|
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl MidiOutputApi for $Struct {
|
||||||
|
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_note (&mut self) -> &mut Vec<u8> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl MidiPlayerApi for $Struct {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_jack_api!(TransportTui::jack);
|
||||||
|
impl_jack_api!(SequencerTui::jack);
|
||||||
|
impl_jack_api!(ArrangerTui::jack);
|
||||||
|
impl_clock_api!(TransportTui::state);
|
||||||
|
impl_clock_api!(SequencerTui::transport);
|
||||||
|
impl_clock_api!(ArrangerTui::transport);
|
||||||
|
impl_clock_api!(PhrasePlayerModel::transport);
|
||||||
|
impl_clock_api!(ArrangerTrack);
|
||||||
|
impl_playhead_api!(TransportTui::state);
|
||||||
|
impl_playhead_api!(SequencerTui::transport);
|
||||||
|
impl_playhead_api!(ArrangerTui::transport);
|
||||||
|
impl_playhead_api!(PhrasePlayerModel::transport);
|
||||||
|
impl_playhead_api!(ArrangerTrack);
|
||||||
|
impl_has_phrases!(PhrasesModel::phrases);
|
||||||
|
impl_has_phrases!(SequencerTui::phrases);
|
||||||
|
impl_has_phrases!(ArrangerTui::phrases);
|
||||||
|
impl_has_phrase!(SequencerTui::player);
|
||||||
|
impl_has_phrase!(ArrangerTrack::player);
|
||||||
|
impl_has_phrase!(PhrasePlayerModel);
|
||||||
|
|
||||||
|
impl HasScenes<ArrangerScene> for ArrangerTui {
|
||||||
|
fn scenes (&self) -> &Vec<ArrangerScene> {
|
||||||
|
&self.scenes
|
||||||
|
}
|
||||||
|
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene> {
|
||||||
|
&mut self.scenes
|
||||||
|
}
|
||||||
|
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||||
|
-> Usually<&mut ArrangerScene>
|
||||||
|
{
|
||||||
|
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
|
||||||
|
let scene = ArrangerScene {
|
||||||
|
name: Arc::new(name.into()),
|
||||||
|
clips: vec![None;self.tracks().len()],
|
||||||
|
color: color.unwrap_or_else(||ItemColor::random()),
|
||||||
|
};
|
||||||
|
self.scenes_mut().push(scene);
|
||||||
|
let index = self.scenes().len() - 1;
|
||||||
|
Ok(&mut self.scenes_mut()[index])
|
||||||
|
}
|
||||||
|
fn selected_scene (&self) -> Option<&ArrangerScene> {
|
||||||
|
self.selected.scene().map(|s|self.scenes().get(s)).flatten()
|
||||||
|
}
|
||||||
|
fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
|
||||||
|
self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasTracks<ArrangerTrack> for ArrangerTui {
|
||||||
|
fn tracks (&self) -> &Vec<ArrangerTrack> {
|
||||||
|
&self.tracks
|
||||||
|
}
|
||||||
|
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
||||||
|
&mut self.tracks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,18 +5,10 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
current: Instant::default(),
|
cursor: (0, 0),
|
||||||
cursor: (0, 0),
|
state: TransportModel::from(jack.read().unwrap().transport()),
|
||||||
focus: TransportFocus::PlayPause,
|
jack: jack.clone(),
|
||||||
focused: false,
|
size: Measure::new(),
|
||||||
jack: jack.clone(),
|
|
||||||
metronome: false,
|
|
||||||
playing: RwLock::new(None),
|
|
||||||
quant: Quantize::default(),
|
|
||||||
size: Measure::new(),
|
|
||||||
started: RwLock::new(None),
|
|
||||||
sync: LaunchSync::default(),
|
|
||||||
transport: jack.read().unwrap().transport(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,33 +17,17 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
current: Instant::default(),
|
|
||||||
cursor: (0, 0),
|
cursor: (0, 0),
|
||||||
entered: false,
|
entered: false,
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
metronome: false,
|
|
||||||
midi_buf: vec![],
|
|
||||||
midi_inputs: vec![],
|
|
||||||
midi_outputs: vec![],
|
|
||||||
monitoring: true,
|
|
||||||
next_phrase: None,
|
|
||||||
note_buf: vec![],
|
|
||||||
notes_in: RwLock::new([false;128]).into(),
|
|
||||||
notes_out: RwLock::new([false;128]).into(),
|
|
||||||
overdub: true,
|
|
||||||
phrases: vec![],
|
phrases: vec![],
|
||||||
phrases_mode: None,
|
phrases_mode: None,
|
||||||
play_phrase: None,
|
|
||||||
playing: RwLock::new(None),
|
|
||||||
quant: Quantize::default(),
|
|
||||||
recording: true,
|
|
||||||
reset: false,
|
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
split: 20,
|
split: 20,
|
||||||
started: RwLock::new(None),
|
|
||||||
sync: LaunchSync::default(),
|
|
||||||
transport: jack.read().unwrap().transport(),
|
|
||||||
view_phrase: 0,
|
view_phrase: 0,
|
||||||
|
transport: TransportModel::from(jack.read().unwrap().transport()),
|
||||||
|
player: PhrasePlayerModel::default(),
|
||||||
|
editor: PhraseEditorModel::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,13 +37,11 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
color: Color::Rgb(28, 35, 25).into(),
|
color: Color::Rgb(28, 35, 25).into(),
|
||||||
current: Instant::default(),
|
|
||||||
cursor: (0, 0),
|
cursor: (0, 0),
|
||||||
entered: false,
|
entered: false,
|
||||||
history: vec![],
|
history: vec![],
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
menu_bar: None,
|
menu_bar: None,
|
||||||
metronome: false,
|
|
||||||
midi_buf: vec![],
|
midi_buf: vec![],
|
||||||
mode: ArrangerMode::Vertical(2),
|
mode: ArrangerMode::Vertical(2),
|
||||||
name: Arc::new(RwLock::new(String::new())),
|
name: Arc::new(RwLock::new(String::new())),
|
||||||
|
|
@ -75,17 +49,13 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
||||||
phrase: 0,
|
phrase: 0,
|
||||||
phrases: vec![],
|
phrases: vec![],
|
||||||
phrases_mode: None,
|
phrases_mode: None,
|
||||||
playing: None.into(),
|
|
||||||
quant: Default::default(),
|
|
||||||
scenes: vec![],
|
scenes: vec![],
|
||||||
selected: ArrangerSelection::Clip(0, 0),
|
selected: ArrangerSelection::Clip(0, 0),
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
splits: [20, 20],
|
splits: [20, 20],
|
||||||
started: None.into(),
|
|
||||||
status_bar: None,
|
status_bar: None,
|
||||||
sync: Default::default(),
|
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
transport: jack.read().unwrap().transport(),
|
transport: TransportModel::from(jack.read().unwrap().transport()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ use crate::*;
|
||||||
|
|
||||||
impl<T> InputToCommand<Tui, T> for TransportCommand
|
impl<T> InputToCommand<Tui, T> for TransportCommand
|
||||||
where
|
where
|
||||||
T: TransportControl + HasFocus<Item = TransportFocus>
|
T: TransportControl
|
||||||
|
+ HasFocus<Item = TransportFocus>
|
||||||
|
+ FocusEnter<Item = TransportFocus>
|
||||||
{
|
{
|
||||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||||
use KeyCode::Char;
|
use KeyCode::Char;
|
||||||
|
|
@ -53,7 +55,9 @@ where
|
||||||
impl<T> InputToCommand<Tui, T> for SequencerCommand
|
impl<T> InputToCommand<Tui, T> for SequencerCommand
|
||||||
where
|
where
|
||||||
T: SequencerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi
|
T: SequencerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi
|
||||||
+ HasFocus<Item = SequencerFocus> + FocusGrid<Item = SequencerFocus>
|
+ HasFocus<Item = SequencerFocus>
|
||||||
|
+ FocusGrid<Item = SequencerFocus>
|
||||||
|
+ FocusEnter<Item = SequencerFocus>
|
||||||
{
|
{
|
||||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||||
use FocusCommand::*;
|
use FocusCommand::*;
|
||||||
|
|
@ -91,12 +95,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> InputToCommand<Tui, T> for ArrangerCommand
|
impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
|
||||||
where
|
fn input_to_command (state: &ArrangerCommand, input: &TuiInput) -> Option<Self> {
|
||||||
T: ArrangerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi
|
|
||||||
+ HasFocus<Item = ArrangerFocus> + FocusGrid<Item = ArrangerFocus>
|
|
||||||
{
|
|
||||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
|
||||||
use FocusCommand::*;
|
use FocusCommand::*;
|
||||||
use ArrangerCommand::*;
|
use ArrangerCommand::*;
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
|
|
@ -240,7 +240,12 @@ where
|
||||||
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Char('c')) => Clip(Clip::RandomColor),
|
key!(KeyCode::Char('c')) => match state.selected() {
|
||||||
|
Select::Mix => Color(ItemColor::random()),
|
||||||
|
Select::Track(t) => Track(Track::Delete(t)),
|
||||||
|
Select::Scene(s) => Scene(Scene::Delete(s)),
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
},
|
||||||
|
|
||||||
key!(KeyCode::Char('s')) => match state.selected() {
|
key!(KeyCode::Char('s')) => match state.selected() {
|
||||||
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
|
@ -279,7 +284,7 @@ impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
||||||
key!(KeyCode::Down) => Some(Self::Select(0)),
|
key!(KeyCode::Down) => Some(Self::Select(0)),
|
||||||
key!(KeyCode::Char(',')) => {
|
key!(KeyCode::Char(',')) => {
|
||||||
if index > 1 {
|
if index > 1 {
|
||||||
*state.phrase_index_mut() -= 1;
|
state.set_phrase_index(state.phrase_index().saturating_sub(1));
|
||||||
Some(Self::Phrase(Phrase::Swap(index - 1, index)))
|
Some(Self::Phrase(Phrase::Swap(index - 1, index)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -287,7 +292,7 @@ impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('.')) => {
|
key!(KeyCode::Char('.')) => {
|
||||||
if index < count.saturating_sub(1) {
|
if index < count.saturating_sub(1) {
|
||||||
*state.phrase_index_mut() += 1;
|
state.set_phrase_index(state.phrase_index() + 1);
|
||||||
Some(Self::Phrase(Phrase::Swap(index + 1, index)))
|
Some(Self::Phrase(Phrase::Swap(index + 1, index)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -295,7 +300,7 @@ impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
||||||
},
|
},
|
||||||
key!(KeyCode::Delete) => {
|
key!(KeyCode::Delete) => {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
*state.phrase_index_mut() = index.min(count.saturating_sub(1));
|
state.set_phrase_index(index.min(count.saturating_sub(1)));
|
||||||
Some(Self::Phrase(Phrase::Delete(index)))
|
Some(Self::Phrase(Phrase::Delete(index)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -362,39 +367,47 @@ impl<T: PhrasesControl> InputToCommand<Tui, T> for PhraseRenameCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PhraseControl> InputToCommand<Tui, T> for PhraseCommand {
|
impl<T> InputToCommand<Tui, T> for PhraseCommand
|
||||||
|
where
|
||||||
|
T: PhraseControl + FocusEnter
|
||||||
|
{
|
||||||
fn input_to_command (state: &T, from: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &T, from: &TuiInput) -> Option<Self> {
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
Some(match from.event() {
|
Some(match from.event() {
|
||||||
key!(KeyCode::Char('`')) => ToggleDirection,
|
key!(KeyCode::Char('`')) => ToggleDirection,
|
||||||
key!(KeyCode::Enter) => EnterEditMode,
|
key!(KeyCode::Enter) => EnterEditMode,
|
||||||
key!(KeyCode::Esc) => ExitEditMode,
|
key!(KeyCode::Esc) => ExitEditMode,
|
||||||
key!(KeyCode::Char('[')) => NoteLengthSet(0),
|
|
||||||
key!(KeyCode::Char(']')) => NoteLengthSet(0),
|
|
||||||
key!(KeyCode::Char('a')) => NoteAppend,
|
key!(KeyCode::Char('a')) => NoteAppend,
|
||||||
key!(KeyCode::Char('s')) => NoteSet,
|
key!(KeyCode::Char('s')) => NoteSet,
|
||||||
key!(KeyCode::Char('-')) => TimeZoomSet(0),
|
key!(KeyCode::Char('[')) => NoteLengthSet(prev_note_length(state.note_len())),
|
||||||
key!(KeyCode::Char('_')) => TimeZoomSet(0),
|
key!(KeyCode::Char(']')) => NoteLengthSet(next_note_length(state.note_len())),
|
||||||
key!(KeyCode::Char('=')) => TimeZoomSet(0),
|
key!(KeyCode::Char('-')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
|
||||||
key!(KeyCode::Char('+')) => TimeZoomSet(0),
|
key!(KeyCode::Char('_')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
|
||||||
key!(KeyCode::PageUp) => NoteScrollSet(0),
|
key!(KeyCode::Char('=')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
|
||||||
key!(KeyCode::PageDown) => NoteScrollSet(0),
|
key!(KeyCode::Char('+')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
|
||||||
|
key!(KeyCode::Up) => match state.phrase_entered() {
|
||||||
key!(KeyCode::Up) => match state.phrase_entered() {
|
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(1)),
|
||||||
true => NoteCursorSet(0),
|
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(1)),
|
||||||
false => NoteScrollSet(0),
|
|
||||||
},
|
},
|
||||||
key!(KeyCode::Down) => match state.phrase_entered() {
|
key!(KeyCode::Down) => match state.phrase_entered() {
|
||||||
true => NoteCursorSet(0),
|
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(1)),
|
||||||
false => NoteScrollSet(0),
|
false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(1)),
|
||||||
},
|
},
|
||||||
key!(KeyCode::Left) => match state.phrase_entered() {
|
key!(KeyCode::PageUp) => match state.phrase_entered() {
|
||||||
true => TimeCursorSet(0),
|
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(3)),
|
||||||
false => TimeScrollSet(0),
|
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(3)),
|
||||||
},
|
},
|
||||||
key!(KeyCode::Right) => match state.phrase_entered() {
|
key!(KeyCode::PageDown) => match state.phrase_entered() {
|
||||||
true => TimeCursorSet(0),
|
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(3)),
|
||||||
false => TimeScrollSet(0),
|
false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(3)),
|
||||||
|
},
|
||||||
|
key!(KeyCode::Left) => match state.phrase_entered() {
|
||||||
|
true => TimeCursorSet(state.note_axis().write().unwrap().point_minus(1)),
|
||||||
|
false => TimeScrollSet(state.note_axis().write().unwrap().start_minus(1)),
|
||||||
|
},
|
||||||
|
key!(KeyCode::Right) => match state.phrase_entered() {
|
||||||
|
true => TimeCursorSet(state.note_axis().write().unwrap().point_plus(1)),
|
||||||
|
false => TimeScrollSet(state.note_axis().write().unwrap().start_plus(1)),
|
||||||
},
|
},
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,23 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl JackApi for TransportTui {
|
impl Audio for TransportTui {
|
||||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
&self.jack
|
PlayheadAudio(self).process(client, scope)
|
||||||
}
|
|
||||||
}
|
|
||||||
impl JackApi for SequencerTui {
|
|
||||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl JackApi for ArrangerTui {
|
|
||||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
|
||||||
&self.jack
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Audio for SequencerTui {
|
impl Audio for SequencerTui {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
self.model.process(client, scope)
|
if PlayheadAudio(self).process(client, scope) == Control::Quit {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
if PlayerAudio(self.player).process(client, scope) == Control::Quit {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Audio for ArrangerTui {
|
impl Audio for ArrangerTui {
|
||||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
if TracksAudio(
|
if TracksAudio(
|
||||||
|
|
@ -36,156 +33,20 @@ impl Audio for ArrangerTui {
|
||||||
let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
|
let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
|
||||||
if let Some(Some(Some(phrase))) = phrase {
|
if let Some(Some(Some(phrase))) = phrase {
|
||||||
if let Some(track) = self.tracks().get(t) {
|
if let Some(track) = self.tracks().get(t) {
|
||||||
if let Some((ref started_at, Some(ref playing))) = track.play_phrase {
|
if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
|
||||||
let phrase = phrase.read().unwrap();
|
let phrase = phrase.read().unwrap();
|
||||||
if *playing.read().unwrap() == *phrase {
|
if *playing.read().unwrap() == *phrase {
|
||||||
let pulse = self.current().pulse.get();
|
let pulse = self.current().pulse.get();
|
||||||
let start = started_at.pulse.get();
|
let start = started_at.pulse.get();
|
||||||
let now = (pulse - start) % phrase.length as f64;
|
let now = (pulse - start) % phrase.length as f64;
|
||||||
self.editor.now.set(now);
|
self.now.set(now);
|
||||||
return Control::Continue
|
return Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.editor.now.set(0.);
|
self.now.set(0.);
|
||||||
return Control::Continue
|
return Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClockApi for ArrangerTui {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
&self.current.timebase
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
&self.quant
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
&self.sync
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for ArrangerTui {
|
|
||||||
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 Audio for TransportTui {
|
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
PlayheadAudio(self).process(client, scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl ClockApi for TransportTui {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
&self.current.timebase
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
&self.quant
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
&self.sync
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for TransportTui {
|
|
||||||
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 MidiInputApi for SequencerTui {
|
|
||||||
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiOutputApi for SequencerTui {
|
|
||||||
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockApi for SequencerTui {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for SequencerTui {
|
|
||||||
fn current(&self) -> &Instant {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn transport(&self) -> &Transport {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayerApi for SequencerTui {}
|
|
||||||
|
|
|
||||||
|
|
@ -1,204 +1,165 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Stores and displays time-related info.
|
pub struct TransportModel {
|
||||||
pub struct TransportTui {
|
|
||||||
pub(crate) jack: Arc<RwLock<JackClient>>,
|
|
||||||
/// Playback state
|
/// Playback state
|
||||||
pub(crate) playing: RwLock<Option<TransportState>>,
|
|
||||||
/// Global sample and usec at which playback started
|
|
||||||
pub(crate) started: RwLock<Option<(usize, usize)>>,
|
|
||||||
/// Current moment in time
|
|
||||||
pub(crate) current: Instant,
|
|
||||||
/// Note quantization factor
|
|
||||||
pub(crate) quant: Quantize,
|
|
||||||
/// Launch quantization factor
|
|
||||||
pub(crate) sync: LaunchSync,
|
|
||||||
/// JACK transport handle.
|
|
||||||
pub(crate) transport: jack::Transport,
|
|
||||||
/// Enable metronome?
|
|
||||||
pub(crate) metronome: bool,
|
|
||||||
pub(crate) focus: TransportFocus,
|
|
||||||
pub(crate) focused: bool,
|
|
||||||
pub(crate) size: Measure<Tui>,
|
|
||||||
pub(crate) cursor: (usize, usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for TransportTui {
|
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
f.debug_struct("transport")
|
|
||||||
.field("jack", &self.jack)
|
|
||||||
.field("metronome", &self.metronome)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Root view for standalone `tek_sequencer`.
|
|
||||||
pub struct SequencerTui {
|
|
||||||
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) view_phrase: usize,
|
|
||||||
pub(crate) split: u16,
|
|
||||||
/// Start time and phrase being played
|
|
||||||
pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
|
||||||
/// Start time and next phrase
|
|
||||||
pub(crate) next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
|
||||||
/// Play input through output.
|
|
||||||
pub(crate) monitoring: bool,
|
|
||||||
/// Write input to sequence.
|
|
||||||
pub(crate) recording: bool,
|
|
||||||
/// Overdub input to sequence.
|
|
||||||
pub(crate) overdub: bool,
|
|
||||||
/// Send all notes off
|
|
||||||
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
|
||||||
/// Record from MIDI ports to current sequence.
|
|
||||||
pub(crate) midi_inputs: Vec<Port<MidiIn>>,
|
|
||||||
/// Play from current sequence to MIDI ports
|
|
||||||
pub(crate) midi_outputs: Vec<Port<MidiOut>>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
pub(crate) note_buf: Vec<u8>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
/// Notes currently held at input
|
|
||||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// Notes currently held at output
|
|
||||||
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
|
||||||
|
|
||||||
pub(crate) entered: bool,
|
|
||||||
pub(crate) cursor: (usize, usize),
|
|
||||||
pub(crate) size: Measure<Tui>,
|
|
||||||
|
|
||||||
/// Mode switch for phrase pool
|
|
||||||
pub(crate) phrases_mode: Option<PhrasesMode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrases for SequencerTui {
|
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&self.phrases
|
|
||||||
}
|
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&mut self.phrases
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrase for SequencerTui {
|
|
||||||
fn reset (&self) -> bool {
|
|
||||||
self.reset
|
|
||||||
}
|
|
||||||
fn reset_mut (&mut self) -> &mut bool {
|
|
||||||
&mut self.reset
|
|
||||||
}
|
|
||||||
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Root view for standalone `tek_arranger`
|
|
||||||
pub struct ArrangerTui {
|
|
||||||
pub(crate) jack: Arc<RwLock<JackClient>>,
|
|
||||||
pub(crate) transport: jack::Transport,
|
|
||||||
pub(crate) playing: RwLock<Option<TransportState>>,
|
pub(crate) playing: RwLock<Option<TransportState>>,
|
||||||
|
/// Global sample and usec at which playback started
|
||||||
pub(crate) started: RwLock<Option<(usize, usize)>>,
|
pub(crate) started: RwLock<Option<(usize, usize)>>,
|
||||||
|
/// Current moment in time
|
||||||
pub(crate) current: Instant,
|
pub(crate) current: Instant,
|
||||||
|
/// Note quantization factor
|
||||||
pub(crate) quant: Quantize,
|
pub(crate) quant: Quantize,
|
||||||
|
/// Launch quantization factor
|
||||||
pub(crate) sync: LaunchSync,
|
pub(crate) sync: LaunchSync,
|
||||||
|
/// JACK transport handle.
|
||||||
|
pub(crate) transport: jack::Transport,
|
||||||
|
/// Enable metronome?
|
||||||
pub(crate) metronome: bool,
|
pub(crate) metronome: bool,
|
||||||
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
/// Selected transport component
|
||||||
pub(crate) phrase: usize,
|
pub(crate) focus: TransportFocus,
|
||||||
pub(crate) tracks: Vec<ArrangerTrack>,
|
/// Whether the transport is focused
|
||||||
pub(crate) scenes: Vec<ArrangerScene>,
|
pub(crate) is_focused: bool,
|
||||||
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<Tui>,
|
|
||||||
pub(crate) note_buf: Vec<u8>,
|
|
||||||
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
pub(crate) cursor: (usize, usize),
|
|
||||||
pub(crate) menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
|
|
||||||
pub(crate) status_bar: Option<ArrangerStatus>,
|
|
||||||
pub(crate) history: Vec<ArrangerCommand>,
|
|
||||||
/// Mode switch for phrase pool
|
|
||||||
pub(crate) phrases_mode: Option<PhrasesMode>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasPhrases for ArrangerTui {
|
impl From<jack::Transport> for TransportModel {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
fn from (transport: jack::Transport) -> Self {
|
||||||
&self.phrases
|
Self {
|
||||||
}
|
current: Instant::default(),
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
metronome: false,
|
||||||
&mut self.phrases
|
playing: RwLock::new(None),
|
||||||
}
|
quant: Quantize::default(),
|
||||||
}
|
started: RwLock::new(None),
|
||||||
|
sync: LaunchSync::default(),
|
||||||
impl HasScenes<ArrangerScene> for ArrangerTui {
|
focus: TransportFocus::PlayPause,
|
||||||
fn scenes (&self) -> &Vec<ArrangerScene> {
|
is_focused: true,
|
||||||
&self.scenes
|
transport,
|
||||||
}
|
|
||||||
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene> {
|
|
||||||
&mut self.scenes
|
|
||||||
}
|
|
||||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
|
||||||
-> Usually<&mut ArrangerScene>
|
|
||||||
{
|
|
||||||
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
|
|
||||||
let scene = ArrangerScene {
|
|
||||||
name: Arc::new(name.into()),
|
|
||||||
clips: vec![None;self.tracks().len()],
|
|
||||||
color: color.unwrap_or_else(||ItemColor::random()),
|
|
||||||
};
|
|
||||||
self.scenes_mut().push(scene);
|
|
||||||
let index = self.scenes().len() - 1;
|
|
||||||
Ok(&mut self.scenes_mut()[index])
|
|
||||||
}
|
|
||||||
fn selected_scene (&self) -> Option<&ArrangerScene> {
|
|
||||||
self.selected.scene().map(|s|self.scenes().get(s)).flatten()
|
|
||||||
}
|
|
||||||
fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
|
|
||||||
self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Display mode of arranger
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub enum ArrangerMode {
|
|
||||||
/// Tracks are rows
|
|
||||||
Horizontal,
|
|
||||||
/// Tracks are columns
|
|
||||||
Vertical(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Arranger display mode can be cycled
|
|
||||||
impl ArrangerMode {
|
|
||||||
/// Cycle arranger display mode
|
|
||||||
pub fn to_next (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::Horizontal => Self::Vertical(1),
|
|
||||||
Self::Vertical(1) => Self::Vertical(2),
|
|
||||||
Self::Vertical(2) => Self::Vertical(2),
|
|
||||||
Self::Vertical(0) => Self::Horizontal,
|
|
||||||
Self::Vertical(_) => Self::Vertical(0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains state for playing a phrase
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PhrasePlayerModel {
|
||||||
|
/// Start time and phrase being played
|
||||||
|
pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Start time and next phrase
|
||||||
|
pub(crate) next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Play input through output.
|
||||||
|
pub(crate) monitoring: bool,
|
||||||
|
/// Write input to sequence.
|
||||||
|
pub(crate) recording: bool,
|
||||||
|
/// Overdub input to sequence.
|
||||||
|
pub(crate) overdub: bool,
|
||||||
|
/// Send all notes off
|
||||||
|
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
||||||
|
/// Record from MIDI ports to current sequence.
|
||||||
|
pub(crate) midi_ins: Vec<Port<MidiIn>>,
|
||||||
|
/// Play from current sequence to MIDI ports
|
||||||
|
pub(crate) midi_outs: Vec<Port<MidiOut>>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
pub(crate) note_buf: Vec<u8>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
|
/// Notes currently held at input
|
||||||
|
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// Notes currently held at output
|
||||||
|
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PhrasePlayerModel {
|
||||||
|
fn default () -> Self {
|
||||||
|
Self {
|
||||||
|
midi_ins: vec![],
|
||||||
|
midi_outs: vec![],
|
||||||
|
reset: true,
|
||||||
|
recording: false,
|
||||||
|
monitoring: false,
|
||||||
|
overdub: false,
|
||||||
|
play_phrase: None,
|
||||||
|
next_phrase: None,
|
||||||
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
|
note_buf: vec![],
|
||||||
|
midi_buf: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains state for viewing and editing a phrase
|
||||||
|
pub struct PhraseEditorModel {
|
||||||
|
/// Phrase being played
|
||||||
|
pub(crate) phrase: Option<Arc<RwLock<Phrase>>>,
|
||||||
|
/// Length of note that will be inserted, in pulses
|
||||||
|
pub(crate) note_len: usize,
|
||||||
|
/// The full piano keys are rendered to this buffer
|
||||||
|
pub(crate) keys: Buffer,
|
||||||
|
/// The full piano roll is rendered to this buffer
|
||||||
|
pub(crate) buffer: BigBuffer,
|
||||||
|
/// Cursor/scroll/zoom in pitch axis
|
||||||
|
pub(crate) note_axis: RwLock<FixedAxis<usize>>,
|
||||||
|
/// Cursor/scroll/zoom in time axis
|
||||||
|
pub(crate) time_axis: RwLock<ScaledAxis<usize>>,
|
||||||
|
/// Whether this widget is focused
|
||||||
|
pub(crate) focused: bool,
|
||||||
|
/// Whether note enter mode is enabled
|
||||||
|
pub(crate) entered: bool,
|
||||||
|
/// Display mode
|
||||||
|
pub(crate) mode: bool,
|
||||||
|
/// Notes currently held at input
|
||||||
|
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// Notes currently held at output
|
||||||
|
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// Current position of global playhead
|
||||||
|
pub(crate) now: Arc<Pulse>,
|
||||||
|
/// Width and height of notes area at last render
|
||||||
|
pub(crate) size: Measure<Tui>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PhraseEditorModel {
|
||||||
|
fn default () -> Self {
|
||||||
|
Self {
|
||||||
|
phrase: None,
|
||||||
|
note_len: 24,
|
||||||
|
keys: keys_vert(),
|
||||||
|
buffer: Default::default(),
|
||||||
|
focused: false,
|
||||||
|
entered: false,
|
||||||
|
mode: false,
|
||||||
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
|
now: Instant::default().into(),
|
||||||
|
size: Measure::new(),
|
||||||
|
note_axis: RwLock::new(FixedAxis {
|
||||||
|
start: 12,
|
||||||
|
point: Some(36),
|
||||||
|
clamp: Some(127)
|
||||||
|
}),
|
||||||
|
time_axis: RwLock::new(ScaledAxis {
|
||||||
|
start: 00,
|
||||||
|
point: Some(00),
|
||||||
|
clamp: Some(000),
|
||||||
|
scale: 24
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PhrasesModel {
|
||||||
|
/// Collection of phrases
|
||||||
|
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
|
/// Selected phrase
|
||||||
|
pub(crate) phrase: AtomicUsize,
|
||||||
|
/// Scroll offset
|
||||||
|
pub(crate) scroll: usize,
|
||||||
|
/// Mode switch
|
||||||
|
pub(crate) mode: Option<PhrasesMode>,
|
||||||
|
/// Whether this widget is focused
|
||||||
|
pub(crate) focused: bool,
|
||||||
|
/// Whether this widget is entered
|
||||||
|
pub(crate) entered: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct ArrangerScene {
|
pub struct ArrangerScene {
|
||||||
/// Name of scene
|
/// Name of scene
|
||||||
|
|
@ -221,34 +182,17 @@ impl ArrangerSceneApi for ArrangerScene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasTracks<ArrangerTrack> for ArrangerTui {
|
|
||||||
fn tracks (&self) -> &Vec<ArrangerTrack> {
|
|
||||||
&self.tracks
|
|
||||||
}
|
|
||||||
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
|
||||||
&mut self.tracks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
|
impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
|
||||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||||
-> Usually<&mut ArrangerTrack>
|
-> Usually<&mut ArrangerTrack>
|
||||||
{
|
{
|
||||||
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
||||||
let track = ArrangerTrack {
|
let track = ArrangerTrack {
|
||||||
width: name.len() + 2,
|
width: name.len() + 2,
|
||||||
name: Arc::new(name.into()),
|
name: Arc::new(name.into()),
|
||||||
color: color.unwrap_or_else(||ItemColor::random()),
|
color: color.unwrap_or_else(||ItemColor::random()),
|
||||||
midi_ins: vec![],
|
player: PhrasePlayerModel::default(),
|
||||||
midi_outs: vec![],
|
editor: PhraseEditorModel::default(),
|
||||||
reset: true,
|
|
||||||
recording: false,
|
|
||||||
monitoring: false,
|
|
||||||
overdub: false,
|
|
||||||
play_phrase: None,
|
|
||||||
next_phrase: None,
|
|
||||||
notes_in: RwLock::new([false;128]).into(),
|
|
||||||
notes_out: RwLock::new([false;128]).into(),
|
|
||||||
};
|
};
|
||||||
self.tracks_mut().push(track);
|
self.tracks_mut().push(track);
|
||||||
let index = self.tracks().len() - 1;
|
let index = self.tracks().len() - 1;
|
||||||
|
|
@ -265,41 +209,15 @@ impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ArrangerTrack {
|
pub struct ArrangerTrack {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
pub(crate) name: Arc<RwLock<String>>,
|
pub(crate) name: Arc<RwLock<String>>,
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
pub(crate) width: usize,
|
pub(crate) width: usize,
|
||||||
/// Identifying color of track
|
/// Identifying color of track
|
||||||
pub(crate) color: ItemColor,
|
pub(crate) color: ItemColor,
|
||||||
/// Start time and phrase being played
|
/// MIDI player state
|
||||||
pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
pub(crate) player: PhrasePlayerModel,
|
||||||
/// Start time and next phrase
|
/// MIDI editor state
|
||||||
pub(crate) next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
pub(crate) editor: PhraseEditorModel,
|
||||||
/// Play input through output.
|
|
||||||
pub(crate) monitoring: bool,
|
|
||||||
/// Write input to sequence.
|
|
||||||
pub(crate) recording: bool,
|
|
||||||
/// Overdub input to sequence.
|
|
||||||
pub(crate) overdub: bool,
|
|
||||||
/// Send all notes off
|
|
||||||
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
|
||||||
/// Record from MIDI ports to current sequence.
|
|
||||||
pub(crate) midi_ins: Vec<Port<MidiIn>>,
|
|
||||||
/// Play from current sequence to MIDI ports
|
|
||||||
pub(crate) midi_outs: Vec<Port<MidiOut>>,
|
|
||||||
/// Notes currently held at input
|
|
||||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// Notes currently held at output
|
|
||||||
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
|
||||||
///// MIDI output buffer
|
|
||||||
//midi_note: Vec<u8>,
|
|
||||||
///// MIDI output buffer
|
|
||||||
//midi_chunk: Vec<Vec<Vec<u8>>>,
|
|
||||||
/// Whether this widget is focused
|
|
||||||
pub(crate) focused: bool,
|
|
||||||
/// Width and height of notes area at last render
|
|
||||||
pub(crate) size: Measure<Tui>,
|
|
||||||
/// Mode switch
|
|
||||||
pub(crate) phrases_mode: Option<PhrasesMode>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArrangerTrackApi for ArrangerTrack {
|
impl ArrangerTrackApi for ArrangerTrack {
|
||||||
|
|
@ -321,126 +239,8 @@ impl ArrangerTrackApi for ArrangerTrack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasPhrase for ArrangerTrack {
|
|
||||||
fn reset (&self) -> bool {
|
|
||||||
self.reset
|
|
||||||
}
|
|
||||||
fn reset_mut (&mut self) -> &mut bool {
|
|
||||||
&mut self.reset
|
|
||||||
}
|
|
||||||
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiInputApi for ArrangerTrack {
|
|
||||||
fn midi_ins (&self) -> &Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_ins_mut (&self) -> &mut Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording (&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording_mut (&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring (&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring_mut (&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub (&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub_mut (&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiOutputApi for ArrangerTrack {
|
|
||||||
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockApi for ArrangerTrack {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for ArrangerTrack {
|
|
||||||
fn current (&self) -> &Instant {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn transport (&self) -> &Transport {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayerApi for ArrangerTrack {}
|
|
||||||
|
|
||||||
pub struct PhrasesTui {
|
|
||||||
/// Collection of phrases
|
|
||||||
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
|
||||||
/// Selected phrase
|
|
||||||
pub(crate) phrase: usize,
|
|
||||||
/// Scroll offset
|
|
||||||
pub(crate) scroll: usize,
|
|
||||||
/// Mode switch
|
|
||||||
pub(crate) mode: Option<PhrasesMode>,
|
|
||||||
/// Whether this widget is focused
|
|
||||||
pub(crate) focused: bool,
|
|
||||||
/// Whether this widget is entered
|
|
||||||
pub(crate) entered: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrases for PhrasesTui {
|
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&self.phrases
|
|
||||||
}
|
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&mut self.phrases
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modes for phrase pool
|
/// Modes for phrase pool
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum PhrasesMode {
|
pub enum PhrasesMode {
|
||||||
/// Renaming a pattern
|
/// Renaming a pattern
|
||||||
Rename(usize, String),
|
Rename(usize, String),
|
||||||
|
|
@ -484,32 +284,15 @@ impl PhraseLength {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains state for viewing and editing a phrase
|
impl PhrasesModel {
|
||||||
pub struct PhraseTui {
|
pub fn new (phrases: Vec<Arc<RwLock<Phrase>>>) -> Self {
|
||||||
/// Phrase being played
|
Self {
|
||||||
pub(crate) phrase: Option<Arc<RwLock<Phrase>>>,
|
scroll: 0,
|
||||||
/// Length of note that will be inserted, in pulses
|
phrase: 0,
|
||||||
pub(crate) note_len: usize,
|
mode: None,
|
||||||
/// The full piano keys are rendered to this buffer
|
focused: false,
|
||||||
pub(crate) keys: Buffer,
|
entered: false,
|
||||||
/// The full piano roll is rendered to this buffer
|
phrases,
|
||||||
pub(crate) buffer: BigBuffer,
|
}
|
||||||
/// Cursor/scroll/zoom in pitch axis
|
}
|
||||||
pub(crate) note_axis: RwLock<FixedAxis<usize>>,
|
|
||||||
/// Cursor/scroll/zoom in time axis
|
|
||||||
pub(crate) time_axis: RwLock<ScaledAxis<usize>>,
|
|
||||||
/// Whether this widget is focused
|
|
||||||
pub(crate) focused: bool,
|
|
||||||
/// Whether note enter mode is enabled
|
|
||||||
pub(crate) entered: bool,
|
|
||||||
/// Display mode
|
|
||||||
pub(crate) mode: bool,
|
|
||||||
/// Notes currently held at input
|
|
||||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// Notes currently held at output
|
|
||||||
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// Current position of global playhead
|
|
||||||
pub(crate) now: Arc<Pulse>,
|
|
||||||
/// Width and height of notes area at last render
|
|
||||||
pub(crate) size: Measure<Tui>
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@ use crate::*;
|
||||||
|
|
||||||
pub struct TransportView<'a, T: TransportViewState>(pub &'a T);
|
pub struct TransportView<'a, T: TransportViewState>(pub &'a T);
|
||||||
|
|
||||||
|
pub struct PhrasesView<'a, T: PhrasesViewState>(pub &'a T);
|
||||||
|
|
||||||
|
pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T);
|
||||||
|
|
||||||
pub trait TransportViewState: ClockApi + PlayheadApi + Send + Sync {
|
pub trait TransportViewState: ClockApi + PlayheadApi + Send + Sync {
|
||||||
fn transport_selected (&self) -> TransportFocus;
|
fn transport_selected (&self) -> TransportFocus;
|
||||||
fn transport_focused (&self) -> bool;
|
fn transport_focused (&self) -> bool;
|
||||||
|
|
@ -20,9 +24,35 @@ pub trait TransportViewState: ClockApi + PlayheadApi + Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ArrangerViewState {
|
||||||
|
fn arranger_focused (&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhrasesViewState: Send + Sync {
|
||||||
|
fn phrases_focused (&self) -> bool;
|
||||||
|
fn entered (&self) -> bool;
|
||||||
|
fn phrases (&self) -> Vec<Arc<RwLock<Phrase>>>;
|
||||||
|
fn phrase (&self) -> usize;
|
||||||
|
fn mode (&self) -> &Option<PhrasesMode>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhraseViewState: Send + Sync {
|
||||||
|
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
||||||
|
fn phrase_focused (&self) -> bool;
|
||||||
|
fn phrase_editor_size (&self) -> &Measure<Tui>;
|
||||||
|
fn entered (&self) -> bool;
|
||||||
|
fn keys (&self) -> &Buffer;
|
||||||
|
fn buffer (&self) -> &BigBuffer;
|
||||||
|
fn note_len (&self) -> usize;
|
||||||
|
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||||
|
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
||||||
|
fn now (&self) -> &Arc<Pulse>;
|
||||||
|
fn size (&self) -> &Measure<Tui>;
|
||||||
|
}
|
||||||
|
|
||||||
impl TransportViewState for TransportTui {
|
impl TransportViewState for TransportTui {
|
||||||
fn transport_selected (&self) -> TransportFocus {
|
fn transport_selected (&self) -> TransportFocus {
|
||||||
self.focus
|
self.state.focus
|
||||||
}
|
}
|
||||||
fn transport_focused (&self) -> bool {
|
fn transport_focused (&self) -> bool {
|
||||||
true
|
true
|
||||||
|
|
@ -34,7 +64,7 @@ impl TransportViewState for TransportTui {
|
||||||
|
|
||||||
impl TransportViewState for SequencerTui {
|
impl TransportViewState for SequencerTui {
|
||||||
fn transport_selected (&self) -> TransportFocus {
|
fn transport_selected (&self) -> TransportFocus {
|
||||||
self.focus
|
self.transport.focus
|
||||||
}
|
}
|
||||||
fn transport_focused (&self) -> bool {
|
fn transport_focused (&self) -> bool {
|
||||||
self.focused() == SequencerFocus::Transport
|
self.focused() == SequencerFocus::Transport
|
||||||
|
|
@ -46,7 +76,7 @@ impl TransportViewState for SequencerTui {
|
||||||
|
|
||||||
impl TransportViewState for ArrangerTui {
|
impl TransportViewState for ArrangerTui {
|
||||||
fn transport_selected (&self) -> TransportFocus {
|
fn transport_selected (&self) -> TransportFocus {
|
||||||
self.focus
|
self.transport.focus
|
||||||
}
|
}
|
||||||
fn transport_focused (&self) -> bool {
|
fn transport_focused (&self) -> bool {
|
||||||
self.focused() == ArrangerFocus::Transport
|
self.focused() == ArrangerFocus::Transport
|
||||||
|
|
@ -56,27 +86,13 @@ impl TransportViewState for ArrangerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ArrangerViewState {
|
|
||||||
fn arranger_focused (&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerViewState for ArrangerTui {
|
impl ArrangerViewState for ArrangerTui {
|
||||||
fn arranger_focused (&self) -> bool {
|
fn arranger_focused (&self) -> bool {
|
||||||
self.focused() == ArrangerFocus::Arranger
|
self.focused() == ArrangerFocus::Arranger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PhrasesView<'a, T: PhrasesViewState>(pub &'a T);
|
impl PhrasesViewState for PhrasesModel {
|
||||||
|
|
||||||
pub trait PhrasesViewState: Send + Sync {
|
|
||||||
fn phrases_focused (&self) -> bool;
|
|
||||||
fn entered (&self) -> bool;
|
|
||||||
fn phrases (&self) -> Vec<Arc<RwLock<Phrase>>>;
|
|
||||||
fn phrase (&self) -> usize;
|
|
||||||
fn mode (&self) -> &Option<PhrasesMode>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhrasesViewState for PhrasesTui {
|
|
||||||
fn phrases_focused (&self) -> bool {
|
fn phrases_focused (&self) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
@ -130,22 +146,7 @@ impl PhrasesViewState for ArrangerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T);
|
impl PhraseViewState for PhraseEditorModel {
|
||||||
|
|
||||||
pub trait PhraseViewState: Send + Sync {
|
|
||||||
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
|
||||||
fn phrase_focused (&self) -> bool;
|
|
||||||
fn phrase_editor_size (&self) -> &Measure<Tui>;
|
|
||||||
fn entered (&self) -> bool;
|
|
||||||
fn keys (&self) -> &Buffer;
|
|
||||||
fn buffer (&self) -> &BigBuffer;
|
|
||||||
fn note_len (&self) -> usize;
|
|
||||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
|
||||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
|
||||||
fn now (&self) -> &Arc<Pulse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhraseViewState for PhraseTui {
|
|
||||||
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||||
&self.phrase
|
&self.phrase
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +177,9 @@ impl PhraseViewState for PhraseTui {
|
||||||
fn now (&self) -> &Arc<Pulse> {
|
fn now (&self) -> &Arc<Pulse> {
|
||||||
&self.now
|
&self.now
|
||||||
}
|
}
|
||||||
|
fn size (&self) -> &Measure<Tui> {
|
||||||
|
&self.size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseViewState for SequencerTui {
|
impl PhraseViewState for SequencerTui {
|
||||||
|
|
@ -209,6 +213,9 @@ impl PhraseViewState for SequencerTui {
|
||||||
fn now (&self) -> &Arc<Pulse> {
|
fn now (&self) -> &Arc<Pulse> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
fn size (&self) -> &Measure<Tui> {
|
||||||
|
&self.size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseViewState for ArrangerTui {
|
impl PhraseViewState for ArrangerTui {
|
||||||
|
|
@ -242,6 +249,9 @@ impl PhraseViewState for ArrangerTui {
|
||||||
fn now (&self) -> &Arc<Pulse> {
|
fn now (&self) -> &Arc<Pulse> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
fn size (&self) -> &Measure<Tui> {
|
||||||
|
&self.size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> {
|
fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> {
|
||||||
|
|
@ -681,7 +691,7 @@ const NTH_OCTAVE: [&'static str; 11] = [
|
||||||
"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8",
|
"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl PhraseTui {
|
impl PhraseEditorModel {
|
||||||
pub fn put (&mut self) {
|
pub fn put (&mut self) {
|
||||||
if let (Some(phrase), Some(time), Some(note)) = (
|
if let (Some(phrase), Some(time), Some(note)) = (
|
||||||
&self.phrase,
|
&self.phrase,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ impl Widget for TransportTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for PhrasesTui {
|
impl Widget for PhrasesModel {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
PhrasesView(self).layout(to)
|
PhrasesView(self).layout(to)
|
||||||
|
|
@ -20,7 +20,7 @@ impl Widget for PhrasesTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for PhraseTui {
|
impl Widget for PhraseEditorModel {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
PhraseView(self).layout(to)
|
PhraseView(self).layout(to)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue