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
|
// Start profiling cycle
|
||||||
let t0 = self.perf.get_t0();
|
let t0 = self.perf.get_t0();
|
||||||
// Update transport clock
|
// Update transport clock
|
||||||
if ClockAudio(self)
|
if Control::Quit == ClockAudio(self).process(client, scope) {
|
||||||
.process(client, scope) == Control::Quit
|
|
||||||
{
|
|
||||||
return Control::Quit
|
return Control::Quit
|
||||||
}
|
}
|
||||||
// Update MIDI sequencer
|
// Update MIDI sequencer
|
||||||
if PlayerAudio(&mut self.player, &mut self.note_buf, &mut self.midi_buf)
|
if Control::Quit == PlayerAudio(
|
||||||
.process(client, scope) == Control::Quit
|
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
||||||
{
|
).process(client, scope) {
|
||||||
return Control::Quit
|
return Control::Quit
|
||||||
}
|
}
|
||||||
// End profiling cycle
|
// End profiling cycle
|
||||||
self.perf.update(t0, scope);
|
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
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,201 +6,6 @@ pub trait HasEditor {
|
||||||
fn editor_entered (&self) -> bool;
|
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)]
|
#[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
|
||||||
|
|
@ -220,18 +25,18 @@ pub enum PhraseCommand {
|
||||||
impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
||||||
fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> {
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
use KeyCode::{Char, Esc, Up, Down, PageUp, PageDown, Left, Right};
|
use KeyCode::{Char, Up, Down, PageUp, PageDown, Left, Right};
|
||||||
let note_lo = state.note_lo.load(Ordering::Relaxed);
|
let note_lo = state.range.note_lo.load(Ordering::Relaxed);
|
||||||
let note_point = state.note_point.load(Ordering::Relaxed);
|
let note_point = state.point.note_point.load(Ordering::Relaxed);
|
||||||
let time_start = state.time_start.load(Ordering::Relaxed);
|
let time_start = state.range.time_start.load(Ordering::Relaxed);
|
||||||
let time_point = state.time_point.load(Ordering::Relaxed);
|
let time_point = state.point.time_point.load(Ordering::Relaxed);
|
||||||
let time_zoom = state.view_mode.time_zoom();
|
let time_zoom = state.mode.time_zoom();
|
||||||
let length = state.phrase().read().unwrap().as_ref()
|
let length = state.phrase().read().unwrap().as_ref()
|
||||||
.map(|p|p.read().unwrap().length).unwrap_or(1);
|
.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() {
|
Some(match from.event() {
|
||||||
key!(Char('`')) => ToggleDirection,
|
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(next_note_length(time_zoom)),
|
key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||||
key!(Char('=')) => SetTimeZoom(prev_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> {
|
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
match self {
|
match self {
|
||||||
Show(phrase) => { state.show_phrase(phrase); },
|
Show(phrase) => { state.set_phrase(phrase); },
|
||||||
PutNote => { state.put_note(false); },
|
PutNote => { state.put_note(false); },
|
||||||
AppendNote => { state.put_note(true); },
|
AppendNote => { state.put_note(true); },
|
||||||
SetTimeZoom(zoom) => { state.view_mode.set_time_zoom(zoom); },
|
SetTimeZoom(zoom) => { state.mode.set_time_zoom(zoom); },
|
||||||
SetTimeZoomLock(lock) => { state.view_mode.set_time_zoom_lock(lock); },
|
SetTimeZoomLock(lock) => { state.mode.set_time_zoom_lock(lock); },
|
||||||
SetTimeScroll(time) => { state.time_start.store(time, Ordering::Relaxed); },
|
SetTimeScroll(time) => { state.range.time_start.store(time, Ordering::Relaxed); },
|
||||||
SetTimeCursor(time) => { state.time_point.store(time, Ordering::Relaxed); },
|
SetTimeCursor(time) => { state.point.time_point.store(time, Ordering::Relaxed); },
|
||||||
SetNoteLength(time) => { state.note_len.store(time, Ordering::Relaxed); },
|
SetNoteLength(time) => { state.point.note_len.store(time, Ordering::Relaxed); },
|
||||||
SetNoteScroll(note) => { state.note_lo.store(note, Ordering::Relaxed); },
|
SetNoteScroll(note) => { state.range.note_lo.store(note, Ordering::Relaxed); },
|
||||||
SetNoteCursor(note) => {
|
SetNoteCursor(note) => {
|
||||||
let note = 127.min(note);
|
let note = 127.min(note);
|
||||||
let start = state.note_lo.load(Ordering::Relaxed);
|
let start = state.range.note_lo.load(Ordering::Relaxed);
|
||||||
state.note_point.store(note, Ordering::Relaxed);
|
state.point.note_point.store(note, Ordering::Relaxed);
|
||||||
if note < start {
|
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)
|
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 crate::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// A phrase, rendered as a horizontal piano roll.
|
||||||
pub struct PianoHorizontal {
|
pub struct PianoHorizontal {
|
||||||
pub(crate) phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||||
pub(crate) time_lock: bool,
|
time_lock: bool,
|
||||||
pub(crate) time_zoom: usize,
|
time_zoom: Arc<AtomicUsize>,
|
||||||
pub(crate) note_zoom: PhraseViewNoteZoom,
|
note_len: Arc<AtomicUsize>,
|
||||||
pub(crate) note_len: Arc<AtomicUsize>,
|
buffer: BigBuffer,
|
||||||
pub(crate) buffer: BigBuffer,
|
/// Width and height of notes area at last render
|
||||||
pub(crate) focused: bool,
|
size: Measure<Tui>,
|
||||||
}
|
}
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum PhraseViewNoteZoom {
|
impl PianoHorizontal {
|
||||||
N(usize),
|
pub fn new (
|
||||||
Half,
|
phrase: &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||||
Octant,
|
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|{
|
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()
|
let fg = self.phrase().read().unwrap()
|
||||||
.as_ref().map(|p|p.read().unwrap().color)
|
.as_ref().map(|p|p.read().unwrap().color)
|
||||||
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
|
.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 {
|
Tui::bg(fg.dark.rgb, PianoHorizontalTimeline {
|
||||||
start: "TIMELINE".into()
|
start: "|0".into()
|
||||||
}),
|
}),
|
||||||
Split::right(false, 5, PianoHorizontalKeys {
|
Split::right(false, 5, PianoHorizontalKeys {
|
||||||
color: ItemPalette::random(),
|
color: ItemPalette::random(),
|
||||||
|
|
@ -30,6 +44,7 @@ render!(|self: PianoHorizontal|{
|
||||||
note_hi: 0,
|
note_hi: 0,
|
||||||
note_point: None
|
note_point: None
|
||||||
}, lay!([
|
}, lay!([
|
||||||
|
self.size,
|
||||||
PianoHorizontalNotes {
|
PianoHorizontalNotes {
|
||||||
source: &self.buffer,
|
source: &self.buffer,
|
||||||
time_start: 0,
|
time_start: 0,
|
||||||
|
|
@ -47,12 +62,14 @@ render!(|self: PianoHorizontal|{
|
||||||
])),
|
])),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct PianoHorizontalTimeline {
|
pub struct PianoHorizontalTimeline {
|
||||||
start: String
|
start: String
|
||||||
}
|
}
|
||||||
render!(|self: PianoHorizontalTimeline|{
|
render!(|self: PianoHorizontalTimeline|{
|
||||||
Tui::fg(TuiTheme::g(224), Tui::push_x(5, self.start.as_str()))
|
Tui::fg(TuiTheme::g(224), Tui::push_x(5, self.start.as_str()))
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct PianoHorizontalKeys {
|
pub struct PianoHorizontalKeys {
|
||||||
color: ItemPalette,
|
color: ItemPalette,
|
||||||
note_lo: usize,
|
note_lo: usize,
|
||||||
|
|
@ -89,6 +106,7 @@ render!(|self: PianoHorizontalKeys|render(|to|Ok({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
|
|
||||||
pub struct PianoHorizontalCursor {
|
pub struct PianoHorizontalCursor {
|
||||||
time_zoom: usize,
|
time_zoom: usize,
|
||||||
time_point: usize,
|
time_point: usize,
|
||||||
|
|
@ -119,6 +137,7 @@ render!(|self: PianoHorizontalCursor|render(|to|Ok({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
|
|
||||||
pub struct PianoHorizontalNotes<'a> {
|
pub struct PianoHorizontalNotes<'a> {
|
||||||
source: &'a BigBuffer,
|
source: &'a BigBuffer,
|
||||||
time_start: usize,
|
time_start: usize,
|
||||||
|
|
@ -146,6 +165,7 @@ render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
|
|
||||||
impl PianoHorizontal {
|
impl PianoHorizontal {
|
||||||
/// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
/// 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) {
|
fn draw_bg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize, note_len: usize) {
|
||||||
|
|
@ -204,15 +224,16 @@ impl PianoHorizontal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseViewMode for PianoHorizontal {
|
impl PhraseViewMode for PianoHorizontal {
|
||||||
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> {
|
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> {
|
||||||
&self.phrase
|
&self.phrase
|
||||||
}
|
}
|
||||||
fn time_zoom (&self) -> usize {
|
fn time_zoom (&self) -> usize {
|
||||||
self.time_zoom
|
self.time_zoom.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
fn set_time_zoom (&mut self, time_zoom: usize) {
|
fn set_time_zoom (&mut self, time_zoom: usize) {
|
||||||
self.time_zoom = time_zoom;
|
self.time_zoom.store(time_zoom, Ordering::Relaxed);
|
||||||
self.redraw()
|
self.redraw()
|
||||||
}
|
}
|
||||||
fn time_zoom_lock (&self) -> bool {
|
fn time_zoom_lock (&self) -> bool {
|
||||||
|
|
@ -224,13 +245,7 @@ impl PhraseViewMode for PianoHorizontal {
|
||||||
}
|
}
|
||||||
/// Determine the required space to render the phrase.
|
/// Determine the required space to render the phrase.
|
||||||
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
|
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
|
||||||
let width = phrase.length / self.time_zoom();
|
(phrase.length / self.time_zoom(), 128)
|
||||||
let height = match self.note_zoom {
|
|
||||||
PhraseViewNoteZoom::Half => 64,
|
|
||||||
PhraseViewNoteZoom::N(n) => 128*n,
|
|
||||||
_ => unimplemented!()
|
|
||||||
};
|
|
||||||
(width, height)
|
|
||||||
}
|
}
|
||||||
fn redraw (&mut self) {
|
fn redraw (&mut self) {
|
||||||
let buffer = if let Some(phrase) = &*self.phrase().read().unwrap() {
|
let buffer = if let Some(phrase) = &*self.phrase().read().unwrap() {
|
||||||
|
|
@ -246,11 +261,11 @@ impl PhraseViewMode for PianoHorizontal {
|
||||||
self.buffer = buffer
|
self.buffer = buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for PianoHorizontal {
|
impl std::fmt::Debug for PianoHorizontal {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
f.debug_struct("PianoHorizontal")
|
f.debug_struct("PianoHorizontal")
|
||||||
.field("time_zoom", &self.time_zoom)
|
.field("time_zoom", &self.time_zoom)
|
||||||
.field("note_zoom", &self.note_zoom)
|
|
||||||
.field("buffer", &format!("{}x{}", self.buffer.width, self.buffer.height))
|
.field("buffer", &format!("{}x{}", self.buffer.width, self.buffer.height))
|
||||||
.finish()
|
.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