mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
remove FixedAxis and ScaledAxis
This commit is contained in:
parent
54057afad8
commit
286dec0f40
6 changed files with 223 additions and 290 deletions
|
|
@ -33,72 +33,6 @@ impl<T> Coordinate for T where T: Send + Sync + Copy
|
|||
+ Into<f64>
|
||||
{}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FixedAxis<T> {
|
||||
pub start: T,
|
||||
pub point: Option<T>,
|
||||
pub clamp: Option<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScaledAxis<T> {
|
||||
pub start: T,
|
||||
pub scale: T,
|
||||
pub point: Option<T>,
|
||||
pub clamp: Option<T>,
|
||||
}
|
||||
|
||||
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
||||
impl $A<$T> {
|
||||
#[inline] pub fn start_set (&mut self, n: $T) -> $T {
|
||||
self.start = n;
|
||||
self.start
|
||||
}
|
||||
#[inline] pub fn start_plus (&self, n: $T) -> $T {
|
||||
(self.start + n).min(self.clamp.unwrap_or(<$T>::MAX))
|
||||
}
|
||||
#[inline] pub fn start_inc (&mut self, n: $T) -> $T {
|
||||
self.start_set(self.start_plus(n))
|
||||
}
|
||||
#[inline] pub fn start_minus (&self, n: $T) -> $T {
|
||||
self.start.saturating_sub(n)
|
||||
}
|
||||
#[inline] pub fn start_dec (&mut self, n: $T) -> $T {
|
||||
self.start = self.start_minus(n);
|
||||
self.start
|
||||
}
|
||||
|
||||
#[inline] pub fn point_set (&mut self, n: Option<$T>) -> Option<$T> {
|
||||
self.point = n;
|
||||
self.point
|
||||
}
|
||||
#[inline] pub fn point_plus (&self, n: $T) -> Option<$T> {
|
||||
self.point.map(|p|(p + n).min(self.clamp.unwrap_or(<$T>::MAX)))
|
||||
}
|
||||
#[inline] pub fn point_inc (&mut self, n: $T) -> Option<$T> {
|
||||
self.point_set(self.point_plus(n))
|
||||
}
|
||||
#[inline] pub fn point_minus (&self, n: $T) -> Option<$T> {
|
||||
self.point.map(|p|p.saturating_sub(n))
|
||||
}
|
||||
#[inline] pub fn point_dec (&mut self, n: $T) -> Option<$T> {
|
||||
self.point_set(self.point_minus(n))
|
||||
}
|
||||
|
||||
}
|
||||
} }
|
||||
impl_axis_common!(FixedAxis u16);
|
||||
impl_axis_common!(FixedAxis usize);
|
||||
impl_axis_common!(ScaledAxis u16);
|
||||
impl_axis_common!(ScaledAxis usize);
|
||||
|
||||
impl<T: Copy> ScaledAxis<T> {
|
||||
#[inline] pub fn scale_set (&mut self, n: T) -> T {
|
||||
self.scale = n;
|
||||
n
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: return impl Point and impl Size instead of [N;x]
|
||||
// to disambiguate between usage of 2-"tuple"s
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ impl Command<ArrangerTui> for ArrangerCommand {
|
|||
Track(cmd) => cmd.execute(state)?.map(Track),
|
||||
Clip(cmd) => cmd.execute(state)?.map(Clip),
|
||||
Phrases(cmd) => cmd.execute(&mut state.phrases)?.map(Phrases),
|
||||
Editor(cmd) => cmd.execute(state)?.map(Editor),
|
||||
Editor(cmd) => cmd.execute(&mut state.editor)?.map(Editor),
|
||||
Clock(cmd) => cmd.execute(state)?.map(Clock),
|
||||
Zoom(zoom) => { todo!(); },
|
||||
Select(selected) => {
|
||||
|
|
@ -140,7 +140,9 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
|
|||
return None
|
||||
}
|
||||
Some(match input.event() {
|
||||
key!(Char('e')) => Cmd::Editor(PhraseCommand::Show(state.phrase_to_edit().clone())),
|
||||
key!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some(
|
||||
state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()
|
||||
))),
|
||||
_ => match state.focused() {
|
||||
ArrangerFocus::Transport(_) => {
|
||||
match TransportCommand::input_to_command(state, input)? {
|
||||
|
|
@ -149,7 +151,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
|
|||
}
|
||||
},
|
||||
ArrangerFocus::PhraseEditor => {
|
||||
Cmd::Editor(PhraseCommand::input_to_command(state, input)?)
|
||||
Cmd::Editor(PhraseCommand::input_to_command(&state.editor, input)?)
|
||||
},
|
||||
ArrangerFocus::Phrases => {
|
||||
Cmd::Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?)
|
||||
|
|
|
|||
|
|
@ -3,105 +3,141 @@ use crate::*;
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum PhraseCommand {
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||
ToggleDirection,
|
||||
EnterEditMode,
|
||||
ExitEditMode,
|
||||
NoteAppend,
|
||||
NoteSet,
|
||||
NoteCursorSet(Option<usize>),
|
||||
NoteLengthSet(usize),
|
||||
NoteScrollSet(usize),
|
||||
TimeCursorSet(Option<usize>),
|
||||
TimeScrollSet(usize),
|
||||
TimeZoomSet(usize),
|
||||
AppendNote,
|
||||
PutNote,
|
||||
SetNoteCursor(usize),
|
||||
SetNoteLength(usize),
|
||||
SetNoteScroll(usize),
|
||||
SetTimeCursor(usize),
|
||||
SetTimeScroll(usize),
|
||||
SetTimeZoom(usize),
|
||||
Show(Option<Arc<RwLock<Phrase>>>),
|
||||
SetEditMode(PhraseEditMode),
|
||||
ToggleDirection,
|
||||
}
|
||||
|
||||
impl<T: PhraseEditorControl + HasEnter> InputToCommand<Tui, T> for PhraseCommand {
|
||||
fn input_to_command (state: &T, from: &TuiInput) -> Option<Self> {
|
||||
impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
||||
fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> {
|
||||
use PhraseCommand::*;
|
||||
use KeyCode::{Char, Enter, Esc, Up, Down, PageUp, PageDown, Left, Right};
|
||||
Some(match from.event() {
|
||||
key!(Char('`')) => ToggleDirection,
|
||||
key!(Enter) => EnterEditMode,
|
||||
key!(Esc) => ExitEditMode,
|
||||
key!(Char('a')) => NoteAppend,
|
||||
key!(Char('s')) => NoteSet,
|
||||
key!(Char('[')) => NoteLengthSet(prev_note_length(state.note_len())),
|
||||
key!(Char(']')) => NoteLengthSet(next_note_length(state.note_len())),
|
||||
key!(Char('-')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(Char('_')) => TimeZoomSet(next_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(Char('=')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(Char('+')) => TimeZoomSet(prev_note_length(state.time_axis().read().unwrap().scale)),
|
||||
key!(Up) => match state.phrase_editor_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(1)),
|
||||
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(1)),
|
||||
},
|
||||
key!(Down) => match state.phrase_editor_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(1)),
|
||||
false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(1)),
|
||||
},
|
||||
key!(PageUp) => match state.phrase_editor_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_plus(3)),
|
||||
false => NoteScrollSet(state.note_axis().write().unwrap().start_plus(3)),
|
||||
},
|
||||
key!(PageDown) => match state.phrase_editor_entered() {
|
||||
true => NoteCursorSet(state.note_axis().write().unwrap().point_minus(3)),
|
||||
false => NoteScrollSet(state.note_axis().write().unwrap().start_minus(3)),
|
||||
},
|
||||
key!(Left) => match state.phrase_editor_entered() {
|
||||
true => TimeCursorSet(state.note_axis().write().unwrap().point_minus(1)),
|
||||
false => TimeScrollSet(state.note_axis().write().unwrap().start_minus(1)),
|
||||
},
|
||||
key!(Right) => match state.phrase_editor_entered() {
|
||||
true => TimeCursorSet(state.note_axis().write().unwrap().point_plus(1)),
|
||||
false => TimeScrollSet(state.note_axis().write().unwrap().start_plus(1)),
|
||||
},
|
||||
key!(Enter) => SetEditMode(PhraseEditMode::Note),
|
||||
key!(Esc) => SetEditMode(PhraseEditMode::Scroll),
|
||||
key!(Char('-')) => SetTimeZoom(
|
||||
next_note_length(state.time_scale.load(Ordering::Relaxed))
|
||||
),
|
||||
key!(Char('_')) => SetTimeZoom(
|
||||
next_note_length(state.time_scale.load(Ordering::Relaxed))
|
||||
),
|
||||
key!(Char('=')) => SetTimeZoom(
|
||||
prev_note_length(state.time_scale.load(Ordering::Relaxed))
|
||||
),
|
||||
key!(Char('+')) => SetTimeZoom(
|
||||
prev_note_length(state.time_scale.load(Ordering::Relaxed))
|
||||
),
|
||||
_ => match state.edit_mode {
|
||||
PhraseEditMode::Scroll => match from.event() {
|
||||
key!(Up) => SetNoteCursor(
|
||||
state.note_point.load(Ordering::Relaxed) + 1
|
||||
),
|
||||
key!(Down) => SetNoteCursor(
|
||||
state.note_point.load(Ordering::Relaxed).saturating_sub(1)
|
||||
),
|
||||
key!(PageUp) => SetNoteCursor(
|
||||
state.note_point.load(Ordering::Relaxed) + 3
|
||||
),
|
||||
key!(PageDown) => SetNoteCursor(
|
||||
state.note_point.load(Ordering::Relaxed).saturating_sub(3)
|
||||
),
|
||||
key!(Left) => SetTimeCursor(
|
||||
state.note_point.load(Ordering::Relaxed).saturating_sub(1)
|
||||
),
|
||||
key!(Right) => SetTimeCursor(
|
||||
state.note_point.load(Ordering::Relaxed) + 1
|
||||
),
|
||||
_ => return None
|
||||
},
|
||||
PhraseEditMode::Note => match from.event() {
|
||||
key!(Up) => SetNoteScroll(
|
||||
state.note_start.load(Ordering::Relaxed) + 1
|
||||
),
|
||||
key!(Down) => SetNoteScroll(
|
||||
state.note_start.load(Ordering::Relaxed).saturating_sub(1)
|
||||
),
|
||||
key!(PageUp) => SetNoteScroll(
|
||||
state.note_start.load(Ordering::Relaxed) + 3
|
||||
),
|
||||
key!(PageDown) => SetNoteScroll(
|
||||
state.note_start.load(Ordering::Relaxed).saturating_sub(3)
|
||||
),
|
||||
key!(Left) => SetTimeScroll(
|
||||
state.note_start.load(Ordering::Relaxed).saturating_sub(1)
|
||||
),
|
||||
key!(Right) => SetTimeScroll(
|
||||
state.note_start.load(Ordering::Relaxed) + 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
|
||||
},
|
||||
}
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PhraseEditorControl + HasEnter> Command<T> for PhraseCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
impl Command<PhraseEditorModel> for PhraseCommand {
|
||||
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
|
||||
use PhraseCommand::*;
|
||||
Ok(match self {
|
||||
Show(phrase) => {
|
||||
state.edit_phrase(phrase);
|
||||
state.phrase = phrase;
|
||||
None
|
||||
},
|
||||
ToggleDirection => { todo!() },
|
||||
EnterEditMode => {
|
||||
state.focus_enter();
|
||||
ToggleDirection => {
|
||||
todo!()
|
||||
},
|
||||
SetEditMode(mode) => {
|
||||
state.edit_mode = mode;
|
||||
None
|
||||
}
|
||||
AppendNote => {
|
||||
state.put_note();
|
||||
state.time_cursor_advance();
|
||||
None
|
||||
},
|
||||
ExitEditMode => {
|
||||
state.focus_exit();
|
||||
PutNote => {
|
||||
state.put_note();
|
||||
None
|
||||
},
|
||||
NoteAppend => {
|
||||
if state.phrase_editor_entered() {
|
||||
state.put_note();
|
||||
state.time_cursor_advance();
|
||||
}
|
||||
SetTimeCursor(time) => {
|
||||
state.time_point.store(time, Ordering::Relaxed);
|
||||
None
|
||||
},
|
||||
NoteSet => { if state.phrase_editor_entered() { state.put_note(); } None },
|
||||
TimeCursorSet(time) => { state.time_axis().write().unwrap().point_set(time); None },
|
||||
TimeScrollSet(time) => { state.time_axis().write().unwrap().start_set(time); None },
|
||||
TimeZoomSet(zoom) => { state.time_axis().write().unwrap().scale_set(zoom); None },
|
||||
NoteScrollSet(note) => { state.note_axis().write().unwrap().start_set(note); None },
|
||||
NoteLengthSet(time) => { *state.note_len_mut() = time; None },
|
||||
NoteCursorSet(note) => {
|
||||
let mut axis = state.note_axis().write().unwrap();
|
||||
axis.point_set(note);
|
||||
if let Some(point) = axis.point {
|
||||
if point > 73 {
|
||||
axis.point = Some(73);
|
||||
}
|
||||
if point < axis.start {
|
||||
axis.start = (point / 2) * 2;
|
||||
}
|
||||
SetTimeScroll(time) => {
|
||||
state.time_start.store(time, Ordering::Relaxed);
|
||||
None
|
||||
},
|
||||
SetTimeZoom(zoom) => {
|
||||
state.time_scale.store(zoom, Ordering::Relaxed);
|
||||
None
|
||||
},
|
||||
SetNoteScroll(note) => {
|
||||
state.note_start.store(note, Ordering::Relaxed);
|
||||
None
|
||||
},
|
||||
SetNoteLength(time) => {
|
||||
state.note_len = time;
|
||||
None
|
||||
},
|
||||
SetNoteCursor(note) => {
|
||||
let start = state.note_start.load(Ordering::Relaxed);
|
||||
state.note_point.store(note, Ordering::Relaxed);
|
||||
if note < start {
|
||||
state.note_start.store((note / 2) * 2, Ordering::Relaxed);
|
||||
}
|
||||
None
|
||||
},
|
||||
|
|
@ -109,75 +145,3 @@ impl<T: PhraseEditorControl + HasEnter> Command<T> for PhraseCommand {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PhraseEditorControl {
|
||||
fn edit_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>);
|
||||
fn phrase_to_edit (&self) -> Option<Arc<RwLock<Phrase>>>;
|
||||
fn phrase_editing (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
||||
fn phrase_editor_entered (&self) -> bool;
|
||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||
fn note_len (&self) -> usize;
|
||||
fn note_len_mut (&mut self) -> &mut usize;
|
||||
fn put_note (&mut self);
|
||||
fn time_cursor_advance (&self) {
|
||||
let point = self.time_axis().read().unwrap().point;
|
||||
let length = self.phrase_editing().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
let forward = |time|(time + self.note_len()) % length;
|
||||
self.time_axis().write().unwrap().point = point.map(forward);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_phrase_editor_control {
|
||||
(
|
||||
$Struct:ident $(:: $field:ident)*
|
||||
[$Focus:expr]
|
||||
[$self1:ident: $phrase_to_edit:expr]
|
||||
[$self2:ident, $phrase:ident: $edit_phrase:expr]
|
||||
) => {
|
||||
impl PhraseEditorControl for $Struct {
|
||||
fn phrase_to_edit (&$self1) -> Option<Arc<RwLock<Phrase>>> {
|
||||
$phrase_to_edit
|
||||
}
|
||||
fn edit_phrase (&mut $self2, $phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||
$edit_phrase
|
||||
//self.editor.show(self.selected_phrase().as_ref());
|
||||
//state.editor.phrase = phrase.clone();
|
||||
//state.focus(ArrangerFocus::PhraseEditor);
|
||||
//state.focus_enter();
|
||||
//todo!("edit_phrase")
|
||||
}
|
||||
fn phrase_editing (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||
todo!("phrase_editing")
|
||||
}
|
||||
fn phrase_editor_entered (&self) -> bool {
|
||||
self.entered && self.focused() == $Focus
|
||||
}
|
||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
|
||||
&self.editor.time_axis
|
||||
}
|
||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>> {
|
||||
&self.editor.note_axis
|
||||
}
|
||||
fn note_len (&self) -> usize {
|
||||
self.editor.note_len
|
||||
}
|
||||
fn note_len_mut (&mut self) -> &mut usize {
|
||||
&mut self.editor.note_len
|
||||
}
|
||||
fn put_note (&mut self) {
|
||||
todo!("put_note")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_phrase_editor_control!(SequencerTui
|
||||
[SequencerFocus::PhraseEditor]
|
||||
[self: Some(self.phrases.phrases[self.phrases.phrase.load(Ordering::Relaxed)].clone())]
|
||||
[self, phrase: self.editor.show(phrase)]
|
||||
);
|
||||
impl_phrase_editor_control!(ArrangerTui
|
||||
[ArrangerFocus::PhraseEditor]
|
||||
[self: todo!()]
|
||||
[self, phrase: todo!()]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ impl Command<SequencerTui> for SequencerCommand {
|
|||
Ok(match self {
|
||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||
Phrases(cmd) => cmd.execute(&mut state.phrases)?.map(Phrases),
|
||||
Editor(cmd) => cmd.execute(state)?.map(Editor),
|
||||
Editor(cmd) => cmd.execute(&mut state.editor)?.map(Editor),
|
||||
Clock(cmd) => cmd.execute(state)?.map(Clock),
|
||||
Enqueue(phrase) => {
|
||||
state.player.enqueue_next(phrase.as_ref());
|
||||
|
|
@ -78,7 +78,7 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
|
|||
_ => return None,
|
||||
},
|
||||
SequencerFocus::PhraseEditor => Editor(
|
||||
PhraseCommand::input_to_command(state, input)?
|
||||
PhraseCommand::input_to_command(&state.editor, input)?
|
||||
),
|
||||
SequencerFocus::PhraseList => match input.event() {
|
||||
key!(Enter) => Enqueue(Some(
|
||||
|
|
|
|||
|
|
@ -10,12 +10,6 @@ pub struct PhraseEditorModel {
|
|||
pub(crate) keys: Buffer,
|
||||
/// The full piano roll is rendered to this buffer
|
||||
pub(crate) buffer: BigBuffer,
|
||||
/// Cursor/scroll/zoom in pitch axis
|
||||
pub(crate) note_axis: RwLock<FixedAxis<usize>>,
|
||||
/// Cursor/scroll/zoom in time axis
|
||||
pub(crate) time_axis: RwLock<ScaledAxis<usize>>,
|
||||
/// Display mode
|
||||
pub(crate) mode: bool,
|
||||
/// Notes currently held at input
|
||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||
/// Notes currently held at output
|
||||
|
|
@ -23,14 +17,34 @@ pub struct PhraseEditorModel {
|
|||
/// Current position of global playhead
|
||||
pub(crate) now: Arc<Pulse>,
|
||||
/// Width and height of notes area at last render
|
||||
pub(crate) size: Measure<Tui>
|
||||
pub(crate) size: Measure<Tui>,
|
||||
|
||||
pub(crate) note_start: AtomicUsize,
|
||||
pub(crate) note_point: AtomicUsize,
|
||||
pub(crate) note_clamp: AtomicUsize,
|
||||
|
||||
pub(crate) time_start: AtomicUsize,
|
||||
pub(crate) time_point: AtomicUsize,
|
||||
pub(crate) time_clamp: AtomicUsize,
|
||||
pub(crate) time_scale: AtomicUsize,
|
||||
|
||||
pub(crate) edit_mode: PhraseEditMode,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PhraseEditorModel {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("PhraseEditorModel")
|
||||
.field("note_axis", &self.time_axis)
|
||||
.field("time_axis", &self.note_axis)
|
||||
.field("note_axis", &format!("{} {} {}",
|
||||
self.note_start.load(Ordering::Relaxed),
|
||||
self.note_point.load(Ordering::Relaxed),
|
||||
self.note_clamp.load(Ordering::Relaxed),
|
||||
))
|
||||
.field("time_axis", &format!("{} {} {} {}",
|
||||
self.time_start.load(Ordering::Relaxed),
|
||||
self.time_point.load(Ordering::Relaxed),
|
||||
self.time_clamp.load(Ordering::Relaxed),
|
||||
self.time_scale.load(Ordering::Relaxed),
|
||||
))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -38,30 +52,44 @@ impl std::fmt::Debug for PhraseEditorModel {
|
|||
impl Default for PhraseEditorModel {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
phrase: None,
|
||||
note_len: 24,
|
||||
keys: keys_vert(),
|
||||
buffer: Default::default(),
|
||||
mode: false,
|
||||
notes_in: RwLock::new([false;128]).into(),
|
||||
notes_out: RwLock::new([false;128]).into(),
|
||||
now: Pulse::default().into(),
|
||||
size: Measure::new(),
|
||||
note_axis: RwLock::new(FixedAxis {
|
||||
start: 12,
|
||||
point: Some(36),
|
||||
clamp: Some(127)
|
||||
}),
|
||||
time_axis: RwLock::new(ScaledAxis {
|
||||
start: 00,
|
||||
point: Some(00),
|
||||
clamp: Some(000),
|
||||
scale: 24
|
||||
}),
|
||||
phrase: None,
|
||||
note_len: 24,
|
||||
keys: keys_vert(),
|
||||
buffer: Default::default(),
|
||||
notes_in: RwLock::new([false;128]).into(),
|
||||
notes_out: RwLock::new([false;128]).into(),
|
||||
now: Pulse::default().into(),
|
||||
size: Measure::new(),
|
||||
edit_mode: PhraseEditMode::Scroll,
|
||||
note_start: 12.into(),
|
||||
note_point: 36.into(),
|
||||
note_clamp: 127.into(),
|
||||
time_start: 0.into(),
|
||||
time_point: 0.into(),
|
||||
time_clamp: 0.into(),
|
||||
time_scale: 24.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PhraseEditorModel {
|
||||
pub fn put_note (&mut self) {
|
||||
todo!("put_note")
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum PhraseEditMode {
|
||||
Note,
|
||||
Scroll,
|
||||
}
|
||||
|
||||
pub trait HasEditor {
|
||||
fn editor (&self) -> &PhraseEditorModel;
|
||||
fn editor_focused (&self) -> bool;
|
||||
|
|
|
|||
|
|
@ -8,24 +8,36 @@ pub struct PhraseView<'a> {
|
|||
pub(crate) keys: &'a Buffer,
|
||||
pub(crate) buffer: &'a BigBuffer,
|
||||
pub(crate) note_len: usize,
|
||||
pub(crate) note_axis: &'a RwLock<FixedAxis<usize>>,
|
||||
pub(crate) time_axis: &'a RwLock<ScaledAxis<usize>>,
|
||||
pub(crate) now: &'a Arc<Pulse>,
|
||||
|
||||
pub(crate) note_start: &'a AtomicUsize,
|
||||
pub(crate) note_point: &'a AtomicUsize,
|
||||
pub(crate) note_clamp: &'a AtomicUsize,
|
||||
|
||||
pub(crate) time_start: &'a AtomicUsize,
|
||||
pub(crate) time_point: &'a AtomicUsize,
|
||||
pub(crate) time_clamp: &'a AtomicUsize,
|
||||
pub(crate) time_scale: &'a AtomicUsize,
|
||||
}
|
||||
|
||||
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||
fn from (state: &'a T) -> Self {
|
||||
Self {
|
||||
focused: state.editor_focused(),
|
||||
entered: state.editor_entered(),
|
||||
note_len: state.editor().note_len,
|
||||
phrase: &state.editor().phrase,
|
||||
size: &state.editor().size,
|
||||
keys: &state.editor().keys,
|
||||
buffer: &state.editor().buffer,
|
||||
note_axis: &state.editor().note_axis,
|
||||
time_axis: &state.editor().time_axis,
|
||||
now: &state.editor().now
|
||||
focused: state.editor_focused(),
|
||||
entered: state.editor_entered(),
|
||||
note_len: state.editor().note_len,
|
||||
phrase: &state.editor().phrase,
|
||||
size: &state.editor().size,
|
||||
keys: &state.editor().keys,
|
||||
buffer: &state.editor().buffer,
|
||||
now: &state.editor().now,
|
||||
note_start: &state.editor().note_start,
|
||||
note_point: &state.editor().note_point,
|
||||
note_clamp: &state.editor().note_clamp,
|
||||
time_start: &state.editor().time_start,
|
||||
time_point: &state.editor().time_point,
|
||||
time_clamp: &state.editor().time_clamp,
|
||||
time_scale: &state.editor().time_scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,13 +45,14 @@ impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
|||
impl<'a> Content for PhraseView<'a> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self {
|
||||
focused, entered, phrase, size, keys, buffer, note_len, note_axis, time_axis, now
|
||||
} = self;
|
||||
let FixedAxis { start: note_start, point: note_point, clamp: note_clamp }
|
||||
= *note_axis.read().unwrap();
|
||||
let ScaledAxis { start: time_start, point: time_point, clamp: time_clamp, scale: time_scale }
|
||||
= *time_axis.read().unwrap();
|
||||
let Self { focused, entered, phrase, size, keys, buffer, note_len, now, .. } = self;
|
||||
let note_start = self.note_start.load(Ordering::Relaxed);
|
||||
let note_point = self.note_point.load(Ordering::Relaxed);
|
||||
let note_clamp = self.note_clamp.load(Ordering::Relaxed);
|
||||
let time_start = self.time_start.load(Ordering::Relaxed);
|
||||
let time_point = self.time_point.load(Ordering::Relaxed);
|
||||
let time_clamp = self.time_clamp.load(Ordering::Relaxed);
|
||||
let time_scale = self.time_scale.load(Ordering::Relaxed);
|
||||
//let color = Color::Rgb(0,255,0);
|
||||
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
|
||||
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
||||
|
|
@ -57,11 +70,8 @@ impl<'a> Content for PhraseView<'a> {
|
|||
let area = to.area();
|
||||
let h = area.h() as usize;
|
||||
size.set_wh(area.w(), h);
|
||||
let mut axis = note_axis.write().unwrap();
|
||||
if let Some(point) = axis.point {
|
||||
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
|
||||
axis.start += 2;
|
||||
}
|
||||
if note_point.saturating_sub(note_start) > (h * 2).saturating_sub(1) {
|
||||
self.note_start.store(note_start + 2, Ordering::Relaxed);
|
||||
}
|
||||
Ok(if to.area().h() >= 2 {
|
||||
let area = to.area();
|
||||
|
|
@ -82,14 +92,12 @@ impl<'a> Content for PhraseView<'a> {
|
|||
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
Ok(if *focused && *entered {
|
||||
let area = to.area();
|
||||
if let (Some(time), Some(note)) = (time_point, note_point) {
|
||||
let x1 = area.x() + (time / time_scale) as u16;
|
||||
let x2 = x1 + (note_len / time_scale) as u16;
|
||||
let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
for x in x1..x2 {
|
||||
to.blit(&c, x, y, Some(Style::default().fg(Color::Rgb(0,255,0))));
|
||||
}
|
||||
let x1 = area.x() + (time_point / time_scale) as u16;
|
||||
let x2 = x1 + (note_len / time_scale) as u16;
|
||||
let y = area.y() + note_point.saturating_sub(note_start) as u16 / 2;
|
||||
let c = if note_point % 2 == 0 { "▀" } else { "▄" };
|
||||
for x in x1..x2 {
|
||||
to.blit(&c, x, y, Some(Style::default().fg(Color::Rgb(0,255,0))));
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
@ -100,8 +108,7 @@ impl<'a> Content for PhraseView<'a> {
|
|||
move|to: &mut TuiOutput|{
|
||||
if let Some(_) = phrase {
|
||||
let now = now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
||||
let time_clamp = time_clamp
|
||||
.expect("time_axis of sequencer expected to be clamped");
|
||||
let time_clamp = time_clamp;
|
||||
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
|
||||
let this_step = time_start + (x + 0) * time_scale;
|
||||
let next_step = time_start + (x + 1) * time_scale;
|
||||
|
|
@ -136,7 +143,7 @@ impl<'a> Content for PhraseView<'a> {
|
|||
//);
|
||||
if *focused && *entered {
|
||||
lower_right = format!("┤Note: {} {}├─{lower_right}",
|
||||
note_axis.read().unwrap().point.unwrap(),
|
||||
note_point,
|
||||
pulses_to_name(*note_len));
|
||||
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||
//pulses_to_name(*note_len),
|
||||
|
|
@ -206,11 +213,9 @@ const NTH_OCTAVE: [&'static str; 11] = [
|
|||
|
||||
impl PhraseEditorModel {
|
||||
pub fn put (&mut self) {
|
||||
if let (Some(phrase), Some(time), Some(note)) = (
|
||||
&self.phrase,
|
||||
self.time_axis.read().unwrap().point,
|
||||
self.note_axis.read().unwrap().point,
|
||||
) {
|
||||
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();
|
||||
|
|
@ -225,11 +230,11 @@ impl PhraseEditorModel {
|
|||
pub fn show (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||
if let Some(phrase) = phrase {
|
||||
self.phrase = Some(phrase.clone());
|
||||
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
||||
self.time_clamp.store(phrase.read().unwrap().length, Ordering::Relaxed);
|
||||
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
||||
} else {
|
||||
self.phrase = None;
|
||||
self.time_axis.write().unwrap().clamp = Some(0);
|
||||
self.time_clamp.store(0, Ordering::Relaxed);
|
||||
self.buffer = Default::default();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue