mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
simplifying phrase editor
This commit is contained in:
parent
9619ef9739
commit
46467d3972
3 changed files with 261 additions and 256 deletions
|
|
@ -66,31 +66,17 @@ impl Audio for SequencerTui {
|
|||
// Start profiling cycle
|
||||
let t0 = self.perf.get_t0();
|
||||
// Update transport clock
|
||||
if ClockAudio(self)
|
||||
.process(client, scope) == Control::Quit
|
||||
{
|
||||
if Control::Quit == ClockAudio(self).process(client, scope) {
|
||||
return Control::Quit
|
||||
}
|
||||
// Update MIDI sequencer
|
||||
if PlayerAudio(&mut self.player, &mut self.note_buf, &mut self.midi_buf)
|
||||
.process(client, scope) == Control::Quit
|
||||
{
|
||||
if Control::Quit == PlayerAudio(
|
||||
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
||||
).process(client, scope) {
|
||||
return Control::Quit
|
||||
}
|
||||
// End profiling cycle
|
||||
self.perf.update(t0, scope);
|
||||
|
||||
// Update sequencer playhead indicator
|
||||
//self.now().set(0.);
|
||||
//if let Some((ref started_at, Some(ref playing))) = self.player.play_phrase {
|
||||
//let phrase = phrase.read().unwrap();
|
||||
//if *playing.read().unwrap() == *phrase {
|
||||
//let pulse = self.current().pulse.get();
|
||||
//let start = started_at.pulse.get();
|
||||
//let now = (pulse - start) % phrase.length as f64;
|
||||
//self.now().set(now);
|
||||
//}
|
||||
//}
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,201 +6,6 @@ pub trait HasEditor {
|
|||
fn editor_entered (&self) -> bool;
|
||||
}
|
||||
|
||||
/// Contains state for viewing and editing a phrase
|
||||
pub struct PhraseEditorModel {
|
||||
/// Phrase being played
|
||||
pub(crate) phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||
/// Renders the phrase
|
||||
pub(crate) view_mode: Box<dyn PhraseViewMode>,
|
||||
// Lowest note displayed
|
||||
pub(crate) note_lo: AtomicUsize,
|
||||
/// Note coordinate of cursor
|
||||
pub(crate) note_point: AtomicUsize,
|
||||
/// Length of note that will be inserted, in pulses
|
||||
pub(crate) note_len: Arc<AtomicUsize>,
|
||||
/// Notes currently held at input
|
||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||
/// Notes currently held at output
|
||||
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
||||
/// Earliest time displayed
|
||||
pub(crate) time_start: AtomicUsize,
|
||||
/// Time coordinate of cursor
|
||||
pub(crate) time_point: AtomicUsize,
|
||||
/// Current position of global playhead
|
||||
pub(crate) now: Arc<Pulse>,
|
||||
/// Width and height of notes area at last render
|
||||
pub(crate) size: Measure<Tui>,
|
||||
}
|
||||
|
||||
impl From<&Arc<RwLock<Phrase>>> for PhraseEditorModel {
|
||||
fn from (phrase: &Arc<RwLock<Phrase>>) -> Self {
|
||||
Self::from(Some(phrase.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Arc<RwLock<Phrase>>>> for PhraseEditorModel {
|
||||
fn from (phrase: Option<Arc<RwLock<Phrase>>>) -> Self {
|
||||
let model = Self::default();
|
||||
*model.phrase.write().unwrap() = phrase;
|
||||
model
|
||||
}
|
||||
}
|
||||
|
||||
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", &format!("{} {}",
|
||||
self.note_lo.load(Ordering::Relaxed),
|
||||
self.note_point.load(Ordering::Relaxed),
|
||||
))
|
||||
.field("time_axis", &format!("{} {}",
|
||||
self.time_start.load(Ordering::Relaxed),
|
||||
self.time_point.load(Ordering::Relaxed),
|
||||
))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PhraseEditorModel {
|
||||
fn default () -> Self {
|
||||
let phrase = Arc::new(RwLock::new(None));
|
||||
let note_len = Arc::from(AtomicUsize::from(24));
|
||||
Self {
|
||||
size: Measure::new(),
|
||||
phrase: phrase.clone(),
|
||||
now: Pulse::default().into(),
|
||||
time_start: 0.into(),
|
||||
time_point: 0.into(),
|
||||
note_lo: 0.into(),
|
||||
note_point: 0.into(),
|
||||
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: Arc::new(RwLock::new(None)),
|
||||
buffer: Default::default(),
|
||||
time_zoom: 24,
|
||||
time_lock: true,
|
||||
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, 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);
|
||||
let mut phrase = phrase.write().unwrap();
|
||||
let key: u7 = u7::from(note as u8);
|
||||
let vel: u7 = 100.into();
|
||||
let start = time;
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
fn set_time_zoom_lock (&mut self, time_zoom: bool);
|
||||
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize);
|
||||
fn redraw (&mut self);
|
||||
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),
|
||||
time_start: usize,
|
||||
time_point: usize,
|
||||
note_len: usize,
|
||||
phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||
view_mode: &'a Box<dyn PhraseViewMode>,
|
||||
now: &'a Arc<Pulse>,
|
||||
size: &'a Measure<Tui>,
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
}
|
||||
|
||||
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||
fn from (state: &'a T) -> Self {
|
||||
let editor = state.editor();
|
||||
let height = editor.size.h();
|
||||
let note_point = editor.note_point.load(Ordering::Relaxed);
|
||||
let mut note_lo = editor.note_lo.load(Ordering::Relaxed);
|
||||
let mut note_hi = 127.min((note_lo + height).saturating_sub(2));
|
||||
if note_point > note_hi {
|
||||
note_lo += note_point - note_hi;
|
||||
note_hi = note_point;
|
||||
editor.note_lo.store(note_lo, Ordering::Relaxed);
|
||||
}
|
||||
Self {
|
||||
note_point,
|
||||
note_range: (note_lo, note_hi),
|
||||
time_start: editor.time_start.load(Ordering::Relaxed),
|
||||
time_point: editor.time_point.load(Ordering::Relaxed),
|
||||
note_len: editor.note_len.load(Ordering::Relaxed),
|
||||
phrase: editor.phrase.clone(),
|
||||
view_mode: &editor.view_mode,
|
||||
size: &editor.size,
|
||||
now: &editor.now,
|
||||
focused: state.editor_focused(),
|
||||
entered: state.editor_entered(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PhraseCommand {
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||
|
|
@ -220,18 +25,18 @@ pub enum PhraseCommand {
|
|||
impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
||||
fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> {
|
||||
use PhraseCommand::*;
|
||||
use KeyCode::{Char, Esc, Up, Down, PageUp, PageDown, Left, Right};
|
||||
let note_lo = state.note_lo.load(Ordering::Relaxed);
|
||||
let note_point = state.note_point.load(Ordering::Relaxed);
|
||||
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();
|
||||
use KeyCode::{Char, Up, Down, PageUp, PageDown, Left, Right};
|
||||
let note_lo = state.range.note_lo.load(Ordering::Relaxed);
|
||||
let note_point = state.point.note_point.load(Ordering::Relaxed);
|
||||
let time_start = state.range.time_start.load(Ordering::Relaxed);
|
||||
let time_point = state.point.time_point.load(Ordering::Relaxed);
|
||||
let time_zoom = state.mode.time_zoom();
|
||||
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.point.note_len.load(Ordering::Relaxed);
|
||||
Some(match from.event() {
|
||||
key!(Char('`')) => ToggleDirection,
|
||||
key!(Char('z')) => SetTimeZoomLock(!state.view_mode.time_zoom_lock()),
|
||||
key!(Char('z')) => SetTimeZoomLock(!state.mode.time_zoom_lock()),
|
||||
key!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||
|
|
@ -271,21 +76,21 @@ impl Command<PhraseEditorModel> for PhraseCommand {
|
|||
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
|
||||
use PhraseCommand::*;
|
||||
match self {
|
||||
Show(phrase) => { state.show_phrase(phrase); },
|
||||
Show(phrase) => { state.set_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); },
|
||||
SetTimeZoom(zoom) => { state.mode.set_time_zoom(zoom); },
|
||||
SetTimeZoomLock(lock) => { state.mode.set_time_zoom_lock(lock); },
|
||||
SetTimeScroll(time) => { state.range.time_start.store(time, Ordering::Relaxed); },
|
||||
SetTimeCursor(time) => { state.point.time_point.store(time, Ordering::Relaxed); },
|
||||
SetNoteLength(time) => { state.point.note_len.store(time, Ordering::Relaxed); },
|
||||
SetNoteScroll(note) => { state.range.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);
|
||||
let start = state.range.note_lo.load(Ordering::Relaxed);
|
||||
state.point.note_point.store(note, Ordering::Relaxed);
|
||||
if note < start {
|
||||
state.note_lo.store(note, Ordering::Relaxed);
|
||||
state.range.note_lo.store(note, Ordering::Relaxed);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -294,3 +99,191 @@ impl Command<PhraseEditorModel> for PhraseCommand {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains state for viewing and editing a phrase
|
||||
pub struct PhraseEditorModel {
|
||||
/// Phrase being played
|
||||
pub phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||
/// Renders the phrase
|
||||
pub mode: Box<dyn PhraseViewMode>,
|
||||
/// The display window
|
||||
pub range: PhraseEditorRange,
|
||||
/// The note cursor
|
||||
pub point: PhraseEditorPoint,
|
||||
}
|
||||
|
||||
impl Default for PhraseEditorModel {
|
||||
fn default () -> Self {
|
||||
let phrase = Arc::new(RwLock::new(None));
|
||||
let range = PhraseEditorRange::default();
|
||||
let point = PhraseEditorPoint::default();
|
||||
let mode = PianoHorizontal::new(&phrase, &range, &point);
|
||||
Self { phrase, mode: Box::new(mode), range, point }
|
||||
}
|
||||
}
|
||||
|
||||
render!(|self: PhraseEditorModel|self.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;
|
||||
fn set_time_zoom_lock (&mut self, time_zoom: bool);
|
||||
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize);
|
||||
fn redraw (&mut self);
|
||||
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>;
|
||||
fn set_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||
*self.phrase().write().unwrap() = phrase;
|
||||
self.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
impl PhraseViewMode for PhraseEditorModel {
|
||||
fn time_zoom (&self) -> usize {
|
||||
self.mode.time_zoom()
|
||||
}
|
||||
fn set_time_zoom (&mut self, time_zoom: usize) {
|
||||
self.mode.set_time_zoom(time_zoom)
|
||||
}
|
||||
fn time_zoom_lock (&self) -> bool {
|
||||
self.mode.time_zoom_lock()
|
||||
}
|
||||
fn set_time_zoom_lock (&mut self, time_lock: bool) {
|
||||
self.mode.set_time_zoom_lock(time_lock);
|
||||
}
|
||||
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
|
||||
self.mode.buffer_size(phrase)
|
||||
}
|
||||
fn redraw (&mut self) {
|
||||
self.mode.redraw()
|
||||
}
|
||||
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> {
|
||||
self.mode.phrase()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PhraseEditorRange {
|
||||
/// Earliest time displayed
|
||||
pub time_start: Arc<AtomicUsize>,
|
||||
/// Time step
|
||||
pub time_zoom: Arc<AtomicUsize>,
|
||||
// Lowest note displayed
|
||||
pub note_lo: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Default for PhraseEditorRange {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
time_start: Arc::new(0.into()),
|
||||
time_zoom: Arc::new(24.into()),
|
||||
note_lo: Arc::new(0.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PhraseEditorPoint {
|
||||
/// Time coordinate of cursor
|
||||
pub time_point: Arc<AtomicUsize>,
|
||||
/// Note coordinate of cursor
|
||||
pub note_point: Arc<AtomicUsize>,
|
||||
/// Length of note that will be inserted, in pulses
|
||||
pub note_len: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Default for PhraseEditorPoint {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
time_point: Arc::new(0.into()),
|
||||
note_point: Arc::new(0.into()),
|
||||
note_len: Arc::new(24.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PhraseEditorModel {
|
||||
/// Put note at current position
|
||||
pub fn put_note (&mut self, advance: bool) {
|
||||
if let Some(phrase) = &*self.phrase.read().unwrap() {
|
||||
let note_len = self.point.note_len.load(Ordering::Relaxed);
|
||||
let time = self.point.time_point.load(Ordering::Relaxed);
|
||||
let note = self.point.note_point.load(Ordering::Relaxed);
|
||||
let mut phrase = phrase.write().unwrap();
|
||||
let key: u7 = u7::from(note as u8);
|
||||
let vel: u7 = 100.into();
|
||||
let start = time;
|
||||
let end = (start + note_len) % phrase.length;
|
||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
||||
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
||||
self.mode.redraw();
|
||||
if advance {
|
||||
let point = self.point.time_point.load(Ordering::Relaxed);
|
||||
let length = phrase.length;
|
||||
let forward = |time|(time + note_len) % length;
|
||||
self.point.time_point.store(forward(point), Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Arc<RwLock<Phrase>>> for PhraseEditorModel {
|
||||
fn from (phrase: &Arc<RwLock<Phrase>>) -> Self {
|
||||
Self::from(Some(phrase.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Arc<RwLock<Phrase>>>> for PhraseEditorModel {
|
||||
fn from (phrase: Option<Arc<RwLock<Phrase>>>) -> Self {
|
||||
let model = Self::default();
|
||||
*model.phrase.write().unwrap() = phrase;
|
||||
model
|
||||
}
|
||||
}
|
||||
|
||||
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("range", &self.range)
|
||||
.field("point", &self.point)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn autoscroll_notes (
|
||||
range: &PhraseEditorRange, point: &PhraseEditorPoint, height: usize
|
||||
) -> (usize, (usize, usize)) {
|
||||
let note_point = point.note_point.load(Ordering::Relaxed);
|
||||
let mut note_lo = range.note_lo.load(Ordering::Relaxed);
|
||||
let mut note_hi = 127.min((note_lo + height).saturating_sub(2));
|
||||
if note_point > note_hi {
|
||||
note_lo += note_point - note_hi;
|
||||
note_hi = note_point;
|
||||
range.note_lo.store(note_lo, Ordering::Relaxed);
|
||||
}
|
||||
(note_point, (note_lo, note_hi))
|
||||
}
|
||||
|
||||
//impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||
//fn from (state: &'a T) -> Self {
|
||||
//let editor = state.editor();
|
||||
//let (note_point, note_range) = autoscroll_notes(
|
||||
//&editor.range,
|
||||
//&editor.point,
|
||||
//editor.size.h()
|
||||
//);
|
||||
//Self {
|
||||
//note_point,
|
||||
//note_range,
|
||||
//time_start: editor.range.time_start.load(Ordering::Relaxed),
|
||||
//time_point: editor.point.time_point.load(Ordering::Relaxed),
|
||||
//note_len: editor.point.note_len.load(Ordering::Relaxed),
|
||||
//phrase: editor.phrase.clone(),
|
||||
//mode: &editor.mode,
|
||||
//size: &editor.size,
|
||||
//now: &editor.now,
|
||||
//focused: state.editor_focused(),
|
||||
//entered: state.editor_entered(),
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,42 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
||||
/// A phrase, rendered as a horizontal piano roll.
|
||||
pub struct PianoHorizontal {
|
||||
pub(crate) phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||
pub(crate) time_lock: bool,
|
||||
pub(crate) time_zoom: usize,
|
||||
pub(crate) note_zoom: PhraseViewNoteZoom,
|
||||
pub(crate) note_len: Arc<AtomicUsize>,
|
||||
pub(crate) buffer: BigBuffer,
|
||||
pub(crate) focused: bool,
|
||||
phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||
time_lock: bool,
|
||||
time_zoom: Arc<AtomicUsize>,
|
||||
note_len: Arc<AtomicUsize>,
|
||||
buffer: BigBuffer,
|
||||
/// Width and height of notes area at last render
|
||||
size: Measure<Tui>,
|
||||
}
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum PhraseViewNoteZoom {
|
||||
N(usize),
|
||||
Half,
|
||||
Octant,
|
||||
|
||||
impl PianoHorizontal {
|
||||
pub fn new (
|
||||
phrase: &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||
range: &PhraseEditorRange,
|
||||
point: &PhraseEditorPoint,
|
||||
) -> Self {
|
||||
Self {
|
||||
phrase: phrase.clone(),
|
||||
buffer: Default::default(),
|
||||
time_lock: true,
|
||||
time_zoom: range.time_zoom.clone(),
|
||||
note_len: point.note_len.clone(),
|
||||
size: Measure::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render!(|self: PianoHorizontal|{
|
||||
let bg = if self.focused { TuiTheme::g(32) } else { Color::Reset };
|
||||
let bg = TuiTheme::g(32);
|
||||
let fg = self.phrase().read().unwrap()
|
||||
.as_ref().map(|p|p.read().unwrap().color)
|
||||
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
|
||||
Tui::bg(bg, Tui::split_up(false, 2,
|
||||
Tui::bg(bg, Tui::split_down(false, 1,
|
||||
Tui::bg(fg.dark.rgb, PianoHorizontalTimeline {
|
||||
start: "TIMELINE".into()
|
||||
start: "|0".into()
|
||||
}),
|
||||
Split::right(false, 5, PianoHorizontalKeys {
|
||||
color: ItemPalette::random(),
|
||||
|
|
@ -30,6 +44,7 @@ render!(|self: PianoHorizontal|{
|
|||
note_hi: 0,
|
||||
note_point: None
|
||||
}, lay!([
|
||||
self.size,
|
||||
PianoHorizontalNotes {
|
||||
source: &self.buffer,
|
||||
time_start: 0,
|
||||
|
|
@ -47,12 +62,14 @@ render!(|self: PianoHorizontal|{
|
|||
])),
|
||||
))
|
||||
});
|
||||
|
||||
pub struct PianoHorizontalTimeline {
|
||||
start: String
|
||||
}
|
||||
render!(|self: PianoHorizontalTimeline|{
|
||||
Tui::fg(TuiTheme::g(224), Tui::push_x(5, self.start.as_str()))
|
||||
});
|
||||
|
||||
pub struct PianoHorizontalKeys {
|
||||
color: ItemPalette,
|
||||
note_lo: usize,
|
||||
|
|
@ -89,6 +106,7 @@ render!(|self: PianoHorizontalKeys|render(|to|Ok({
|
|||
};
|
||||
}
|
||||
})));
|
||||
|
||||
pub struct PianoHorizontalCursor {
|
||||
time_zoom: usize,
|
||||
time_point: usize,
|
||||
|
|
@ -119,6 +137,7 @@ render!(|self: PianoHorizontalCursor|render(|to|Ok({
|
|||
}
|
||||
}
|
||||
})));
|
||||
|
||||
pub struct PianoHorizontalNotes<'a> {
|
||||
source: &'a BigBuffer,
|
||||
time_start: usize,
|
||||
|
|
@ -146,6 +165,7 @@ render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({
|
|||
}
|
||||
}
|
||||
})));
|
||||
|
||||
impl PianoHorizontal {
|
||||
/// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
||||
fn draw_bg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize, note_len: usize) {
|
||||
|
|
@ -204,15 +224,16 @@ impl PianoHorizontal {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PhraseViewMode for PianoHorizontal {
|
||||
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> {
|
||||
&self.phrase
|
||||
}
|
||||
fn time_zoom (&self) -> usize {
|
||||
self.time_zoom
|
||||
self.time_zoom.load(Ordering::Relaxed)
|
||||
}
|
||||
fn set_time_zoom (&mut self, time_zoom: usize) {
|
||||
self.time_zoom = time_zoom;
|
||||
self.time_zoom.store(time_zoom, Ordering::Relaxed);
|
||||
self.redraw()
|
||||
}
|
||||
fn time_zoom_lock (&self) -> bool {
|
||||
|
|
@ -224,13 +245,7 @@ impl PhraseViewMode for PianoHorizontal {
|
|||
}
|
||||
/// Determine the required space to render the phrase.
|
||||
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
|
||||
let width = phrase.length / self.time_zoom();
|
||||
let height = match self.note_zoom {
|
||||
PhraseViewNoteZoom::Half => 64,
|
||||
PhraseViewNoteZoom::N(n) => 128*n,
|
||||
_ => unimplemented!()
|
||||
};
|
||||
(width, height)
|
||||
(phrase.length / self.time_zoom(), 128)
|
||||
}
|
||||
fn redraw (&mut self) {
|
||||
let buffer = if let Some(phrase) = &*self.phrase().read().unwrap() {
|
||||
|
|
@ -246,11 +261,11 @@ impl PhraseViewMode for PianoHorizontal {
|
|||
self.buffer = buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PianoHorizontal {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("PianoHorizontal")
|
||||
.field("time_zoom", &self.time_zoom)
|
||||
.field("note_zoom", &self.note_zoom)
|
||||
.field("buffer", &format!("{}x{}", self.buffer.width, self.buffer.height))
|
||||
.finish()
|
||||
}
|
||||
|
|
@ -287,3 +302,14 @@ impl std::fmt::Debug for PianoHorizontal {
|
|||
//}
|
||||
//}
|
||||
//}
|
||||
// Update sequencer playhead indicator
|
||||
//self.now().set(0.);
|
||||
//if let Some((ref started_at, Some(ref playing))) = self.player.play_phrase {
|
||||
//let phrase = phrase.read().unwrap();
|
||||
//if *playing.read().unwrap() == *phrase {
|
||||
//let pulse = self.current().pulse.get();
|
||||
//let start = started_at.pulse.get();
|
||||
//let now = (pulse - start) % phrase.length as f64;
|
||||
//self.now().set(now);
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue