wip: p.51, e=7, collecting tui by layer

This commit is contained in:
🪞👃🪞 2024-11-17 17:11:01 +01:00
parent b3ac9e60e3
commit 7b3c013aa7
26 changed files with 786 additions and 799 deletions

View file

@ -12,13 +12,18 @@ pub(crate) use std::fs::read_dir;
use std::fmt::Debug;
submod! {
tui_model
tui_jack
tui_handle
tui_focus
tui_status
tui_menu
tui_arranger
tui_arranger_cmd
tui_arranger_focus
tui_arranger_jack
tui_arranger_scene
tui_arranger_select
tui_arranger_status
tui_arranger_track
tui_arranger_view
@ -38,17 +43,11 @@ submod! {
//tui_sampler_cmd
tui_sequencer
tui_sequencer_cmd
tui_sequencer_jack
tui_sequencer_view
tui_sequencer_focus
tui_sequencer_status
tui_status
tui_theme
tui_transport
tui_transport_cmd
tui_transport_focus
tui_transport_jack
tui_transport_view
}

View file

@ -1,69 +1,5 @@
use crate::*;
/// Root view for standalone `tek_arranger`
pub struct ArrangerTui {
pub jack: Arc<RwLock<JackClient>>,
pub transport: jack::Transport,
pub playing: RwLock<Option<TransportState>>,
pub started: RwLock<Option<(usize, usize)>>,
pub current: Instant,
pub quant: Quantize,
pub sync: LaunchSync,
pub metronome: bool,
pub phrases: Vec<Arc<RwLock<Phrase>>>,
pub phrase: usize,
pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>,
pub name: Arc<RwLock<String>>,
pub splits: [u16;2],
pub selected: ArrangerSelection,
pub mode: ArrangerMode,
pub color: ItemColor,
pub entered: bool,
pub size: Measure<Tui>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub cursor: (usize, usize),
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
pub status_bar: Option<ArrangerStatus>,
pub history: Vec<ArrangerCommand>,
}
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 {
name: Arc::new(RwLock::new(String::new())),
phrases: vec![],
phrase: 0,
scenes: vec![],
tracks: vec![],
metronome: false,
playing: None.into(),
started: None.into(),
transport: jack.read().unwrap().transport(),
current: Instant::default(),
jack: jack.clone(),
selected: ArrangerSelection::Clip(0, 0),
mode: ArrangerMode::Vertical(2),
color: Color::Rgb(28, 35, 25).into(),
size: Measure::new(),
entered: false,
quant: Default::default(),
sync: Default::default(),
splits: [20, 20],
note_buf: vec![],
midi_buf: vec![],
cursor: (0, 0),
entered: false,
history: vec![],
size: Measure::new(),
menu_bar: None,
status_bar: None,
})
}
}
impl HasPhrases for ArrangerTui {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.phrases

View file

@ -1,12 +1,5 @@
use crate::*;
/// Handle top-level events in standalone arranger.
impl Handle<Tui> for ArrangerTui {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
ArrangerCommand::execute_with_state(self, i)
}
}
#[derive(Clone, Debug)]
pub enum ArrangerCommand {
Focus(FocusCommand),

View file

@ -1,74 +0,0 @@
use crate::*;
/// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ArrangerFocus {
/// The menu bar is focused
Menu,
/// The transport (toolbar) is focused
Transport,
/// The arrangement (grid) is focused
Arranger,
/// The phrase list (pool) is focused
PhrasePool,
/// The phrase editor (sequencer) is focused
PhraseEditor,
}
impl FocusEnter for ArrangerTui {
type Item = ArrangerFocus;
fn focus_enter (&mut self) {
use ArrangerFocus::*;
let focused = self.focused();
if !self.entered {
self.entered = focused == Arranger;
self.app.editor.entered = focused == PhraseEditor;
self.app.phrases.entered = focused == PhrasePool;
}
}
fn focus_exit (&mut self) {
if self.entered {
self.entered = false;
self.app.editor.entered = false;
self.app.phrases.entered = false;
}
}
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
None
}
}
}
/// Focus layout of arranger app
impl FocusGrid for ArrangerTui {
type Item = ArrangerFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use ArrangerFocus::*;
&[
&[Menu, Menu ],
&[Transport, Transport ],
&[Arranger, Arranger ],
&[PhrasePool, PhraseEditor],
]
}
fn focus_update (&mut self) {
use ArrangerFocus::*;
let focused = self.focused();
if let Some(mut status_bar) = self.status_bar {
status_bar.update(&(
self.focused(),
self.app.selected,
focused == PhraseEditor && self.entered
))
}
}
}

View file

@ -1,67 +0,0 @@
use crate::*;
impl JackApi for ArrangerTui {
fn jack (&self) -> &Arc<RwLock<JackClient>> {
&self.jack
}
}
impl Audio for ArrangerTui {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if TracksAudio(
&mut self.app.tracks,
&mut self.app.note_buf,
&mut self.app.midi_buf,
Default::default(),
).process(client, scope) == Control::Quit {
return Control::Quit
}
// FIXME: one of these per playing track
if let ArrangerSelection::Clip(t, s) = self.selected {
let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
if let Some(Some(Some(phrase))) = phrase {
if let Some(track) = self.tracks().get(t) {
if let Some((ref started_at, Some(ref playing))) = track.player.phrase {
let phrase = phrase.read().unwrap();
if *playing.read().unwrap() == *phrase {
let pulse = self.current().pulse.get();
let start = started_at.pulse.get();
let now = (pulse - start) % phrase.length as f64;
self.editor.now.set(now);
return Control::Continue
}
}
}
}
}
self.editor.now.set(0.);
return Control::Continue
}
}
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
}
}

View file

@ -1,120 +0,0 @@
use crate::*;
/// Status bar for arranger app
#[derive(Copy, Clone, Debug)]
pub enum ArrangerStatus {
Transport,
ArrangerMix,
ArrangerTrack,
ArrangerScene,
ArrangerClip,
PhrasePool,
PhraseView,
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::Transport => ArrangerStatus::Transport,
ArrangerFocus::Arranger => match selected {
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
},
ArrangerFocus::PhrasePool => ArrangerStatus::PhrasePool,
ArrangerFocus::PhraseEditor => match entered {
true => ArrangerStatus::PhraseEdit,
false => ArrangerStatus::PhraseView,
},
}
}
}
impl Content for ArrangerStatus {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
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 = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg);
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}"));
row!(mode, commands).fill_x().bg(status_bar_bg)
}
}

View file

@ -0,0 +1,215 @@
use crate::*;
/// Which item of the transport toolbar is focused
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransportFocus {
Menu,
Bpm,
Sync,
PlayPause,
Clock,
Quant,
}
/// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum SequencerFocus {
/// The menu bar is focused
Menu,
/// The transport (toolbar) is focused
Transport,
/// The phrase list (pool) is focused
PhrasePool,
/// The phrase editor (sequencer) is focused
PhraseEditor,
}
/// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ArrangerFocus {
/// The menu bar is focused
Menu,
/// The transport (toolbar) is focused
Transport,
/// The arrangement (grid) is focused
Arranger,
/// The phrase list (pool) is focused
PhrasePool,
/// The phrase editor (sequencer) is focused
PhraseEditor,
}
impl TransportFocus {
pub fn next (&mut self) {
*self = match self {
Self::PlayPause => Self::Bpm,
Self::Bpm => Self::Quant,
Self::Quant => Self::Sync,
Self::Sync => Self::Clock,
Self::Clock => Self::PlayPause,
}
}
pub fn prev (&mut self) {
*self = match self {
Self::PlayPause => Self::Clock,
Self::Bpm => Self::PlayPause,
Self::Quant => Self::Bpm,
Self::Sync => Self::Quant,
Self::Clock => Self::Sync,
}
}
pub fn wrap <'a, W: Widget<Engine = Tui>> (
self, parent_focus: bool, focus: Self, widget: &'a W
) -> impl Widget<Engine = Tui> + 'a {
let focused = parent_focus && focus == self;
let corners = focused.then_some(CORNERS);
let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
lay!(corners, highlight, *widget)
}
}
impl HasFocus for TransportTui {
type Item = TransportFocus;
}
impl FocusEnter for TransportTui {
type Item = TransportFocus;
fn focus_enter (&mut self) {
self.entered = true;
}
fn focus_exit (&mut self) {
self.entered = false;
}
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
None
}
}
}
impl FocusGrid for TransportTui {
type Item = TransportFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use TransportFocus::*;
&[
&[Menu],
&[Bpm, Sync, PlayPause, Clock, Quant],
]
}
fn focus_update (&mut self) {
// TODO
}
}
impl HasFocus for SequencerTui {
type Item = SequencerFocus;
}
impl FocusEnter for SequencerTui {
type Item = SequencerFocus;
fn focus_enter (&mut self) {
let focused = self.focused();
if !self.entered {
self.entered = true;
// TODO
}
}
fn focus_exit (&mut self) {
if self.entered {
self.entered = false;
// TODO
}
}
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
None
}
}
}
impl FocusGrid for SequencerTui {
type Item = SequencerFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use SequencerFocus::*;
&[
&[Menu, Menu ],
&[Transport, Transport ],
&[PhrasePool, PhraseEditor],
]
}
fn focus_update (&mut self) {
// TODO
}
}
impl FocusEnter for ArrangerTui {
type Item = ArrangerFocus;
fn focus_enter (&mut self) {
use ArrangerFocus::*;
let focused = self.focused();
if !self.entered {
self.entered = focused == Arranger;
self.app.editor.entered = focused == PhraseEditor;
self.app.phrases.entered = focused == PhrasePool;
}
}
fn focus_exit (&mut self) {
if self.entered {
self.entered = false;
self.app.editor.entered = false;
self.app.phrases.entered = false;
}
}
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
None
}
}
}
/// Focus layout of arranger app
impl FocusGrid for ArrangerTui {
type Item = ArrangerFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use ArrangerFocus::*;
&[
&[Menu, Menu ],
&[Transport, Transport ],
&[Arranger, Arranger ],
&[PhrasePool, PhraseEditor],
]
}
fn focus_update (&mut self) {
use ArrangerFocus::*;
let focused = self.focused();
if let Some(mut status_bar) = self.status_bar {
status_bar.update(&(
self.focused(),
self.app.selected,
focused == PhraseEditor && self.entered
))
}
}
}

View file

@ -0,0 +1,27 @@
use crate::*;
impl Handle<Tui> for TransportTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
TransportCommand::execute_with_state(self, from)
}
}
impl Handle<Tui> for SequencerTui {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
SequencerCommand::execute_with_state(self, i)
}
}
impl Handle<Tui> for ArrangerTui {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
ArrangerCommand::execute_with_state(self, i)
}
}
impl Handle<Tui> for PhrasesTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhrasesCommand::execute_with_state(self, from)
}
}
impl Handle<Tui> for PhraseTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhraseCommand::execute_with_state(self, from)
}
}

View file

@ -0,0 +1,68 @@
use crate::*;
/// Create app state from JACK handle.
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 {
metronome: false,
transport: jack.read().unwrap().transport(),
jack: jack.clone(),
focused: false,
focus: TransportFocus::PlayPause,
size: Measure::new(),
})
}
}
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::new(SequencerTui {
phrases: vec![],
metronome: false,
transport: jack.read().unwrap().transport(),
jack: jack.clone(),
focused: false,
focus: TransportFocus::PlayPause,
size: Measure::new(),
phrases: vec![],
}.into(), None, None))
}
}
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 {
name: Arc::new(RwLock::new(String::new())),
phrases: vec![],
phrase: 0,
scenes: vec![],
tracks: vec![],
metronome: false,
playing: None.into(),
started: None.into(),
transport: jack.read().unwrap().transport(),
current: Instant::default(),
jack: jack.clone(),
selected: ArrangerSelection::Clip(0, 0),
mode: ArrangerMode::Vertical(2),
color: Color::Rgb(28, 35, 25).into(),
size: Measure::new(),
entered: false,
quant: Default::default(),
sync: Default::default(),
splits: [20, 20],
note_buf: vec![],
midi_buf: vec![],
cursor: (0, 0),
entered: false,
history: vec![],
size: Measure::new(),
menu_bar: None,
status_bar: None,
})
}
}

View file

@ -0,0 +1,191 @@
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 SequencerTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
self.model.process(client, scope)
}
}
impl Audio for ArrangerTui {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if TracksAudio(
&mut self.app.tracks,
&mut self.app.note_buf,
&mut self.app.midi_buf,
Default::default(),
).process(client, scope) == Control::Quit {
return Control::Quit
}
// FIXME: one of these per playing track
if let ArrangerSelection::Clip(t, s) = self.selected {
let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
if let Some(Some(Some(phrase))) = phrase {
if let Some(track) = self.tracks().get(t) {
if let Some((ref started_at, Some(ref playing))) = track.player.phrase {
let phrase = phrase.read().unwrap();
if *playing.read().unwrap() == *phrase {
let pulse = self.current().pulse.get();
let start = started_at.pulse.get();
let now = (pulse - start) % phrase.length as f64;
self.editor.now.set(now);
return Control::Continue
}
}
}
}
}
self.editor.now.set(0.);
return Control::Continue
}
}
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

@ -0,0 +1 @@
use crate::*;

View file

@ -0,0 +1,100 @@
use crate::*;
/// Stores and displays time-related info.
pub struct TransportTui {
jack: Arc<RwLock<JackClient>>,
/// Playback state
playing: RwLock<Option<TransportState>>,
/// Global sample and usec at which playback started
started: RwLock<Option<(usize, usize)>>,
/// Current moment in time
current: Instant,
/// Note quantization factor
quant: Quantize,
/// Launch quantization factor
sync: LaunchSync,
/// JACK transport handle.
transport: jack::Transport,
/// Enable metronome?
metronome: bool,
focus: TransportFocus,
focused: bool,
size: Measure<Tui>,
}
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 {
jack: Arc<RwLock<JackClient>>,
playing: RwLock<Option<TransportState>>,
started: RwLock<Option<(usize, usize)>>,
current: Instant,
quant: Quantize,
sync: LaunchSync,
transport: jack::Transport,
metronome: bool,
phrases: Vec<Arc<RwLock<Phrase>>>,
view_phrase: usize,
split: u16,
/// Start time and phrase being played
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Start time and next phrase
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Play input through output.
monitoring: bool,
/// Write input to sequence.
recording: bool,
/// Overdub input to sequence.
overdub: bool,
/// Send all notes off
reset: bool, // TODO?: after Some(nframes)
/// Record from MIDI ports to current sequence.
midi_inputs: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
midi_outputs: Vec<Port<MidiOut>>,
/// MIDI output buffer
midi_note: Vec<u8>,
/// MIDI output buffer
midi_chunk: Vec<Vec<Vec<u8>>>,
/// Notes currently held at input
notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
notes_out: Arc<RwLock<[bool; 128]>>,
}
/// Root view for standalone `tek_arranger`
pub struct ArrangerTui {
pub jack: Arc<RwLock<JackClient>>,
pub transport: jack::Transport,
pub playing: RwLock<Option<TransportState>>,
pub started: RwLock<Option<(usize, usize)>>,
pub current: Instant,
pub quant: Quantize,
pub sync: LaunchSync,
pub metronome: bool,
pub phrases: Vec<Arc<RwLock<Phrase>>>,
pub phrase: usize,
pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>,
pub name: Arc<RwLock<String>>,
pub splits: [u16;2],
pub selected: ArrangerSelection,
pub mode: ArrangerMode,
pub color: ItemColor,
pub entered: bool,
pub size: Measure<Tui>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub cursor: (usize, usize),
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
pub status_bar: Option<ArrangerStatus>,
pub history: Vec<ArrangerCommand>,
}

View file

@ -1,11 +1,5 @@
use crate::*;
impl Handle<Tui> for PhraseTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhraseCommand::execute_with_state(self, from)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum PhraseCommand {
// TODO: 1-9 seek markers that by default start every 8th of the phrase

View file

@ -1,11 +1,5 @@
use crate::*;
impl Handle<Tui> for PhrasesTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhrasesCommand::execute_with_state(self, from)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum PhrasesCommand {
Select(usize),

View file

@ -1,79 +1,5 @@
use crate::*;
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::new(SequencerTui {
phrases: vec![],
metronome: false,
transport: jack.read().unwrap().transport(),
jack: jack.clone(),
focused: false,
focus: TransportFocus::PlayPause,
size: Measure::new(),
phrases: vec![],
}.into(), None, None))
}
}
/// Root view for standalone `tek_sequencer`.
pub struct SequencerTui {
jack: Arc<RwLock<JackClient>>,
playing: RwLock<Option<TransportState>>,
started: RwLock<Option<(usize, usize)>>,
current: Instant,
quant: Quantize,
sync: LaunchSync,
transport: jack::Transport,
metronome: bool,
phrases: Vec<Arc<RwLock<Phrase>>>,
view_phrase: usize,
split: u16,
/// Start time and phrase being played
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Start time and next phrase
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Play input through output.
monitoring: bool,
/// Write input to sequence.
recording: bool,
/// Overdub input to sequence.
overdub: bool,
/// Send all notes off
reset: bool, // TODO?: after Some(nframes)
/// Record from MIDI ports to current sequence.
midi_inputs: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
midi_outputs: Vec<Port<MidiOut>>,
/// MIDI output buffer
midi_note: Vec<u8>,
/// MIDI output buffer
midi_chunk: Vec<Vec<Vec<u8>>>,
/// Notes currently held at input
notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
notes_out: Arc<RwLock<[bool; 128]>>,
}
/// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum SequencerFocus {
/// The menu bar is focused
Menu,
/// The transport (toolbar) is focused
Transport,
/// The phrase list (pool) is focused
PhrasePool,
/// The phrase editor (sequencer) is focused
PhraseEditor,
}
impl Audio for SequencerTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
self.model.process(client, scope)
}
}
impl HasPhrases for SequencerTui {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.phrases

View file

@ -1,11 +1,5 @@
use crate::*;
impl Handle<Tui> for SequencerTui {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
SequencerCommand::execute_with_state(self, i)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SequencerCommand {
Focus(FocusCommand),

View file

@ -1,50 +0,0 @@
use crate::*;
impl HasFocus for SequencerTui {
type Item = SequencerFocus;
}
impl FocusEnter for SequencerTui {
type Item = SequencerFocus;
fn focus_enter (&mut self) {
let focused = self.focused();
if !self.entered {
self.entered = true;
// TODO
}
}
fn focus_exit (&mut self) {
if self.entered {
self.entered = false;
// TODO
}
}
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
None
}
}
}
impl FocusGrid for SequencerTui {
type Item = SequencerFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use SequencerFocus::*;
&[
&[Menu, Menu ],
&[Transport, Transport ],
&[PhrasePool, PhraseEditor],
]
}
fn focus_update (&mut self) {
// TODO
}
}

View file

@ -1,81 +0,0 @@
use crate::*;
impl JackApi for SequencerTui {
fn jack (&self) -> &Arc<RwLock<JackClient>> {
&self.jack
}
}
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,27 +0,0 @@
use crate::*;
/// Status bar for sequencer app
#[derive(Copy, Clone)]
pub enum SequencerStatusBar {
Transport,
PhrasePool,
PhraseEditor,
}
impl StatusBar for SequencerStatusBar {
type State = ();
fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg()
}
fn update (&mut self, state: &()) {
todo!()
}
}
impl Content for SequencerStatusBar {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
todo!();
""
}
}

View file

@ -26,3 +26,169 @@ pub trait StatusBar: Copy + Widget<Engine = Tui> {
})
}
}
#[derive(Copy, Clone)]
pub struct TransportStatusBar;
impl StatusBar for TransportStatusBar {
type State = ();
fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg()
}
fn update (&mut self, state: &()) {
todo!()
}
}
impl Content for TransportStatusBar {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
todo!();
""
}
}
/// Status bar for sequencer app
#[derive(Copy, Clone)]
pub enum SequencerStatusBar {
Transport,
PhrasePool,
PhraseEditor,
}
impl StatusBar for SequencerStatusBar {
type State = ();
fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg()
}
fn update (&mut self, state: &()) {
todo!()
}
}
impl Content for SequencerStatusBar {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
todo!();
""
}
}
/// Status bar for arranger app
#[derive(Copy, Clone, Debug)]
pub enum ArrangerStatus {
Transport,
ArrangerMix,
ArrangerTrack,
ArrangerScene,
ArrangerClip,
PhrasePool,
PhraseView,
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::Transport => ArrangerStatus::Transport,
ArrangerFocus::Arranger => match selected {
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
},
ArrangerFocus::PhrasePool => ArrangerStatus::PhrasePool,
ArrangerFocus::PhraseEditor => match entered {
true => ArrangerStatus::PhraseEdit,
false => ArrangerStatus::PhraseView,
},
}
}
}
impl Content for ArrangerStatus {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
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 = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg);
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}"));
row!(mode, commands).fill_x().bg(status_bar_bg)
}
}

View file

@ -1,47 +1 @@
use crate::*;
/// Stores and displays time-related info.
pub struct TransportTui {
jack: Arc<RwLock<JackClient>>,
/// Playback state
playing: RwLock<Option<TransportState>>,
/// Global sample and usec at which playback started
started: RwLock<Option<(usize, usize)>>,
/// Current moment in time
current: Instant,
/// Note quantization factor
quant: Quantize,
/// Launch quantization factor
sync: LaunchSync,
/// JACK transport handle.
transport: jack::Transport,
/// Enable metronome?
metronome: bool,
focus: TransportFocus,
focused: bool,
size: Measure<Tui>,
}
/// Create app state from JACK handle.
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 {
metronome: false,
transport: jack.read().unwrap().transport(),
jack: jack.clone(),
focused: false,
focus: TransportFocus::PlayPause,
size: Measure::new(),
})
}
}
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()
}
}

View file

@ -1,12 +1,5 @@
use crate::*;
/// Handle input.
impl Handle<Tui> for TransportTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
TransportCommand::execute_with_state(self, from)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TransportCommand {
Focus(FocusCommand),

View file

@ -1,82 +0,0 @@
use crate::*;
/// Which item of the transport toolbar is focused
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransportFocus {
Menu,
Bpm,
Sync,
PlayPause,
Clock,
Quant,
}
impl TransportFocus {
pub fn next (&mut self) {
*self = match self {
Self::PlayPause => Self::Bpm,
Self::Bpm => Self::Quant,
Self::Quant => Self::Sync,
Self::Sync => Self::Clock,
Self::Clock => Self::PlayPause,
}
}
pub fn prev (&mut self) {
*self = match self {
Self::PlayPause => Self::Clock,
Self::Bpm => Self::PlayPause,
Self::Quant => Self::Bpm,
Self::Sync => Self::Quant,
Self::Clock => Self::Sync,
}
}
pub fn wrap <'a, W: Widget<Engine = Tui>> (
self, parent_focus: bool, focus: Self, widget: &'a W
) -> impl Widget<Engine = Tui> + 'a {
let focused = parent_focus && focus == self;
let corners = focused.then_some(CORNERS);
let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
lay!(corners, highlight, *widget)
}
}
impl HasFocus for TransportTui {
type Item = TransportFocus;
}
impl FocusEnter for TransportTui {
type Item = TransportFocus;
fn focus_enter (&mut self) {
self.entered = true;
}
fn focus_exit (&mut self) {
self.entered = false;
}
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
None
}
}
}
impl FocusGrid for TransportTui {
type Item = TransportFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use TransportFocus::*;
&[
&[Menu],
&[Bpm, Sync, PlayPause, Clock, Quant],
]
}
fn focus_update (&mut self) {
// TODO
}
}

View file

@ -1,40 +0,0 @@
use crate::*;
impl Audio for TransportTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
PlayheadAudio(self).process(client, scope)
}
}
impl JackApi for TransportTui {
fn jack (&self) -> &Arc<RwLock<JackClient>> {
&self.jack
}
}
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
}
}

View file

@ -1,22 +0,0 @@
use crate::*;
#[derive(Copy, Clone)]
pub struct TransportStatusBar;
impl StatusBar for TransportStatusBar {
type State = ();
fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg()
}
fn update (&mut self, state: &()) {
todo!()
}
}
impl Content for TransportStatusBar {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
todo!();
""
}
}