mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
delegate more responsibilities to PhraseViewMode
This commit is contained in:
parent
1b44dc0ce8
commit
2795c05275
3 changed files with 71 additions and 131 deletions
|
|
@ -143,7 +143,7 @@ render!(|self: ArrangerTui|{
|
|||
false,
|
||||
self.splits[1],
|
||||
PhraseListView::from(self),
|
||||
PhraseView::from(self),
|
||||
&self.editor,
|
||||
)
|
||||
])
|
||||
])
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue