diff --git a/Cargo.lock b/Cargo.lock index 999db3cd..976237da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1417,9 +1417,9 @@ dependencies = [ "symphonia", "tek_jack", "tek_midi", + "tek_time", "tek_tui", "toml", - "uuid", "wavers", ] @@ -1452,6 +1452,7 @@ dependencies = [ "tek_jack", "tek_time", "tek_tui", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f4e5c62a..1ecadb51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,10 @@ edition = "2021" version = "0.2.0" [dependencies] -tek_tui = { path = "./tui" } -tek_jack = { path = "./jack" } -tek_midi = { path = "./midi" } +tek_tui = { path = "./tui" } +tek_jack = { path = "./jack" } +tek_time = { path = "./time" } +tek_midi = { path = "./midi" } backtrace = "0.3.72" clap = { version = "4.5.4", features = [ "derive" ] } @@ -16,7 +17,6 @@ palette = { version = "0.7.6", features = [ "random" ] } rand = "0.8.5" symphonia = { version = "0.5.4", features = [ "all" ] } toml = "0.8.12" -uuid = { version = "1.10.0", features = [ "v4" ] } wavers = "1.4.3" #no_deadlocks = "1.3.2" #suil-rs = { path = "../suil" } diff --git a/midi/Cargo.toml b/midi/Cargo.toml index f6b72964..7ed369a4 100644 --- a/midi/Cargo.toml +++ b/midi/Cargo.toml @@ -10,3 +10,4 @@ tek_time = { path = "../time" } jack = { path = "../rust-jack" } midly = "0.5" +uuid = { version = "1.10.0", features = [ "v4" ] } diff --git a/midi/src/lib.rs b/midi/src/lib.rs index 125aafc3..ffa7a50f 100644 --- a/midi/src/lib.rs +++ b/midi/src/lib.rs @@ -1,6 +1,3 @@ -pub(crate) use ::tek_tui::{*, tek_input::*, tek_output::*, crossterm::event::KeyCode}; -pub(crate) use ::tek_jack::*; - mod midi_pool; pub(crate) use midi_pool::*; mod midi_clip; pub(crate) use midi_clip::*; mod midi_launch; pub(crate) use midi_launch::*; @@ -8,11 +5,34 @@ mod midi_player; pub(crate) use midi_player::*; mod midi_in; pub(crate) use midi_in::*; mod midi_out; pub(crate) use midi_out::*; -mod midi_note; pub(crate) use midi_note::*; +mod midi_pitch; pub(crate) use midi_pitch::*; mod midi_range; pub(crate) use midi_range::*; mod midi_point; pub(crate) use midi_point::*; mod midi_view; pub(crate) use midi_view::*; mod midi_editor; pub(crate) use midi_editor::*; +mod midi_select; pub(crate) use midi_select::*; + +mod piano_h; pub(crate) use self::piano_h::*; +mod piano_h_cursor; pub(crate) use self::piano_h_cursor::*; +mod piano_h_keys; pub(crate) use self::piano_h_keys::*; +mod piano_h_notes; pub(crate) use self::piano_h_notes::*; +mod piano_h_time; pub(crate) use self::piano_h_time::*; + +pub(crate) use ::tek_time::*; +pub(crate) use ::tek_jack::{*, jack::*}; +pub(crate) use ::tek_tui::{ + *, + tek_input::*, + tek_output::*, + crossterm::event::KeyCode, + ratatui::style::{Style, Stylize, Color} +}; + +pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; +pub(crate) use std::path::PathBuf; +pub(crate) use std::fmt::Debug; + +pub use ::midly; pub(crate) use ::midly::{*, num::*, live::*}; /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut [Vec>]) { @@ -24,7 +44,7 @@ pub fn all_notes_off (output: &mut [Vec>]) { } /// Return boxed iterator of MIDI events -pub fn parse_midi_input (input: MidiIter) -> Box + '_> { +pub fn parse_midi_input <'a> (input: MidiIter<'a>) -> Box, &'a [u8])> + 'a> { Box::new(input.map(|RawMidi { time, bytes }|( time as usize, LiveEvent::parse(bytes).unwrap(), @@ -41,3 +61,41 @@ pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { } } +/// A phrase, rendered as a horizontal piano roll. +pub struct PianoHorizontal { + phrase: Option>>, + /// Buffer where the whole phrase is rerendered on change + buffer: Arc>, + /// Size of actual notes area + size: Measure, + /// The display window + range: MidiRangeModel, + /// The note cursor + point: MidiPointModel, + /// The highlight color palette + color: ItemPalette, + /// Width of the keyboard + keys_width: u16, +} + +impl PianoHorizontal { + pub fn new (phrase: Option<&Arc>>) -> Self { + let size = Measure::new(); + let mut range = MidiRangeModel::from((24, true)); + range.time_axis = size.x.clone(); + range.note_axis = size.y.clone(); + let mut piano = Self { + keys_width: 5, + size, + range, + buffer: RwLock::new(Default::default()).into(), + point: MidiPointModel::default(), + phrase: phrase.cloned(), + color: phrase.as_ref() + .map(|p|p.read().unwrap().color) + .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))), + }; + piano.redraw(); + piano + } +} diff --git a/midi/src/midi_editor.rs b/midi/src/midi_editor.rs index e7c59c7a..c943f3aa 100644 --- a/midi/src/midi_editor.rs +++ b/midi/src/midi_editor.rs @@ -204,16 +204,16 @@ keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { alt(key(Right)) => SetTimeCursor((s.time_point() + s.time_zoom().get()) % s.phrase_length()), key(Char('d')) => SetTimeCursor((s.time_point() + s.note_len()) % s.phrase_length()), key(Char('z')) => SetTimeLock(!s.time_lock().get()), - key(Char('-')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { Note::next(s.time_zoom().get()) }), - key(Char('_')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { Note::next(s.time_zoom().get()) }), - key(Char('=')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { Note::prev(s.time_zoom().get()) }), - key(Char('+')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { Note::prev(s.time_zoom().get()) }), + key(Char('-')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }), + key(Char('_')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }), + key(Char('=')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::prev(s.time_zoom().get()) }), + key(Char('+')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::prev(s.time_zoom().get()) }), key(Enter) => PutNote, ctrl(key(Enter)) => AppendNote, - key(Char(',')) => SetNoteLength(Note::prev(s.note_len())), - key(Char('.')) => SetNoteLength(Note::next(s.note_len())), - key(Char('<')) => SetNoteLength(Note::prev(s.note_len())), - key(Char('>')) => SetNoteLength(Note::next(s.note_len())), + key(Char(',')) => SetNoteLength(NoteDuration::prev(s.note_len())), + key(Char('.')) => SetNoteLength(NoteDuration::next(s.note_len())), + key(Char('<')) => SetNoteLength(NoteDuration::prev(s.note_len())), + key(Char('>')) => SetNoteLength(NoteDuration::next(s.note_len())), //// TODO: kpat!(Char('/')) => // toggle 3plet //// TODO: kpat!(Char('?')) => // toggle dotted }); diff --git a/midi/src/midi_note.rs b/midi/src/midi_note.rs deleted file mode 100644 index e49d19df..00000000 --- a/midi/src/midi_note.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::*; - -pub struct Note; - -impl Note { - pub const NAMES: [&str; 128] = [ - "C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0", - "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1", - "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", - "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", - "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4", - "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5", - "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6", - "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7", - "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8", - "C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9", - "C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10", - ]; - pub fn pitch_to_name (n: usize) -> &'static str { - if n > 127 { - panic!("to_note_name({n}): must be 0-127"); - } - Self::NAMES[n] - } - - /// (pulses, name), assuming 96 PPQ - pub const DURATIONS: [(usize, &str);26] = [ - (1, "1/384"), (2, "1/192"), - (3, "1/128"), (4, "1/96"), - (6, "1/64"), (8, "1/48"), - (12, "1/32"), (16, "1/24"), - (24, "1/16"), (32, "1/12"), - (48, "1/8"), (64, "1/6"), - (96, "1/4"), (128, "1/3"), - (192, "1/2"), (256, "2/3"), - (384, "1/1"), (512, "4/3"), - (576, "3/2"), (768, "2/1"), - (1152, "3/1"), (1536, "4/1"), - (2304, "6/1"), (3072, "8/1"), - (3456, "9/1"), (6144, "16/1"), - ]; - /// Returns the next shorter length - pub fn prev (pulses: usize) -> usize { - for (length, _) in Self::DURATIONS.iter().rev() { if *length < pulses { return *length } } - pulses - } - /// Returns the next longer length - pub fn next (pulses: usize) -> usize { - for (length, _) in Self::DURATIONS.iter() { if *length > pulses { return *length } } - pulses - } - pub fn pulses_to_name (pulses: usize) -> &'static str { - for (length, name) in Self::DURATIONS.iter() { if *length == pulses { return name } } - "" - } -} diff --git a/midi/src/midi_pitch.rs b/midi/src/midi_pitch.rs new file mode 100644 index 00000000..b0e1f4e2 --- /dev/null +++ b/midi/src/midi_pitch.rs @@ -0,0 +1,25 @@ +use crate::*; + +pub struct Note; + +impl Note { + pub const NAMES: [&str; 128] = [ + "C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0", + "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1", + "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", + "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3", + "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4", + "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5", + "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6", + "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7", + "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8", + "C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9", + "C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10", + ]; + pub fn pitch_to_name (n: usize) -> &'static str { + if n > 127 { + panic!("to_note_name({n}): must be 0-127"); + } + Self::NAMES[n] + } +} diff --git a/midi/src/midi_select.rs b/midi/src/midi_select.rs new file mode 100644 index 00000000..0f79add0 --- /dev/null +++ b/midi/src/midi_select.rs @@ -0,0 +1,73 @@ +use crate::*; + +pub struct ClipSelected { + pub(crate) title: &'static str, + pub(crate) name: Arc, + pub(crate) color: ItemPalette, + pub(crate) time: Arc, +} + +render!(TuiOut: (self: ClipSelected) => + FieldV(self.color, self.title, format!("{} {}", self.time, self.name))); + +impl ClipSelected { + + /// Shows currently playing phrase with beats elapsed + pub fn play_phrase (state: &T) -> Self { + let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { + let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); + (name.clone().into(), color) + } else { + ("".to_string().into(), TuiTheme::g(64).into()) + }; + Self { + title: "Now", + name, + color, + time: state.pulses_since_start_looped() + .map(|(times, time)|format!("{:>3}x {:>}", + times+1.0, + state.clock().timebase.format_beats_1(time))) + .unwrap_or_else(||String::from(" ")).into() + } + } + + /// Shows next phrase with beats remaining until switchover + pub fn next_phrase (state: &T) -> Self { + let mut time: Arc = String::from("--.-.--").into(); + let mut name: Arc = String::from("").into(); + let mut color = ItemPalette::from(TuiTheme::g(64)); + if let Some((t, Some(phrase))) = state.next_phrase() { + let phrase = phrase.read().unwrap(); + name = phrase.name.clone(); + color = phrase.color.clone(); + time = { + let target = t.pulse.get(); + let current = state.clock().playhead.pulse.get(); + if target > current { + let remaining = target - current; + format!("-{:>}", state.clock().timebase.format_beats_1(remaining)) + } else { + String::new() + } + }.into() + } else if let Some((t, Some(phrase))) = state.play_phrase() { + let phrase = phrase.read().unwrap(); + if phrase.looped { + name = phrase.name.clone(); + color = phrase.color.clone(); + let target = t.pulse.get() + phrase.length as f64; + let current = state.clock().playhead.pulse.get(); + if target > current { + time = format!("-{:>}", state.clock().timebase.format_beats_0( + target - current + )).into() + } + } else { + name = "Stop".to_string().into(); + } + }; + Self { title: "Next", time, name, color, } + } + +} diff --git a/midi/src/midi_view.rs b/midi/src/midi_view.rs index f9f20e06..bc05973e 100644 --- a/midi/src/midi_view.rs +++ b/midi/src/midi_view.rs @@ -30,7 +30,7 @@ pub trait MidiViewer: HasSize + MidiRange + MidiPoint + Debug + Send + S let time_zoom = self.time_zoom().get(); let time_area = time_axis * time_zoom; if time_area > time_len { - let next_time_zoom = Note::prev(time_zoom); + let next_time_zoom = NoteDuration::prev(time_zoom); if next_time_zoom <= 1 { break } @@ -41,7 +41,7 @@ pub trait MidiViewer: HasSize + MidiRange + MidiPoint + Debug + Send + S break } } else if time_area < time_len { - let prev_time_zoom = Note::next(time_zoom); + let prev_time_zoom = NoteDuration::next(time_zoom); if prev_time_zoom > 384 { break } diff --git a/src/piano/piano_h.rs b/midi/src/piano_h.rs similarity index 100% rename from src/piano/piano_h.rs rename to midi/src/piano_h.rs diff --git a/src/piano/piano_h_cursor.rs b/midi/src/piano_h_cursor.rs similarity index 100% rename from src/piano/piano_h_cursor.rs rename to midi/src/piano_h_cursor.rs diff --git a/src/piano/piano_h_keys.rs b/midi/src/piano_h_keys.rs similarity index 100% rename from src/piano/piano_h_keys.rs rename to midi/src/piano_h_keys.rs diff --git a/src/piano/piano_h_notes.rs b/midi/src/piano_h_notes.rs similarity index 100% rename from src/piano/piano_h_notes.rs rename to midi/src/piano_h_notes.rs diff --git a/src/piano/piano_h_time.rs b/midi/src/piano_h_time.rs similarity index 100% rename from src/piano/piano_h_time.rs rename to midi/src/piano_h_time.rs diff --git a/src/arranger.rs b/src/arranger.rs index 493c6cdc..ea19ced9 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -1,6 +1,5 @@ use crate::*; -mod arranger_audio; pub(crate) use self::arranger_audio::*; mod arranger_command; pub(crate) use self::arranger_command::*; mod arranger_scene; pub(crate) use self::arranger_scene::*; mod arranger_select; pub(crate) use self::arranger_select::*; @@ -27,6 +26,42 @@ pub struct Arranger { pub perf: PerfModel, pub compact: bool, } +audio!(|self: Arranger, client, scope|{ + // Start profiling cycle + let t0 = self.perf.get_t0(); + // Update transport clock + //if Control::Quit == ClockAudio(self).process(client, scope) { + //return Control::Quit + //} + //// Update MIDI sequencers + //let tracks = &mut self.tracks; + //let note_buf = &mut self.note_buf; + //let midi_buf = &mut self.midi_buf; + //if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { + //return Control::Quit + //} + // FIXME: one of these per playing track + //self.now.set(0.); + //if let ArrangerSelection::Clip(t, s) = self.selected { + //let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t)); + //if let Some(Some(Some(phrase))) = phrase { + //if let Some(track) = self.tracks().get(t) { + //if let Some((ref started_at, Some(ref playing))) = track.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); + //} + //} + //} + //} + //} + // End profiling cycle + self.perf.update(t0, scope); + return Control::Continue +}); has_clock!(|self: Arranger|&self.clock); has_phrases!(|self: Arranger|self.pool.phrases); has_editor!(|self: Arranger|self.editor); diff --git a/src/arranger/arranger_audio.rs b/src/arranger/arranger_audio.rs deleted file mode 100644 index 3662af6a..00000000 --- a/src/arranger/arranger_audio.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::*; - -audio!(|self: Arranger, client, scope|{ - // Start profiling cycle - let t0 = self.perf.get_t0(); - // Update transport clock - //if Control::Quit == ClockAudio(self).process(client, scope) { - //return Control::Quit - //} - //// Update MIDI sequencers - //let tracks = &mut self.tracks; - //let note_buf = &mut self.note_buf; - //let midi_buf = &mut self.midi_buf; - //if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { - //return Control::Quit - //} - // FIXME: one of these per playing track - //self.now.set(0.); - //if let ArrangerSelection::Clip(t, s) = self.selected { - //let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t)); - //if let Some(Some(Some(phrase))) = phrase { - //if let Some(track) = self.tracks().get(t) { - //if let Some((ref started_at, Some(ref playing))) = track.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); - //} - //} - //} - //} - //} - // End profiling cycle - self.perf.update(t0, scope); - return Control::Continue -}); diff --git a/src/groovebox.rs b/src/groovebox.rs index 04ef661a..ff386907 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -1,4 +1,3 @@ -mod groovebox_audio; pub use self::groovebox_audio::*; mod groovebox_command; pub use self::groovebox_command::*; mod groovebox_tui; pub use self::groovebox_tui::*; @@ -25,6 +24,39 @@ pub struct Groovebox { pub perf: PerfModel, } +audio!(|self: Groovebox, client, scope|{ + let t0 = self.perf.get_t0(); + if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { + return Control::Quit + } + if Control::Quit == PlayerAudio( + &mut self.player, &mut self.note_buf, &mut self.midi_buf + ).process(client, scope) { + return Control::Quit + } + if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { + return Control::Quit + } + // TODO move these to editor and sampler: + for RawMidi { time, bytes } in self.player.midi_ins[0].iter(scope) { + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + match message { + MidiMessage::NoteOn { ref key, .. } => { + self.editor.set_note_point(key.as_int() as usize); + }, + MidiMessage::Controller { controller, value } => { + if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { + sample.write().unwrap().handle_cc(controller, value) + } + } + _ => {} + } + } + } + self.perf.update(t0, scope); + Control::Continue +}); + has_clock!(|self: Groovebox|self.player.clock()); impl EdnViewData for &Groovebox { diff --git a/src/groovebox/groovebox_audio.rs b/src/groovebox/groovebox_audio.rs deleted file mode 100644 index d0459959..00000000 --- a/src/groovebox/groovebox_audio.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::*; -use super::*; - -audio!(|self: Groovebox, client, scope|{ - let t0 = self.perf.get_t0(); - if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { - return Control::Quit - } - if Control::Quit == PlayerAudio( - &mut self.player, &mut self.note_buf, &mut self.midi_buf - ).process(client, scope) { - return Control::Quit - } - if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { - return Control::Quit - } - // TODO move these to editor and sampler: - for RawMidi { time, bytes } in self.player.midi_ins[0].iter(scope) { - if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { - match message { - MidiMessage::NoteOn { ref key, .. } => { - self.editor.set_note_point(key.as_int() as usize); - }, - MidiMessage::Controller { controller, value } => { - if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { - sample.write().unwrap().handle_cc(controller, value) - } - } - _ => {} - } - } - } - self.perf.update(t0, scope); - Control::Continue -}); diff --git a/src/lib.rs b/src/lib.rs index e9e8b380..fa7ab3fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,20 @@ #![feature(impl_trait_in_assoc_type)] #![feature(associated_type_defaults)] +pub mod arranger; pub use self::arranger::*; +pub mod file; pub use self::file::*; +pub mod focus; pub use self::focus::*; +pub mod groovebox; pub use self::groovebox::*; +pub mod meter; pub use self::meter::*; +pub mod mixer; pub use self::mixer::*; +pub mod plugin; pub use self::plugin::*; +pub mod pool; pub use self::pool::*; +pub mod sampler; pub use self::sampler::*; +pub mod sequencer; pub use self::sequencer::*; + +pub use ::tek_time; pub(crate) use ::tek_time::*; +pub use ::tek_jack; pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}}; +pub use ::tek_midi; pub(crate) use ::tek_midi::{*, midly::{*, num::*, live::*}}; pub use ::tek_tui::{self, tek_edn, tek_input, tek_output}; pub(crate) use ::tek_tui::{ *, @@ -24,8 +38,6 @@ pub(crate) use ::tek_tui::{ buffer::Cell, } }; -pub use ::tek_jack; -pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}}; pub(crate) use std::cmp::{Ord, Eq, PartialEq}; pub(crate) use std::collections::BTreeMap; @@ -41,55 +53,235 @@ pub(crate) use std::sync::{Arc, Mutex, RwLock}; pub(crate) use std::thread::{spawn, JoinHandle}; pub(crate) use std::time::Duration; -pub mod arranger; pub use self::arranger::*; -pub mod file; pub use self::file::*; -pub mod focus; pub use self::focus::*; -pub mod groovebox; pub use self::groovebox::*; -pub mod meter; pub use self::meter::*; -pub mod mixer; pub use self::mixer::*; -pub mod piano; pub use self::piano::*; -pub mod plugin; pub use self::plugin::*; -pub mod pool; pub use self::pool::*; -pub mod sampler; pub use self::sampler::*; -pub mod sequencer; pub use self::sequencer::*; +//#[cfg(test)] mod test_focus { + //use super::focus::*; + //#[test] fn test_focus () { -pub use ::midly::{self, num::u7}; -pub(crate) use ::midly::{ - Smf, - MidiMessage, - TrackEventKind, - live::LiveEvent, -}; + //struct FocusTest { + //focused: char, + //cursor: (usize, usize) + //} -testmod! { test } + //impl HasFocus for FocusTest { + //type Item = char; + //fn focused (&self) -> Self::Item { + //self.focused + //} + //fn set_focused (&mut self, to: Self::Item) { + //self.focused = to + //} + //} -/// Define test modules. -#[macro_export] macro_rules! testmod { - ($($name:ident)*) => { $(#[cfg(test)] mod $name;)* }; -} + //impl FocusGrid for FocusTest { + //fn focus_cursor (&self) -> (usize, usize) { + //self.cursor + //} + //fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { + //&mut self.cursor + //} + //fn focus_layout (&self) -> &[&[Self::Item]] { + //&[ + //&['a', 'a', 'a', 'b', 'b', 'd'], + //&['a', 'a', 'a', 'b', 'b', 'd'], + //&['a', 'a', 'a', 'c', 'c', 'd'], + //&['a', 'a', 'a', 'c', 'c', 'd'], + //&['e', 'e', 'e', 'e', 'e', 'e'], + //] + //} + //} -#[derive(Default)] -pub struct BigBuffer { - pub width: usize, - pub height: usize, - pub content: Vec -} + //let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; -impl BigBuffer { - pub fn new (width: usize, height: usize) -> Self { - Self { width, height, content: vec![Cell::default(); width*height] } - } - pub fn get (&self, x: usize, y: usize) -> Option<&Cell> { - let i = self.index_of(x, y); - self.content.get(i) - } - pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> { - let i = self.index_of(x, y); - self.content.get_mut(i) - } - pub fn index_of (&self, x: usize, y: usize) -> usize { - y * self.width + x - } -} + //tester.focus_right(); + //assert_eq!(tester.cursor.0, 3); + //assert_eq!(tester.focused, 'b'); -from!(|size:(usize, usize)| BigBuffer = Self::new(size.0, size.1)); + //tester.focus_down(); + //assert_eq!(tester.cursor.1, 2); + //assert_eq!(tester.focused, 'c'); + + //} +//} +//use crate::*; + +//struct TestEngine([u16;4], Vec>); + +//impl Engine for TestEngine { + //type Unit = u16; + //type Size = [Self::Unit;2]; + //type Area = [Self::Unit;4]; + //type Input = Self; + //type Handled = bool; + //fn exited (&self) -> bool { + //true + //} +//} + +//#[derive(Copy, Clone)] +//struct TestArea(u16, u16); + +//impl Render for TestArea { + //fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + //Ok(Some([to[0], to[1], self.0, self.1])) + //} + //fn render (&self, to: &mut TestEngine) -> Perhaps<[u16;4]> { + //if let Some(layout) = self.layout(to.area())? { + //for y in layout.y()..layout.y()+layout.h()-1 { + //for x in layout.x()..layout.x()+layout.w()-1 { + //to.1[y as usize][x as usize] = '*'; + //} + //} + //Ok(Some(layout)) + //} else { + //Ok(None) + //} + //} +//} + +//#[test] +//fn test_plus_minus () -> Usually<()> { + //let area = [0, 0, 10, 10]; + //let engine = TestEngine(area, vec![vec![' ';10];10]); + //let test = TestArea(4, 4); + //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); + //assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4])); + //Ok(()) +//} + +//#[test] +//fn test_outset_align () -> Usually<()> { + //let area = [0, 0, 10, 10]; + //let engine = TestEngine(area, vec![vec![' ';10];10]); + //let test = TestArea(4, 4); + //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); + //assert_eq!(Margin::X(1, test).layout(area)?, Some([0, 0, 6, 4])); + //assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4])); + //assert_eq!(Align::X(Margin::X(1, test)).layout(area)?, Some([2, 0, 6, 4])); + //assert_eq!(Margin::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4])); + //Ok(()) +//} + +////#[test] +////fn test_misc () -> Usually<()> { + ////let area: [u16;4] = [0, 0, 10, 10]; + ////let test = TestArea(4, 4); + ////assert_eq!(test.layout(area)?, + ////Some([0, 0, 4, 4])); + ////assert_eq!(Align::Center(test).layout(area)?, + ////Some([3, 3, 4, 4])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&test)?; + ////add(&test) + ////})).layout(area)?, + ////Some([3, 1, 4, 8])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&test) + ////})).layout(area)?, + ////Some([2, 0, 6, 10])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) + ////})).layout(area)?, + ////Some([2, 1, 6, 8])); + ////assert_eq!(Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) + ////}).layout(area)?, + ////Some([0, 0, 6, 8])); + ////assert_eq!(Stack::right(|add|{ + ////add(&Stack::down(|add|{ + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) + ////}))?; + ////add(&Align::Center(TestArea(2 ,2))) + ////}).layout(area)?, + ////Some([0, 0, 8, 8])); + ////Ok(()) +////} + +////#[test] +////fn test_offset () -> Usually<()> { + ////let area: [u16;4] = [50, 50, 100, 100]; + ////let test = TestArea(3, 3); + ////assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3])); + ////assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3])); + ////assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3])); + ////Ok(()) +////} + +////#[test] +////fn test_outset () -> Usually<()> { + ////let area: [u16;4] = [50, 50, 100, 100]; + ////let test = TestArea(3, 3); + ////assert_eq!(Margin::X(1, test).layout(area)?, Some([49, 50, 5, 3])); + ////assert_eq!(Margin::Y(1, test).layout(area)?, Some([50, 49, 3, 5])); + ////assert_eq!(Margin::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5])); + ////Ok(()) +////} + +////#[test] +////fn test_padding () -> Usually<()> { + ////let area: [u16;4] = [50, 50, 100, 100]; + ////let test = TestArea(3, 3); + ////assert_eq!(Padding::X(1, test).layout(area)?, Some([51, 50, 1, 3])); + ////assert_eq!(Padding::Y(1, test).layout(area)?, Some([50, 51, 3, 1])); + ////assert_eq!(Padding::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1])); + ////Ok(()) +////} + +////#[test] +////fn test_stuff () -> Usually<()> { + ////let area: [u16;4] = [0, 0, 100, 100]; + ////assert_eq!("1".layout(area)?, + ////Some([0, 0, 1, 1])); + ////assert_eq!("333".layout(area)?, + ////Some([0, 0, 3, 1])); + ////assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?, + ////Some([0, 0, 3, 1])); + ////assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?, + ////Some([0, 0, 3, 2])); + ////assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?, + ////Some([0, 0, 4, 1])); + ////assert_eq!(Stack::down(|add|{ + ////add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?; + ////add(&"55555") + ////}).layout(area)?, + ////Some([0, 0, 5, 2])); + ////let area: [u16;4] = [1, 1, 100, 100]; + ////assert_eq!(Margin::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////Some([0, 1, 6, 1])); + ////assert_eq!(Margin::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////Some([1, 0, 4, 3])); + ////assert_eq!(Margin::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////Some([0, 0, 6, 3])); + ////assert_eq!(Stack::down(|add|{ + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333")) + ////}).layout(area)?, + ////Some([1, 1, 5, 6])); + ////let area: [u16;4] = [1, 1, 95, 100]; + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333")) + ////})).layout(area)?, + ////Some([46, 48, 5, 6])); + ////assert_eq!(Align::Center(Stack::down(|add|{ + ////add(&Layers::new(|add|{ + //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,128,0))))?; + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333"))?; + //////add(&Background(Color::Rgb(0,128,0)))?; + ////Ok(()) + ////}))?; + ////add(&Layers::new(|add|{ + //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,0,128))))?; + ////add(&Margin::XY(1, 1, "555"))?; + ////add(&Margin::XY(1, 1, "777777"))?; + //////add(&Background(Color::Rgb(0,0,128)))?; + ////Ok(()) + ////})) + ////})).layout(area)?, + ////Some([46, 48, 5, 6])); + ////Ok(()) +////} diff --git a/src/piano.rs b/src/piano.rs index 69e9c3d0..e69de29b 100644 --- a/src/piano.rs +++ b/src/piano.rs @@ -1,47 +0,0 @@ -use crate::*; -use super::*; - -mod piano_h; pub(crate) use self::piano_h::*; -mod piano_h_cursor; pub(crate) use self::piano_h_cursor::*; -mod piano_h_keys; pub(crate) use self::piano_h_keys::*; -mod piano_h_notes; pub(crate) use self::piano_h_notes::*; -mod piano_h_time; pub(crate) use self::piano_h_time::*; - -/// A phrase, rendered as a horizontal piano roll. -pub struct PianoHorizontal { - phrase: Option>>, - /// Buffer where the whole phrase is rerendered on change - buffer: Arc>, - /// Size of actual notes area - size: Measure, - /// The display window - range: MidiRangeModel, - /// The note cursor - point: MidiPointModel, - /// The highlight color palette - color: ItemPalette, - /// Width of the keyboard - keys_width: u16, -} - -impl PianoHorizontal { - pub fn new (phrase: Option<&Arc>>) -> Self { - let size = Measure::new(); - let mut range = MidiRangeModel::from((24, true)); - range.time_axis = size.x.clone(); - range.note_axis = size.y.clone(); - let mut piano = Self { - keys_width: 5, - size, - range, - buffer: RwLock::new(Default::default()).into(), - point: MidiPointModel::default(), - phrase: phrase.cloned(), - color: phrase.as_ref() - .map(|p|p.read().unwrap().color) - .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))), - }; - piano.redraw(); - piano - } -} diff --git a/src/pool/clip_select.rs b/src/pool/clip_select.rs index 0f79add0..c7b7e813 100644 --- a/src/pool/clip_select.rs +++ b/src/pool/clip_select.rs @@ -1,73 +1 @@ use crate::*; - -pub struct ClipSelected { - pub(crate) title: &'static str, - pub(crate) name: Arc, - pub(crate) color: ItemPalette, - pub(crate) time: Arc, -} - -render!(TuiOut: (self: ClipSelected) => - FieldV(self.color, self.title, format!("{} {}", self.time, self.name))); - -impl ClipSelected { - - /// Shows currently playing phrase with beats elapsed - pub fn play_phrase (state: &T) -> Self { - let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { - let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); - (name.clone().into(), color) - } else { - ("".to_string().into(), TuiTheme::g(64).into()) - }; - Self { - title: "Now", - name, - color, - time: state.pulses_since_start_looped() - .map(|(times, time)|format!("{:>3}x {:>}", - times+1.0, - state.clock().timebase.format_beats_1(time))) - .unwrap_or_else(||String::from(" ")).into() - } - } - - /// Shows next phrase with beats remaining until switchover - pub fn next_phrase (state: &T) -> Self { - let mut time: Arc = String::from("--.-.--").into(); - let mut name: Arc = String::from("").into(); - let mut color = ItemPalette::from(TuiTheme::g(64)); - if let Some((t, Some(phrase))) = state.next_phrase() { - let phrase = phrase.read().unwrap(); - name = phrase.name.clone(); - color = phrase.color.clone(); - time = { - let target = t.pulse.get(); - let current = state.clock().playhead.pulse.get(); - if target > current { - let remaining = target - current; - format!("-{:>}", state.clock().timebase.format_beats_1(remaining)) - } else { - String::new() - } - }.into() - } else if let Some((t, Some(phrase))) = state.play_phrase() { - let phrase = phrase.read().unwrap(); - if phrase.looped { - name = phrase.name.clone(); - color = phrase.color.clone(); - let target = t.pulse.get() + phrase.length as f64; - let current = state.clock().playhead.pulse.get(); - if target > current { - time = format!("-{:>}", state.clock().timebase.format_beats_0( - target - current - )).into() - } - } else { - name = "Stop".to_string().into(); - } - }; - Self { title: "Next", time, name, color, } - } - -} diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 34c6e4aa..00000000 --- a/src/test.rs +++ /dev/null @@ -1,232 +0,0 @@ -//#[cfg(test)] mod test_focus { - //use super::focus::*; - //#[test] fn test_focus () { - - //struct FocusTest { - //focused: char, - //cursor: (usize, usize) - //} - - //impl HasFocus for FocusTest { - //type Item = char; - //fn focused (&self) -> Self::Item { - //self.focused - //} - //fn set_focused (&mut self, to: Self::Item) { - //self.focused = to - //} - //} - - //impl FocusGrid for FocusTest { - //fn focus_cursor (&self) -> (usize, usize) { - //self.cursor - //} - //fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { - //&mut self.cursor - //} - //fn focus_layout (&self) -> &[&[Self::Item]] { - //&[ - //&['a', 'a', 'a', 'b', 'b', 'd'], - //&['a', 'a', 'a', 'b', 'b', 'd'], - //&['a', 'a', 'a', 'c', 'c', 'd'], - //&['a', 'a', 'a', 'c', 'c', 'd'], - //&['e', 'e', 'e', 'e', 'e', 'e'], - //] - //} - //} - - //let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; - - //tester.focus_right(); - //assert_eq!(tester.cursor.0, 3); - //assert_eq!(tester.focused, 'b'); - - //tester.focus_down(); - //assert_eq!(tester.cursor.1, 2); - //assert_eq!(tester.focused, 'c'); - - //} -//} -//use crate::*; - -//struct TestEngine([u16;4], Vec>); - -//impl Engine for TestEngine { - //type Unit = u16; - //type Size = [Self::Unit;2]; - //type Area = [Self::Unit;4]; - //type Input = Self; - //type Handled = bool; - //fn exited (&self) -> bool { - //true - //} -//} - -//#[derive(Copy, Clone)] -//struct TestArea(u16, u16); - -//impl Render for TestArea { - //fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - //Ok(Some([to[0], to[1], self.0, self.1])) - //} - //fn render (&self, to: &mut TestEngine) -> Perhaps<[u16;4]> { - //if let Some(layout) = self.layout(to.area())? { - //for y in layout.y()..layout.y()+layout.h()-1 { - //for x in layout.x()..layout.x()+layout.w()-1 { - //to.1[y as usize][x as usize] = '*'; - //} - //} - //Ok(Some(layout)) - //} else { - //Ok(None) - //} - //} -//} - -//#[test] -//fn test_plus_minus () -> Usually<()> { - //let area = [0, 0, 10, 10]; - //let engine = TestEngine(area, vec![vec![' ';10];10]); - //let test = TestArea(4, 4); - //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); - //assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4])); - //Ok(()) -//} - -//#[test] -//fn test_outset_align () -> Usually<()> { - //let area = [0, 0, 10, 10]; - //let engine = TestEngine(area, vec![vec![' ';10];10]); - //let test = TestArea(4, 4); - //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); - //assert_eq!(Margin::X(1, test).layout(area)?, Some([0, 0, 6, 4])); - //assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4])); - //assert_eq!(Align::X(Margin::X(1, test)).layout(area)?, Some([2, 0, 6, 4])); - //assert_eq!(Margin::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4])); - //Ok(()) -//} - -////#[test] -////fn test_misc () -> Usually<()> { - ////let area: [u16;4] = [0, 0, 10, 10]; - ////let test = TestArea(4, 4); - ////assert_eq!(test.layout(area)?, - ////Some([0, 0, 4, 4])); - ////assert_eq!(Align::Center(test).layout(area)?, - ////Some([3, 3, 4, 4])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&test)?; - ////add(&test) - ////})).layout(area)?, - ////Some([3, 1, 4, 8])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&test) - ////})).layout(area)?, - ////Some([2, 0, 6, 10])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&Padding::XY(2, 2, test)) - ////})).layout(area)?, - ////Some([2, 1, 6, 8])); - ////assert_eq!(Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&Padding::XY(2, 2, test)) - ////}).layout(area)?, - ////Some([0, 0, 6, 8])); - ////assert_eq!(Stack::right(|add|{ - ////add(&Stack::down(|add|{ - ////add(&Margin::XY(2, 2, test))?; - ////add(&Padding::XY(2, 2, test)) - ////}))?; - ////add(&Align::Center(TestArea(2 ,2))) - ////}).layout(area)?, - ////Some([0, 0, 8, 8])); - ////Ok(()) -////} - -////#[test] -////fn test_offset () -> Usually<()> { - ////let area: [u16;4] = [50, 50, 100, 100]; - ////let test = TestArea(3, 3); - ////assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3])); - ////assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3])); - ////assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3])); - ////Ok(()) -////} - -////#[test] -////fn test_outset () -> Usually<()> { - ////let area: [u16;4] = [50, 50, 100, 100]; - ////let test = TestArea(3, 3); - ////assert_eq!(Margin::X(1, test).layout(area)?, Some([49, 50, 5, 3])); - ////assert_eq!(Margin::Y(1, test).layout(area)?, Some([50, 49, 3, 5])); - ////assert_eq!(Margin::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5])); - ////Ok(()) -////} - -////#[test] -////fn test_padding () -> Usually<()> { - ////let area: [u16;4] = [50, 50, 100, 100]; - ////let test = TestArea(3, 3); - ////assert_eq!(Padding::X(1, test).layout(area)?, Some([51, 50, 1, 3])); - ////assert_eq!(Padding::Y(1, test).layout(area)?, Some([50, 51, 3, 1])); - ////assert_eq!(Padding::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1])); - ////Ok(()) -////} - -////#[test] -////fn test_stuff () -> Usually<()> { - ////let area: [u16;4] = [0, 0, 100, 100]; - ////assert_eq!("1".layout(area)?, - ////Some([0, 0, 1, 1])); - ////assert_eq!("333".layout(area)?, - ////Some([0, 0, 3, 1])); - ////assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?, - ////Some([0, 0, 3, 1])); - ////assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?, - ////Some([0, 0, 3, 2])); - ////assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?, - ////Some([0, 0, 4, 1])); - ////assert_eq!(Stack::down(|add|{ - ////add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?; - ////add(&"55555") - ////}).layout(area)?, - ////Some([0, 0, 5, 2])); - ////let area: [u16;4] = [1, 1, 100, 100]; - ////assert_eq!(Margin::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, - ////Some([0, 1, 6, 1])); - ////assert_eq!(Margin::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, - ////Some([1, 0, 4, 3])); - ////assert_eq!(Margin::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, - ////Some([0, 0, 6, 3])); - ////assert_eq!(Stack::down(|add|{ - ////add(&Margin::XY(1, 1, "1"))?; - ////add(&Margin::XY(1, 1, "333")) - ////}).layout(area)?, - ////Some([1, 1, 5, 6])); - ////let area: [u16;4] = [1, 1, 95, 100]; - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Margin::XY(1, 1, "1"))?; - ////add(&Margin::XY(1, 1, "333")) - ////})).layout(area)?, - ////Some([46, 48, 5, 6])); - ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Layers::new(|add|{ - //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,128,0))))?; - ////add(&Margin::XY(1, 1, "1"))?; - ////add(&Margin::XY(1, 1, "333"))?; - //////add(&Background(Color::Rgb(0,128,0)))?; - ////Ok(()) - ////}))?; - ////add(&Layers::new(|add|{ - //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,0,128))))?; - ////add(&Margin::XY(1, 1, "555"))?; - ////add(&Margin::XY(1, 1, "777777"))?; - //////add(&Background(Color::Rgb(0,0,128)))?; - ////Ok(()) - ////})) - ////})).layout(area)?, - ////Some([46, 48, 5, 6])); - ////Ok(()) -////} diff --git a/time/src/lib.rs b/time/src/lib.rs index 8156a18c..aaa51224 100644 --- a/time/src/lib.rs +++ b/time/src/lib.rs @@ -1,12 +1,13 @@ -mod clock_tui; pub use self::clock_tui::*; -mod microsecond; pub use self::microsecond::*; -mod moment; pub use self::moment::*; -mod perf; pub use self::perf::*; -mod pulse; pub use self::pulse::*; -mod sample_count; pub use self::sample_count::*; -mod sample_rate; pub use self::sample_rate::*; -mod timebase; pub use self::timebase::*; -mod unit; pub use self::unit::*; +mod clock_tui; pub use self::clock_tui::*; +mod microsecond; pub use self::microsecond::*; +mod moment; pub use self::moment::*; +mod note_duration; pub use self::note_duration::*; +mod perf; pub use self::perf::*; +mod pulse; pub use self::pulse::*; +mod sample_count; pub use self::sample_count::*; +mod sample_rate; pub use self::sample_rate::*; +mod timebase; pub use self::timebase::*; +mod unit; pub use self::unit::*; pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}}; pub(crate) use std::sync::{Arc, Mutex, RwLock, atomic::{AtomicUsize, Ordering::*}}; diff --git a/time/src/note_duration.rs b/time/src/note_duration.rs new file mode 100644 index 00000000..5f92e0bd --- /dev/null +++ b/time/src/note_duration.rs @@ -0,0 +1,35 @@ +pub struct NoteDuration; + +/// (pulses, name), assuming 96 PPQ +pub const NOTE_DURATIONS: [(usize, &str);26] = [ + (1, "1/384"), (2, "1/192"), + (3, "1/128"), (4, "1/96"), + (6, "1/64"), (8, "1/48"), + (12, "1/32"), (16, "1/24"), + (24, "1/16"), (32, "1/12"), + (48, "1/8"), (64, "1/6"), + (96, "1/4"), (128, "1/3"), + (192, "1/2"), (256, "2/3"), + (384, "1/1"), (512, "4/3"), + (576, "3/2"), (768, "2/1"), + (1152, "3/1"), (1536, "4/1"), + (2304, "6/1"), (3072, "8/1"), + (3456, "9/1"), (6144, "16/1"), +]; + +impl NoteDuration { + /// Returns the next shorter length + pub fn prev (pulses: usize) -> usize { + for (length, _) in NOTE_DURATIONS.iter().rev() { if *length < pulses { return *length } } + pulses + } + /// Returns the next longer length + pub fn next (pulses: usize) -> usize { + for (length, _) in NOTE_DURATIONS.iter() { if *length > pulses { return *length } } + pulses + } + pub fn pulses_to_name (pulses: usize) -> &'static str { + for (length, name) in NOTE_DURATIONS.iter() { if *length == pulses { return name } } + "" + } +} diff --git a/time/src/pulse.rs b/time/src/pulse.rs index 7f8c9097..5505bd03 100644 --- a/time/src/pulse.rs +++ b/time/src/pulse.rs @@ -22,10 +22,10 @@ impl_time_unit!(BeatsPerMinute); impl_time_unit!(LaunchSync); impl LaunchSync { pub fn next (&self) -> f64 { - Note::next(self.get() as usize) as f64 + NoteDuration::next(self.get() as usize) as f64 } pub fn prev (&self) -> f64 { - Note::prev(self.get() as usize) as f64 + NoteDuration::prev(self.get() as usize) as f64 } } @@ -34,10 +34,10 @@ impl LaunchSync { impl_time_unit!(Quantize); impl Quantize { pub fn next (&self) -> f64 { - Note::next(self.get() as usize) as f64 + NoteDuration::next(self.get() as usize) as f64 } pub fn prev (&self) -> f64 { - Note::prev(self.get() as usize) as f64 + NoteDuration::prev(self.get() as usize) as f64 } } diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 940a2d8e..26e77fdc 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -18,6 +18,7 @@ mod tui_style; pub use self::tui_style::*; mod tui_theme; pub use self::tui_theme::*; mod tui_border; pub use self::tui_border::*; mod tui_field; pub use self::tui_field::*; +mod tui_buffer; pub use self::tui_buffer::*; pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::*}}; pub(crate) use std::io::{stdout, Stdout}; diff --git a/tui/src/tui_buffer.rs b/tui/src/tui_buffer.rs new file mode 100644 index 00000000..37f41739 --- /dev/null +++ b/tui/src/tui_buffer.rs @@ -0,0 +1,27 @@ +use crate::*; + +#[derive(Default)] +pub struct BigBuffer { + pub width: usize, + pub height: usize, + pub content: Vec +} + +impl BigBuffer { + pub fn new (width: usize, height: usize) -> Self { + Self { width, height, content: vec![Cell::default(); width*height] } + } + pub fn get (&self, x: usize, y: usize) -> Option<&Cell> { + let i = self.index_of(x, y); + self.content.get(i) + } + pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> { + let i = self.index_of(x, y); + self.content.get_mut(i) + } + pub fn index_of (&self, x: usize, y: usize) -> usize { + y * self.width + x + } +} + +from!(|size:(usize, usize)| BigBuffer = Self::new(size.0, size.1));