mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-04-03 21:00:44 +02:00
wip: nromalize
This commit is contained in:
parent
7ff1d989a9
commit
513b8354a3
14 changed files with 2324 additions and 2337 deletions
596
app/sequence.rs
Normal file
596
app/sequence.rs
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
|
||||
/// Contains state for viewing and editing a clip.
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::{Arc, RwLock};
|
||||
/// let clip = tek::MidiClip::stop_all();
|
||||
/// let mut editor = tek::MidiEditor {
|
||||
/// mode: tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))),
|
||||
/// size: Default::default(),
|
||||
/// //keys: Default::default(),
|
||||
/// };
|
||||
/// let _ = editor.put_note(true);
|
||||
/// let _ = editor.put_note(false);
|
||||
/// let _ = editor.clip_status();
|
||||
/// let _ = editor.edit_status();
|
||||
/// ```
|
||||
pub struct MidiEditor {
|
||||
/// Size of editor on screen
|
||||
pub size: Measure<Tui>,
|
||||
/// View mode and state of editor
|
||||
pub mode: PianoHorizontal,
|
||||
}
|
||||
|
||||
/// A clip, rendered as a horizontal piano roll.
|
||||
///
|
||||
/// ```
|
||||
/// let piano = tek::PianoHorizontal::default();
|
||||
/// ```
|
||||
#[derive(Clone, Default)] pub struct PianoHorizontal {
|
||||
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
||||
/// Buffer where the whole clip is rerendered on change
|
||||
pub buffer: Arc<RwLock<BigBuffer>>,
|
||||
/// Size of actual notes area
|
||||
pub size: Measure<Tui>,
|
||||
/// The display window
|
||||
pub range: MidiSelection,
|
||||
/// The note cursor
|
||||
pub point: MidiCursor,
|
||||
/// The highlight color palette
|
||||
pub color: ItemTheme,
|
||||
/// Width of the keyboard
|
||||
pub keys_width: u16,
|
||||
}
|
||||
|
||||
/// 12 piano keys, some highlighted.
|
||||
///
|
||||
/// ```
|
||||
/// let keys = tek::OctaveVertical::default();
|
||||
/// ```
|
||||
#[derive(Copy, Clone)] pub struct OctaveVertical {
|
||||
pub on: [bool; 12],
|
||||
pub colors: [Color; 3]
|
||||
}
|
||||
/// A MIDI sequence.
|
||||
///
|
||||
/// ```
|
||||
/// let clip = tek::MidiClip::default();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)] pub struct MidiClip {
|
||||
pub uuid: uuid::Uuid,
|
||||
/// Name of clip
|
||||
pub name: Arc<str>,
|
||||
/// Temporal resolution in pulses per quarter note
|
||||
pub ppq: usize,
|
||||
/// Length of clip in pulses
|
||||
pub length: usize,
|
||||
/// Notes in clip
|
||||
pub notes: MidiData,
|
||||
/// Whether to loop the clip or play it once
|
||||
pub looped: bool,
|
||||
/// Start of loop
|
||||
pub loop_start: usize,
|
||||
/// Length of loop
|
||||
pub loop_length: usize,
|
||||
/// All notes are displayed with minimum length
|
||||
pub percussive: bool,
|
||||
/// Identifying color of clip
|
||||
pub color: ItemTheme,
|
||||
}
|
||||
|
||||
/// Contains state for playing a clip
|
||||
///
|
||||
/// ```
|
||||
/// let clip = tek::MidiClip::default();
|
||||
/// println!("Empty clip: {clip:?}");
|
||||
///
|
||||
/// let clip = tek::MidiClip::stop_all();
|
||||
/// println!("Panic clip: {clip:?}");
|
||||
///
|
||||
/// let mut clip = tek::MidiClip::new("clip", true, 1, None, None);
|
||||
/// clip.set_length(96);
|
||||
/// clip.toggle_loop();
|
||||
/// clip.record_event(12, midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
|
||||
/// assert!(clip.contains_note_on(36.into(), 6, 18));
|
||||
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
||||
///
|
||||
/// let clip = std::sync::Arc::new(clip);
|
||||
/// assert_eq!(clip.clone(), clip);
|
||||
///
|
||||
/// let sequencer = tek::Sequencer::default();
|
||||
/// println!("{sequencer:?}");
|
||||
/// ```
|
||||
pub struct Sequencer {
|
||||
/// State of clock and playhead
|
||||
#[cfg(feature = "clock")] pub clock: Clock,
|
||||
/// Start time and clip being played
|
||||
#[cfg(feature = "clip")] pub play_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||
/// Start time and next clip
|
||||
#[cfg(feature = "clip")] pub next_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||
/// Record from MIDI ports to current sequence.
|
||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||
/// Play from current sequence to MIDI ports
|
||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||
/// Play input through output.
|
||||
pub monitoring: bool,
|
||||
/// Write input to sequence.
|
||||
pub recording: bool,
|
||||
/// Overdub input to sequence.
|
||||
pub overdub: bool,
|
||||
/// Send all notes off
|
||||
pub reset: bool, // TODO?: after Some(nframes)
|
||||
/// Notes currently held at input
|
||||
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
||||
/// Notes currently held at output
|
||||
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
||||
/// MIDI output buffer
|
||||
pub note_buf: Vec<u8>,
|
||||
/// MIDI output buffer
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
/// A track consists of a sequencer and zero or more devices chained after it.
|
||||
///
|
||||
/// ```
|
||||
/// let track: tek::Track = Default::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub struct Track {
|
||||
/// Name of track
|
||||
pub name: Arc<str>,
|
||||
/// Identifying color of track
|
||||
pub color: ItemTheme,
|
||||
/// Preferred width of track column
|
||||
pub width: usize,
|
||||
/// MIDI sequencer state
|
||||
pub sequencer: Sequencer,
|
||||
/// Device chain
|
||||
pub devices: Vec<Device>,
|
||||
}
|
||||
|
||||
|
||||
pub trait HasPlayClip: HasClock {
|
||||
|
||||
fn reset (&self) -> bool;
|
||||
|
||||
fn reset_mut (&mut self) -> &mut bool;
|
||||
|
||||
fn play_clip (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||
|
||||
fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||
|
||||
fn next_clip (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||
|
||||
fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||
|
||||
fn pulses_since_start (&self) -> Option<f64> {
|
||||
if let Some((started, Some(_))) = self.play_clip().as_ref() {
|
||||
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
|
||||
return Some(elapsed)
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn pulses_since_start_looped (&self) -> Option<(f64, f64)> {
|
||||
if let Some((started, Some(clip))) = self.play_clip().as_ref() {
|
||||
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
|
||||
let length = clip.read().unwrap().length.max(1); // prevent div0 on empty clip
|
||||
let times = (elapsed as usize / length) as f64;
|
||||
let elapsed = (elapsed as usize % length) as f64;
|
||||
return Some((times, elapsed))
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn enqueue_next (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
||||
*self.next_clip_mut() = Some((self.clock().next_launch_instant(), clip.cloned()));
|
||||
*self.reset_mut() = true;
|
||||
}
|
||||
|
||||
fn play_status (&self) -> impl Draw<Tui> {
|
||||
let (name, color): (Arc<str>, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() {
|
||||
let MidiClip { ref name, color, .. } = *clip.read().unwrap();
|
||||
(name.clone(), color)
|
||||
} else {
|
||||
("".into(), Tui::g(64).into())
|
||||
};
|
||||
let time: String = self.pulses_since_start_looped()
|
||||
.map(|(times, time)|format!("{:>3}x {:>}", times+1.0, self.clock().timebase.format_beats_1(time)))
|
||||
.unwrap_or_else(||String::from(" ")).into();
|
||||
field_v(color, "Now:", format!("{} {}", time, name))
|
||||
}
|
||||
|
||||
fn next_status (&self) -> impl Draw<Tui> {
|
||||
let mut time: Arc<str> = String::from("--.-.--").into();
|
||||
let mut name: Arc<str> = String::from("").into();
|
||||
let mut color = ItemTheme::G[64];
|
||||
let clock = self.clock();
|
||||
if let Some((t, Some(clip))) = self.next_clip() {
|
||||
let clip = clip.read().unwrap();
|
||||
name = clip.name.clone();
|
||||
color = clip.color.clone();
|
||||
time = {
|
||||
let target = t.pulse.get();
|
||||
let current = clock.playhead.pulse.get();
|
||||
if target > current {
|
||||
let remaining = target - current;
|
||||
format!("-{:>}", clock.timebase.format_beats_1(remaining))
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}.into()
|
||||
} else if let Some((t, Some(clip))) = self.play_clip() {
|
||||
let clip = clip.read().unwrap();
|
||||
if clip.looped {
|
||||
name = clip.name.clone();
|
||||
color = clip.color.clone();
|
||||
let target = t.pulse.get() + clip.length as f64;
|
||||
let current = clock.playhead.pulse.get();
|
||||
if target > current {
|
||||
time = format!("-{:>}", clock.timebase.format_beats_0(target - current)).into()
|
||||
}
|
||||
} else {
|
||||
name = "Stop".to_string().into();
|
||||
}
|
||||
};
|
||||
field_v(color, "Next:", format!("{} {}", time, name))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MidiMonitor: HasMidiIns + HasMidiBuffers {
|
||||
/// Input note flags.
|
||||
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
|
||||
/// Current monitoring status.
|
||||
fn monitoring (&self) -> bool;
|
||||
/// Mutable monitoring status.
|
||||
fn monitoring_mut (&mut self) -> &mut bool;
|
||||
/// Enable or disable monitoring.
|
||||
fn toggle_monitor (&mut self) { *self.monitoring_mut() = !self.monitoring(); }
|
||||
/// Perform monitoring.
|
||||
fn monitor (&mut self, _scope: &ProcessScope) { /* do nothing by default */ }
|
||||
}
|
||||
|
||||
pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip {
|
||||
fn recording (&self) -> bool;
|
||||
fn recording_mut (&mut self) -> &mut bool;
|
||||
fn toggle_record (&mut self) {
|
||||
*self.recording_mut() = !self.recording();
|
||||
}
|
||||
|
||||
fn overdub (&self) -> bool;
|
||||
fn overdub_mut (&mut self) -> &mut bool;
|
||||
fn toggle_overdub (&mut self) {
|
||||
*self.overdub_mut() = !self.overdub();
|
||||
}
|
||||
|
||||
fn record_clip (
|
||||
&mut self,
|
||||
scope: &ProcessScope,
|
||||
started: Moment,
|
||||
clip: &Option<Arc<RwLock<MidiClip>>>,
|
||||
) {
|
||||
if let Some(clip) = clip {
|
||||
let sample0 = scope.last_frame_time() as usize;
|
||||
let start = started.sample.get() as usize;
|
||||
let _recording = self.recording();
|
||||
let timebase = self.clock().timebase().clone();
|
||||
let quant = self.clock().quant.get();
|
||||
let mut clip = clip.write().unwrap();
|
||||
let length = clip.length;
|
||||
for input in self.midi_ins_mut().iter() {
|
||||
for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) {
|
||||
if let LiveEvent::Midi { message, .. } = event {
|
||||
clip.record_event({
|
||||
let sample = (sample0 + sample - start) as f64;
|
||||
let pulse = timebase.samples_to_pulse(sample);
|
||||
let quantized = (pulse / quant).round() * quant;
|
||||
quantized as usize % length
|
||||
}, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_next (&mut self) {
|
||||
// TODO switch to next clip and record into it
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
|
||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
||||
fn redraw (&self);
|
||||
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>>;
|
||||
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>>;
|
||||
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
||||
*self.clip_mut() = clip.cloned();
|
||||
self.redraw();
|
||||
}
|
||||
/// Make sure cursor is within note range
|
||||
fn autoscroll (&self) {
|
||||
let note_pos = self.get_note_pos().min(127);
|
||||
let note_lo = self.get_note_lo();
|
||||
let note_hi = self.get_note_hi();
|
||||
if note_pos < note_lo {
|
||||
self.note_lo().set(note_pos);
|
||||
} else if note_pos > note_hi {
|
||||
self.note_lo().set((note_lo + note_pos).saturating_sub(note_hi));
|
||||
}
|
||||
}
|
||||
/// Make sure time range is within display
|
||||
fn autozoom (&self) {
|
||||
if self.time_lock().get() {
|
||||
let time_len = self.get_time_len();
|
||||
let time_axis = self.get_time_axis();
|
||||
let time_zoom = self.get_time_zoom();
|
||||
loop {
|
||||
let time_zoom = self.time_zoom().get();
|
||||
let time_area = time_axis * time_zoom;
|
||||
if time_area > time_len {
|
||||
let next_time_zoom = note_duration_prev(time_zoom);
|
||||
if next_time_zoom <= 1 {
|
||||
break
|
||||
}
|
||||
let next_time_area = time_axis * next_time_zoom;
|
||||
if next_time_area >= time_len {
|
||||
self.time_zoom().set(next_time_zoom);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else if time_area < time_len {
|
||||
let prev_time_zoom = note_duration_next(time_zoom);
|
||||
if prev_time_zoom > 384 {
|
||||
break
|
||||
}
|
||||
let prev_time_area = time_axis * prev_time_zoom;
|
||||
if prev_time_area <= time_len {
|
||||
self.time_zoom().set(prev_time_zoom);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if time_zoom != self.time_zoom().get() {
|
||||
self.redraw()
|
||||
}
|
||||
}
|
||||
//while time_len.div_ceil(time_zoom) > time_axis {
|
||||
//println!("\r{time_len} {time_zoom} {time_axis}");
|
||||
//time_zoom = Note::next(time_zoom);
|
||||
//}
|
||||
//self.time_zoom().set(time_zoom);
|
||||
}
|
||||
}
|
||||
pub type MidiData =
|
||||
Vec<Vec<MidiMessage>>;
|
||||
pub type ClipPool =
|
||||
Vec<Arc<RwLock<MidiClip>>>;
|
||||
pub type CollectedMidiInput<'a> =
|
||||
Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
||||
|
||||
pub trait HasClips {
|
||||
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>;
|
||||
fn add_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||
let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None)));
|
||||
self.clips_mut().push(clip.clone());
|
||||
(self.clips().len() - 1, clip)
|
||||
}
|
||||
}
|
||||
/// ```
|
||||
/// use tek::{*, tengri::*};
|
||||
///
|
||||
/// struct Test(Option<MidiEditor>);
|
||||
/// impl_as_ref_opt!(MidiEditor: |self: Test|self.0.as_ref());
|
||||
/// impl_as_mut_opt!(MidiEditor: |self: Test|self.0.as_mut());
|
||||
///
|
||||
/// let mut host = Test(Some(MidiEditor::default()));
|
||||
/// let _ = host.editor();
|
||||
/// let _ = host.editor_mut();
|
||||
/// let _ = host.is_editing();
|
||||
/// let _ = host.editor_w();
|
||||
/// let _ = host.editor_h();
|
||||
/// ```
|
||||
pub trait HasEditor: AsRefOpt<MidiEditor> + AsMutOpt<MidiEditor> {
|
||||
fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() }
|
||||
fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() }
|
||||
fn is_editing (&self) -> bool { self.editor().is_some() }
|
||||
fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize }
|
||||
fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize }
|
||||
}
|
||||
/// Trait for thing that may receive MIDI.
|
||||
pub trait HasMidiIns {
|
||||
fn midi_ins (&self) -> &Vec<MidiInput>;
|
||||
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput>;
|
||||
/// Collect MIDI input from app ports (TODO preallocate large buffers)
|
||||
fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> {
|
||||
self.midi_ins().iter()
|
||||
.map(|port|port.port().iter(scope)
|
||||
.map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes)))
|
||||
.collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
fn midi_ins_with_sizes <'a> (&'a self) ->
|
||||
impl Iterator<Item=(usize, &'a Arc<str>, &'a [Connect], usize, usize)> + Send + Sync + 'a
|
||||
{
|
||||
let mut y = 0;
|
||||
self.midi_ins().iter().enumerate().map(move|(i, input)|{
|
||||
let height = 1 + input.connections().len();
|
||||
let data = (i, input.port_name(), input.connections(), y, y + height);
|
||||
y += height;
|
||||
data
|
||||
})
|
||||
}
|
||||
}
|
||||
/// Trait for thing that may output MIDI.
|
||||
pub trait HasMidiOuts {
|
||||
fn midi_outs (&self) -> &Vec<MidiOutput>;
|
||||
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput>;
|
||||
fn midi_outs_with_sizes <'a> (&'a self) ->
|
||||
impl Iterator<Item=(usize, &'a Arc<str>, &'a [Connect], usize, usize)> + Send + Sync + 'a
|
||||
{
|
||||
let mut y = 0;
|
||||
self.midi_outs().iter().enumerate().map(move|(i, output)|{
|
||||
let height = 1 + output.connections().len();
|
||||
let data = (i, output.port_name(), output.connections(), y, y + height);
|
||||
y += height;
|
||||
data
|
||||
})
|
||||
}
|
||||
fn midi_outs_emit (&mut self, scope: &ProcessScope) {
|
||||
for port in self.midi_outs_mut().iter_mut() {
|
||||
port.buffer_emit(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub trait HasMidiClip {
|
||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
||||
}
|
||||
pub trait HasSequencer: AsRef<Sequencer> + AsMut<Sequencer> {
|
||||
fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() }
|
||||
fn sequencer (&self) -> &Sequencer { self.as_ref() }
|
||||
}
|
||||
pub trait HasMidiBuffers {
|
||||
fn note_buf_mut (&mut self) -> &mut Vec<u8>;
|
||||
fn midi_buf_mut (&mut self) -> &mut Vec<Vec<Vec<u8>>>;
|
||||
}
|
||||
|
||||
|
||||
pub trait NotePoint {
|
||||
fn note_len (&self) -> &AtomicUsize;
|
||||
/// Get the current length of the note cursor.
|
||||
fn get_note_len (&self) -> usize {
|
||||
self.note_len().load(Relaxed)
|
||||
}
|
||||
/// Set the length of the note cursor, returning the previous value.
|
||||
fn set_note_len (&self, x: usize) -> usize {
|
||||
self.note_len().swap(x, Relaxed)
|
||||
}
|
||||
|
||||
fn note_pos (&self) -> &AtomicUsize;
|
||||
/// Get the current pitch of the note cursor.
|
||||
fn get_note_pos (&self) -> usize {
|
||||
self.note_pos().load(Relaxed).min(127)
|
||||
}
|
||||
/// Set the current pitch fo the note cursor, returning the previous value.
|
||||
fn set_note_pos (&self, x: usize) -> usize {
|
||||
self.note_pos().swap(x.min(127), Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TimePoint {
|
||||
fn time_pos (&self) -> &AtomicUsize;
|
||||
/// Get the current time position of the note cursor.
|
||||
fn get_time_pos (&self) -> usize {
|
||||
self.time_pos().load(Relaxed)
|
||||
}
|
||||
/// Set the current time position of the note cursor, returning the previous value.
|
||||
fn set_time_pos (&self, x: usize) -> usize {
|
||||
self.time_pos().swap(x, Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MidiPoint: NotePoint + TimePoint {
|
||||
/// Get the current end of the note cursor.
|
||||
fn get_note_end (&self) -> usize {
|
||||
self.get_time_pos() + self.get_note_len()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TimeRange {
|
||||
fn time_len (&self) -> &AtomicUsize;
|
||||
fn get_time_len (&self) -> usize {
|
||||
self.time_len().load(Ordering::Relaxed)
|
||||
}
|
||||
fn time_zoom (&self) -> &AtomicUsize;
|
||||
fn get_time_zoom (&self) -> usize {
|
||||
self.time_zoom().load(Ordering::Relaxed)
|
||||
}
|
||||
fn set_time_zoom (&self, value: usize) -> usize {
|
||||
self.time_zoom().swap(value, Ordering::Relaxed)
|
||||
}
|
||||
fn time_lock (&self) -> &AtomicBool;
|
||||
fn get_time_lock (&self) -> bool {
|
||||
self.time_lock().load(Ordering::Relaxed)
|
||||
}
|
||||
fn set_time_lock (&self, value: bool) -> bool {
|
||||
self.time_lock().swap(value, Ordering::Relaxed)
|
||||
}
|
||||
fn time_start (&self) -> &AtomicUsize;
|
||||
fn get_time_start (&self) -> usize {
|
||||
self.time_start().load(Ordering::Relaxed)
|
||||
}
|
||||
fn set_time_start (&self, value: usize) -> usize {
|
||||
self.time_start().swap(value, Ordering::Relaxed)
|
||||
}
|
||||
fn time_axis (&self) -> &AtomicUsize;
|
||||
fn get_time_axis (&self) -> usize {
|
||||
self.time_axis().load(Ordering::Relaxed)
|
||||
}
|
||||
fn get_time_end (&self) -> usize {
|
||||
self.time_start().get() + self.time_axis().get() * self.time_zoom().get()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NoteRange {
|
||||
fn note_lo (&self) -> &AtomicUsize;
|
||||
fn get_note_lo (&self) -> usize {
|
||||
self.note_lo().load(Ordering::Relaxed)
|
||||
}
|
||||
fn set_note_lo (&self, x: usize) -> usize {
|
||||
self.note_lo().swap(x, Ordering::Relaxed)
|
||||
}
|
||||
fn note_axis (&self) -> &AtomicUsize;
|
||||
fn get_note_axis (&self) -> usize {
|
||||
self.note_axis().load(Ordering::Relaxed)
|
||||
}
|
||||
fn get_note_hi (&self) -> usize {
|
||||
(self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MidiRange: TimeRange + NoteRange {}
|
||||
|
||||
///
|
||||
/// ```
|
||||
/// let _ = tek::MidiCursor::default();
|
||||
/// ```
|
||||
#[derive(Debug, Clone)] pub struct MidiCursor {
|
||||
/// Time coordinate of cursor
|
||||
pub time_pos: Arc<AtomicUsize>,
|
||||
/// Note coordinate of cursor
|
||||
pub note_pos: Arc<AtomicUsize>,
|
||||
/// Length of note that will be inserted, in pulses
|
||||
pub note_len: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
///
|
||||
/// ```
|
||||
/// use tek::{TimeRange, NoteRange};
|
||||
/// let model = tek::MidiSelection::from((1, false));
|
||||
///
|
||||
/// let _ = model.get_time_len();
|
||||
/// let _ = model.get_time_zoom();
|
||||
/// let _ = model.get_time_lock();
|
||||
/// let _ = model.get_time_start();
|
||||
/// let _ = model.get_time_axis();
|
||||
/// let _ = model.get_time_end();
|
||||
///
|
||||
/// let _ = model.get_note_lo();
|
||||
/// let _ = model.get_note_axis();
|
||||
/// let _ = model.get_note_hi();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)] pub struct MidiSelection {
|
||||
pub time_len: Arc<AtomicUsize>,
|
||||
/// Length of visible time axis
|
||||
pub time_axis: Arc<AtomicUsize>,
|
||||
/// Earliest time displayed
|
||||
pub time_start: Arc<AtomicUsize>,
|
||||
/// Time step
|
||||
pub time_zoom: Arc<AtomicUsize>,
|
||||
/// Auto rezoom to fit in time axis
|
||||
pub time_lock: Arc<AtomicBool>,
|
||||
/// Length of visible note axis
|
||||
pub note_axis: Arc<AtomicUsize>,
|
||||
// Lowest note displayed
|
||||
pub note_lo: Arc<AtomicUsize>,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue