mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
reenabling phrase editing
This commit is contained in:
parent
37d4a42a83
commit
82a326d6f8
6 changed files with 95 additions and 77 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
&[
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue