mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-04-03 21:00:44 +02:00
596 lines
20 KiB
Rust
596 lines
20 KiB
Rust
|
|
/// 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>,
|
|
}
|