wip: p.42, e=22, more intermediary trait layers

This commit is contained in:
🪞👃🪞 2024-11-16 21:31:04 +01:00
parent 638298ad32
commit dbf6e353b7
15 changed files with 937 additions and 688 deletions

View file

@ -1,6 +1,6 @@
use crate::*; use crate::*;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub enum ClockCommand { pub enum ClockCommand {
SetBpm(f64), SetBpm(f64),
SetQuant(f64), SetQuant(f64),

View file

@ -7,27 +7,10 @@ pub trait HasPlayer: HasJack {
pub trait PlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {} pub trait PlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
pub trait HasMidiBuffer { pub trait HasPhrase: PlayheadApi {
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>>;
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>>;
fn reset (&self) -> bool; fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool; fn reset_mut (&mut self) -> &mut bool;
/// Clear the section of the output buffer that we will be using,
/// emitting "all notes off" at start of buffer if requested.
fn clear (&mut self, scope: &ProcessScope, force_reset: bool) {
for frame in &mut self.midi_buffer_mut()[0..scope.n_frames() as usize] {
frame.clear();
}
if self.reset() || force_reset {
all_notes_off(&mut self.midi_buffer_mut());
*self.reset_mut() = false;
}
}
}
pub trait HasPhrase: PlayheadApi + HasMidiBuffer {
fn phrase (&self) fn phrase (&self)
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>; -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
fn phrase_mut (&self) fn phrase_mut (&self)
@ -54,7 +37,7 @@ pub trait HasPhrase: PlayheadApi + HasMidiBuffer {
} }
} }
pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { pub trait MidiInputApi: PlayheadApi + HasPhrase {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>; fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&self) -> &mut Vec<Port<MidiIn>>; fn midi_ins_mut (&self) -> &mut Vec<Port<MidiIn>>;
fn has_midi_ins (&self) -> bool { fn has_midi_ins (&self) -> bool {
@ -80,7 +63,11 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>; fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
fn record (&mut self, scope: &ProcessScope) { fn record (
&mut self,
scope: &ProcessScope,
midi_buf: &mut Vec<Vec<Vec<u8>>>,
) {
let sample0 = scope.last_frame_time() as usize; let sample0 = scope.last_frame_time() as usize;
if let (true, Some((started, phrase))) = (self.is_rolling(), self.phrase()) { if let (true, Some((started, phrase))) = (self.is_rolling(), self.phrase()) {
let start = started.sample.get() as usize; let start = started.sample.get() as usize;
@ -92,7 +79,7 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event { if let LiveEvent::Midi { message, .. } = event {
if self.monitoring() { if self.monitoring() {
self.midi_buffer_mut()[sample].push(bytes.to_vec()) midi_buf[sample].push(bytes.to_vec())
} }
if self.recording() { if self.recording() {
if let Some(phrase) = phrase { if let Some(phrase) = phrase {
@ -117,12 +104,16 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
} }
} }
fn monitor (&mut self, scope: &ProcessScope) { fn monitor (
&mut self,
scope: &ProcessScope,
midi_buf: &mut Vec<Vec<Vec<u8>>>,
) {
let mut notes_in = self.notes_in().write().unwrap(); let mut notes_in = self.notes_in().write().unwrap();
for input in self.midi_ins_mut().iter() { for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event { if let LiveEvent::Midi { message, .. } = event {
self.midi_buffer_mut()[sample].push(bytes.to_vec()); midi_buf[sample].push(bytes.to_vec());
update_keys(&mut notes_in, &message); update_keys(&mut notes_in, &message);
} }
} }
@ -131,7 +122,7 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
} }
pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase { pub trait MidiOutputApi: PlayheadApi + HasPhrase {
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>>;
@ -144,6 +135,22 @@ pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
self.midi_outs().len() > 0 self.midi_outs().len() > 0
} }
/// Clear the section of the output buffer that we will be using,
/// emitting "all notes off" at start of buffer if requested.
fn clear (
&mut self,
scope: &ProcessScope,
midi_buf: &mut Vec<Vec<Vec<u8>>>,
reset: bool
) {
for frame in &mut midi_buf[0..scope.n_frames() as usize] {
frame.clear();
}
if reset {
all_notes_off(midi_buf);
}
}
fn play ( fn play (
&mut self, &mut self,
scope: &ProcessScope, scope: &ProcessScope,
@ -298,26 +305,26 @@ pub struct PlayerAudio<'a, T: PlayerApi>(
/// JACK process callback for a sequencer's phrase player/recorder. /// JACK process callback for a sequencer's phrase player/recorder.
impl<'a, T: PlayerApi> Audio for PlayerAudio<'a, T> { impl<'a, T: PlayerApi> Audio for PlayerAudio<'a, T> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let model = &mut self.0; let model = &mut self.0;
let note_buffer = &mut self.1; let note_buf = &mut self.1;
let output_buffer = &mut self.2; let midi_buf = &mut self.2;
// Clear output buffer(s) // Clear output buffer(s)
model.clear(scope, false); model.clear(scope, midi_buf, false);
// Write chunk of phrase to output, handle switchover // Write chunk of phrase to output, handle switchover
if model.play(scope, note_buffer, output_buffer) { if model.play(scope, note_buf, midi_buf) {
model.switchover(scope, note_buffer, output_buffer); model.switchover(scope, note_buf, midi_buf);
} }
if model.has_midi_ins() { if model.has_midi_ins() {
if model.recording() || model.monitoring() { if model.recording() || model.monitoring() {
// Record and/or monitor input // Record and/or monitor input
model.record(scope) model.record(scope, midi_buf)
} else if model.has_midi_outs() && model.monitoring() { } else if model.has_midi_outs() && model.monitoring() {
// Monitor input to output // Monitor input to output
model.monitor(scope) model.monitor(scope, midi_buf)
} }
} }
// Write to output port(s) // Write to output port(s)
model.write(scope, output_buffer); model.write(scope, midi_buf);
Control::Continue Control::Continue
} }
} }

View file

@ -1,6 +1,6 @@
use crate::*; use crate::*;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub enum PlayheadCommand { pub enum PlayheadCommand {
Play(Option<usize>), Play(Option<usize>),
Pause(Option<usize>), Pause(Option<usize>),

View file

@ -3,6 +3,18 @@ use crate::*;
pub trait HasTracks<T: ArrangerTrackApi>: Send + Sync { pub trait HasTracks<T: ArrangerTrackApi>: Send + Sync {
fn tracks (&self) -> &Vec<T>; fn tracks (&self) -> &Vec<T>;
fn tracks_mut (&mut self) -> &mut Vec<T>; fn tracks_mut (&mut self) -> &mut Vec<T>;
}
impl<T: ArrangerTrackApi> HasTracks<T> for Vec<T> {
fn tracks (&self) -> &Vec<T> {
self
}
fn tracks_mut (&mut self) -> &mut Vec<T> {
self
}
}
pub trait ArrangerTracksApi<T: ArrangerTrackApi>: HasTracks<T> {
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)-> Usually<&mut T>; fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)-> Usually<&mut T>;
fn track_del (&mut self, index: usize); fn track_del (&mut self, index: usize);
fn track_default_name (&self) -> String { fn track_default_name (&self) -> String {
@ -21,16 +33,6 @@ pub enum ArrangerTrackCommand {
SetZoom(usize), SetZoom(usize),
} }
//impl<T: ArrangerApi> Command<T> for ArrangerTrackCommand {
//fn execute (self, state: &mut T) -> Perhaps<Self> {
//match self {
//Self::Delete(index) => { state.track_del(index); },
//_ => todo!()
//}
//Ok(None)
//}
//}
pub trait ArrangerTrackApi: PlayerApi + Send + Sync + Sized { pub trait ArrangerTrackApi: PlayerApi + Send + Sync + Sized {
/// Name of track /// Name of track
fn name (&self) -> &Arc<RwLock<String>>; fn name (&self) -> &Arc<RwLock<String>>;

View file

@ -12,7 +12,7 @@ pub enum FocusCommand {
Exit Exit
} }
impl<F: FocusGrid> Command<F> for FocusCommand { impl<F: FocusOrder + FocusGrid + FocusEnter> Command<F> for FocusCommand {
fn execute (self, state: &mut F) -> Perhaps<FocusCommand> { fn execute (self, state: &mut F) -> Perhaps<FocusCommand> {
use FocusCommand::*; use FocusCommand::*;
match self { match self {
@ -24,93 +24,124 @@ impl<F: FocusGrid> Command<F> for FocusCommand {
Right => { state.focus_right(); }, Right => { state.focus_right(); },
Enter => { state.focus_enter(); }, Enter => { state.focus_enter(); },
Exit => { state.focus_exit(); }, Exit => { state.focus_exit(); },
_ => {}
} }
Ok(None) Ok(None)
} }
} }
pub trait FocusGrid { pub trait HasFocus {
type Item: Copy + PartialEq + Debug;
fn focused (&self) -> Self::Item;
fn focus (&mut self, target: Self::Item);
}
pub trait FocusOrder {
fn focus_next (&mut self);
fn focus_prev (&mut self);
}
pub trait FocusEnter {
type Item: Copy + PartialEq + Debug; type Item: Copy + PartialEq + Debug;
fn layout (&self) -> &[&[Self::Item]];
fn cursor (&self) -> (usize, usize);
fn cursor_mut (&mut self) -> &mut (usize, usize);
fn update_focus (&mut self) {}
fn focus_enter (&mut self) {} fn focus_enter (&mut self) {}
fn focus_exit (&mut self) {} fn focus_exit (&mut self) {}
fn entered (&self) -> Option<Self::Item>; fn focus_entered (&self) -> Option<Self::Item>;
}
pub trait FocusGrid {
type Item: Copy + PartialEq + Debug;
fn focus_layout (&self) -> &[&[Self::Item]];
fn focus_cursor (&self) -> (usize, usize);
fn focus_cursor_mut (&mut self) -> &mut (usize, usize);
fn focus_update (&mut self) {}
fn focus_up (&mut self) {
let layout = self.focus_layout();
let (x, y) = self.focus_cursor();
let next_y = if y == 0 { layout.len().saturating_sub(1) } else { y - 1 };
let next_x = if layout[y].len() == layout[next_y].len() { x } else {
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
};
*self.focus_cursor_mut() = (next_x, next_y);
self.focus_update();
}
fn focus_down (&mut self) {
let layout = self.focus_layout();
let (x, y) = self.focus_cursor();
let next_y = if y >= layout.len().saturating_sub(1) { 0 } else { y + 1 };
let next_x = if layout[y].len() == layout[next_y].len() { x } else {
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
};
*self.focus_cursor_mut() = (next_x, next_y);
self.focus_update();
}
fn focus_left (&mut self) {
let layout = self.focus_layout();
let (x, y) = self.focus_cursor();
let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 };
*self.focus_cursor_mut() = (next_x, y);
self.focus_update();
}
fn focus_right (&mut self) {
let layout = self.focus_layout();
let (x, y) = self.focus_cursor();
let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 };
*self.focus_cursor_mut() = (next_x, y);
self.focus_update();
}
}
impl<T, U> HasFocus for U
where
T: Copy + PartialEq + Debug,
U: FocusGrid<Item = T> + FocusOrder,
{
type Item = T;
fn focused (&self) -> Self::Item { fn focused (&self) -> Self::Item {
let (x, y) = self.cursor(); let (x, y) = self.focus_cursor();
self.layout()[y][x] self.focus_layout()[y][x]
} }
fn focus (&mut self, target: Self::Item) { fn focus (&mut self, target: Self::Item) {
while self.focused() != target { while self.focused() != target {
self.focus_next() self.focus_next()
} }
} }
fn focus_up (&mut self) { }
let layout = self.layout();
let (x, y) = self.cursor(); impl<T, U> FocusOrder for U
let next_y = if y == 0 { layout.len().saturating_sub(1) } else { y - 1 }; where
let next_x = if layout[y].len() == layout[next_y].len() { x } else { T: Copy + PartialEq + Debug,
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize U: HasFocus<Item = T> + FocusGrid<Item = T> + FocusEnter
}; {
*self.cursor_mut() = (next_x, next_y);
self.update_focus();
}
fn focus_down (&mut self) {
let layout = self.layout();
let (x, y) = self.cursor();
let next_y = if y >= layout.len().saturating_sub(1) { 0 } else { y + 1 };
let next_x = if layout[y].len() == layout[next_y].len() { x } else {
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
};
*self.cursor_mut() = (next_x, next_y);
self.update_focus();
}
fn focus_left (&mut self) {
let layout = self.layout();
let (x, y) = self.cursor();
let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 };
*self.cursor_mut() = (next_x, y);
self.update_focus();
}
fn focus_right (&mut self) {
let layout = self.layout();
let (x, y) = self.cursor();
let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 };
*self.cursor_mut() = (next_x, y);
self.update_focus();
}
fn focus_next (&mut self) { fn focus_next (&mut self) {
let current = self.focused(); let current = self.focused();
let (x, y) = self.cursor(); let (x, y) = self.focus_cursor();
if x < self.layout()[y].len().saturating_sub(1) { if x < self.focus_layout()[y].len().saturating_sub(1) {
self.focus_right(); self.focus_right();
} else { } else {
self.focus_down(); self.focus_down();
self.cursor_mut().0 = 0; self.focus_cursor_mut().0 = 0;
} }
if self.focused() == current { // FIXME: prevent infinite loop if self.focused() == current { // FIXME: prevent infinite loop
self.focus_next() self.focus_next()
} }
self.focus_exit(); self.focus_exit();
self.update_focus(); self.focus_update();
} }
fn focus_prev (&mut self) { fn focus_prev (&mut self) {
let current = self.focused(); let current = self.focused();
let (x, _) = self.cursor(); let (x, _) = self.focus_cursor();
if x > 0 { if x > 0 {
self.focus_left(); self.focus_left();
} else { } else {
self.focus_up(); self.focus_up();
let (_, y) = self.cursor(); let (_, y) = self.focus_cursor();
let next_x = self.layout()[y].len().saturating_sub(1); let next_x = self.focus_layout()[y].len().saturating_sub(1);
self.cursor_mut().0 = next_x; self.focus_cursor_mut().0 = next_x;
} }
if self.focused() == current { // FIXME: prevent infinite loop if self.focused() == current { // FIXME: prevent infinite loop
self.focus_prev() self.focus_prev()
} }
self.focus_exit(); self.focus_exit();
self.update_focus(); self.focus_update();
} }
} }

View file

@ -883,6 +883,7 @@ impl<E: Engine> Measure<E> {
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) } pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) }
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); } pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) } pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) }
pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) }
} }
impl<E: Engine> Widget for Measure<E> { impl<E: Engine> Widget for Measure<E> {

View file

@ -13,6 +13,8 @@ use std::fmt::Debug;
submod! { submod! {
tui_arranger tui_arranger
tui_arranger_track
tui_arranger_scene
//tui_mixer // TODO //tui_mixer // TODO
tui_phrase tui_phrase
//tui_plugin // TODO //tui_plugin // TODO
@ -24,9 +26,11 @@ submod! {
//tui_sampler // TODO //tui_sampler // TODO
//tui_sampler_cmd //tui_sampler_cmd
tui_sequencer tui_sequencer
tui_sequencer_cmd
tui_status tui_status
tui_theme tui_theme
tui_transport tui_transport
tui_transport_cmd
} }
pub struct AppView<E, A, C, S> pub struct AppView<E, A, C, S>

View file

@ -6,17 +6,25 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerApp<Tui> {
Ok(Self::new(ArrangerView { Ok(Self::new(ArrangerView {
name: Arc::new(RwLock::new(String::new())), name: Arc::new(RwLock::new(String::new())),
phrases: vec![], phrases: vec![],
phrase: 0,
scenes: vec![], scenes: vec![],
tracks: vec![], tracks: vec![],
metronome: false, metronome: false,
playing: None.into(),
started: None.into(),
transport: jack.read().unwrap().transport(), transport: jack.read().unwrap().transport(),
current: Instant::default(),
jack: jack.clone(), jack: jack.clone(),
selected: ArrangerSelection::Clip(0, 0), selected: ArrangerSelection::Clip(0, 0),
mode: ArrangerMode::Vertical(2), mode: ArrangerMode::Vertical(2),
color: Color::Rgb(28, 35, 25).into(), color: Color::Rgb(28, 35, 25).into(),
size: Measure::new(), size: Measure::new(),
focused: false,
entered: false, entered: false,
quant: Default::default(),
sync: Default::default(),
splits: [20, 20],
note_buf: vec![],
midi_buf: vec![],
}.into(), None, None)) }.into(), None, None))
} }
} }
@ -28,9 +36,14 @@ pub type ArrangerApp<E: Engine> = AppView<
ArrangerStatusBar ArrangerStatusBar
>; >;
impl<E: Engine> Audio for ArrangerApp<E> { impl Audio for ArrangerApp<Tui> {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
ArrangerRefAudio(self.app).process(client, scope) TracksAudio(
&mut self.app.tracks,
&mut self.app.note_buf,
&mut self.app.midi_buf,
Default::default(),
).process(client, scope)
} }
} }
@ -78,39 +91,35 @@ impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
Self::App(Playhead(PlayheadCommand::Play(None))) Self::App(Playhead(PlayheadCommand::Play(None)))
}, },
_ => Self::App(match view.focused() { _ => Self::App(match view.focused() {
Content(ArrangerViewFocus::Transport) => { Content(ArrangerFocus::Transport) => {
match TransportCommand::input_to_command(&view.app.transport, input)? { use TransportCommand::{Clock, Playhead};
Focus(command) => { match TransportCommand::input_to_command(view, input)? {
Clock(command) => {
todo!() todo!()
}, },
App(Clock(command)) => { Playhead(command) => {
todo!()
},
App(Playhead(command)) => {
todo!() todo!()
}, },
} }
}, },
Content(ArrangerViewFocus::PhraseEditor) => Editor( Content(ArrangerFocus::PhraseEditor) => Editor(
PhraseEditorCommand::input_to_command(&view.app.editor, input)? PhraseEditorCommand::input_to_command(&view.app.editor, input)?
), ),
Content(ArrangerViewFocus::PhrasePool) => match input.event() { Content(ArrangerFocus::PhrasePool) => match input.event() {
key!(KeyCode::Char('e')) => EditPhrase( key!(KeyCode::Char('e')) => EditPhrase(
Some(view.app.phrases.phrase().clone()) Some(view.app.phrase().clone())
), ),
_ => Phrases( _ => Phrases(
PhrasePoolViewCommand::input_to_command(&view.app.phrases, input)? PhrasePoolViewCommand::input_to_command(view, input)?
) )
}, },
Content(ArrangerViewFocus::Arranger) => { Content(ArrangerFocus::Arranger) => {
use ArrangerSelection as Select; use ArrangerSelection as Select;
use ArrangerTrackCommand as Track; use ArrangerTrackCommand as Track;
use ArrangerClipCommand as Clip; use ArrangerClipCommand as Clip;
use ArrangerSceneCommand as Scene; use ArrangerSceneCommand as Scene;
match input.event() { match input.event() {
key!(KeyCode::Char('e')) => EditPhrase( key!(KeyCode::Char('e')) => EditPhrase(view.phrase()),
view.selected_phrase()
),
_ => match input.event() { _ => match input.event() {
// FIXME: boundary conditions // FIXME: boundary conditions
@ -250,7 +259,7 @@ impl Command<ArrangerApp<Tui>> for ArrangerViewCommand {
Select(selected) => { state.selected = selected; Ok(None) }, Select(selected) => { state.selected = selected; Ok(None) },
EditPhrase(phrase) => { EditPhrase(phrase) => {
state.editor.phrase = phrase.clone(); state.editor.phrase = phrase.clone();
state.focus(ArrangerViewFocus::PhraseEditor); state.focus(ArrangerFocus::PhraseEditor);
state.focus_enter(); state.focus_enter();
Ok(None) Ok(None)
} }
@ -277,10 +286,10 @@ pub struct ArrangerView<E: Engine> {
selected: ArrangerSelection, selected: ArrangerSelection,
mode: ArrangerMode, mode: ArrangerMode,
color: ItemColor, color: ItemColor,
editor: PhraseEditor<E>,
focused: bool,
entered: bool, entered: bool,
size: Measure<E>, size: Measure<E>,
note_buf: Vec<u8>,
midi_buf: Vec<Vec<Vec<u8>>>,
} }
impl HasJack for ArrangerView<Tui> { impl HasJack for ArrangerView<Tui> {
@ -332,15 +341,27 @@ impl HasTracks<ArrangerTrack> for ArrangerView<Tui> {
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> { fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
&mut self.tracks &mut self.tracks
} }
}
impl ArrangerTracksApi<ArrangerTrack> for ArrangerView<Tui> {
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>) fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
-> Usually<&mut ArrangerTrack> -> Usually<&mut ArrangerTrack>
{ {
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
let track = ArrangerTrack { let track = ArrangerTrack {
name: Arc::new(name.into()), width: name.len() + 2,
color: color.unwrap_or_else(||ItemColor::random()), name: Arc::new(name.into()),
width: name.len() + 2, color: color.unwrap_or_else(||ItemColor::random()),
//player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?, midi_ins: vec![],
midi_outs: vec![],
reset: true,
recording: false,
monitoring: false,
overdub: false,
play_phrase: None,
next_phrase: None,
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
}; };
self.tracks_mut().push(track); self.tracks_mut().push(track);
let index = self.tracks().len() - 1; let index = self.tracks().len() - 1;
@ -387,7 +408,7 @@ pub enum ArrangerMode {
/// Sections in the arranger app that may be focused /// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ArrangerViewFocus { pub enum ArrangerFocus {
/// The transport (toolbar) is focused /// The transport (toolbar) is focused
Transport, Transport,
/// The arrangement (grid) is focused /// The arrangement (grid) is focused
@ -444,9 +465,9 @@ impl Content for ArrangerView<Tui> {
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
Split::up( Split::up(
1, 1,
widget(&self.transport), widget(&TransportRef(self)),
Split::down( Split::down(
self.split, self.splits[0],
lay!( lay!(
Layers::new(move |add|{ Layers::new(move |add|{
match self.mode { match self.mode {
@ -471,9 +492,9 @@ impl Content for ArrangerView<Tui> {
.push_x(1), .push_x(1),
), ),
Split::right( Split::right(
self.split, self.splits[1],
widget(&self.phrases), widget(&self.phrases),
widget(&self.editor), widget(&PhraseEditorRef(self)),
) )
) )
) )
@ -481,7 +502,7 @@ impl Content for ArrangerView<Tui> {
} }
/// General methods for arranger /// General methods for arranger
impl<E: Engine> ArrangerView<E> { impl ArrangerView<Tui> {
/// Focus the editor with the current phrase /// Focus the editor with the current phrase
pub fn show_phrase (&mut self) { pub fn show_phrase (&mut self) {
@ -562,6 +583,30 @@ impl<E: Engine> ArrangerView<E> {
} }
} }
impl TransportViewState for ArrangerView<Tui> {
fn focus (&self) -> TransportViewFocus {
self.focus
}
fn focused (&self) -> bool {
self.focused
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
fn bpm_value (&self) -> f64 {
self.bpm().get()
}
fn sync_value (&self) -> f64 {
self.sync().get()
}
fn format_beat (&self) -> String {
self.current().format_beat()
}
fn format_msu (&self) -> String {
self.current().usec.format_msu()
}
}
impl<E: Engine> Audio for ArrangerView<E> { impl<E: Engine> Audio for ArrangerView<E> {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if self.process(client, scope) == Control::Quit { if self.process(client, scope) == Control::Quit {
@ -635,7 +680,7 @@ impl<E: Engine> Audio for ArrangerView<E> {
//self.arrangement.phrase_put(); //self.arrangement.phrase_put();
//} //}
//self.show_phrase(); //self.show_phrase();
//self.focus(ArrangerViewFocus::PhraseEditor); //self.focus(ArrangerFocus::PhraseEditor);
//self.editor.entered = true; //self.editor.entered = true;
//} //}
@ -672,18 +717,10 @@ impl<E: Engine> Audio for ArrangerView<E> {
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone() //self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
//} //}
/// Focus layout of arranger app impl FocusEnter for ArrangerApp<Tui> {
impl FocusGrid for ArrangerApp<Tui> {
type Item = AppViewFocus<ArrangerViewFocus>;
fn cursor (&self) -> (usize, usize) {
self.cursor
}
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_enter (&mut self) { fn focus_enter (&mut self) {
use AppViewFocus::*; use AppViewFocus::*;
use ArrangerViewFocus::*; use ArrangerFocus::*;
let focused = self.focused(); let focused = self.focused();
if !self.entered { if !self.entered {
self.entered = focused == Content(Arranger); self.entered = focused == Content(Arranger);
@ -698,16 +735,27 @@ impl FocusGrid for ArrangerApp<Tui> {
self.app.phrases.entered = false; self.app.phrases.entered = false;
} }
} }
fn entered (&self) -> Option<Self::Item> { fn focus_entered (&self) -> Option<Self::Item> {
if self.entered { if self.entered {
Some(self.focused()) Some(self.focused())
} else { } else {
None None
} }
} }
fn layout (&self) -> &[&[Self::Item]] { }
/// Focus layout of arranger app
impl FocusGrid for ArrangerApp<Tui> {
type Item = AppViewFocus<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 AppViewFocus::*; use AppViewFocus::*;
use ArrangerViewFocus::*; use ArrangerFocus::*;
&[ &[
&[Menu, Menu ], &[Menu, Menu ],
&[Content(Transport), Content(Transport) ], &[Content(Transport), Content(Transport) ],
@ -715,19 +763,15 @@ impl FocusGrid for ArrangerApp<Tui> {
&[Content(PhrasePool), Content(PhraseEditor)], &[Content(PhrasePool), Content(PhraseEditor)],
] ]
} }
fn update_focus (&mut self) { fn focus_update (&mut self) {
use AppViewFocus::*; use AppViewFocus::*;
use ArrangerViewFocus::*; use ArrangerFocus::*;
let focused = self.focused(); let focused = self.focused();
self.app.focused = focused == Content(Arranger);
self.app.transport.focused = focused == Content(Transport);
self.app.phrases.focused = focused == Content(PhrasePool);
self.app.editor.focused = focused == Content(PhraseEditor);
if let Some(mut status_bar) = self.status_bar { if let Some(mut status_bar) = self.status_bar {
status_bar.update(&( status_bar.update(&(
self.focused(), self.focused(),
self.app.selected, self.app.selected,
self.app.editor.entered focused == Content(PhraseEditor) && self.entered
)) ))
} }
} }
@ -827,7 +871,7 @@ impl ArrangerSelection {
impl StatusBar for ArrangerStatusBar { impl StatusBar for ArrangerStatusBar {
type State = (AppViewFocus<ArrangerViewFocus>, ArrangerSelection, bool); type State = (AppViewFocus<ArrangerFocus>, ArrangerSelection, bool);
fn hotkey_fg () -> Color where Self: Sized { fn hotkey_fg () -> Color where Self: Sized {
TuiTheme::hotkey_fg() TuiTheme::hotkey_fg()
@ -836,15 +880,15 @@ impl StatusBar for ArrangerStatusBar {
use AppViewFocus::*; use AppViewFocus::*;
if let Content(focused) = focused { if let Content(focused) = focused {
*self = match focused { *self = match focused {
ArrangerViewFocus::Transport => ArrangerStatusBar::Transport, ArrangerFocus::Transport => ArrangerStatusBar::Transport,
ArrangerViewFocus::Arranger => match selected { ArrangerFocus::Arranger => match selected {
ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix, ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix,
ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack, ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack,
ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene, ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene,
ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip, ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip,
}, },
ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool, ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
ArrangerViewFocus::PhraseEditor => match entered { ArrangerFocus::PhraseEditor => match entered {
true => ArrangerStatusBar::PhraseEdit, true => ArrangerStatusBar::PhraseEdit,
false => ArrangerStatusBar::PhraseView, false => ArrangerStatusBar::PhraseView,
}, },
@ -1386,182 +1430,3 @@ pub fn arranger_content_horizontal (
) )
) )
} }
#[derive(Default, Debug, Clone)]
pub struct ArrangerScene {
/// Name of scene
pub name: Arc<RwLock<String>>,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
/// Identifying color of scene
pub color: ItemColor,
}
impl ArrangerSceneApi for ArrangerScene {
fn name (&self) -> &Arc<RwLock<String>> {
&self.name
}
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> {
&self.clips
}
fn color (&self) -> ItemColor {
self.color
}
}
#[derive(Debug)]
pub struct ArrangerTrack {
/// Name of track
pub name: Arc<RwLock<String>>,
/// Preferred width of track column
pub width: usize,
/// Identifying color of track
pub color: ItemColor,
/// 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]>>,
}
impl ArrangerTrackApi for ArrangerTrack {
/// Name of track
fn name (&self) -> &Arc<RwLock<String>> {
&self.name
}
/// Preferred width of track column
fn width (&self) -> usize {
self.width
}
/// Preferred width of track column
fn width_mut (&mut self) -> &mut usize {
&mut self.width
}
/// Identifying color of track
fn color (&self) -> ItemColor {
self.color
}
}
impl HasMidiBuffer for ArrangerTrack {
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>> {
todo!()
}
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>> {
todo!()
}
fn reset (&self) -> bool {
todo!()
}
fn reset_mut (&mut self) -> &mut bool {
todo!()
}
}
impl HasPhrase for ArrangerTrack {
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
}
impl MidiInputApi for ArrangerTrack {
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
todo!()
}
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
todo!()
}
fn recording(&self) -> bool {
todo!()
}
fn recording_mut(&mut self) -> &mut bool {
todo!()
}
fn monitoring(&self) -> bool {
todo!()
}
fn monitoring_mut(&mut self) -> &mut bool {
todo!()
}
fn overdub(&self) -> bool {
todo!()
}
fn overdub_mut(&mut self) -> &mut bool {
todo!()
}
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
todo!()
}
}
impl MidiOutputApi for ArrangerTrack {
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
todo!()
}
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
todo!()
}
fn midi_note (&mut self) -> &mut Vec<u8> {
todo!()
}
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
todo!()
}
}
impl ClockApi for ArrangerTrack {
fn timebase (&self) -> &Arc<Timebase> {
todo!()
}
fn quant (&self) -> &Quantize {
todo!()
}
fn sync (&self) -> &LaunchSync {
todo!()
}
}
impl PlayheadApi for ArrangerTrack {
fn current(&self) -> &Instant {
todo!()
}
fn transport(&self) -> &Transport {
todo!()
}
fn playing(&self) -> &RwLock<Option<TransportState>> {
todo!()
}
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
todo!()
}
}
impl PlayerApi for ArrangerTrack {}

View file

@ -0,0 +1,23 @@
use crate::*;
#[derive(Default, Debug, Clone)]
pub struct ArrangerScene {
/// Name of scene
pub name: Arc<RwLock<String>>,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
/// Identifying color of scene
pub color: ItemColor,
}
impl ArrangerSceneApi for ArrangerScene {
fn name (&self) -> &Arc<RwLock<String>> {
&self.name
}
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> {
&self.clips
}
fn color (&self) -> ItemColor {
self.color
}
}

View file

@ -0,0 +1,150 @@
use crate::*;
#[derive(Debug)]
pub struct ArrangerTrack {
/// Name of track
name: Arc<RwLock<String>>,
/// Preferred width of track column
width: usize,
/// Identifying color of track
color: ItemColor,
/// 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_ins: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
midi_outs: Vec<Port<MidiOut>>,
/// Notes currently held at input
notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
notes_out: Arc<RwLock<[bool; 128]>>,
///// MIDI output buffer
//midi_note: Vec<u8>,
///// MIDI output buffer
//midi_chunk: Vec<Vec<Vec<u8>>>,
}
impl ArrangerTrackApi for ArrangerTrack {
/// Name of track
fn name (&self) -> &Arc<RwLock<String>> {
&self.name
}
/// Preferred width of track column
fn width (&self) -> usize {
self.width
}
/// Preferred width of track column
fn width_mut (&mut self) -> &mut usize {
&mut self.width
}
/// Identifying color of track
fn color (&self) -> ItemColor {
self.color
}
}
impl HasPhrase for ArrangerTrack {
fn reset (&self) -> bool {
self.reset
}
fn reset_mut (&mut self) -> &mut bool {
&mut self.reset
}
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
}
impl MidiInputApi for ArrangerTrack {
fn midi_ins (&self) -> &Vec<Port<jack::MidiIn>> {
todo!()
}
fn midi_ins_mut (&self) -> &mut Vec<Port<jack::MidiIn>> {
todo!()
}
fn recording (&self) -> bool {
todo!()
}
fn recording_mut (&mut self) -> &mut bool {
todo!()
}
fn monitoring (&self) -> bool {
todo!()
}
fn monitoring_mut (&mut self) -> &mut bool {
todo!()
}
fn overdub (&self) -> bool {
todo!()
}
fn overdub_mut (&mut self) -> &mut bool {
todo!()
}
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
todo!()
}
}
impl MidiOutputApi for ArrangerTrack {
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
todo!()
}
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
todo!()
}
fn midi_note (&mut self) -> &mut Vec<u8> {
todo!()
}
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
todo!()
}
}
impl ClockApi for ArrangerTrack {
fn timebase (&self) -> &Arc<Timebase> {
todo!()
}
fn quant (&self) -> &Quantize {
todo!()
}
fn sync (&self) -> &LaunchSync {
todo!()
}
}
impl PlayheadApi for ArrangerTrack {
fn current (&self) -> &Instant {
todo!()
}
fn transport (&self) -> &Transport {
todo!()
}
fn playing (&self) -> &RwLock<Option<TransportState>> {
todo!()
}
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
todo!()
}
}
impl PlayerApi for ArrangerTrack {}

View file

@ -27,22 +27,84 @@ pub struct PhraseEditor<E: Engine> {
pub notes_out: Arc<RwLock<[bool; 128]>>, pub notes_out: Arc<RwLock<[bool; 128]>>,
/// Current position of global playhead /// Current position of global playhead
pub now: Arc<Pulse>, pub now: Arc<Pulse>,
/// Width of notes area at last render /// Width and height of notes area at last render
pub width: AtomicUsize, pub size: Measure<E>
/// Height of notes area at last render
pub height: AtomicUsize,
} }
impl Content for PhraseEditor<Tui> { impl Widget for PhraseEditor<Tui> {
type Engine = Tui;
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
PhraseEditorRef(&self, Default::default()).layout(to)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
PhraseEditorRef(&self, Default::default()).render(to)
}
}
pub struct PhraseEditorRef<'a, T: PhraseEditorViewState>(pub &'a T);
pub trait PhraseEditorViewState: Send + Sync {
fn focused (&self) -> bool;
fn entered (&self) -> bool;
fn keys (&self) -> &Buffer;
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
fn buffer (&self) -> &BigBuffer;
fn note_len (&self) -> usize;
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
fn size (&self) -> &Measure<Tui>;
fn now (&self) -> &Arc<Pulse>;
}
impl PhraseEditorViewState for PhraseEditor<Tui> {
fn focused (&self) -> bool {
&self.focused
}
fn entered (&self) -> bool {
&self.entered
}
fn keys (&self) -> &Buffer {
&self.keys
}
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
&self.phrase
}
fn buffer (&self) -> &BigBuffer {
&self.buffer
}
fn note_len (&self) -> usize {
&self.note_len
}
fn note_axis (&self) -> &RwLock<FixedAxis<usize>> {
&self.note_axis
}
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
&self.time_axis
}
fn size (&self) -> &Measure<Tui> {
&self.size
}
fn now (&self) -> &Arc<Pulse> {
&self.now
}
}
impl<'a, T: PhraseEditorViewState> Content for PhraseEditorRef<'a, T> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
let Self { focused, entered, keys, phrase, buffer, note_len, .. } = self; let phrase = self.0.phrase();
let FixedAxis { let size = self.0.size();
start: note_start, point: note_point, clamp: note_clamp let focused = self.0.focused();
} = *self.note_axis.read().unwrap(); let entered = self.0.entered();
let ScaledAxis { let keys = self.0.keys();
start: time_start, point: time_point, clamp: time_clamp, scale: time_scale let buffer = self.0.buffer();
} = *self.time_axis.read().unwrap(); let note_len = self.0.note_len();
let note_axis = self.0.note_axis();
let time_axis = self.0.time_axis();
let FixedAxis { start: note_start, point: note_point, clamp: note_clamp }
= *note_axis.read().unwrap();
let ScaledAxis { start: time_start, point: time_point, clamp: time_clamp, scale: time_scale }
= *time_axis.read().unwrap();
//let color = Color::Rgb(0,255,0); //let color = Color::Rgb(0,255,0);
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color); //let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{ let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
@ -59,9 +121,8 @@ impl Content for PhraseEditor<Tui> {
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{ let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
let area = to.area(); let area = to.area();
let h = area.h() as usize; let h = area.h() as usize;
self.height.store(h, Ordering::Relaxed); size.set_wh(area.w(), h);
self.width.store(area.w() as usize, Ordering::Relaxed); let mut axis = note_axis.write().unwrap();
let mut axis = self.note_axis.write().unwrap();
if let Some(point) = axis.point { if let Some(point) = axis.point {
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) { if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
axis.start += 2; axis.start += 2;
@ -84,11 +145,11 @@ impl Content for PhraseEditor<Tui> {
}) })
}).fill_x(); }).fill_x();
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{ let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
Ok(if *focused && *entered { Ok(if focused && entered {
let area = to.area(); let area = to.area();
if let (Some(time), Some(note)) = (time_point, note_point) { if let (Some(time), Some(note)) = (time_point, note_point) {
let x1 = area.x() + (time / time_scale) as u16; let x1 = area.x() + (time / time_scale) as u16;
let x2 = x1 + (self.note_len / time_scale) as u16; let x2 = x1 + (note_len / time_scale) as u16;
let y = area.y() + note.saturating_sub(note_start) as u16 / 2; let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
let c = if note % 2 == 0 { "" } else { "" }; let c = if note % 2 == 0 { "" } else { "" };
for x in x1..x2 { for x in x1..x2 {
@ -103,7 +164,7 @@ impl Content for PhraseEditor<Tui> {
|to:[u16;2]|Ok(Some(to.clip_h(1))), |to:[u16;2]|Ok(Some(to.clip_h(1))),
move|to: &mut TuiOutput|{ move|to: &mut TuiOutput|{
if let Some(_) = phrase { if let Some(_) = phrase {
let now = self.now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length; let now = self.0.now().get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
let time_clamp = time_clamp let time_clamp = time_clamp
.expect("time_axis of sequencer expected to be clamped"); .expect("time_axis of sequencer expected to be clamped");
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) { for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
@ -119,32 +180,28 @@ impl Content for PhraseEditor<Tui> {
Ok(()) Ok(())
} }
).push_x(6).align_sw(); ).push_x(6).align_sw();
let border_color = if *focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)}; let border_color = if focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)};
let title_color = if *focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; let title_color = if focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color)); let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
let note_area = lay!(notes, cursor).fill_x(); let note_area = lay!(notes, cursor).fill_x();
let piano_roll = row!(keys, note_area).fill_x(); let piano_roll = row!(keys, note_area).fill_x();
let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border); let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border);
let content = lay!(content, playhead); let content = lay!(content, playhead);
let mut upper_left = format!("[{}] Sequencer", if *entered {""} else {" "}); let mut upper_left = format!("[{}] Sequencer", if entered {""} else {" "});
if let Some(phrase) = phrase { if let Some(phrase) = phrase {
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name); upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
} }
let mut lower_right = format!( let mut lower_right = format!("{}", size.format());
"┤{}x{}├",
self.width.load(Ordering::Relaxed),
self.height.load(Ordering::Relaxed),
);
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale)); lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})", //lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
//pulses_to_name(time_scale), //pulses_to_name(time_scale),
//time_start, time_point.unwrap_or(0), //time_start, time_point.unwrap_or(0),
//time_scale, time_clamp.unwrap_or(0), //time_scale, time_clamp.unwrap_or(0),
//); //);
if *focused && *entered { if focused && entered {
lower_right = format!("┤Note: {} {}├─{lower_right}", lower_right = format!("┤Note: {} {}├─{lower_right}",
self.note_axis.read().unwrap().point.unwrap(), note_axis.read().unwrap().point.unwrap(),
pulses_to_name(*note_len)); pulses_to_name(note_len));
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}", //lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
//pulses_to_name(*note_len), //pulses_to_name(*note_len),
//note_start, //note_start,

View file

@ -1,5 +1,12 @@
use crate::*; use crate::*;
pub type SequencerApp<E: Engine> = AppView<
E,
SequencerView<E>,
SequencerViewCommand,
SequencerStatusBar,
>;
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> { impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
type Error = Box<dyn std::error::Error>; type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> { fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
@ -12,82 +19,15 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
focused: false, focused: false,
focus: TransportViewFocus::PlayPause, focus: TransportViewFocus::PlayPause,
size: Measure::new(), size: Measure::new(),
phrases: PhrasePoolView::from(&model.phrases()), phrases: vec![],
editor: PhraseEditor::new(), editor: PhraseEditor::new(),
}.into(), None, None)) }.into(), None, None))
} }
} }
pub type SequencerApp<E: Engine> = AppView<
E,
SequencerView<E>,
SequencerViewCommand,
SequencerStatusBar,
>;
impl Handle<Tui> for SequencerApp<Tui> { impl Handle<Tui> for SequencerApp<Tui> {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
SequencerAppCommand::execute_with_state(self, i) SequencerCommand::execute_with_state(self, i)
}
}
pub type SequencerAppCommand = AppViewCommand<SequencerViewCommand>;
#[derive(Clone, Debug, PartialEq)]
pub enum SequencerViewCommand {
Transport(TransportCommand),
Phrases(PhrasePoolViewCommand),
Editor(PhraseEditorCommand),
}
impl InputToCommand<Tui, SequencerApp<Tui>> for SequencerAppCommand {
fn input_to_command (state: &SequencerApp<Tui>, input: &TuiInput) -> Option<Self> {
use AppViewFocus::*;
use FocusCommand::*;
use SequencerViewCommand::*;
match input.event() {
key!(KeyCode::Tab) => Some(Self::Focus(Next)),
key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)),
key!(KeyCode::BackTab) => Some(Self::Focus(Prev)),
key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)),
key!(KeyCode::Up) => Some(Self::Focus(Up)),
key!(KeyCode::Down) => Some(Self::Focus(Down)),
key!(KeyCode::Left) => Some(Self::Focus(Left)),
key!(KeyCode::Right) => Some(Self::Focus(Right)),
_ => Some(Self::App(match state.focused() {
Content(SequencerFocus::Transport) =>
TransportCommand::input_to_command(&state.transport, input)
.map(Transport),
Content(SequencerFocus::PhrasePool) =>
PhrasePoolViewCommand::input_to_command(&state.phrases, input)
.map(Phrases),
Content(SequencerFocus::PhraseEditor) =>
PhraseEditorCommand::input_to_command(&state.editor, input)
.map(Editor),
_ => return None,
}))
}
}
}
impl Command<SequencerApp<Tui>> for SequencerAppCommand {
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
use AppViewCommand::*;
match self {
Focus(cmd) => delegate(cmd, Focus, state),
App(cmd) => delegate(cmd, App, state),
}
}
}
impl Command<SequencerApp<Tui>> for SequencerViewCommand {
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
use SequencerViewCommand::*;
match self {
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
Transport(cmd) => delegate(cmd, Transport, &mut state.transport)
}
} }
} }
@ -154,7 +94,7 @@ impl Content for SequencerView<Tui> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
col!( col!(
self.transport, widget(&TransportRefView(self)),
Split::right(20, Split::right(20,
widget(&self.phrases), widget(&self.phrases),
widget(&self.editor) widget(&self.editor)
@ -187,14 +127,11 @@ impl Content for SequencerStatusBar {
} }
} }
impl FocusGrid for SequencerApp<Tui> { impl HasFocus for SequencerApp<Tui> {
type Item = AppViewFocus<SequencerFocus>; type Item = AppViewFocus<SequencerFocus>;
fn cursor (&self) -> (usize, usize) { }
self.cursor
} impl FocusEnter for SequencerApp<Tui> {
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_enter (&mut self) { fn focus_enter (&mut self) {
let focused = self.focused(); let focused = self.focused();
if !self.entered { if !self.entered {
@ -208,14 +145,24 @@ impl FocusGrid for SequencerApp<Tui> {
// TODO // TODO
} }
} }
fn entered (&self) -> Option<Self::Item> { fn focus_entered (&self) -> Option<Self::Item> {
if self.entered { if self.entered {
Some(self.focused()) Some(self.focused())
} else { } else {
None None
} }
} }
fn layout (&self) -> &[&[Self::Item]] { }
impl FocusGrid for SequencerApp<Tui> {
type Item = AppViewFocus<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 AppViewFocus::*; use AppViewFocus::*;
use SequencerFocus::*; use SequencerFocus::*;
&[ &[
@ -224,14 +171,14 @@ impl FocusGrid for SequencerApp<Tui> {
&[Content(PhrasePool), Content(PhraseEditor)], &[Content(PhrasePool), Content(PhraseEditor)],
] ]
} }
fn update_focus (&mut self) { fn focus_update (&mut self) {
// TODO // TODO
} }
} }
impl<E: Engine> HasJack for SequencerView<E> { impl<E: Engine> HasJack for SequencerView<E> {
fn jack (&self) -> &Arc<RwLock<JackClient>> { fn jack (&self) -> &Arc<RwLock<JackClient>> {
self.transport.jack() &self.jack
} }
} }
@ -244,106 +191,121 @@ impl<E: Engine> HasPhrases for SequencerView<E> {
} }
} }
impl<E: Engine> HasMidiBuffer for SequencerView<E> {
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>> {
todo!()
}
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>> {
todo!()
}
fn reset (&self) -> bool {
todo!()
}
fn reset_mut (&mut self) -> &mut bool {
todo!()
}
}
impl<E: Engine> HasPhrase for SequencerView<E> { impl<E: Engine> HasPhrase for SequencerView<E> {
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> { fn reset (&self) -> bool {
todo!() self.reset
} }
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> { fn reset_mut (&mut self) -> &mut bool {
todo!() &mut self.reset
} }
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> { fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!() todo!()
} }
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> { fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!() todo!()
} }
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
todo!()
}
} }
impl<E: Engine> MidiInputApi for SequencerView<E> { impl<E: Engine> MidiInputApi for SequencerView<E> {
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> { fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
todo!() todo!()
} }
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> { fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
todo!() todo!()
} }
fn recording(&self) -> bool { fn recording(&self) -> bool {
todo!() todo!()
} }
fn recording_mut(&mut self) -> &mut bool { fn recording_mut(&mut self) -> &mut bool {
todo!() todo!()
} }
fn monitoring(&self) -> bool { fn monitoring(&self) -> bool {
todo!() todo!()
} }
fn monitoring_mut(&mut self) -> &mut bool { fn monitoring_mut(&mut self) -> &mut bool {
todo!() todo!()
} }
fn overdub(&self) -> bool { fn overdub(&self) -> bool {
todo!() todo!()
} }
fn overdub_mut(&mut self) -> &mut bool { fn overdub_mut(&mut self) -> &mut bool {
todo!() todo!()
} }
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> { fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
todo!() todo!()
} }
} }
impl<E: Engine> MidiOutputApi for SequencerView<E> { impl<E: Engine> MidiOutputApi for SequencerView<E> {
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> { fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
todo!() todo!()
} }
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> { fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
todo!() todo!()
} }
fn midi_note (&mut self) -> &mut Vec<u8> { fn midi_note (&mut self) -> &mut Vec<u8> {
todo!() todo!()
} }
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> { fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
todo!() todo!()
} }
} }
impl<E: Engine> ClockApi for SequencerView<E> { impl<E: Engine> ClockApi for SequencerView<E> {
fn timebase (&self) -> &Arc<Timebase> { fn timebase (&self) -> &Arc<Timebase> {
todo!() todo!()
} }
fn quant (&self) -> &Quantize { fn quant (&self) -> &Quantize {
todo!() todo!()
} }
fn sync (&self) -> &LaunchSync { fn sync (&self) -> &LaunchSync {
todo!() todo!()
} }
} }
impl<E: Engine> PlayheadApi for SequencerView<E> { impl<E: Engine> PlayheadApi for SequencerView<E> {
fn current(&self) -> &Instant { fn current(&self) -> &Instant {
todo!() todo!()
} }
fn transport(&self) -> &Transport { fn transport(&self) -> &Transport {
todo!() todo!()
} }
fn playing(&self) -> &RwLock<Option<TransportState>> { fn playing(&self) -> &RwLock<Option<TransportState>> {
todo!() todo!()
} }
fn started(&self) -> &RwLock<Option<(usize, usize)>> { fn started(&self) -> &RwLock<Option<(usize, usize)>> {
todo!() todo!()
} }
} }
impl<E: Engine> PlayerApi for SequencerView<E> {} impl<E: Engine> PlayerApi for SequencerView<E> {}
impl TransportViewState for SequencerView<Tui> {
fn focus (&self) -> TransportViewFocus {
self.focus
}
fn focused (&self) -> bool {
self.focused
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
fn bpm_value (&self) -> f64 {
self.bpm().get()
}
fn sync_value (&self) -> f64 {
self.sync().get()
}
fn format_beat (&self) -> String {
self.current().format_beat()
}
fn format_msu (&self) -> String {
self.current().usec.format_msu()
}
}

View file

@ -0,0 +1,73 @@
use crate::*;
pub type SequencerCommand = AppViewCommand<SequencerViewCommand>;
#[derive(Clone, Debug, PartialEq)]
pub enum SequencerViewCommand {
Transport(TransportCommand),
Phrases(PhrasePoolViewCommand),
Editor(PhraseEditorCommand),
}
impl InputToCommand<Tui, SequencerApp<Tui>> for SequencerCommand {
fn input_to_command (state: &SequencerApp<Tui>, input: &TuiInput) -> Option<Self> {
use AppViewFocus::*;
use FocusCommand::*;
use SequencerViewCommand::*;
match input.event() {
key!(KeyCode::Tab) => Some(Self::Focus(Next)),
key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)),
key!(KeyCode::BackTab) => Some(Self::Focus(Prev)),
key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)),
key!(KeyCode::Up) => Some(Self::Focus(Up)),
key!(KeyCode::Down) => Some(Self::Focus(Down)),
key!(KeyCode::Left) => Some(Self::Focus(Left)),
key!(KeyCode::Right) => Some(Self::Focus(Right)),
_ => Some(Self::App(match state.focused() {
Content(SequencerFocus::Transport) =>
TransportCommand::input_to_command(&state, input)
.map(Transport),
Content(SequencerFocus::PhrasePool) =>
PhrasePoolViewCommand::input_to_command(&state.phrases, input)
.map(Phrases),
Content(SequencerFocus::PhraseEditor) =>
PhraseEditorCommand::input_to_command(&state.editor, input)
.map(Editor),
_ => return None,
}))
}
}
}
impl Command<SequencerApp<Tui>> for SequencerCommand {
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
use AppViewCommand::*;
match self {
Focus(cmd) => delegate(cmd, Focus, state),
App(cmd) => delegate(cmd, App, state),
}
}
}
impl Command<SequencerApp<Tui>> for SequencerViewCommand {
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
use SequencerViewCommand::*;
match self {
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
Transport(cmd) => delegate(cmd, Transport, &mut state.transport)
}
}
}
impl TransportControl for SequencerApp<Tui> {
fn bpm (&self) -> &BeatsPerMinute {
self.app.bpm()
}
fn quant (&self) -> &Quantize {
self.app.quant()
}
fn sync (&self) -> &LaunchSync {
self.app.sync()
}
}

View file

@ -1,5 +1,13 @@
use crate::*; use crate::*;
/// Root type of application.
pub type TransportApp<E: Engine> = AppView<
E,
TransportView<E>,
AppViewCommand<TransportCommand>,
TransportStatusBar
>;
/// Create app state from JACK handle. /// Create app state from JACK handle.
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> { impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
type Error = Box<dyn std::error::Error>; type Error = Box<dyn std::error::Error>;
@ -15,121 +23,6 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
} }
} }
/// Root type of application.
pub type TransportApp<E: Engine> = AppView<
E,
TransportView<E>,
TransportAppCommand,
TransportStatusBar
>;
/// Handle input.
impl Handle<Tui> for TransportApp<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
TransportAppCommand::execute_with_state(self, from)
}
}
pub type TransportAppCommand = AppViewCommand<TransportCommand>;
#[derive(Clone, Debug)]
pub enum TransportCommand {
Clock(ClockCommand),
Playhead(PlayheadCommand),
}
impl InputToCommand<Tui, TransportApp<Tui>> for TransportAppCommand {
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
use KeyCode::{Left, Right};
use FocusCommand::{Prev, Next};
use AppViewCommand::{Focus, App};
Some(match input.event() {
key!(Left) => Focus(Prev),
key!(Right) => Focus(Next),
_ => TransportCommand::input_to_command(app.app, input).map(App)
})
}
}
impl InputToCommand<Tui, TransportApp<Tui>> for TransportCommand {
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
use KeyCode::Char;
use AppViewFocus::Content;
use ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportViewFocus as Focus;
use TransportCommand::{Clock, Playhead};
let timebase = app.app.timebase();
Some(match input.event() {
key!(Char('.')) => match app.focused() {
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 1.0)),
Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)),
Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char(',')) => match app.focused() {
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 1.0)),
Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)),
Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char('>')) => match app.focused() {
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 0.001)),
Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)),
Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char('<')) => match app.focused() {
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 0.001)),
Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)),
Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
_ => return None
})
}
}
impl Command<TransportApp<Tui>> for TransportAppCommand {
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
use AppViewCommand::{Focus, App};
use FocusCommand::{Next, Prev};
Ok(Some(match self {
App(command) => if let Some(undo) = TransportCommand::execute(command, state)? {
App(undo)
} else {
return Ok(None)
},
Focus(command) => Focus(match command {
Next => { todo!() },
Prev => { todo!() },
_ => { todo!() }
}),
_ => todo!()
}))
}
}
impl Command<TransportApp<Tui>> for TransportCommand {
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
use TransportCommand::{Clock, Playhead};
use ClockCommand::{SetBpm, SetQuant, SetSync};
Ok(Some(match self {
Clock(SetBpm(bpm)) => Clock(SetBpm(state.timebase().bpm.set(bpm))),
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))),
_ => return Ok(None)
}))
}
}
/// Stores and displays time-related info. /// Stores and displays time-related info.
pub struct TransportView<E: Engine> { pub struct TransportView<E: Engine> {
_engine: PhantomData<E>, _engine: PhantomData<E>,
@ -153,6 +46,15 @@ pub struct TransportView<E: Engine> {
size: Measure<E>, size: Measure<E>,
} }
impl<E: Engine> std::fmt::Debug for TransportView<E> {
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()
}
}
/// Which item of the transport toolbar is focused /// Which item of the transport toolbar is focused
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransportViewFocus { pub enum TransportViewFocus {
@ -195,13 +97,60 @@ impl TransportViewFocus {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct TransportStatusBar; pub struct TransportStatusBar;
impl Content for TransportView<Tui> { impl Widget for TransportView<Tui> {
type Engine = Tui;
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
TransportRef(&self, Default::default()).layout(to)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
TransportRef(&self, Default::default()).render(to)
}
}
pub struct TransportRef<'a, T: TransportViewState>(pub &'a T);
pub trait TransportViewState: Send + Sync {
fn focus (&self) -> TransportViewFocus;
fn focused (&self) -> bool;
fn transport_state (&self) -> Option<TransportState>;
fn bpm_value (&self) -> f64;
fn sync_value (&self) -> f64;
fn format_beat (&self) -> String;
fn format_msu (&self) -> String;
}
impl<E: Engine> TransportViewState for TransportView<E> {
fn focus (&self) -> TransportViewFocus {
self.focus
}
fn focused (&self) -> bool {
self.focused
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
fn bpm_value (&self) -> f64 {
self.bpm().get()
}
fn sync_value (&self) -> f64 {
self.sync().get()
}
fn format_beat (&self) -> String {
self.current().format_beat()
}
fn format_msu (&self) -> String {
self.current().usec.format_msu()
}
}
impl<'a, T: TransportViewState> Content for TransportRef<'a, T> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
let state = self.0;
lay!( lay!(
self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled( state.focus().wrap(state.focused(), TransportViewFocus::PlayPause, &Styled(
None, None,
match *self.playing().read().unwrap() { match state.transport_state() {
Some(TransportState::Rolling) => "▶ PLAYING", Some(TransportState::Rolling) => "▶ PLAYING",
Some(TransportState::Starting) => "READY ...", Some(TransportState::Starting) => "READY ...",
Some(TransportState::Stopped) => "⏹ STOPPED", Some(TransportState::Stopped) => "⏹ STOPPED",
@ -210,21 +159,21 @@ impl Content for TransportView<Tui> {
).min_xy(11, 2).push_x(1)).align_x().fill_x(), ).min_xy(11, 2).push_x(1)).align_x().fill_x(),
row!( row!(
self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, { state.focus().wrap(state.focused(), TransportViewFocus::Bpm, &Outset::X(1u16, {
let bpm = self.timebase().bpm.get(); let bpm = state.bpm_value();
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) } row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) }
})), })),
//let quant = self.focus.wrap(self.focused, TransportViewFocus::Quant, &Outset::X(1u16, row! { //let quant = state.focus().wrap(state.focused(), TransportViewFocus::Quant, &Outset::X(1u16, row! {
//"QUANT ", ppq_to_name(self.quant as usize) //"QUANT ", ppq_to_name(state.0.quant as usize)
//})), //})),
self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! { state.focus().wrap(state.focused(), TransportViewFocus::Sync, &Outset::X(1u16, row! {
"SYNC ", pulses_to_name(self.sync().get() as usize) "SYNC ", pulses_to_name(state.sync_value() as usize)
})) }))
).align_w().fill_x(), ).align_w().fill_x(),
self.focus.wrap(self.focused, TransportViewFocus::Clock, &{ state.focus().wrap(state.focused(), TransportViewFocus::Clock, &{
let time1 = self.current().format_beat(); let time1 = state.format_beat();
let time2 = self.current().usec.format_msu(); let time2 = state.format_msu();
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1) row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
}).align_e().fill_x(), }).align_e().fill_x(),
@ -234,7 +183,7 @@ impl Content for TransportView<Tui> {
impl Audio for TransportView<Tui> { impl Audio for TransportView<Tui> {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
self.model.process(client, scope) PlayheadAudio(self).process(client, scope)
} }
} }
@ -256,35 +205,35 @@ impl Content for TransportStatusBar {
} }
} }
impl FocusGrid for TransportApp<Tui> { impl HasFocus for TransportApp<Tui> {
type Item = AppViewFocus<TransportViewFocus>; type Item = AppViewFocus<TransportViewFocus>;
fn cursor (&self) -> (usize, usize) { }
self.cursor
} impl FocusEnter for TransportApp<Tui> {
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_enter (&mut self) { fn focus_enter (&mut self) {
let focused = self.focused(); self.entered = true;
if !self.entered {
self.entered = true;
// TODO
}
} }
fn focus_exit (&mut self) { fn focus_exit (&mut self) {
if self.entered { self.entered = false;
self.entered = false;
// TODO
}
} }
fn entered (&self) -> Option<Self::Item> { fn focus_entered (&self) -> Option<Self::Item> {
if self.entered { if self.entered {
Some(self.focused()) Some(self.focused())
} else { } else {
None None
} }
} }
fn layout (&self) -> &[&[Self::Item]] { }
impl FocusGrid for TransportApp<Tui> {
type Item = AppViewFocus<TransportViewFocus>;
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 AppViewFocus::*; use AppViewFocus::*;
use TransportViewFocus::*; use TransportViewFocus::*;
&[ &[
@ -298,7 +247,7 @@ impl FocusGrid for TransportApp<Tui> {
], ],
] ]
} }
fn update_focus (&mut self) { fn focus_update (&mut self) {
// TODO // TODO
} }
} }
@ -335,14 +284,3 @@ impl<E: Engine> PlayheadApi for TransportView<E> {
&self.started &self.started
} }
} }
impl<E: Engine> std::fmt::Debug for TransportView<E> {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), Error> {
f.debug_struct("transport")
.field("jack", &self.jack)
.field("transport", &"(JACK transport)")
.field("clock", &self.clock)
.field("metronome", &self.metronome)
.finish()
}
}

View file

@ -0,0 +1,136 @@
use crate::*;
/// Handle input.
impl Handle<Tui> for TransportApp<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
AppViewCommand::<TransportCommand>::execute_with_state(self, from)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TransportCommand {
Clock(ClockCommand),
Playhead(PlayheadCommand),
}
impl InputToCommand<Tui, TransportApp<Tui>> for AppViewCommand<TransportCommand> {
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
use KeyCode::{Left, Right};
use FocusCommand::{Prev, Next};
use AppViewCommand::{Focus, App};
Some(match input.event() {
key!(Left) => Focus(Prev),
key!(Right) => Focus(Next),
_ => TransportCommand::input_to_command(&app, input).map(App)?
})
}
}
pub trait TransportControl: FocusGrid<Item = AppViewFocus<TransportViewFocus>> {
fn quant (&self) -> &Quantize;
fn bpm (&self) -> &BeatsPerMinute;
fn next_quant (&self) -> f64 {
next_note_length(self.quant().get() as usize) as f64
}
fn prev_quant (&self) -> f64 {
prev_note_length(self.quant().get() as usize) as f64
}
fn sync (&self) -> &LaunchSync;
fn next_sync (&self) -> f64 {
next_note_length(self.sync().get() as usize) as f64
}
fn prev_sync (&self) -> f64 {
prev_note_length(self.sync().get() as usize) as f64
}
}
impl TransportControl for TransportApp<Tui> {
fn bpm (&self) -> &BeatsPerMinute {
self.app.bpm()
}
fn quant (&self) -> &Quantize {
self.app.quant()
}
fn sync (&self) -> &LaunchSync {
self.app.sync()
}
}
impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use KeyCode::Char;
use AppViewFocus::Content;
use ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportViewFocus as Focus;
use TransportCommand::{Clock, Playhead};
let focused = state.focused();
Some(match input.event() {
key!(Char('.')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 1.0)),
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())),
Content(Focus::Sync) => Clock(SetSync(state.next_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char(',')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 1.0)),
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())),
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char('>')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 0.001)),
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())),
Content(Focus::Sync) => Clock(SetSync(state.next_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char('<')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 0.001)),
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())),
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
_ => {todo!()}
},
_ => return None
})
}
}
impl Command<TransportApp<Tui>> for AppViewCommand<TransportCommand> {
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
use AppViewCommand::{Focus, App};
use FocusCommand::{Next, Prev};
Ok(Some(match self {
App(command) => if let Some(undo) = TransportCommand::execute(command, &mut state.app)? {
App(undo)
} else {
return Ok(None)
},
Focus(command) => Focus(match command {
Next => { todo!() },
Prev => { todo!() },
_ => { todo!() }
}),
_ => todo!()
}))
}
}
impl<T: TransportControl> Command<T> for TransportCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
use TransportCommand::{Clock, Playhead};
use ClockCommand::{SetBpm, SetQuant, SetSync};
Ok(Some(match self {
Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))),
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))),
_ => return Ok(None)
}))
}
}