mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +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>
|
||||
{}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FixedAxis<T> {
|
||||
pub start: T,
|
||||
pub point: Option<T>,
|
||||
pub clamp: Option<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScaledAxis<T> {
|
||||
pub start: T,
|
||||
pub scale: T,
|
||||
pub point: Option<T>,
|
||||
pub clamp: Option<T>,
|
||||
}
|
||||
|
||||
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
||||
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 {
|
||||
self.start = (self.start + n).min(self.clamp.unwrap_or(<$T>::MAX));
|
||||
self.start = self.start_plus(n);
|
||||
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 {
|
||||
self.start = self.start.saturating_sub(n);
|
||||
self.start = self.start_minus(n);
|
||||
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> {
|
||||
self.point = self.point.map(|p|(p + n).min(self.clamp.unwrap_or(<$T>::MAX)));
|
||||
self.point = self.point_plus(n);
|
||||
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> {
|
||||
self.point = self.point.map(|p|p.saturating_sub(n));
|
||||
self.point = self.point_minus(n);
|
||||
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
|
||||
#[derive(Debug)]
|
||||
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> {
|
||||
pub fn w (&self) -> usize { self.1.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;
|
||||
|
||||
submod! {
|
||||
tui_apis
|
||||
tui_apps
|
||||
tui_command
|
||||
tui_content
|
||||
tui_control
|
||||
tui_debug
|
||||
tui_focus
|
||||
tui_handle
|
||||
tui_init
|
||||
tui_input
|
||||
tui_impls
|
||||
tui_jack
|
||||
tui_menu
|
||||
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
|
||||
where
|
||||
T: PhrasesControl + PhraseControl + ClockApi + PlayheadApi
|
||||
T: PhrasesControl + PhraseControl + PlayheadApi
|
||||
+ FocusGrid<Item = SequencerFocus> + FocusEnter<Item = SequencerFocus>
|
||||
{
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
|
|
@ -62,6 +62,7 @@ pub enum ArrangerCommand {
|
|||
Undo,
|
||||
Redo,
|
||||
Clear,
|
||||
Color(ItemColor),
|
||||
Clock(ClockCommand),
|
||||
Playhead(PlayheadCommand),
|
||||
Scene(ArrangerSceneCommand),
|
||||
|
|
@ -74,12 +75,8 @@ pub enum ArrangerCommand {
|
|||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||
}
|
||||
|
||||
impl<T> Command<T> for ArrangerCommand
|
||||
where
|
||||
T: ArrangerControl + HasPhrases + PhraseControl + ClockApi + PlayheadApi
|
||||
+ FocusGrid<Item = ArrangerFocus> + FocusEnter<Item = ArrangerFocus>
|
||||
{
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
impl Command<ArrangerTui> for ArrangerCommand {
|
||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
use ArrangerCommand::*;
|
||||
Ok(match self {
|
||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||
|
|
@ -92,35 +89,33 @@ where
|
|||
Playhead(cmd) => cmd.execute(state)?.map(Playhead),
|
||||
Zoom(zoom) => { todo!(); },
|
||||
Select(selected) => {
|
||||
state.selected = selected;
|
||||
*state.selected_mut() = selected;
|
||||
None
|
||||
},
|
||||
EditPhrase(phrase) => {
|
||||
state.editor.phrase = phrase.clone();
|
||||
state.focus(ArrangerFocus::PhraseEditor);
|
||||
state.focus_enter();
|
||||
state.show_phrase(phrase);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ArrangerControl> Command<T> for ArrangerSceneCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
impl Command<ArrangerTui> for ArrangerSceneCommand {
|
||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ArrangerControl> Command<T> for ArrangerTrackCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
impl Command<ArrangerTui> for ArrangerTrackCommand {
|
||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
todo!();
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ArrangerControl> Command<T> for ArrangerClipCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
impl Command<ArrangerTui> for ArrangerClipCommand {
|
||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
todo!();
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -141,7 +136,7 @@ impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
|||
Self::Rename(command) => command.execute(state)?.map(Self::Rename),
|
||||
Self::Length(command) => command.execute(state)?.map(Self::Length),
|
||||
Self::Select(phrase) => {
|
||||
*state.phrase_index_mut() = phrase;
|
||||
state.set_phrase_index(phrase);
|
||||
None
|
||||
},
|
||||
})
|
||||
|
|
@ -268,118 +263,34 @@ impl<T> Command<T> for PhraseCommand
|
|||
where
|
||||
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> {
|
||||
use PhraseCommand::*;
|
||||
Ok(match self {
|
||||
ToggleDirection => {
|
||||
state.phrase_mode_mut() = !state.mode;
|
||||
ToggleDirection => { todo!() },
|
||||
EnterEditMode => { state.focus_enter(); None },
|
||||
ExitEditMode => { state.focus_exit(); None },
|
||||
NoteAppend => {
|
||||
if state.phrase_entered() {
|
||||
state.put_note();
|
||||
state.time_cursor_advance();
|
||||
}
|
||||
None
|
||||
},
|
||||
EnterEditMode => {
|
||||
state.focus_enter();
|
||||
None
|
||||
},
|
||||
ExitEditMode => {
|
||||
state.focus_exit();
|
||||
None
|
||||
},
|
||||
TimeZoomOut => {
|
||||
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);
|
||||
NoteSet => { if state.phrase_entered() { state.put_note(); } None },
|
||||
TimeCursorSet(time) => { state.time_axis().write().unwrap().point_set(time); None },
|
||||
TimeScrollSet(time) => { state.time_axis().write().unwrap().start_set(time); None },
|
||||
TimeZoomSet(zoom) => { state.time_axis().write().unwrap().scale_set(zoom); None },
|
||||
NoteScrollSet(note) => { state.note_axis().write().unwrap().start_set(note); None },
|
||||
NoteLengthSet(time) => { *state.note_len_mut() = time; None },
|
||||
NoteCursorSet(note) => {
|
||||
let axis = state.note_axis().write().unwrap();
|
||||
axis.point_set(note);
|
||||
if let Some(point) = axis.point {
|
||||
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; }
|
||||
}
|
||||
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!()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
impl Content for ArrangerTui {
|
||||
type Engine = Tui;
|
||||
|
|
@ -59,7 +82,7 @@ impl Content for ArrangerTui {
|
|||
let arranger_focused = self.arranger_focused();
|
||||
Split::up(
|
||||
1,
|
||||
widget(&TransportView(self)),
|
||||
TransportView(self),
|
||||
Split::down(
|
||||
self.splits[0],
|
||||
lay!(
|
||||
|
|
@ -87,8 +110,8 @@ impl Content for ArrangerTui {
|
|||
),
|
||||
Split::right(
|
||||
self.splits[1],
|
||||
widget(&PhrasesView(self)),
|
||||
widget(&PhraseView(self)),
|
||||
PhrasesView(self),
|
||||
PhraseView(self),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,31 +2,95 @@ use crate::*;
|
|||
|
||||
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 SequencerTui {}
|
||||
|
||||
impl TransportControl for ArrangerTui {}
|
||||
|
||||
pub trait SequencerControl {}
|
||||
|
||||
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 {
|
||||
fn selected (&self) -> ArrangerSelection {
|
||||
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());
|
||||
//state.editor.phrase = phrase.clone();
|
||||
//state.focus(ArrangerFocus::PhraseEditor);
|
||||
//state.focus_enter();
|
||||
}
|
||||
fn activate (&mut self) {
|
||||
if let ArrangerSelection::Scene(s) = self.selected {
|
||||
|
|
@ -73,99 +137,36 @@ impl ArrangerControl for ArrangerTui {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait PhrasesControl: HasPhrases {
|
||||
fn phrase_index (&self) -> usize;
|
||||
fn phrase_index_mut (&mut self) -> &mut 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)
|
||||
)
|
||||
impl HasPhrasesModel for ArrangerTui {
|
||||
fn phrases_model (&self) -> &PhrasesModel {
|
||||
&self.phrases
|
||||
}
|
||||
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.phrase_index_mut() += 1;
|
||||
fn phrases_model_mut (&mut self) -> &mut PhrasesModel {
|
||||
&mut self.phrases
|
||||
}
|
||||
}
|
||||
|
||||
impl PhrasesControl for SequencerTui {
|
||||
fn phrase_index (&self) -> usize {
|
||||
self.view_phrase
|
||||
impl HasPhrasesModel for SequencerTui {
|
||||
fn phrases_model (&self) -> &PhrasesModel {
|
||||
&self.phrases
|
||||
}
|
||||
fn phrase_index_mut (&mut self) -> &mut usize {
|
||||
&mut self.view_phrase
|
||||
fn phrases_model_mut (&mut self) -> &mut PhrasesModel {
|
||||
&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> {
|
||||
&self.phrases_mode
|
||||
&self.phrases_model().mode
|
||||
}
|
||||
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode> {
|
||||
&mut self.phrases_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);
|
||||
&mut self.phrases_model_mut().mode
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +186,9 @@ impl PhraseControl for SequencerTui {
|
|||
fn note_len_mut (&mut self) -> &mut usize {
|
||||
todo!()
|
||||
}
|
||||
fn put_note (&mut self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl PhraseControl for ArrangerTui {
|
||||
|
|
@ -203,4 +207,7 @@ impl PhraseControl for ArrangerTui {
|
|||
fn note_len_mut (&mut self) -> &mut usize {
|
||||
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`
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum PhraseLengthFocus {
|
||||
/// Editing the number of bars
|
||||
Bar,
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ impl Handle<Tui> for ArrangerTui {
|
|||
ArrangerCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for PhrasesTui {
|
||||
impl Handle<Tui> for PhrasesModel {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
PhrasesCommand::execute_with_state(self, from)
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for PhraseTui {
|
||||
impl Handle<Tui> for PhraseEditorModel {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
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>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
current: Instant::default(),
|
||||
cursor: (0, 0),
|
||||
focus: TransportFocus::PlayPause,
|
||||
focused: false,
|
||||
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(),
|
||||
cursor: (0, 0),
|
||||
state: TransportModel::from(jack.read().unwrap().transport()),
|
||||
jack: jack.clone(),
|
||||
size: Measure::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -25,33 +17,17 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
|||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
current: Instant::default(),
|
||||
cursor: (0, 0),
|
||||
entered: false,
|
||||
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_mode: None,
|
||||
play_phrase: None,
|
||||
playing: RwLock::new(None),
|
||||
quant: Quantize::default(),
|
||||
recording: true,
|
||||
reset: false,
|
||||
size: Measure::new(),
|
||||
split: 20,
|
||||
started: RwLock::new(None),
|
||||
sync: LaunchSync::default(),
|
||||
transport: jack.read().unwrap().transport(),
|
||||
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> {
|
||||
Ok(Self {
|
||||
color: Color::Rgb(28, 35, 25).into(),
|
||||
current: Instant::default(),
|
||||
cursor: (0, 0),
|
||||
entered: false,
|
||||
history: vec![],
|
||||
jack: jack.clone(),
|
||||
menu_bar: None,
|
||||
metronome: false,
|
||||
midi_buf: vec![],
|
||||
mode: ArrangerMode::Vertical(2),
|
||||
name: Arc::new(RwLock::new(String::new())),
|
||||
|
|
@ -75,17 +49,13 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
|||
phrase: 0,
|
||||
phrases: vec![],
|
||||
phrases_mode: None,
|
||||
playing: None.into(),
|
||||
quant: Default::default(),
|
||||
scenes: vec![],
|
||||
selected: ArrangerSelection::Clip(0, 0),
|
||||
size: Measure::new(),
|
||||
splits: [20, 20],
|
||||
started: None.into(),
|
||||
status_bar: None,
|
||||
sync: Default::default(),
|
||||
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
|
||||
where
|
||||
T: TransportControl + HasFocus<Item = TransportFocus>
|
||||
T: TransportControl
|
||||
+ HasFocus<Item = TransportFocus>
|
||||
+ FocusEnter<Item = TransportFocus>
|
||||
{
|
||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||
use KeyCode::Char;
|
||||
|
|
@ -53,7 +55,9 @@ where
|
|||
impl<T> InputToCommand<Tui, T> for SequencerCommand
|
||||
where
|
||||
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> {
|
||||
use FocusCommand::*;
|
||||
|
|
@ -91,12 +95,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> InputToCommand<Tui, T> for ArrangerCommand
|
||||
where
|
||||
T: ArrangerControl + TransportControl + PhrasesControl + PhraseControl + PlayheadApi
|
||||
+ HasFocus<Item = ArrangerFocus> + FocusGrid<Item = ArrangerFocus>
|
||||
{
|
||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||
impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
|
||||
fn input_to_command (state: &ArrangerCommand, input: &TuiInput) -> Option<Self> {
|
||||
use FocusCommand::*;
|
||||
use ArrangerCommand::*;
|
||||
Some(match input.event() {
|
||||
|
|
@ -240,7 +240,12 @@ where
|
|||
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() {
|
||||
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::Char(',')) => {
|
||||
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)))
|
||||
} else {
|
||||
None
|
||||
|
|
@ -287,7 +292,7 @@ impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
|||
},
|
||||
key!(KeyCode::Char('.')) => {
|
||||
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)))
|
||||
} else {
|
||||
None
|
||||
|
|
@ -295,7 +300,7 @@ impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
|||
},
|
||||
key!(KeyCode::Delete) => {
|
||||
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)))
|
||||
} else {
|
||||
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> {
|
||||
use PhraseCommand::*;
|
||||
Some(match from.event() {
|
||||
key!(KeyCode::Char('`')) => ToggleDirection,
|
||||
key!(KeyCode::Enter) => EnterEditMode,
|
||||
key!(KeyCode::Esc) => ExitEditMode,
|
||||
key!(KeyCode::Char('[')) => NoteLengthSet(0),
|
||||
key!(KeyCode::Char(']')) => NoteLengthSet(0),
|
||||
key!(KeyCode::Char('a')) => NoteAppend,
|
||||
key!(KeyCode::Char('s')) => NoteSet,
|
||||
key!(KeyCode::Char('-')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('_')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('=')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('+')) => TimeZoomSet(0),
|
||||
key!(KeyCode::PageUp) => NoteScrollSet(0),
|
||||
key!(KeyCode::PageDown) => NoteScrollSet(0),
|
||||
|
||||
key!(KeyCode::Up) => match state.phrase_entered() {
|
||||
true => NoteCursorSet(0),
|
||||
false => NoteScrollSet(0),
|
||||
key!(KeyCode::Char('[')) => NoteLengthSet(prev_note_length(state.note_len())),
|
||||
key!(KeyCode::Char(']')) => NoteLengthSet(next_note_length(state.note_len())),
|
||||
key!(KeyCode::Char('-')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(KeyCode::Char('_')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(KeyCode::Char('=')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(KeyCode::Char('+')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(KeyCode::Up) => match state.phrase_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(1)),
|
||||
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(1)),
|
||||
},
|
||||
key!(KeyCode::Down) => match state.phrase_entered() {
|
||||
true => NoteCursorSet(0),
|
||||
false => NoteScrollSet(0),
|
||||
key!(KeyCode::Down) => match state.phrase_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(1)),
|
||||
false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(1)),
|
||||
},
|
||||
key!(KeyCode::Left) => match state.phrase_entered() {
|
||||
true => TimeCursorSet(0),
|
||||
false => TimeScrollSet(0),
|
||||
key!(KeyCode::PageUp) => match state.phrase_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(3)),
|
||||
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(3)),
|
||||
},
|
||||
key!(KeyCode::Right) => match state.phrase_entered() {
|
||||
true => TimeCursorSet(0),
|
||||
false => TimeScrollSet(0),
|
||||
key!(KeyCode::PageDown) => match state.phrase_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(3)),
|
||||
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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,26 +1,23 @@
|
|||
use crate::*;
|
||||
|
||||
impl JackApi for TransportTui {
|
||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||
&self.jack
|
||||
}
|
||||
}
|
||||
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 TransportTui {
|
||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
PlayheadAudio(self).process(client, scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio for SequencerTui {
|
||||
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 {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
if TracksAudio(
|
||||
|
|
@ -36,156 +33,20 @@ impl Audio for ArrangerTui {
|
|||
let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
|
||||
if let Some(Some(Some(phrase))) = phrase {
|
||||
if let Some(track) = self.tracks().get(t) {
|
||||
if let Some((ref started_at, Some(ref playing))) = track.play_phrase {
|
||||
if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
|
||||
let phrase = phrase.read().unwrap();
|
||||
if *playing.read().unwrap() == *phrase {
|
||||
let pulse = self.current().pulse.get();
|
||||
let start = started_at.pulse.get();
|
||||
let now = (pulse - start) % phrase.length as f64;
|
||||
self.editor.now.set(now);
|
||||
self.now.set(now);
|
||||
return Control::Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.editor.now.set(0.);
|
||||
self.now.set(0.);
|
||||
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::*;
|
||||
|
||||
/// Stores and displays time-related info.
|
||||
pub struct TransportTui {
|
||||
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||
pub struct TransportModel {
|
||||
/// 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>>,
|
||||
/// 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) phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||
pub(crate) phrase: usize,
|
||||
pub(crate) tracks: Vec<ArrangerTrack>,
|
||||
pub(crate) scenes: Vec<ArrangerScene>,
|
||||
pub(crate) name: Arc<RwLock<String>>,
|
||||
pub(crate) splits: [u16;2],
|
||||
pub(crate) selected: ArrangerSelection,
|
||||
pub(crate) mode: ArrangerMode,
|
||||
pub(crate) color: ItemColor,
|
||||
pub(crate) entered: bool,
|
||||
pub(crate) size: Measure<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>,
|
||||
/// Selected transport component
|
||||
pub(crate) focus: TransportFocus,
|
||||
/// Whether the transport is focused
|
||||
pub(crate) is_focused: bool,
|
||||
}
|
||||
|
||||
impl HasPhrases for ArrangerTui {
|
||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||
&self.phrases
|
||||
}
|
||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
||||
&mut self.phrases
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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),
|
||||
impl From<jack::Transport> for TransportModel {
|
||||
fn from (transport: jack::Transport) -> Self {
|
||||
Self {
|
||||
current: Instant::default(),
|
||||
metronome: false,
|
||||
playing: RwLock::new(None),
|
||||
quant: Quantize::default(),
|
||||
started: RwLock::new(None),
|
||||
sync: LaunchSync::default(),
|
||||
focus: TransportFocus::PlayPause,
|
||||
is_focused: true,
|
||||
transport,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub struct ArrangerScene {
|
||||
/// 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 {
|
||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||
-> Usually<&mut ArrangerTrack>
|
||||
{
|
||||
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
||||
let track = ArrangerTrack {
|
||||
width: name.len() + 2,
|
||||
name: Arc::new(name.into()),
|
||||
color: color.unwrap_or_else(||ItemColor::random()),
|
||||
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(),
|
||||
width: name.len() + 2,
|
||||
name: Arc::new(name.into()),
|
||||
color: color.unwrap_or_else(||ItemColor::random()),
|
||||
player: PhrasePlayerModel::default(),
|
||||
editor: PhraseEditorModel::default(),
|
||||
};
|
||||
self.tracks_mut().push(track);
|
||||
let index = self.tracks().len() - 1;
|
||||
|
|
@ -265,41 +209,15 @@ impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
|
|||
#[derive(Debug)]
|
||||
pub struct ArrangerTrack {
|
||||
/// Name of track
|
||||
pub(crate) name: Arc<RwLock<String>>,
|
||||
pub(crate) name: Arc<RwLock<String>>,
|
||||
/// Preferred width of track column
|
||||
pub(crate) width: usize,
|
||||
pub(crate) width: usize,
|
||||
/// Identifying color of track
|
||||
pub(crate) color: ItemColor,
|
||||
/// 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>>,
|
||||
/// 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>,
|
||||
pub(crate) color: ItemColor,
|
||||
/// MIDI player state
|
||||
pub(crate) player: PhrasePlayerModel,
|
||||
/// MIDI editor state
|
||||
pub(crate) editor: PhraseEditorModel,
|
||||
}
|
||||
|
||||
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
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PhrasesMode {
|
||||
/// Renaming a pattern
|
||||
Rename(usize, String),
|
||||
|
|
@ -484,32 +284,15 @@ impl PhraseLength {
|
|||
}
|
||||
}
|
||||
|
||||
/// Contains state for viewing and editing a phrase
|
||||
pub struct PhraseTui {
|
||||
/// 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 PhrasesModel {
|
||||
pub fn new (phrases: Vec<Arc<RwLock<Phrase>>>) -> Self {
|
||||
Self {
|
||||
scroll: 0,
|
||||
phrase: 0,
|
||||
mode: None,
|
||||
focused: false,
|
||||
entered: false,
|
||||
phrases,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ use crate::*;
|
|||
|
||||
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 {
|
||||
fn transport_selected (&self) -> TransportFocus;
|
||||
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 {
|
||||
fn transport_selected (&self) -> TransportFocus {
|
||||
self.focus
|
||||
self.state.focus
|
||||
}
|
||||
fn transport_focused (&self) -> bool {
|
||||
true
|
||||
|
|
@ -34,7 +64,7 @@ impl TransportViewState for TransportTui {
|
|||
|
||||
impl TransportViewState for SequencerTui {
|
||||
fn transport_selected (&self) -> TransportFocus {
|
||||
self.focus
|
||||
self.transport.focus
|
||||
}
|
||||
fn transport_focused (&self) -> bool {
|
||||
self.focused() == SequencerFocus::Transport
|
||||
|
|
@ -46,7 +76,7 @@ impl TransportViewState for SequencerTui {
|
|||
|
||||
impl TransportViewState for ArrangerTui {
|
||||
fn transport_selected (&self) -> TransportFocus {
|
||||
self.focus
|
||||
self.transport.focus
|
||||
}
|
||||
fn transport_focused (&self) -> bool {
|
||||
self.focused() == ArrangerFocus::Transport
|
||||
|
|
@ -56,27 +86,13 @@ impl TransportViewState for ArrangerTui {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait ArrangerViewState {
|
||||
fn arranger_focused (&self) -> bool;
|
||||
}
|
||||
|
||||
impl ArrangerViewState for ArrangerTui {
|
||||
fn arranger_focused (&self) -> bool {
|
||||
self.focused() == ArrangerFocus::Arranger
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PhrasesView<'a, T: PhrasesViewState>(pub &'a T);
|
||||
|
||||
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 {
|
||||
impl PhrasesViewState for PhrasesModel {
|
||||
fn phrases_focused (&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
|
@ -130,22 +146,7 @@ impl PhrasesViewState for ArrangerTui {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T);
|
||||
|
||||
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 {
|
||||
impl PhraseViewState for PhraseEditorModel {
|
||||
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||
&self.phrase
|
||||
}
|
||||
|
|
@ -176,6 +177,9 @@ impl PhraseViewState for PhraseTui {
|
|||
fn now (&self) -> &Arc<Pulse> {
|
||||
&self.now
|
||||
}
|
||||
fn size (&self) -> &Measure<Tui> {
|
||||
&self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl PhraseViewState for SequencerTui {
|
||||
|
|
@ -209,6 +213,9 @@ impl PhraseViewState for SequencerTui {
|
|||
fn now (&self) -> &Arc<Pulse> {
|
||||
todo!()
|
||||
}
|
||||
fn size (&self) -> &Measure<Tui> {
|
||||
&self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl PhraseViewState for ArrangerTui {
|
||||
|
|
@ -242,6 +249,9 @@ impl PhraseViewState for ArrangerTui {
|
|||
fn now (&self) -> &Arc<Pulse> {
|
||||
todo!()
|
||||
}
|
||||
fn size (&self) -> &Measure<Tui> {
|
||||
&self.size
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
];
|
||||
|
||||
impl PhraseTui {
|
||||
impl PhraseEditorModel {
|
||||
pub fn put (&mut self) {
|
||||
if let (Some(phrase), Some(time), Some(note)) = (
|
||||
&self.phrase,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ impl Widget for TransportTui {
|
|||
}
|
||||
}
|
||||
|
||||
impl Widget for PhrasesTui {
|
||||
impl Widget for PhrasesModel {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
PhrasesView(self).layout(to)
|
||||
|
|
@ -20,7 +20,7 @@ impl Widget for PhrasesTui {
|
|||
}
|
||||
}
|
||||
|
||||
impl Widget for PhraseTui {
|
||||
impl Widget for PhraseEditorModel {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
PhraseView(self).layout(to)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue