mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 04:36:45 +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,
|
false,
|
||||||
self.splits[1],
|
self.splits[1],
|
||||||
PhraseListView::from(self),
|
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);
|
phrases.phrase.store(1, Ordering::Relaxed);
|
||||||
|
|
||||||
let mut player = PhrasePlayerModel::from(&clock);
|
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 {
|
Ok(Self {
|
||||||
|
jack: jack.clone(),
|
||||||
clock,
|
clock,
|
||||||
phrases,
|
phrases,
|
||||||
player,
|
player,
|
||||||
editor: PhraseEditorModel::from(&phrase),
|
editor: PhraseEditorModel::from(&phrase),
|
||||||
jack: jack.clone(),
|
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
cursor: (0, 0),
|
cursor: (0, 0),
|
||||||
entered: false,
|
entered: false,
|
||||||
|
|
@ -127,7 +127,7 @@ render!(|self: SequencerTui|{
|
||||||
false
|
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
|
// E: Toggle between editing currently playing or other phrase
|
||||||
key!(Char('e')) => if let Some((_, Some(playing_phrase))) = state.player.play_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();
|
let selected_phrase = state.phrases.phrase().clone();
|
||||||
if Some(selected_phrase.read().unwrap().clone()) != editing_phrase {
|
if Some(selected_phrase.read().unwrap().clone()) != editing_phrase {
|
||||||
Editor(Show(Some(selected_phrase)))
|
Editor(Show(Some(selected_phrase)))
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ impl std::fmt::Debug for PhraseEditorModel {
|
||||||
|
|
||||||
impl Default for PhraseEditorModel {
|
impl Default for PhraseEditorModel {
|
||||||
fn default () -> Self {
|
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 {
|
Self {
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
phrase: phrase.clone(),
|
phrase: phrase.clone(),
|
||||||
|
|
@ -72,24 +73,35 @@ impl Default for PhraseEditorModel {
|
||||||
time_point: 0.into(),
|
time_point: 0.into(),
|
||||||
note_lo: 0.into(),
|
note_lo: 0.into(),
|
||||||
note_point: 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_in: RwLock::new([false;128]).into(),
|
||||||
notes_out: RwLock::new([false;128]).into(),
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
view_mode: Box::new(PianoHorizontal {
|
view_mode: Box::new(PianoHorizontal {
|
||||||
phrase,
|
phrase: Arc::new(RwLock::new(None)),
|
||||||
buffer: Default::default(),
|
buffer: Default::default(),
|
||||||
time_zoom: 24,
|
time_zoom: 24,
|
||||||
time_lock: true,
|
time_lock: true,
|
||||||
note_zoom: PhraseViewNoteZoom::N(1)
|
note_zoom: PhraseViewNoteZoom::N(1),
|
||||||
|
focused: true,
|
||||||
|
note_len
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseEditorModel {
|
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
|
/// Put note at current position
|
||||||
pub fn put_note (&mut self) {
|
pub fn put_note (&mut self, advance: bool) {
|
||||||
if let Some(phrase) = *self.phrase.read().unwrap() {
|
if let Some(phrase) = &*self.phrase.read().unwrap() {
|
||||||
let note_len = self.note_len.load(Ordering::Relaxed);
|
let note_len = self.note_len.load(Ordering::Relaxed);
|
||||||
let time = self.time_point.load(Ordering::Relaxed);
|
let time = self.time_point.load(Ordering::Relaxed);
|
||||||
let note = self.note_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;
|
let end = (start + note_len) % phrase.length;
|
||||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
||||||
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
||||||
self.view_mode.show(Some(&phrase), note_len);
|
self.view_mode.redraw();
|
||||||
}
|
if advance {
|
||||||
}
|
let point = self.time_point.load(Ordering::Relaxed);
|
||||||
/// Move time cursor forward by current note length
|
let length = phrase.length;
|
||||||
pub fn time_cursor_advance (&self) {
|
let forward = |time|(time + note_len) % length;
|
||||||
let point = self.time_point.load(Ordering::Relaxed);
|
self.time_point.store(forward(point), 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 time_zoom (&self) -> usize;
|
||||||
fn set_time_zoom (&mut self, time_zoom: usize);
|
fn set_time_zoom (&mut self, time_zoom: usize);
|
||||||
fn time_zoom_lock (&self) -> bool;
|
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>>>>>;
|
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> {
|
pub struct PhraseView<'a> {
|
||||||
note_point: usize,
|
note_point: usize,
|
||||||
note_range: (usize, usize),
|
note_range: (usize, usize),
|
||||||
|
|
@ -147,23 +173,6 @@ pub struct PhraseView<'a> {
|
||||||
entered: bool,
|
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> {
|
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||||
fn from (state: &'a T) -> Self {
|
fn from (state: &'a T) -> Self {
|
||||||
let editor = state.editor();
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PhraseCommand {
|
pub enum PhraseCommand {
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
// 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_start = state.time_start.load(Ordering::Relaxed);
|
||||||
let time_point = state.time_point.load(Ordering::Relaxed);
|
let time_point = state.time_point.load(Ordering::Relaxed);
|
||||||
let time_zoom = state.view_mode.time_zoom();
|
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);
|
let note_len = state.note_len.load(Ordering::Relaxed);
|
||||||
Some(match from.event() {
|
Some(match from.event() {
|
||||||
key!(Char('`')) => ToggleDirection,
|
key!(Char('`')) => ToggleDirection,
|
||||||
|
|
@ -307,9 +261,6 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
||||||
key!(Ctrl-PageDown) => SetNoteScroll(note_point.saturating_sub(3)),
|
key!(Ctrl-PageDown) => SetNoteScroll(note_point.saturating_sub(3)),
|
||||||
key!(Ctrl-Left) => SetTimeScroll(time_start.saturating_sub(note_len)),
|
key!(Ctrl-Left) => SetTimeScroll(time_start.saturating_sub(note_len)),
|
||||||
key!(Ctrl-Right) => SetTimeScroll(time_start + note_len),
|
key!(Ctrl-Right) => SetTimeScroll(time_start + note_len),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_ => return None
|
_ => return None
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -320,29 +271,16 @@ impl Command<PhraseEditorModel> for PhraseCommand {
|
||||||
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
|
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
match self {
|
match self {
|
||||||
Show(phrase) => {
|
Show(phrase) => { state.show_phrase(phrase); },
|
||||||
state.show_phrase(phrase);
|
PutNote => { state.put_note(false); },
|
||||||
},
|
AppendNote => { state.put_note(true); },
|
||||||
PutNote => {
|
SetTimeZoom(zoom) => { state.view_mode.set_time_zoom(zoom); },
|
||||||
state.put_note();
|
SetTimeZoomLock(lock) => { state.view_mode.set_time_zoom_lock(lock); },
|
||||||
},
|
SetTimeScroll(time) => { state.time_start.store(time, Ordering::Relaxed); },
|
||||||
AppendNote => {
|
SetTimeCursor(time) => { state.time_point.store(time, Ordering::Relaxed); },
|
||||||
state.put_note();
|
SetNoteLength(time) => { state.note_len.store(time, Ordering::Relaxed); },
|
||||||
state.time_cursor_advance();
|
SetNoteScroll(note) => { state.note_lo.store(note, Ordering::Relaxed); },
|
||||||
},
|
SetNoteCursor(note) => {
|
||||||
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) => {
|
|
||||||
let note = 127.min(note);
|
let note = 127.min(note);
|
||||||
let start = state.note_lo.load(Ordering::Relaxed);
|
let start = state.note_lo.load(Ordering::Relaxed);
|
||||||
state.note_point.store(note, Ordering::Relaxed);
|
state.note_point.store(note, Ordering::Relaxed);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue