reenabling phrase editing

This commit is contained in:
🪞👃🪞 2024-12-02 02:44:52 +01:00
parent 37d4a42a83
commit 82a326d6f8
6 changed files with 95 additions and 77 deletions

View file

@ -97,7 +97,7 @@ pub fn to_focus_command (input: &TuiInput) -> Option<FocusCommand> {
}
#[macro_export] macro_rules! impl_focus {
($Struct:ident $Focus:ident $Grid:expr) => {
($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => {
impl HasFocus for $Struct {
type Item = $Focus;
/// Get the currently focused item.
@ -108,6 +108,7 @@ pub fn to_focus_command (input: &TuiInput) -> Option<FocusCommand> {
fn set_focused (&mut self, to: Self::Item) {
self.focus.set_inner(to)
}
$(fn focus_updated (&mut $self) { $update_focus })?
}
impl HasEnter for $Struct {
/// Get the currently focused item.

View file

@ -104,7 +104,13 @@ impl_focus!(SequencerTui SequencerFocus [
PhraseEditor,
PhraseEditor,
],
]);
] => [self: {
if self.focus.is_entered() && self.focus.inner() == SequencerFocus::PhraseEditor {
self.editor.edit_mode = PhraseEditMode::Note
} else {
self.editor.edit_mode = PhraseEditMode::Scroll
}
}]);
/// Status bar for sequencer app
#[derive(Clone)]
@ -144,10 +150,13 @@ impl From<&SequencerTui> for SequencerStatusBar {
Transport(Sync) => " LAUNCH SYNC ",
Transport(Quant) => " REC QUANT ",
Transport(Clock) => " SEEK ",
PhraseList => " PHRASES ",
PhraseEditor => " EDIT MIDI ",
PhrasePlay => " TO PLAY ",
PhraseNext => " UP NEXT ",
PhraseList => " PHRASES ",
PhraseEditor => match state.editor.edit_mode {
PhraseEditMode::Note => " EDIT MIDI ",
PhraseEditMode::Scroll => " VIEW MIDI ",
},
},
help: match state.focused() {
Transport(PlayPause) => &[
@ -164,6 +173,10 @@ impl From<&SequencerTui> for SequencerStatusBar {
Transport(Quant) => &[
("", ".,", " inc/dec"),
],
Transport(Clock) => &[
("", ".,", " by beat"),
("", "<>", " by time"),
],
PhraseList => if state.entered() {
&[
("", "", " pick"),
@ -173,6 +186,14 @@ impl From<&SequencerTui> for SequencerStatusBar {
]
} else {
default_help
},
PhraseEditor => match state.editor.edit_mode {
PhraseEditMode::Note => &[
("", "", " cursor"),
],
PhraseEditMode::Scroll => &[
("", "", " scroll"),
],
}
_ => if state.entered() {
&[

View file

@ -33,29 +33,30 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
key!(Char('_')) => SetTimeZoom(next_note_length(time_scale)),
key!(Char('=')) => SetTimeZoom(prev_note_length(time_scale)),
key!(Char('+')) => SetTimeZoom(prev_note_length(time_scale)),
key!(Char('a')) => AppendNote,
key!(Char('s')) => PutNote,
key!(Char('[')) => SetNoteLength(prev_note_length(state.note_len)),
key!(Char(']')) => SetNoteLength(next_note_length(state.note_len)),
key!(Char('n')) => { todo!("toggle keys vs notes") },
_ => match state.edit_mode {
PhraseEditMode::Scroll => match from.event() {
key!(Char('e')) => SetEditMode(PhraseEditMode::Note),
key!(Up) => SetNoteCursor(note_point + 1),
key!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
key!(PageUp) => SetNoteCursor(note_point + 3),
key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)),
key!(Left) => SetTimeCursor(time_point.saturating_sub(time_scale)),
key!(Right) => SetTimeCursor((time_point + time_scale) % length),
_ => return None
},
PhraseEditMode::Note => match from.event() {
key!(Char('e')) => SetEditMode(PhraseEditMode::Scroll),
key!(Up) => SetNoteScroll(note_lo + 1),
key!(Down) => SetNoteScroll(note_lo.saturating_sub(1)),
key!(PageUp) => SetNoteScroll(note_lo + 3),
key!(PageDown) => SetNoteScroll(note_lo.saturating_sub(3)),
key!(Left) => SetTimeScroll(time_start.saturating_sub(1)),
key!(Right) => SetTimeScroll(time_start + 1),
key!(Char('a')) => AppendNote,
key!(Char('s')) => PutNote,
key!(Char('[')) => SetNoteLength(prev_note_length(state.note_len)),
key!(Char(']')) => SetNoteLength(next_note_length(state.note_len)),
_ => return None
},
PhraseEditMode::Note => match from.event() {
key!(Char('e')) => SetEditMode(PhraseEditMode::Scroll),
key!(Up) => SetNoteCursor(note_point + 1),
key!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
key!(PageUp) => SetNoteCursor(note_point + 3),
key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)),
key!(Left) => SetTimeCursor(time_point.saturating_sub(time_scale)),
key!(Right) => SetTimeCursor((time_point + time_scale) % length),
_ => return None
},
}
@ -69,7 +70,7 @@ impl Command<PhraseEditorModel> for PhraseCommand {
use PhraseCommand::*;
Ok(match self {
Show(phrase) => {
state.phrase = phrase;
state.show_phrase(phrase);
None
},
ToggleDirection => {

View file

@ -99,9 +99,13 @@ fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<Phra
key!(Char('t')) => Cmd::Length(Length::Begin),
key!(Char('m')) => Cmd::Import(Browse::Begin),
key!(Char('x')) => Cmd::Export(Browse::Begin),
key!(Up) => Cmd::Select(index.overflowing_sub(1).0.min(state.phrases().len() - 1)),
key!(Down) => Cmd::Select(index.saturating_add(1) % state.phrases().len()),
key!(Char('c')) => Cmd::Phrase(Pool::SetColor(index, ItemColor::random())),
key!(Up) => Cmd::Select(
index.overflowing_sub(1).0.min(state.phrases().len() - 1)
),
key!(Down) => Cmd::Select(
index.saturating_add(1) % state.phrases().len()
),
key!(Char(',')) => if index > 1 {
state.set_phrase_index(state.phrase_index().saturating_sub(1));
Cmd::Phrase(Pool::Swap(index - 1, index))
@ -120,16 +124,12 @@ fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<Phra
} else {
return None
},
key!(Char('a')) => Cmd::Phrase(Pool::Add(
count, Phrase::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemColorTriplet::random())
)
)),
key!(Char('i')) => Cmd::Phrase(Pool::Add(
index + 1, Phrase::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemColorTriplet::random())
)
)),
key!(Char('a')) => Cmd::Phrase(Pool::Add(count, Phrase::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemColorTriplet::random())
))),
key!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, Phrase::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemColorTriplet::random())
))),
key!(Char('d')) => {
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);

View file

@ -67,15 +67,40 @@ impl Default for PhraseEditorModel {
}
impl PhraseEditorModel {
/// Put note at current position
pub fn put_note (&mut self) {
todo!("put_note")
if let Some(phrase) = &self.phrase {
let time = self.time_point.load(Ordering::Relaxed);
let note = self.note_point.load(Ordering::Relaxed);
let mut phrase = phrase.write().unwrap();
let key: u7 = u7::from((127 - note) as u8);
let vel: u7 = 100.into();
let start = time;
let end = (start + self.note_len) % phrase.length;
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
self.buffer = Self::redraw(&phrase);
}
}
/// 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 let Some(phrase) = phrase {
self.phrase = Some(phrase.clone());
self.time_clamp.store(phrase.read().unwrap().length, Ordering::Relaxed);
self.buffer = Self::redraw(&*phrase.read().unwrap());
} else {
self.phrase = None;
self.time_clamp.store(0, Ordering::Relaxed);
self.buffer = Default::default();
}
}
}
#[derive(Copy, Clone, Debug)]

View file

@ -1,25 +1,23 @@
use crate::*;
pub struct PhraseView<'a> {
pub(crate) focused: bool,
pub(crate) entered: bool,
pub(crate) phrase: &'a Option<Arc<RwLock<Phrase>>>,
pub(crate) buffer: &'a BigBuffer,
pub(crate) note_len: usize,
pub(crate) now: &'a Arc<Pulse>,
focused: bool,
entered: bool,
phrase: &'a Option<Arc<RwLock<Phrase>>>,
buffer: &'a BigBuffer,
note_len: usize,
now: &'a Arc<Pulse>,
size: &'a Measure<Tui>,
width: usize,
height: usize,
size: &'a Measure<Tui>,
note_point: usize,
note_range: (usize, usize),
note_names: (&'a str, &'a str),
pub(crate) time_start: usize,
pub(crate) time_point: usize,
pub(crate) time_clamp: usize,
pub(crate) time_scale: usize,
time_start: usize,
time_point: usize,
time_clamp: usize,
time_scale: usize,
}
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
@ -52,8 +50,6 @@ impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
now: &editor.now,
size: &editor.size,
width,
height,
note_point,
note_range: (note_lo, note_hi),
@ -234,33 +230,7 @@ const NTH_OCTAVE: [&'static str; 11] = [
];
impl PhraseEditorModel {
pub fn put (&mut self) {
if let Some(phrase) = &self.phrase {
let time = self.time_point.load(Ordering::Relaxed);
let note = self.note_point.load(Ordering::Relaxed);
let mut phrase = phrase.write().unwrap();
let key: u7 = u7::from((127 - note) as u8);
let vel: u7 = 100.into();
let start = time;
let end = (start + self.note_len) % phrase.length;
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
self.buffer = Self::redraw(&phrase);
}
}
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
pub fn show (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
if let Some(phrase) = phrase {
self.phrase = Some(phrase.clone());
self.time_clamp.store(phrase.read().unwrap().length, Ordering::Relaxed);
self.buffer = Self::redraw(&*phrase.read().unwrap());
} else {
self.phrase = None;
self.time_clamp.store(0, Ordering::Relaxed);
self.buffer = Default::default();
}
}
fn redraw (phrase: &Phrase) -> BigBuffer {
pub(crate) fn redraw (phrase: &Phrase) -> BigBuffer {
let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65);
Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq);
Self::fill_seq_fg(&mut buf, &phrase);
@ -319,10 +289,10 @@ impl PhraseEditorModel {
break
}
if let Some(block) = half_block(
notes_on[y as usize * 2],
notes_on[y as usize * 2 + 1],
notes_on[127 - (y as usize * 2)],
notes_on[127 - (y as usize * 2 + 1)],
) {
buf.get_mut(x, y).map(|cell|{
buf.get_mut(x, 64 - y).map(|cell|{
cell.set_char(block);
cell.set_fg(Color::White);
});