delegate more responsibilities to PhraseViewMode

This commit is contained in:
🪞👃🪞 2024-12-12 22:54:55 +01:00
parent 1b44dc0ce8
commit 2795c05275
3 changed files with 71 additions and 131 deletions

View file

@ -143,7 +143,7 @@ render!(|self: ArrangerTui|{
false,
self.splits[1],
PhraseListView::from(self),
PhraseView::from(self),
&self.editor,
)
])
])

View file

@ -22,14 +22,14 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
phrases.phrase.store(1, Ordering::Relaxed);
let mut player = PhrasePlayerModel::from(&clock);
player.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase)));
player.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
Ok(Self {
jack: jack.clone(),
clock,
phrases,
player,
editor: PhraseEditorModel::from(&phrase),
jack: jack.clone(),
size: Measure::new(),
cursor: (0, 0),
entered: false,
@ -127,7 +127,7 @@ render!(|self: SequencerTui|{
false
}
))),
PhraseView::from(self)
self.editor
]),
)
)]);
@ -336,7 +336,9 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
// E: Toggle between editing currently playing or other phrase
key!(Char('e')) => if let Some((_, Some(playing_phrase))) = state.player.play_phrase() {
let editing_phrase = state.editor.phrase.read().unwrap().map(|p|p.read().unwrap().clone());
let editing_phrase = state.editor.phrase()
.read().unwrap().as_ref()
.map(|p|p.read().unwrap().clone());
let selected_phrase = state.phrases.phrase().clone();
if Some(selected_phrase.read().unwrap().clone()) != editing_phrase {
Editor(Show(Some(selected_phrase)))

View file

@ -63,7 +63,8 @@ impl std::fmt::Debug for PhraseEditorModel {
impl Default for PhraseEditorModel {
fn default () -> Self {
let phrase = Arc::new(RwLock::new(None));
let phrase = Arc::new(RwLock::new(None));
let note_len = Arc::from(AtomicUsize::from(24));
Self {
size: Measure::new(),
phrase: phrase.clone(),
@ -72,24 +73,35 @@ impl Default for PhraseEditorModel {
time_point: 0.into(),
note_lo: 0.into(),
note_point: 0.into(),
note_len: Arc::from(AtomicUsize::from(24)),
note_len: note_len.clone(),
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
view_mode: Box::new(PianoHorizontal {
phrase,
phrase: Arc::new(RwLock::new(None)),
buffer: Default::default(),
time_zoom: 24,
time_lock: true,
note_zoom: PhraseViewNoteZoom::N(1)
note_zoom: PhraseViewNoteZoom::N(1),
focused: true,
note_len
}),
}
}
}
impl PhraseEditorModel {
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
pub fn show_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
*self.view_mode.phrase().write().unwrap() = if phrase.is_some() {
phrase.clone()
} else {
None
};
self.view_mode.redraw();
}
/// Put note at current position
pub fn put_note (&mut self) {
if let Some(phrase) = *self.phrase.read().unwrap() {
pub fn put_note (&mut self, advance: bool) {
if let Some(phrase) = &*self.phrase.read().unwrap() {
let note_len = self.note_len.load(Ordering::Relaxed);
let time = self.time_point.load(Ordering::Relaxed);
let note = self.note_point.load(Ordering::Relaxed);
@ -100,30 +112,20 @@ impl PhraseEditorModel {
let end = (start + note_len) % phrase.length;
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
self.view_mode.show(Some(&phrase), note_len);
}
}
/// Move time cursor forward by current note length
pub fn time_cursor_advance (&self) {
let point = self.time_point.load(Ordering::Relaxed);
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
let forward = |time|(time + self.note_len) % length;
self.time_point.store(forward(point), Ordering::Relaxed);
}
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
pub fn show_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
if phrase.is_some() {
self.phrase = phrase;
let phrase = &*self.phrase.as_ref().unwrap().read().unwrap();
self.view_mode.show(Some(&phrase), self.note_len);
} else {
self.view_mode.show(None, self.note_len);
self.phrase = None;
self.view_mode.redraw();
if advance {
let point = self.time_point.load(Ordering::Relaxed);
let length = phrase.length;
let forward = |time|(time + note_len) % length;
self.time_point.store(forward(point), Ordering::Relaxed);
}
}
}
}
pub trait PhraseViewMode: Debug + Send + Sync {
render!(|self: PhraseEditorModel|self.view_mode);
pub trait PhraseViewMode: Render<Tui> + Debug + Send + Sync {
fn time_zoom (&self) -> usize;
fn set_time_zoom (&mut self, time_zoom: usize);
fn time_zoom_lock (&self) -> bool;
@ -133,6 +135,30 @@ pub trait PhraseViewMode: Debug + Send + Sync {
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>;
}
impl PhraseViewMode for PhraseEditorModel {
fn time_zoom (&self) -> usize {
self.view_mode.time_zoom()
}
fn set_time_zoom (&mut self, time_zoom: usize) {
self.view_mode.set_time_zoom(time_zoom)
}
fn time_zoom_lock (&self) -> bool {
self.view_mode.time_zoom_lock()
}
fn set_time_zoom_lock (&mut self, time_lock: bool) {
self.view_mode.set_time_zoom_lock(time_lock);
}
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
self.view_mode.buffer_size(phrase)
}
fn redraw (&mut self) {
self.view_mode.redraw()
}
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> {
self.view_mode.phrase()
}
}
pub struct PhraseView<'a> {
note_point: usize,
note_range: (usize, usize),
@ -147,23 +173,6 @@ pub struct PhraseView<'a> {
entered: bool,
}
render!(|self: PhraseView<'a>|{
let bg = if self.focused { TuiTheme::g(32) } else { Color::Reset };
let fg = self.phrase.as_ref()
.map(|p|p.read().unwrap().color.clone())
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
Tui::bg(bg, Tui::split_up(false, 2,
Tui::bg(fg.dark.rgb, col!([
PhraseTimeline(&self, fg),
PhraseViewStats(&self, fg),
])),
Split::right(false, 5, PhraseKeys(&self, fg), lay!([
PhraseNotes(&self, fg),
PhraseCursor(&self),
])),
))
});
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
fn from (state: &'a T) -> Self {
let editor = state.editor();
@ -192,62 +201,6 @@ impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
}
}
pub struct PhraseTimeline<'a>(&'a PhraseView<'a>, ItemPalette);
render!(|self: PhraseTimeline<'a>|Tui::fg(TuiTheme::g(224), Tui::push_x(5, format!("|000.00.00"))));
pub struct PhraseViewStats<'a>(&'a PhraseView<'a>, ItemPalette);
render!(|self: PhraseViewStats<'a>|{
let color = self.1.dark.rgb;//if self.0.focused{self.1.light.rgb}else{self.1.dark.rgb};
row!([
Tui::bg(color, Tui::fg(TuiTheme::g(224), format!(
" {} | Note: {} ({}) | {} ",
self.0.size.format(),
self.0.note_point,
to_note_name(self.0.note_point),
pulses_to_name(self.0.note_len),
))),
{
let mut upper_right = format!("[{}]", if self.0.entered {""} else {" "});
if let Some(phrase) = self.0.phrase {
upper_right = format!(
" Time: {}/{} {} {upper_right} ",
self.0.time_point,
phrase.read().unwrap().length,
pulses_to_name(self.0.view_mode.time_zoom().unwrap()),
)
};
Tui::bg(color, Tui::fg(TuiTheme::g(224), upper_right))
}
])
});
struct PhraseKeys<'a>(&'a PhraseView<'a>, ItemPalette);
render!(|self: PhraseKeys<'a>|{
let layout = |to:[u16;2]|Ok(Some(to.clip_w(5)));
Tui::fill_xy(Widget::new(layout, |to: &mut TuiOutput|Ok(
self.0.view_mode.render_keys(to, self.1.light.rgb, Some(self.0.note_point), self.0.note_range)
)))
});
struct PhraseNotes<'a>(&'a PhraseView<'a>, ItemPalette);
render!(|self: PhraseNotes<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|{
self.0.size.set_wh(to.area.w(), to.area.h() as usize);
Ok(self.0.view_mode.render_notes(to, self.0.time_start, self.0.note_range.1))
})));
struct PhraseCursor<'a>(&'a PhraseView<'a>);
render!(|self: PhraseCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok(
self.0.view_mode.render_cursor(
to,
self.0.time_point,
self.0.time_start,
self.0.note_point,
self.0.note_len,
self.0.note_range.1,
self.0.note_range.0,
)
))));
#[derive(Clone, Debug)]
pub enum PhraseCommand {
// TODO: 1-9 seek markers that by default start every 8th of the phrase
@ -273,7 +226,8 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
let time_start = state.time_start.load(Ordering::Relaxed);
let time_point = state.time_point.load(Ordering::Relaxed);
let time_zoom = state.view_mode.time_zoom();
let length = state.phrase.read().unwrap().map(|p|p.read().unwrap().length).unwrap_or(1);
let length = state.phrase().read().unwrap().as_ref()
.map(|p|p.read().unwrap().length).unwrap_or(1);
let note_len = state.note_len.load(Ordering::Relaxed);
Some(match from.event() {
key!(Char('`')) => ToggleDirection,
@ -307,9 +261,6 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
key!(Ctrl-PageDown) => SetNoteScroll(note_point.saturating_sub(3)),
key!(Ctrl-Left) => SetTimeScroll(time_start.saturating_sub(note_len)),
key!(Ctrl-Right) => SetTimeScroll(time_start + note_len),
_ => return None
},
})
@ -320,29 +271,16 @@ impl Command<PhraseEditorModel> for PhraseCommand {
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
use PhraseCommand::*;
match self {
Show(phrase) => {
state.show_phrase(phrase);
},
PutNote => {
state.put_note();
},
AppendNote => {
state.put_note();
state.time_cursor_advance();
},
SetTimeZoom(zoom) => {
state.view_mode.set_time_zoom(zoom);
state.show_phrase(state.phrase.clone());
},
SetTimeZoomLock(lock) => {
state.view_mode.set_zoom_lock(lock);
state.show_phrase(state.phrase.clone());
},
SetTimeScroll(time) => { state.time_start.store(time, Ordering::Relaxed); },
SetTimeCursor(time) => { state.time_point.store(time, Ordering::Relaxed); },
SetNoteLength(time) => { state.note_len.store(time, Ordering::Relaxed); },
SetNoteScroll(note) => { state.note_lo.store(note, Ordering::Relaxed); },
SetNoteCursor(note) => {
Show(phrase) => { state.show_phrase(phrase); },
PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); },
SetTimeZoom(zoom) => { state.view_mode.set_time_zoom(zoom); },
SetTimeZoomLock(lock) => { state.view_mode.set_time_zoom_lock(lock); },
SetTimeScroll(time) => { state.time_start.store(time, Ordering::Relaxed); },
SetTimeCursor(time) => { state.time_point.store(time, Ordering::Relaxed); },
SetNoteLength(time) => { state.note_len.store(time, Ordering::Relaxed); },
SetNoteScroll(note) => { state.note_lo.store(note, Ordering::Relaxed); },
SetNoteCursor(note) => {
let note = 127.min(note);
let start = state.note_lo.load(Ordering::Relaxed);
state.note_point.store(note, Ordering::Relaxed);