wip(p60,e90): impl macros

This commit is contained in:
🪞👃🪞 2024-11-20 20:46:20 +01:00
parent f4a4b08c8a
commit 9d4fcaa32b
17 changed files with 748 additions and 1083 deletions

View file

@ -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) }

View file

@ -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

View file

@ -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()
//}

View 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>,
}

View file

@ -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!()
})
}

View file

@ -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),
)
)
)

View file

@ -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!()
}
}

View 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()
}
}

View file

@ -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,

View file

@ -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)
}

View 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
}
}

View file

@ -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()),
})
}
}

View file

@ -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
})

View file

@ -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 {}

View file

@ -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,
}
}
}

View file

@ -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,

View file

@ -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)