mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
apply from_jack!
This commit is contained in:
parent
0496ed6251
commit
efda18293d
7 changed files with 522 additions and 582 deletions
|
|
@ -1,6 +1,18 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Trait for things that have a JACK process callback.
|
/// Implement [TryFrom<&Arc<RwLock<JackClient>>>]: create app state from wrapped JACK handle.
|
||||||
|
#[macro_export] macro_rules! from_jack {
|
||||||
|
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackClient>>> for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
fn try_from ($jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
|
Ok($cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for thing that has a JACK process callback.
|
||||||
pub trait Audio: Send + Sync {
|
pub trait Audio: Send + Sync {
|
||||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
|
|
@ -16,6 +28,7 @@ pub trait Audio: Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement [Audio]: provide JACK callbacks.
|
||||||
#[macro_export] macro_rules! audio {
|
#[macro_export] macro_rules! audio {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
|
@ -24,17 +37,7 @@ pub trait Audio: Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! from_jack {
|
/// Trait for thing that may receive MIDI.
|
||||||
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackClient>>> for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from ($jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
||||||
Ok($cb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasMidiIns {
|
pub trait HasMidiIns {
|
||||||
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
|
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
|
||||||
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
|
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
|
||||||
|
|
@ -43,13 +46,15 @@ pub trait HasMidiIns {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for thing that may output MIDI.
|
||||||
pub trait HasMidiOuts {
|
pub trait HasMidiOuts {
|
||||||
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
|
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8>;
|
|
||||||
fn has_midi_outs (&self) -> bool {
|
fn has_midi_outs (&self) -> bool {
|
||||||
self.midi_outs().len() > 0
|
self.midi_outs().len() > 0
|
||||||
}
|
}
|
||||||
|
/// Buffer for serializing a MIDI event. FIXME rename
|
||||||
|
fn midi_note (&mut self) -> &mut Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,6 @@ use crate::*;
|
||||||
use crate::api::ArrangerTrackCommand;
|
use crate::api::ArrangerTrackCommand;
|
||||||
use crate::api::ArrangerSceneCommand;
|
use crate::api::ArrangerSceneCommand;
|
||||||
|
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
jack: jack.clone(),
|
|
||||||
clock: ClockModel::from(jack),
|
|
||||||
phrases: PhraseListModel::default(),
|
|
||||||
editor: PhraseEditorModel::default(),
|
|
||||||
selected: ArrangerSelection::Clip(0, 0),
|
|
||||||
scenes: vec![],
|
|
||||||
tracks: vec![],
|
|
||||||
color: TuiTheme::bg().into(),
|
|
||||||
history: vec![],
|
|
||||||
mode: ArrangerMode::Vertical(2),
|
|
||||||
name: Arc::new(RwLock::new(String::new())),
|
|
||||||
size: Measure::new(),
|
|
||||||
cursor: (0, 0),
|
|
||||||
splits: [20, 20],
|
|
||||||
entered: false,
|
|
||||||
menu_bar: None,
|
|
||||||
status_bar: None,
|
|
||||||
midi_buf: vec![vec![];65536],
|
|
||||||
note_buf: vec![],
|
|
||||||
perf: PerfModel::default(),
|
|
||||||
focus: FocusState::Entered(ArrangerFocus::Transport(TransportFocus::PlayPause)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Root view for standalone `tek_arranger`
|
/// Root view for standalone `tek_arranger`
|
||||||
pub struct ArrangerTui {
|
pub struct ArrangerTui {
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
|
@ -55,7 +26,29 @@ pub struct ArrangerTui {
|
||||||
pub focus: FocusState<ArrangerFocus>,
|
pub focus: FocusState<ArrangerFocus>,
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
from_jack!(|jack| ArrangerTui Self {
|
||||||
|
jack: jack.clone(),
|
||||||
|
clock: ClockModel::from(jack),
|
||||||
|
phrases: PhraseListModel::default(),
|
||||||
|
editor: PhraseEditorModel::default(),
|
||||||
|
selected: ArrangerSelection::Clip(0, 0),
|
||||||
|
scenes: vec![],
|
||||||
|
tracks: vec![],
|
||||||
|
color: TuiTheme::bg().into(),
|
||||||
|
history: vec![],
|
||||||
|
mode: ArrangerMode::Vertical(2),
|
||||||
|
name: Arc::new(RwLock::new(String::new())),
|
||||||
|
size: Measure::new(),
|
||||||
|
cursor: (0, 0),
|
||||||
|
splits: [20, 20],
|
||||||
|
entered: false,
|
||||||
|
menu_bar: None,
|
||||||
|
status_bar: None,
|
||||||
|
midi_buf: vec![vec![];65536],
|
||||||
|
note_buf: vec![],
|
||||||
|
perf: PerfModel::default(),
|
||||||
|
focus: FocusState::Entered(ArrangerFocus::Transport(TransportFocus::PlayPause)),
|
||||||
|
});
|
||||||
has_clock!(|self:ArrangerTui|&self.clock);
|
has_clock!(|self:ArrangerTui|&self.clock);
|
||||||
has_phrases!(|self:ArrangerTui|self.phrases.phrases);
|
has_phrases!(|self:ArrangerTui|self.phrases.phrases);
|
||||||
has_editor!(|self:ArrangerTui|self.editor);
|
has_editor!(|self:ArrangerTui|self.editor);
|
||||||
|
|
@ -146,7 +139,6 @@ impl HasPhraseList for ArrangerTui {
|
||||||
self.phrases.phrase.load(Ordering::Relaxed)
|
self.phrases.phrase.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ArrangerCommand {
|
pub enum ArrangerCommand {
|
||||||
Focus(FocusCommand<ArrangerFocus>),
|
Focus(FocusCommand<ArrangerFocus>),
|
||||||
|
|
@ -163,7 +155,6 @@ pub enum ArrangerCommand {
|
||||||
Phrases(PhrasesCommand),
|
Phrases(PhrasesCommand),
|
||||||
Editor(PhraseCommand),
|
Editor(PhraseCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<ArrangerTui> for ArrangerCommand {
|
impl Command<ArrangerTui> for ArrangerCommand {
|
||||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
use ArrangerCommand::*;
|
use ArrangerCommand::*;
|
||||||
|
|
@ -184,28 +175,24 @@ impl Command<ArrangerTui> for ArrangerCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<ArrangerTui> for ArrangerSceneCommand {
|
impl Command<ArrangerTui> for ArrangerSceneCommand {
|
||||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
//todo!();
|
//todo!();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<ArrangerTui> for ArrangerTrackCommand {
|
impl Command<ArrangerTui> for ArrangerTrackCommand {
|
||||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
//todo!();
|
//todo!();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<ArrangerTui> for ArrangerClipCommand {
|
impl Command<ArrangerTui> for ArrangerClipCommand {
|
||||||
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
fn execute (self, _state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||||
//todo!();
|
//todo!();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ArrangerControl: TransportControl<ArrangerFocus> {
|
pub trait ArrangerControl: TransportControl<ArrangerFocus> {
|
||||||
fn selected (&self) -> ArrangerSelection;
|
fn selected (&self) -> ArrangerSelection;
|
||||||
fn selected_mut (&mut self) -> &mut ArrangerSelection;
|
fn selected_mut (&mut self) -> &mut ArrangerSelection;
|
||||||
|
|
@ -214,7 +201,6 @@ pub trait ArrangerControl: TransportControl<ArrangerFocus> {
|
||||||
fn toggle_loop (&mut self);
|
fn toggle_loop (&mut self);
|
||||||
fn randomize_color (&mut self);
|
fn randomize_color (&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArrangerControl for ArrangerTui {
|
impl ArrangerControl for ArrangerTui {
|
||||||
fn selected (&self) -> ArrangerSelection {
|
fn selected (&self) -> ArrangerSelection {
|
||||||
self.selected
|
self.selected
|
||||||
|
|
@ -272,8 +258,6 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
|
||||||
.or_else(||to_focus_command(input).map(ArrangerCommand::Focus))
|
.or_else(||to_focus_command(input).map(ArrangerCommand::Focus))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<ArrangerCommand> {
|
fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<ArrangerCommand> {
|
||||||
use ArrangerCommand as Cmd;
|
use ArrangerCommand as Cmd;
|
||||||
use KeyCode::Char;
|
use KeyCode::Char;
|
||||||
|
|
@ -318,7 +302,6 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_arranger_mix_command (input: &TuiInput) -> Option<ArrangerCommand> {
|
fn to_arranger_mix_command (input: &TuiInput) -> Option<ArrangerCommand> {
|
||||||
use KeyCode::{Char, Down, Right, Delete};
|
use KeyCode::{Char, Down, Right, Delete};
|
||||||
use ArrangerCommand as Cmd;
|
use ArrangerCommand as Cmd;
|
||||||
|
|
@ -335,7 +318,6 @@ fn to_arranger_mix_command (input: &TuiInput) -> Option<ArrangerCommand> {
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_arranger_track_command (input: &TuiInput, t: usize) -> Option<ArrangerCommand> {
|
fn to_arranger_track_command (input: &TuiInput, t: usize) -> Option<ArrangerCommand> {
|
||||||
use KeyCode::{Char, Down, Left, Right, Delete};
|
use KeyCode::{Char, Down, Left, Right, Delete};
|
||||||
use ArrangerCommand as Cmd;
|
use ArrangerCommand as Cmd;
|
||||||
|
|
@ -354,7 +336,6 @@ fn to_arranger_track_command (input: &TuiInput, t: usize) -> Option<ArrangerComm
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option<ArrangerCommand> {
|
fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option<ArrangerCommand> {
|
||||||
use KeyCode::{Char, Up, Down, Right, Enter, Delete};
|
use KeyCode::{Char, Up, Down, Right, Enter, Delete};
|
||||||
use ArrangerCommand as Cmd;
|
use ArrangerCommand as Cmd;
|
||||||
|
|
@ -374,7 +355,6 @@ fn to_arranger_scene_command (input: &TuiInput, s: usize) -> Option<ArrangerComm
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option<ArrangerCommand> {
|
fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option<ArrangerCommand> {
|
||||||
use KeyCode::{Char, Up, Down, Left, Right, Delete};
|
use KeyCode::{Char, Up, Down, Left, Right, Delete};
|
||||||
use ArrangerCommand as Cmd;
|
use ArrangerCommand as Cmd;
|
||||||
|
|
@ -396,7 +376,6 @@ fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option<Arr
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransportControl<ArrangerFocus> for ArrangerTui {
|
impl TransportControl<ArrangerFocus> for ArrangerTui {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||||
match self.focus.inner() {
|
match self.focus.inner() {
|
||||||
|
|
@ -440,7 +419,7 @@ impl From<&ArrangerTui> for Option<TransportFocus> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_focus!(ArrangerTui ArrangerFocus [
|
impl_focus!(ArrangerTui ArrangerFocus [
|
||||||
//&[
|
//&[
|
||||||
//Menu,
|
//Menu,
|
||||||
//Menu,
|
//Menu,
|
||||||
|
|
@ -482,116 +461,6 @@ pub enum ArrangerStatus {
|
||||||
PhraseEdit,
|
PhraseEdit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusBar for ArrangerStatus {
|
|
||||||
type State = (ArrangerFocus, ArrangerSelection, bool);
|
|
||||||
fn hotkey_fg () -> Color where Self: Sized {
|
|
||||||
TuiTheme::HOTKEY_FG
|
|
||||||
}
|
|
||||||
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
|
||||||
*self = match focused {
|
|
||||||
//ArrangerFocus::Menu => { todo!() },
|
|
||||||
ArrangerFocus::Transport(_) => ArrangerStatus::Transport,
|
|
||||||
ArrangerFocus::Arranger => match selected {
|
|
||||||
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
|
|
||||||
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
|
|
||||||
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
|
|
||||||
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
|
|
||||||
},
|
|
||||||
ArrangerFocus::Phrases => ArrangerStatus::PhrasePool,
|
|
||||||
ArrangerFocus::PhraseEditor => match entered {
|
|
||||||
true => ArrangerStatus::PhraseEdit,
|
|
||||||
false => ArrangerStatus::PhraseView,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render!(<Tui>|self: ArrangerStatus|{
|
|
||||||
|
|
||||||
let label = match self {
|
|
||||||
Self::Transport => "TRANSPORT",
|
|
||||||
Self::ArrangerMix => "PROJECT",
|
|
||||||
Self::ArrangerTrack => "TRACK",
|
|
||||||
Self::ArrangerScene => "SCENE",
|
|
||||||
Self::ArrangerClip => "CLIP",
|
|
||||||
Self::PhrasePool => "SEQ LIST",
|
|
||||||
Self::PhraseView => "VIEW SEQ",
|
|
||||||
Self::PhraseEdit => "EDIT SEQ",
|
|
||||||
};
|
|
||||||
|
|
||||||
let status_bar_bg = TuiTheme::status_bar_bg();
|
|
||||||
|
|
||||||
let mode_bg = TuiTheme::mode_bg();
|
|
||||||
let mode_fg = TuiTheme::mode_fg();
|
|
||||||
let mode = Tui::fg(mode_fg, Tui::bg(mode_bg, Tui::bold(true, format!(" {label} "))));
|
|
||||||
|
|
||||||
let commands = match self {
|
|
||||||
Self::ArrangerMix => Self::command(&[
|
|
||||||
["", "c", "olor"],
|
|
||||||
["", "<>", "resize"],
|
|
||||||
["", "+-", "zoom"],
|
|
||||||
["", "n", "ame/number"],
|
|
||||||
["", "Enter", " stop all"],
|
|
||||||
]),
|
|
||||||
Self::ArrangerClip => Self::command(&[
|
|
||||||
["", "g", "et"],
|
|
||||||
["", "s", "et"],
|
|
||||||
["", "a", "dd"],
|
|
||||||
["", "i", "ns"],
|
|
||||||
["", "d", "up"],
|
|
||||||
["", "e", "dit"],
|
|
||||||
["", "c", "olor"],
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["", ",.", "select"],
|
|
||||||
["", "Enter", " launch"],
|
|
||||||
]),
|
|
||||||
Self::ArrangerTrack => Self::command(&[
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["", ",.", "resize"],
|
|
||||||
["", "<>", "move"],
|
|
||||||
["", "i", "nput"],
|
|
||||||
["", "o", "utput"],
|
|
||||||
["", "m", "ute"],
|
|
||||||
["", "s", "olo"],
|
|
||||||
["", "Del", "ete"],
|
|
||||||
["", "Enter", " stop"],
|
|
||||||
]),
|
|
||||||
Self::ArrangerScene => Self::command(&[
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["", "Del", "ete"],
|
|
||||||
["", "Enter", " launch"],
|
|
||||||
]),
|
|
||||||
Self::PhrasePool => Self::command(&[
|
|
||||||
["", "a", "ppend"],
|
|
||||||
["", "i", "nsert"],
|
|
||||||
["", "d", "uplicate"],
|
|
||||||
["", "Del", "ete"],
|
|
||||||
["", "c", "olor"],
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["leng", "t", "h"],
|
|
||||||
["", ",.", "move"],
|
|
||||||
["", "+-", "resize view"],
|
|
||||||
]),
|
|
||||||
Self::PhraseView => Self::command(&[
|
|
||||||
["", "enter", " edit"],
|
|
||||||
["", "arrows/pgup/pgdn", " scroll"],
|
|
||||||
["", "+=", "zoom"],
|
|
||||||
]),
|
|
||||||
Self::PhraseEdit => Self::command(&[
|
|
||||||
["", "esc", " exit"],
|
|
||||||
["", "a", "ppend"],
|
|
||||||
["", "s", "et"],
|
|
||||||
["", "][", "length"],
|
|
||||||
["", "+-", "zoom"],
|
|
||||||
]),
|
|
||||||
_ => Self::command(&[])
|
|
||||||
};
|
|
||||||
|
|
||||||
//let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}"));
|
|
||||||
Tui::bg(status_bar_bg, Fill::w(row!([mode, commands])))
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Display mode of arranger
|
/// Display mode of arranger
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum ArrangerMode {
|
pub enum ArrangerMode {
|
||||||
|
|
@ -912,201 +781,6 @@ render!(<Tui>|self: ArrangerVerticalContent<'a>|Fixed::h(
|
||||||
})
|
})
|
||||||
));
|
));
|
||||||
|
|
||||||
pub fn arranger_content_horizontal (
|
|
||||||
view: &ArrangerTui,
|
|
||||||
) -> impl Render<Tui> + use<'_> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
//let focused = view.arranger_focused();
|
|
||||||
//let _tracks = view.tracks();
|
|
||||||
//lay!(
|
|
||||||
//focused.then_some(Background(TuiTheme::border_bg())),
|
|
||||||
//row!(
|
|
||||||
//// name
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks, selected) = self;
|
|
||||||
////let yellow = Some(Style::default().yellow().bold().not_dim());
|
|
||||||
////let white = Some(Style::default().white().bold().not_dim());
|
|
||||||
////let area = to.area();
|
|
||||||
////let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
|
|
||||||
////let offset = 0; // track scroll offset
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
////to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2 + offset;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////let selected = selected.track() == Some(index);
|
|
||||||
////let style = if selected { yellow } else { white };
|
|
||||||
////to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
|
|
||||||
////to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// monitor
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let on = Some(Style::default().not_dim().green().bold());
|
|
||||||
////let off = Some(DIM);
|
|
||||||
////area.x += 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////let style = if track.monitoring { on } else { off };
|
|
||||||
////to.blit(&" MON ", area.x(), area.y() + y, style)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// record
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let on = Some(Style::default().not_dim().red().bold());
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x += 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////let style = if track.recording { on } else { off };
|
|
||||||
////to.blit(&" REC ", area.x(), area.y() + y, style)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// overdub
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let on = Some(Style::default().not_dim().yellow().bold());
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x = area.x + 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
|
|
||||||
////on
|
|
||||||
////} else {
|
|
||||||
////off
|
|
||||||
////})?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// erase
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x = area.x + 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(_) = tracks.get(index) {
|
|
||||||
////to.blit(&" DEL ", area.x(), area.y() + y, off)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// gain
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x = area.x() + 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(_) = tracks.get(index) {
|
|
||||||
////to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 7;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// scenes
|
|
||||||
//Widget::new(|_|{todo!()}, |to: &mut TuiOutput|{
|
|
||||||
//let [x, y, _, height] = to.area();
|
|
||||||
//let mut x2 = 0;
|
|
||||||
//Ok(for (scene_index, scene) in view.scenes().iter().enumerate() {
|
|
||||||
//let active_scene = view.selected.scene() == Some(scene_index);
|
|
||||||
//let sep = Some(if active_scene {
|
|
||||||
//Style::default().yellow().not_dim()
|
|
||||||
//} else {
|
|
||||||
//Style::default().dim()
|
|
||||||
//});
|
|
||||||
//for y in y+1..y+height {
|
|
||||||
//to.blit(&"│", x + x2, y, sep);
|
|
||||||
//}
|
|
||||||
//let name = scene.name.read().unwrap();
|
|
||||||
//let mut x3 = name.len() as u16;
|
|
||||||
//to.blit(&*name, x + x2, y, sep);
|
|
||||||
//for (i, clip) in scene.clips.iter().enumerate() {
|
|
||||||
//let active_track = view.selected.track() == Some(i);
|
|
||||||
//if let Some(clip) = clip {
|
|
||||||
//let y2 = y + 2 + i as u16 * 2;
|
|
||||||
//let label = format!("{}", clip.read().unwrap().name);
|
|
||||||
//to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
|
|
||||||
//Style::default().not_dim().yellow().bold()
|
|
||||||
//} else {
|
|
||||||
//Style::default().not_dim()
|
|
||||||
//}));
|
|
||||||
//x3 = x3.max(label.len() as u16)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//x2 = x2 + x3 + 1;
|
|
||||||
//})
|
|
||||||
//}),
|
|
||||||
//)
|
|
||||||
//)
|
|
||||||
//}
|
|
||||||
|
|
||||||
impl HasScenes<ArrangerScene> for ArrangerTui {
|
impl HasScenes<ArrangerScene> for ArrangerTui {
|
||||||
fn scenes (&self) -> &Vec<ArrangerScene> {
|
fn scenes (&self) -> &Vec<ArrangerScene> {
|
||||||
&self.scenes
|
&self.scenes
|
||||||
|
|
@ -1307,3 +981,198 @@ pub enum ArrangerClipCommand {
|
||||||
//Ok(None)
|
//Ok(None)
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
pub fn arranger_content_horizontal (
|
||||||
|
view: &ArrangerTui,
|
||||||
|
) -> impl Render<Tui> + use<'_> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
//let focused = view.arranger_focused();
|
||||||
|
//let _tracks = view.tracks();
|
||||||
|
//lay!(
|
||||||
|
//focused.then_some(Background(TuiTheme::border_bg())),
|
||||||
|
//row!(
|
||||||
|
//// name
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks, selected) = self;
|
||||||
|
////let yellow = Some(Style::default().yellow().bold().not_dim());
|
||||||
|
////let white = Some(Style::default().white().bold().not_dim());
|
||||||
|
////let area = to.area();
|
||||||
|
////let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
|
||||||
|
////let offset = 0; // track scroll offset
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
////to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2 + offset;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////let selected = selected.track() == Some(index);
|
||||||
|
////let style = if selected { yellow } else { white };
|
||||||
|
////to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
|
||||||
|
////to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// monitor
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let on = Some(Style::default().not_dim().green().bold());
|
||||||
|
////let off = Some(DIM);
|
||||||
|
////area.x += 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////let style = if track.monitoring { on } else { off };
|
||||||
|
////to.blit(&" MON ", area.x(), area.y() + y, style)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// record
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let on = Some(Style::default().not_dim().red().bold());
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x += 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////let style = if track.recording { on } else { off };
|
||||||
|
////to.blit(&" REC ", area.x(), area.y() + y, style)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// overdub
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let on = Some(Style::default().not_dim().yellow().bold());
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x = area.x + 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
|
||||||
|
////on
|
||||||
|
////} else {
|
||||||
|
////off
|
||||||
|
////})?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// erase
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x = area.x + 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(_) = tracks.get(index) {
|
||||||
|
////to.blit(&" DEL ", area.x(), area.y() + y, off)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// gain
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x = area.x() + 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(_) = tracks.get(index) {
|
||||||
|
////to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 7;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// scenes
|
||||||
|
//Widget::new(|_|{todo!()}, |to: &mut TuiOutput|{
|
||||||
|
//let [x, y, _, height] = to.area();
|
||||||
|
//let mut x2 = 0;
|
||||||
|
//Ok(for (scene_index, scene) in view.scenes().iter().enumerate() {
|
||||||
|
//let active_scene = view.selected.scene() == Some(scene_index);
|
||||||
|
//let sep = Some(if active_scene {
|
||||||
|
//Style::default().yellow().not_dim()
|
||||||
|
//} else {
|
||||||
|
//Style::default().dim()
|
||||||
|
//});
|
||||||
|
//for y in y+1..y+height {
|
||||||
|
//to.blit(&"│", x + x2, y, sep);
|
||||||
|
//}
|
||||||
|
//let name = scene.name.read().unwrap();
|
||||||
|
//let mut x3 = name.len() as u16;
|
||||||
|
//to.blit(&*name, x + x2, y, sep);
|
||||||
|
//for (i, clip) in scene.clips.iter().enumerate() {
|
||||||
|
//let active_track = view.selected.track() == Some(i);
|
||||||
|
//if let Some(clip) = clip {
|
||||||
|
//let y2 = y + 2 + i as u16 * 2;
|
||||||
|
//let label = format!("{}", clip.read().unwrap().name);
|
||||||
|
//to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
|
||||||
|
//Style::default().not_dim().yellow().bold()
|
||||||
|
//} else {
|
||||||
|
//Style::default().not_dim()
|
||||||
|
//}));
|
||||||
|
//x3 = x3.max(label.len() as u16)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//x2 = x2 + x3 + 1;
|
||||||
|
//})
|
||||||
|
//}),
|
||||||
|
//)
|
||||||
|
//)
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,37 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
use KeyCode::Char;
|
use KeyCode::Char;
|
||||||
|
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for GrooveboxTui {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
||||||
let mut sequencer = SequencerTui::try_from(jack)?;
|
|
||||||
sequencer.status = false;
|
|
||||||
Ok(Self {
|
|
||||||
sequencer,
|
|
||||||
sampler: SamplerTui::try_from(jack)?,
|
|
||||||
split: 16,
|
|
||||||
focus: GrooveboxFocus::Sampler,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GrooveboxTui {
|
pub struct GrooveboxTui {
|
||||||
pub sequencer: SequencerTui,
|
pub sequencer: SequencerTui,
|
||||||
pub sampler: SamplerTui,
|
pub sampler: SamplerTui,
|
||||||
pub split: u16,
|
pub split: u16,
|
||||||
pub focus: GrooveboxFocus
|
pub focus: GrooveboxFocus
|
||||||
}
|
}
|
||||||
|
from_jack!(|jack|GrooveboxTui {
|
||||||
|
let mut sequencer = SequencerTui::try_from(jack)?;
|
||||||
|
sequencer.status = false;
|
||||||
|
Self {
|
||||||
|
sequencer,
|
||||||
|
sampler: SamplerTui::try_from(jack)?,
|
||||||
|
split: 16,
|
||||||
|
focus: GrooveboxFocus::Sampler,
|
||||||
|
}
|
||||||
|
});
|
||||||
pub enum GrooveboxFocus {
|
pub enum GrooveboxFocus {
|
||||||
Sequencer,
|
Sequencer,
|
||||||
Sampler
|
Sampler
|
||||||
}
|
}
|
||||||
|
|
||||||
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
|
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
|
||||||
|
|
||||||
render!(<Tui>|self:GrooveboxTui|Bsp::n(
|
render!(<Tui>|self:GrooveboxTui|Bsp::n(
|
||||||
Fixed::h(2, SequencerStatusBar::from(&self.sequencer)),
|
Fixed::h(2, SequencerStatus::from(&self.sequencer)),
|
||||||
Fill::h(Bsp::s(&self.sequencer, &self.sampler)),
|
Fill::h(Bsp::s(&self.sequencer, &self.sampler)),
|
||||||
));
|
));
|
||||||
|
|
||||||
pub enum GrooveboxCommand {
|
pub enum GrooveboxCommand {
|
||||||
Sequencer(SequencerCommand),
|
Sequencer(SequencerCommand),
|
||||||
Sampler(SamplerCommand),
|
Sampler(SamplerCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
handle!(<Tui>|self:GrooveboxTui,input|GrooveboxCommand::execute_with_state(self, input));
|
handle!(<Tui>|self:GrooveboxTui,input|GrooveboxCommand::execute_with_state(self, input));
|
||||||
|
|
||||||
input_to_command!(GrooveboxCommand: <Tui>|state:GrooveboxTui,input|match input.event() {
|
input_to_command!(GrooveboxCommand: <Tui>|state:GrooveboxTui,input|match input.event() {
|
||||||
// load sample
|
|
||||||
key_pat!(Char('l')) => GrooveboxCommand::Sampler(SamplerCommand::Import(FileBrowserCommand::Begin)),
|
|
||||||
_ => match state.focus {
|
_ => match state.focus {
|
||||||
GrooveboxFocus::Sequencer =>
|
GrooveboxFocus::Sequencer =>
|
||||||
GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?),
|
GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?),
|
||||||
|
|
@ -52,7 +39,6 @@ input_to_command!(GrooveboxCommand: <Tui>|state:GrooveboxTui,input|match input.e
|
||||||
GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?),
|
GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
command!(|self:GrooveboxCommand,state:GrooveboxTui|match self {
|
command!(|self:GrooveboxCommand,state:GrooveboxTui|match self {
|
||||||
GrooveboxCommand::Sequencer(command) =>
|
GrooveboxCommand::Sequencer(command) =>
|
||||||
command.execute(&mut state.sequencer)?.map(GrooveboxCommand::Sequencer),
|
command.execute(&mut state.sequencer)?.map(GrooveboxCommand::Sequencer),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use KeyCode::Char;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use symphonia::core::codecs::CODEC_TYPE_NULL;
|
use symphonia::core::codecs::CODEC_TYPE_NULL;
|
||||||
use symphonia::core::errors::Error;
|
use symphonia::core::errors::Error;
|
||||||
|
|
@ -54,8 +55,11 @@ pub enum SamplerCommand {
|
||||||
}
|
}
|
||||||
input_to_command!(SamplerCommand:<Tui>|state:SamplerTui,input|match state.mode {
|
input_to_command!(SamplerCommand:<Tui>|state:SamplerTui,input|match state.mode {
|
||||||
Some(SamplerMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
|
Some(SamplerMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
|
||||||
_ => todo!()
|
_ => match input.event() {
|
||||||
//_ => match input.event() {
|
// load sample
|
||||||
|
key_pat!(Char('l')) => Self::Import(FileBrowserCommand::Begin),
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
//key_pat!(KeyCode::Up) => state.cursor.0 = if state.cursor.0 == 0 {
|
//key_pat!(KeyCode::Up) => state.cursor.0 = if state.cursor.0 == 0 {
|
||||||
//mapped.len() + unmapped.len() - 1
|
//mapped.len() + unmapped.len() - 1
|
||||||
//} else {
|
//} else {
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,6 @@ use KeyCode::{Tab, BackTab, Char};
|
||||||
use SequencerCommand::*;
|
use SequencerCommand::*;
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
use PhrasePoolCommand::*;
|
use PhrasePoolCommand::*;
|
||||||
|
|
||||||
/// Create app state from JACK handle.
|
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
||||||
let clock = ClockModel::from(jack);
|
|
||||||
let phrase = Arc::new(RwLock::new(Phrase::new(
|
|
||||||
"New", true, 4 * clock.timebase.ppq.get() as usize,
|
|
||||||
None, Some(ItemColor::random().into())
|
|
||||||
)));
|
|
||||||
Ok(Self {
|
|
||||||
_jack: jack.clone(),
|
|
||||||
phrases: PhraseListModel::from(&phrase),
|
|
||||||
editor: PhraseEditorModel::from(&phrase),
|
|
||||||
player: PhrasePlayerModel::from((&clock, &phrase)),
|
|
||||||
clock,
|
|
||||||
size: Measure::new(),
|
|
||||||
midi_buf: vec![vec![];65536],
|
|
||||||
note_buf: vec![],
|
|
||||||
perf: PerfModel::default(),
|
|
||||||
show_pool: true,
|
|
||||||
status: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Root view for standalone `tek_sequencer`.
|
/// Root view for standalone `tek_sequencer`.
|
||||||
pub struct SequencerTui {
|
pub struct SequencerTui {
|
||||||
_jack: Arc<RwLock<JackClient>>,
|
_jack: Arc<RwLock<JackClient>>,
|
||||||
|
|
@ -44,17 +17,91 @@ pub struct SequencerTui {
|
||||||
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
pub(crate) perf: PerfModel,
|
pub(crate) perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
from_jack!(|jack|SequencerTui {
|
||||||
#[derive(Clone, Debug)]
|
let clock = ClockModel::from(jack);
|
||||||
pub enum SequencerCommand {
|
let phrase = Arc::new(RwLock::new(Phrase::new(
|
||||||
|
"New", true, 4 * clock.timebase.ppq.get() as usize,
|
||||||
|
None, Some(ItemColor::random().into())
|
||||||
|
)));
|
||||||
|
Self {
|
||||||
|
_jack: jack.clone(),
|
||||||
|
phrases: PhraseListModel::from(&phrase),
|
||||||
|
editor: PhraseEditorModel::from(&phrase),
|
||||||
|
player: PhrasePlayerModel::from((&clock, &phrase)),
|
||||||
|
clock,
|
||||||
|
size: Measure::new(),
|
||||||
|
midi_buf: vec![vec![];65536],
|
||||||
|
note_buf: vec![],
|
||||||
|
perf: PerfModel::default(),
|
||||||
|
show_pool: true,
|
||||||
|
status: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
render!(<Tui>|self: SequencerTui|{
|
||||||
|
let w = self.size.w();
|
||||||
|
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||||
|
let pool_w = if self.show_pool { phrase_w } else { 0 };
|
||||||
|
let pool = Fill::h(Align::e(PhraseListView(&self.phrases)));
|
||||||
|
let with_pool = move|x|Tui::split_w(false, pool_w, pool, x);
|
||||||
|
let status = SequencerStatus::from(self);
|
||||||
|
let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x);
|
||||||
|
let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x);
|
||||||
|
let with_size = |x|lay!([self.size, x]);
|
||||||
|
let editor = with_editbar(with_pool(Fill::wh(&self.editor)));
|
||||||
|
let color = self.player.play_phrase().as_ref().map(|(_,p)|
|
||||||
|
p.as_ref().map(|p|p.read().unwrap().color)
|
||||||
|
).flatten().clone();
|
||||||
|
let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling()));
|
||||||
|
let transport = Fixed::h(2, TransportView::from((self, color, true)));
|
||||||
|
let toolbar = row!([play, col!([
|
||||||
|
PhraseSelector::play_phrase(&self.player),
|
||||||
|
PhraseSelector::next_phrase(&self.player),
|
||||||
|
]), transport]);
|
||||||
|
Tui::min_y(15, with_size(with_status(col!([ toolbar, editor, ]))))
|
||||||
|
});
|
||||||
|
audio!(|self:SequencerTui, client, scope|{
|
||||||
|
// Start profiling cycle
|
||||||
|
let t0 = self.perf.get_t0();
|
||||||
|
// Update transport clock
|
||||||
|
if Control::Quit == ClockAudio(self).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// Update MIDI sequencer
|
||||||
|
if Control::Quit == PlayerAudio(
|
||||||
|
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
||||||
|
).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// End profiling cycle
|
||||||
|
self.perf.update(t0, scope);
|
||||||
|
Control::Continue
|
||||||
|
});
|
||||||
|
impl HasPhraseList for SequencerTui {
|
||||||
|
fn phrases_focused (&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn phrases_entered (&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn phrases_mode (&self) -> &Option<PhraseListMode> {
|
||||||
|
&self.phrases.mode
|
||||||
|
}
|
||||||
|
fn phrase_index (&self) -> usize {
|
||||||
|
self.phrases.phrase.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
has_size!(<Tui>|self:SequencerTui|&self.size);
|
||||||
|
has_clock!(|self:SequencerTui|&self.clock);
|
||||||
|
has_phrases!(|self:SequencerTui|self.phrases.phrases);
|
||||||
|
has_editor!(|self:SequencerTui|self.editor);
|
||||||
|
handle!(<Tui>|self:SequencerTui,input|SequencerCommand::execute_with_state(self, input));
|
||||||
|
#[derive(Clone, Debug)] pub enum SequencerCommand {
|
||||||
Clock(ClockCommand),
|
Clock(ClockCommand),
|
||||||
Phrases(PhrasesCommand),
|
Phrases(PhrasesCommand),
|
||||||
Editor(PhraseCommand),
|
Editor(PhraseCommand),
|
||||||
Enqueue(Option<Arc<RwLock<Phrase>>>),
|
Enqueue(Option<Arc<RwLock<Phrase>>>),
|
||||||
ShowPool(bool),
|
ShowPool(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
handle!(<Tui>|self:SequencerTui,input|SequencerCommand::execute_with_state(self, input));
|
|
||||||
input_to_command!(SequencerCommand: <Tui>|state:SequencerTui,input|match input.event() {
|
input_to_command!(SequencerCommand: <Tui>|state:SequencerTui,input|match input.event() {
|
||||||
// Transport: Play/pause
|
// Transport: Play/pause
|
||||||
key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||||
|
|
@ -98,7 +145,9 @@ input_to_command!(SequencerCommand: <Tui>|state:SequencerTui,input|match input.e
|
||||||
});
|
});
|
||||||
command!(|self: SequencerCommand, state: SequencerTui|match self {
|
command!(|self: SequencerCommand, state: SequencerTui|match self {
|
||||||
Self::Phrases(cmd) => {
|
Self::Phrases(cmd) => {
|
||||||
let mut default = |cmd: PhrasesCommand|cmd.execute(&mut state.phrases).map(|x|x.map(Phrases));
|
let mut default = |cmd: PhrasesCommand|cmd
|
||||||
|
.execute(&mut state.phrases)
|
||||||
|
.map(|x|x.map(Phrases));
|
||||||
match cmd {
|
match cmd {
|
||||||
// autoselect: automatically load selected phrase in editor
|
// autoselect: automatically load selected phrase in editor
|
||||||
PhrasesCommand::Select(_) => {
|
PhrasesCommand::Select(_) => {
|
||||||
|
|
@ -131,124 +180,3 @@ command!(|self: SequencerCommand, state: SequencerTui|match self {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
has_size!(<Tui>|self:SequencerTui|&self.size);
|
|
||||||
has_clock!(|self:SequencerTui|&self.clock);
|
|
||||||
has_phrases!(|self:SequencerTui|self.phrases.phrases);
|
|
||||||
has_editor!(|self:SequencerTui|self.editor);
|
|
||||||
render!(<Tui>|self: SequencerTui|{
|
|
||||||
let w = self.size.w();
|
|
||||||
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
|
||||||
let pool_w = if self.show_pool { phrase_w } else { 0 };
|
|
||||||
let pool = Fill::h(Align::e(PhraseListView(&self.phrases)));
|
|
||||||
let with_pool = move|x|Tui::split_w(false, pool_w, pool, x);
|
|
||||||
let status = SequencerStatusBar::from(self);
|
|
||||||
let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x);
|
|
||||||
let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x);
|
|
||||||
let with_size = |x|lay!([self.size, x]);
|
|
||||||
let editor = with_editbar(with_pool(Fill::wh(&self.editor)));
|
|
||||||
let color = self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten().clone();
|
|
||||||
let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling()));
|
|
||||||
let transport = Fixed::h(2, TransportView::from((self, color, true)));
|
|
||||||
let toolbar = row!([play, col!([
|
|
||||||
PhraseSelector::play_phrase(&self.player),
|
|
||||||
PhraseSelector::next_phrase(&self.player),
|
|
||||||
]), transport]);
|
|
||||||
Tui::min_y(15, with_size(with_status(col!([ toolbar, editor, ]))))
|
|
||||||
});
|
|
||||||
audio!(|self:SequencerTui, client, scope|{
|
|
||||||
// Start profiling cycle
|
|
||||||
let t0 = self.perf.get_t0();
|
|
||||||
// Update transport clock
|
|
||||||
if Control::Quit == ClockAudio(self).process(client, scope) {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// Update MIDI sequencer
|
|
||||||
if Control::Quit == PlayerAudio(
|
|
||||||
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
|
||||||
).process(client, scope) {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// End profiling cycle
|
|
||||||
self.perf.update(t0, scope);
|
|
||||||
Control::Continue
|
|
||||||
});
|
|
||||||
|
|
||||||
impl HasPhraseList for SequencerTui {
|
|
||||||
fn phrases_focused (&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn phrases_entered (&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn phrases_mode (&self) -> &Option<PhraseListMode> {
|
|
||||||
&self.phrases.mode
|
|
||||||
}
|
|
||||||
fn phrase_index (&self) -> usize {
|
|
||||||
self.phrases.phrase.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Status bar for sequencer app
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SequencerStatusBar {
|
|
||||||
pub(crate) width: usize,
|
|
||||||
pub(crate) cpu: Option<String>,
|
|
||||||
pub(crate) size: String,
|
|
||||||
pub(crate) res: String,
|
|
||||||
pub(crate) playing: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusBar for SequencerStatusBar {
|
|
||||||
type State = SequencerTui;
|
|
||||||
fn hotkey_fg () -> Color {
|
|
||||||
TuiTheme::HOTKEY_FG
|
|
||||||
}
|
|
||||||
fn update (&mut self, _: &SequencerTui) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&SequencerTui> for SequencerStatusBar {
|
|
||||||
fn from (state: &SequencerTui) -> Self {
|
|
||||||
let samples = state.clock.chunk.load(Ordering::Relaxed);
|
|
||||||
let rate = state.clock.timebase.sr.get() as f64;
|
|
||||||
let buffer = samples as f64 / rate;
|
|
||||||
let width = state.size.w();
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
playing: state.clock.is_rolling(),
|
|
||||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
|
||||||
size: format!("{}x{}│", width, state.size.h()),
|
|
||||||
res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render!(<Tui>|self: SequencerStatusBar|Fixed::h(2, lay!([
|
|
||||||
{
|
|
||||||
let single = |binding, command|row!([" ", col!([
|
|
||||||
Tui::fg(TuiTheme::yellow(), binding),
|
|
||||||
command
|
|
||||||
])]);
|
|
||||||
let double = |(b1, c1), (b2, c2)|col!([
|
|
||||||
row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]),
|
|
||||||
row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]),
|
|
||||||
]);
|
|
||||||
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([
|
|
||||||
single("SPACE", "play/pause"),
|
|
||||||
double((" ✣", "cursor"), ("C-✣", "scroll"), ),
|
|
||||||
double(("a", "append"), ("s", "set note"),),
|
|
||||||
double((",.", "note"), ("<>", "triplet"), ),
|
|
||||||
double(("[]", "phrase"), ("{}", "order"), ),
|
|
||||||
double(("q", "enqueue"), ("e", "edit"), ),
|
|
||||||
]))
|
|
||||||
},
|
|
||||||
Fill::wh(Align::se({
|
|
||||||
Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), row!([
|
|
||||||
&self.cpu,
|
|
||||||
&self.res,
|
|
||||||
&self.size,
|
|
||||||
]))
|
|
||||||
})),
|
|
||||||
])));
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use crate::api::ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync};
|
||||||
use TransportCommand::{Focus, Clock};
|
use TransportCommand::{Focus, Clock};
|
||||||
use FocusCommand::{Next, Prev};
|
use FocusCommand::{Next, Prev};
|
||||||
use KeyCode::{Enter, Left, Right, Char};
|
use KeyCode::{Enter, Left, Right, Char};
|
||||||
|
|
||||||
/// Transport clock app.
|
/// Transport clock app.
|
||||||
pub struct TransportTui {
|
pub struct TransportTui {
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
|
@ -12,21 +11,17 @@ pub struct TransportTui {
|
||||||
pub cursor: (usize, usize),
|
pub cursor: (usize, usize),
|
||||||
pub focus: FocusState<TransportFocus>,
|
pub focus: FocusState<TransportFocus>,
|
||||||
}
|
}
|
||||||
|
from_jack!(|jack|TransportTui Self {
|
||||||
/// Create app state from JACK handle.
|
jack: jack.clone(),
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
|
clock: ClockModel::from(jack),
|
||||||
type Error = Box<dyn std::error::Error>;
|
size: Measure::new(),
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
cursor: (0, 0),
|
||||||
Ok(Self {
|
focus: FocusState::Entered(TransportFocus::PlayPause)
|
||||||
jack: jack.clone(),
|
});
|
||||||
clock: ClockModel::from(jack),
|
has_clock!(|self:TransportTui|&self.clock);
|
||||||
size: Measure::new(),
|
audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope));
|
||||||
cursor: (0, 0),
|
handle!(<Tui>|self:TransportTui,from|TransportCommand::execute_with_state(self, from));
|
||||||
focus: FocusState::Entered(TransportFocus::PlayPause)
|
render!(<Tui>|self: TransportTui|TransportView::from((self, None, true)));
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for TransportTui {
|
impl std::fmt::Debug for TransportTui {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
f.debug_struct("TransportTui")
|
f.debug_struct("TransportTui")
|
||||||
|
|
@ -36,12 +31,6 @@ impl std::fmt::Debug for TransportTui {
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
has_clock!(|self:TransportTui|&self.clock);
|
|
||||||
audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope));
|
|
||||||
handle!(<Tui>|self:TransportTui,from|TransportCommand::execute_with_state(self, from));
|
|
||||||
render!(<Tui>|self: TransportTui|TransportView::from((self, None, true)));
|
|
||||||
|
|
||||||
pub struct TransportView {
|
pub struct TransportView {
|
||||||
color: ItemPalette,
|
color: ItemPalette,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
|
@ -187,21 +176,6 @@ impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct TransportStatusBar;
|
|
||||||
|
|
||||||
impl StatusBar for TransportStatusBar {
|
|
||||||
type State = ();
|
|
||||||
fn hotkey_fg () -> Color {
|
|
||||||
TuiTheme::HOTKEY_FG
|
|
||||||
}
|
|
||||||
fn update (&mut self, _: &()) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render!(<Tui>|self: TransportStatusBar|"todo");
|
|
||||||
|
|
||||||
pub trait TransportControl<T>: HasClock + {
|
pub trait TransportControl<T>: HasClock + {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus>;
|
fn transport_focused (&self) -> Option<TransportFocus>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub trait StatusBar: Render<Tui> {
|
pub trait Bar: Render<Tui> {
|
||||||
type State: Send + Sync;
|
type State: Send + Sync;
|
||||||
fn hotkey_fg () -> Color where Self: Sized;
|
fn hotkey_fg () -> Color where Self: Sized;
|
||||||
fn update (&mut self, state: &Self::State) where Self: Sized;
|
fn update (&mut self, state: &Self::State) where Self: Sized;
|
||||||
|
|
@ -21,3 +21,177 @@ pub trait StatusBar: Render<Tui> {
|
||||||
Bsp::n(state.into(), content)
|
Bsp::n(state.into(), content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Status bar for sequencer app
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SequencerStatus {
|
||||||
|
pub(crate) width: usize,
|
||||||
|
pub(crate) cpu: Option<String>,
|
||||||
|
pub(crate) size: String,
|
||||||
|
pub(crate) res: String,
|
||||||
|
pub(crate) playing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bar for SequencerStatus {
|
||||||
|
type State = SequencerTui;
|
||||||
|
fn hotkey_fg () -> Color {
|
||||||
|
TuiTheme::HOTKEY_FG
|
||||||
|
}
|
||||||
|
fn update (&mut self, _: &SequencerTui) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SequencerTui> for SequencerStatus {
|
||||||
|
fn from (state: &SequencerTui) -> Self {
|
||||||
|
let samples = state.clock.chunk.load(Ordering::Relaxed);
|
||||||
|
let rate = state.clock.timebase.sr.get() as f64;
|
||||||
|
let buffer = samples as f64 / rate;
|
||||||
|
let width = state.size.w();
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
playing: state.clock.is_rolling(),
|
||||||
|
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||||
|
size: format!("{}x{}│", width, state.size.h()),
|
||||||
|
res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render!(<Tui>|self: SequencerStatus|Fixed::h(2, lay!([
|
||||||
|
{
|
||||||
|
let single = |binding, command|row!([" ", col!([
|
||||||
|
Tui::fg(TuiTheme::yellow(), binding),
|
||||||
|
command
|
||||||
|
])]);
|
||||||
|
let double = |(b1, c1), (b2, c2)|col!([
|
||||||
|
row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]),
|
||||||
|
row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]),
|
||||||
|
]);
|
||||||
|
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([
|
||||||
|
single("SPACE", "play/pause"),
|
||||||
|
double((" ✣", "cursor"), ("C-✣", "scroll"), ),
|
||||||
|
double(("a", "append"), ("s", "set note"),),
|
||||||
|
double((",.", "note"), ("<>", "triplet"), ),
|
||||||
|
double(("[]", "phrase"), ("{}", "order"), ),
|
||||||
|
double(("q", "enqueue"), ("e", "edit"), ),
|
||||||
|
]))
|
||||||
|
},
|
||||||
|
Fill::wh(Align::se({
|
||||||
|
Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), row!([
|
||||||
|
&self.cpu,
|
||||||
|
&self.res,
|
||||||
|
&self.size,
|
||||||
|
]))
|
||||||
|
})),
|
||||||
|
])));
|
||||||
|
|
||||||
|
impl Bar for ArrangerStatus {
|
||||||
|
type State = (ArrangerFocus, ArrangerSelection, bool);
|
||||||
|
fn hotkey_fg () -> Color where Self: Sized {
|
||||||
|
TuiTheme::HOTKEY_FG
|
||||||
|
}
|
||||||
|
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
||||||
|
*self = match focused {
|
||||||
|
//ArrangerFocus::Menu => { todo!() },
|
||||||
|
ArrangerFocus::Transport(_) => ArrangerStatus::Transport,
|
||||||
|
ArrangerFocus::Arranger => match selected {
|
||||||
|
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
|
||||||
|
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
|
||||||
|
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
|
||||||
|
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
|
||||||
|
},
|
||||||
|
ArrangerFocus::Phrases => ArrangerStatus::PhrasePool,
|
||||||
|
ArrangerFocus::PhraseEditor => match entered {
|
||||||
|
true => ArrangerStatus::PhraseEdit,
|
||||||
|
false => ArrangerStatus::PhraseView,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render!(<Tui>|self: ArrangerStatus|{
|
||||||
|
|
||||||
|
let label = match self {
|
||||||
|
Self::Transport => "TRANSPORT",
|
||||||
|
Self::ArrangerMix => "PROJECT",
|
||||||
|
Self::ArrangerTrack => "TRACK",
|
||||||
|
Self::ArrangerScene => "SCENE",
|
||||||
|
Self::ArrangerClip => "CLIP",
|
||||||
|
Self::PhrasePool => "SEQ LIST",
|
||||||
|
Self::PhraseView => "VIEW SEQ",
|
||||||
|
Self::PhraseEdit => "EDIT SEQ",
|
||||||
|
};
|
||||||
|
|
||||||
|
let status_bar_bg = TuiTheme::status_bar_bg();
|
||||||
|
|
||||||
|
let mode_bg = TuiTheme::mode_bg();
|
||||||
|
let mode_fg = TuiTheme::mode_fg();
|
||||||
|
let mode = Tui::fg(mode_fg, Tui::bg(mode_bg, Tui::bold(true, format!(" {label} "))));
|
||||||
|
|
||||||
|
let commands = match self {
|
||||||
|
Self::ArrangerMix => Self::command(&[
|
||||||
|
["", "c", "olor"],
|
||||||
|
["", "<>", "resize"],
|
||||||
|
["", "+-", "zoom"],
|
||||||
|
["", "n", "ame/number"],
|
||||||
|
["", "Enter", " stop all"],
|
||||||
|
]),
|
||||||
|
Self::ArrangerClip => Self::command(&[
|
||||||
|
["", "g", "et"],
|
||||||
|
["", "s", "et"],
|
||||||
|
["", "a", "dd"],
|
||||||
|
["", "i", "ns"],
|
||||||
|
["", "d", "up"],
|
||||||
|
["", "e", "dit"],
|
||||||
|
["", "c", "olor"],
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["", ",.", "select"],
|
||||||
|
["", "Enter", " launch"],
|
||||||
|
]),
|
||||||
|
Self::ArrangerTrack => Self::command(&[
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["", ",.", "resize"],
|
||||||
|
["", "<>", "move"],
|
||||||
|
["", "i", "nput"],
|
||||||
|
["", "o", "utput"],
|
||||||
|
["", "m", "ute"],
|
||||||
|
["", "s", "olo"],
|
||||||
|
["", "Del", "ete"],
|
||||||
|
["", "Enter", " stop"],
|
||||||
|
]),
|
||||||
|
Self::ArrangerScene => Self::command(&[
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["", "Del", "ete"],
|
||||||
|
["", "Enter", " launch"],
|
||||||
|
]),
|
||||||
|
Self::PhrasePool => Self::command(&[
|
||||||
|
["", "a", "ppend"],
|
||||||
|
["", "i", "nsert"],
|
||||||
|
["", "d", "uplicate"],
|
||||||
|
["", "Del", "ete"],
|
||||||
|
["", "c", "olor"],
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["leng", "t", "h"],
|
||||||
|
["", ",.", "move"],
|
||||||
|
["", "+-", "resize view"],
|
||||||
|
]),
|
||||||
|
Self::PhraseView => Self::command(&[
|
||||||
|
["", "enter", " edit"],
|
||||||
|
["", "arrows/pgup/pgdn", " scroll"],
|
||||||
|
["", "+=", "zoom"],
|
||||||
|
]),
|
||||||
|
Self::PhraseEdit => Self::command(&[
|
||||||
|
["", "esc", " exit"],
|
||||||
|
["", "a", "ppend"],
|
||||||
|
["", "s", "et"],
|
||||||
|
["", "][", "length"],
|
||||||
|
["", "+-", "zoom"],
|
||||||
|
]),
|
||||||
|
_ => Self::command(&[])
|
||||||
|
};
|
||||||
|
|
||||||
|
//let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}"));
|
||||||
|
Tui::bg(status_bar_bg, Fill::w(row!([mode, commands])))
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue