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

@ -13,6 +13,8 @@ use std::fmt::Debug;
submod! {
tui_arranger
tui_arranger_track
tui_arranger_scene
//tui_mixer // TODO
tui_phrase
//tui_plugin // TODO
@ -24,9 +26,11 @@ submod! {
//tui_sampler // TODO
//tui_sampler_cmd
tui_sequencer
tui_sequencer_cmd
tui_status
tui_theme
tui_transport
tui_transport_cmd
}
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 {
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(),
focused: false,
entered: false,
quant: Default::default(),
sync: Default::default(),
splits: [20, 20],
note_buf: vec![],
midi_buf: vec![],
}.into(), None, None))
}
}
@ -28,9 +36,14 @@ pub type ArrangerApp<E: Engine> = AppView<
ArrangerStatusBar
>;
impl<E: Engine> Audio for ArrangerApp<E> {
impl Audio for ArrangerApp<Tui> {
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(match view.focused() {
Content(ArrangerViewFocus::Transport) => {
match TransportCommand::input_to_command(&view.app.transport, input)? {
Focus(command) => {
Content(ArrangerFocus::Transport) => {
use TransportCommand::{Clock, Playhead};
match TransportCommand::input_to_command(view, input)? {
Clock(command) => {
todo!()
},
App(Clock(command)) => {
todo!()
},
App(Playhead(command)) => {
Playhead(command) => {
todo!()
},
}
},
Content(ArrangerViewFocus::PhraseEditor) => Editor(
Content(ArrangerFocus::PhraseEditor) => Editor(
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(
Some(view.app.phrases.phrase().clone())
Some(view.app.phrase().clone())
),
_ => 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 ArrangerTrackCommand as Track;
use ArrangerClipCommand as Clip;
use ArrangerSceneCommand as Scene;
match input.event() {
key!(KeyCode::Char('e')) => EditPhrase(
view.selected_phrase()
),
key!(KeyCode::Char('e')) => EditPhrase(view.phrase()),
_ => match input.event() {
// FIXME: boundary conditions
@ -250,7 +259,7 @@ impl Command<ArrangerApp<Tui>> for ArrangerViewCommand {
Select(selected) => { state.selected = selected; Ok(None) },
EditPhrase(phrase) => {
state.editor.phrase = phrase.clone();
state.focus(ArrangerViewFocus::PhraseEditor);
state.focus(ArrangerFocus::PhraseEditor);
state.focus_enter();
Ok(None)
}
@ -277,10 +286,10 @@ pub struct ArrangerView<E: Engine> {
selected: ArrangerSelection,
mode: ArrangerMode,
color: ItemColor,
editor: PhraseEditor<E>,
focused: bool,
entered: bool,
size: Measure<E>,
note_buf: Vec<u8>,
midi_buf: Vec<Vec<Vec<u8>>>,
}
impl HasJack for ArrangerView<Tui> {
@ -332,15 +341,27 @@ impl HasTracks<ArrangerTrack> for ArrangerView<Tui> {
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
&mut self.tracks
}
}
impl ArrangerTracksApi<ArrangerTrack> for ArrangerView<Tui> {
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
-> Usually<&mut ArrangerTrack>
{
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
let track = ArrangerTrack {
name: Arc::new(name.into()),
color: color.unwrap_or_else(||ItemColor::random()),
width: name.len() + 2,
//player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?,
width: name.len() + 2,
name: Arc::new(name.into()),
color: color.unwrap_or_else(||ItemColor::random()),
midi_ins: vec![],
midi_outs: vec![],
reset: true,
recording: false,
monitoring: false,
overdub: false,
play_phrase: None,
next_phrase: None,
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
};
self.tracks_mut().push(track);
let index = self.tracks().len() - 1;
@ -387,7 +408,7 @@ pub enum ArrangerMode {
/// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ArrangerViewFocus {
pub enum ArrangerFocus {
/// The transport (toolbar) is focused
Transport,
/// The arrangement (grid) is focused
@ -444,9 +465,9 @@ impl Content for ArrangerView<Tui> {
fn content (&self) -> impl Widget<Engine = Tui> {
Split::up(
1,
widget(&self.transport),
widget(&TransportRef(self)),
Split::down(
self.split,
self.splits[0],
lay!(
Layers::new(move |add|{
match self.mode {
@ -471,9 +492,9 @@ impl Content for ArrangerView<Tui> {
.push_x(1),
),
Split::right(
self.split,
self.splits[1],
widget(&self.phrases),
widget(&self.editor),
widget(&PhraseEditorRef(self)),
)
)
)
@ -481,7 +502,7 @@ impl Content for ArrangerView<Tui> {
}
/// General methods for arranger
impl<E: Engine> ArrangerView<E> {
impl ArrangerView<Tui> {
/// Focus the editor with the current phrase
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> {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if self.process(client, scope) == Control::Quit {
@ -635,7 +680,7 @@ impl<E: Engine> Audio for ArrangerView<E> {
//self.arrangement.phrase_put();
//}
//self.show_phrase();
//self.focus(ArrangerViewFocus::PhraseEditor);
//self.focus(ArrangerFocus::PhraseEditor);
//self.editor.entered = true;
//}
@ -672,18 +717,10 @@ impl<E: Engine> Audio for ArrangerView<E> {
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
//}
/// Focus layout of arranger app
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
}
impl FocusEnter for ArrangerApp<Tui> {
fn focus_enter (&mut self) {
use AppViewFocus::*;
use ArrangerViewFocus::*;
use ArrangerFocus::*;
let focused = self.focused();
if !self.entered {
self.entered = focused == Content(Arranger);
@ -698,16 +735,27 @@ impl FocusGrid for ArrangerApp<Tui> {
self.app.phrases.entered = false;
}
}
fn entered (&self) -> Option<Self::Item> {
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
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 ArrangerViewFocus::*;
use ArrangerFocus::*;
&[
&[Menu, Menu ],
&[Content(Transport), Content(Transport) ],
@ -715,19 +763,15 @@ impl FocusGrid for ArrangerApp<Tui> {
&[Content(PhrasePool), Content(PhraseEditor)],
]
}
fn update_focus (&mut self) {
fn focus_update (&mut self) {
use AppViewFocus::*;
use ArrangerViewFocus::*;
use ArrangerFocus::*;
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 {
status_bar.update(&(
self.focused(),
self.app.selected,
self.app.editor.entered
focused == Content(PhraseEditor) && self.entered
))
}
}
@ -827,7 +871,7 @@ impl ArrangerSelection {
impl StatusBar for ArrangerStatusBar {
type State = (AppViewFocus<ArrangerViewFocus>, ArrangerSelection, bool);
type State = (AppViewFocus<ArrangerFocus>, ArrangerSelection, bool);
fn hotkey_fg () -> Color where Self: Sized {
TuiTheme::hotkey_fg()
@ -836,15 +880,15 @@ impl StatusBar for ArrangerStatusBar {
use AppViewFocus::*;
if let Content(focused) = focused {
*self = match focused {
ArrangerViewFocus::Transport => ArrangerStatusBar::Transport,
ArrangerViewFocus::Arranger => match selected {
ArrangerFocus::Transport => ArrangerStatusBar::Transport,
ArrangerFocus::Arranger => match selected {
ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix,
ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack,
ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene,
ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip,
},
ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
ArrangerViewFocus::PhraseEditor => match entered {
ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
ArrangerFocus::PhraseEditor => match entered {
true => ArrangerStatusBar::PhraseEdit,
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]>>,
/// Current position of global playhead
pub now: Arc<Pulse>,
/// Width of notes area at last render
pub width: AtomicUsize,
/// Height of notes area at last render
pub height: AtomicUsize,
/// Width and height of notes area at last render
pub size: Measure<E>
}
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;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { focused, entered, keys, phrase, buffer, note_len, .. } = self;
let FixedAxis {
start: note_start, point: note_point, clamp: note_clamp
} = *self.note_axis.read().unwrap();
let ScaledAxis {
start: time_start, point: time_point, clamp: time_clamp, scale: time_scale
} = *self.time_axis.read().unwrap();
let phrase = self.0.phrase();
let size = self.0.size();
let focused = self.0.focused();
let entered = self.0.entered();
let keys = self.0.keys();
let buffer = self.0.buffer();
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 = 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|{
@ -59,9 +121,8 @@ impl Content for PhraseEditor<Tui> {
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
let area = to.area();
let h = area.h() as usize;
self.height.store(h, Ordering::Relaxed);
self.width.store(area.w() as usize, Ordering::Relaxed);
let mut axis = self.note_axis.write().unwrap();
size.set_wh(area.w(), h);
let mut axis = note_axis.write().unwrap();
if let Some(point) = axis.point {
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
axis.start += 2;
@ -84,11 +145,11 @@ impl Content for PhraseEditor<Tui> {
})
}).fill_x();
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
Ok(if *focused && *entered {
Ok(if focused && entered {
let area = to.area();
if let (Some(time), Some(note)) = (time_point, note_point) {
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 c = if note % 2 == 0 { "" } else { "" };
for x in x1..x2 {
@ -103,7 +164,7 @@ impl Content for PhraseEditor<Tui> {
|to:[u16;2]|Ok(Some(to.clip_h(1))),
move|to: &mut TuiOutput|{
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
.expect("time_axis of sequencer expected to be clamped");
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
@ -119,32 +180,28 @@ impl Content for PhraseEditor<Tui> {
Ok(())
}
).push_x(6).align_sw();
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 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 border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
let note_area = lay!(notes, cursor).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 = 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 {
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
}
let mut lower_right = format!(
"┤{}x{}├",
self.width.load(Ordering::Relaxed),
self.height.load(Ordering::Relaxed),
);
let mut lower_right = format!("{}", size.format());
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
//pulses_to_name(time_scale),
//time_start, time_point.unwrap_or(0),
//time_scale, time_clamp.unwrap_or(0),
//);
if *focused && *entered {
if focused && entered {
lower_right = format!("┤Note: {} {}├─{lower_right}",
self.note_axis.read().unwrap().point.unwrap(),
pulses_to_name(*note_len));
note_axis.read().unwrap().point.unwrap(),
pulses_to_name(note_len));
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
//pulses_to_name(*note_len),
//note_start,

View file

@ -1,5 +1,12 @@
use crate::*;
pub type SequencerApp<E: Engine> = AppView<
E,
SequencerView<E>,
SequencerViewCommand,
SequencerStatusBar,
>;
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
@ -12,82 +19,15 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
focused: false,
focus: TransportViewFocus::PlayPause,
size: Measure::new(),
phrases: PhrasePoolView::from(&model.phrases()),
phrases: vec![],
editor: PhraseEditor::new(),
}.into(), None, None))
}
}
pub type SequencerApp<E: Engine> = AppView<
E,
SequencerView<E>,
SequencerViewCommand,
SequencerStatusBar,
>;
impl Handle<Tui> for SequencerApp<Tui> {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
SequencerAppCommand::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)
}
SequencerCommand::execute_with_state(self, i)
}
}
@ -154,7 +94,7 @@ impl Content for SequencerView<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
col!(
self.transport,
widget(&TransportRefView(self)),
Split::right(20,
widget(&self.phrases),
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>;
fn cursor (&self) -> (usize, usize) {
self.cursor
}
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
}
impl FocusEnter for SequencerApp<Tui> {
fn focus_enter (&mut self) {
let focused = self.focused();
if !self.entered {
@ -208,14 +145,24 @@ impl FocusGrid for SequencerApp<Tui> {
// TODO
}
}
fn entered (&self) -> Option<Self::Item> {
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
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 SequencerFocus::*;
&[
@ -224,14 +171,14 @@ impl FocusGrid for SequencerApp<Tui> {
&[Content(PhrasePool), Content(PhraseEditor)],
]
}
fn update_focus (&mut self) {
fn focus_update (&mut self) {
// TODO
}
}
impl<E: Engine> HasJack for SequencerView<E> {
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> {
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!()
}
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<E: Engine> MidiInputApi for SequencerView<E> {
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!()
}
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<E: Engine> MidiOutputApi for SequencerView<E> {
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!()
}
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<E: Engine> ClockApi for SequencerView<E> {
fn timebase (&self) -> &Arc<Timebase> {
todo!()
}
fn quant (&self) -> &Quantize {
todo!()
}
fn sync (&self) -> &LaunchSync {
todo!()
}
fn timebase (&self) -> &Arc<Timebase> {
todo!()
}
fn quant (&self) -> &Quantize {
todo!()
}
fn sync (&self) -> &LaunchSync {
todo!()
}
}
impl<E: Engine> PlayheadApi for SequencerView<E> {
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!()
}
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<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::*;
/// Root type of application.
pub type TransportApp<E: Engine> = AppView<
E,
TransportView<E>,
AppViewCommand<TransportCommand>,
TransportStatusBar
>;
/// Create app state from JACK handle.
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
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.
pub struct TransportView<E: Engine> {
_engine: PhantomData<E>,
@ -153,6 +46,15 @@ pub struct TransportView<E: Engine> {
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
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransportViewFocus {
@ -195,13 +97,60 @@ impl TransportViewFocus {
#[derive(Copy, Clone)]
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;
fn content (&self) -> impl Widget<Engine = Tui> {
let state = self.0;
lay!(
self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled(
state.focus().wrap(state.focused(), TransportViewFocus::PlayPause, &Styled(
None,
match *self.playing().read().unwrap() {
match state.transport_state() {
Some(TransportState::Rolling) => "▶ PLAYING",
Some(TransportState::Starting) => "READY ...",
Some(TransportState::Stopped) => "⏹ STOPPED",
@ -210,21 +159,21 @@ impl Content for TransportView<Tui> {
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
row!(
self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, {
let bpm = self.timebase().bpm.get();
state.focus().wrap(state.focused(), TransportViewFocus::Bpm, &Outset::X(1u16, {
let bpm = state.bpm_value();
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! {
//"QUANT ", ppq_to_name(self.quant as usize)
//let quant = state.focus().wrap(state.focused(), TransportViewFocus::Quant, &Outset::X(1u16, row! {
//"QUANT ", ppq_to_name(state.0.quant as usize)
//})),
self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! {
"SYNC ", pulses_to_name(self.sync().get() as usize)
state.focus().wrap(state.focused(), TransportViewFocus::Sync, &Outset::X(1u16, row! {
"SYNC ", pulses_to_name(state.sync_value() as usize)
}))
).align_w().fill_x(),
self.focus.wrap(self.focused, TransportViewFocus::Clock, &{
let time1 = self.current().format_beat();
let time2 = self.current().usec.format_msu();
state.focus().wrap(state.focused(), TransportViewFocus::Clock, &{
let time1 = state.format_beat();
let time2 = state.format_msu();
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
}).align_e().fill_x(),
@ -234,7 +183,7 @@ impl Content for TransportView<Tui> {
impl Audio for TransportView<Tui> {
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>;
fn cursor (&self) -> (usize, usize) {
self.cursor
}
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
}
impl FocusEnter for TransportApp<Tui> {
fn focus_enter (&mut self) {
let focused = self.focused();
if !self.entered {
self.entered = true;
// TODO
}
self.entered = true;
}
fn focus_exit (&mut self) {
if self.entered {
self.entered = false;
// TODO
}
self.entered = false;
}
fn entered (&self) -> Option<Self::Item> {
fn focus_entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
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 TransportViewFocus::*;
&[
@ -298,7 +247,7 @@ impl FocusGrid for TransportApp<Tui> {
],
]
}
fn update_focus (&mut self) {
fn focus_update (&mut self) {
// TODO
}
}
@ -335,14 +284,3 @@ impl<E: Engine> PlayheadApi for TransportView<E> {
&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)
}))
}
}