show active clip in sequencer; highlight full clip

This commit is contained in:
🪞👃🪞 2024-10-04 10:31:41 +03:00
parent 7d6a43d87e
commit 4e033da2b4
3 changed files with 33 additions and 28 deletions

View file

@ -10,11 +10,11 @@ use tek_core::clap::{self, Parser};
#[command(version, about, long_about = None)]
pub struct SequencerCli {
/// Name of JACK client
#[arg(short, long)] name: Option<String>,
#[arg(short, long)] name: Option<String>,
/// Pulses per quarter note (sequencer resolution; default: 96)
#[arg(short, long)] ppq: Option<usize>,
#[arg(short, long)] ppq: Option<usize>,
/// Default phrase duration (in pulses; default: 4 * PPQ = 1 bar)
#[arg(short, long)] length: Option<usize>,
#[arg(short, long)] length: Option<usize>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long)] transport: Option<bool>
}
@ -30,9 +30,10 @@ impl Sequencer<Tui> {
seq.ppq = ppq;
}
if let Some(length) = args.length {
if let Some(phrase) = seq.phrase.as_mut() {
phrase.write().unwrap().length = length;
}
// TODO FIXME WTF
//if let Some(phrase) = seq.phrase.as_mut() {
//phrase.write().unwrap().length = length;
//}
}
if args.transport == Some(true) {
seq.transport = Some(Arc::new(RwLock::new(TransportToolbar::new(None))));

View file

@ -452,7 +452,6 @@ pub struct Sequencer<E: Engine> {
pub mode: bool,
pub focused: bool,
pub entered: bool,
pub phrase: Option<Arc<RwLock<Phrase>>>,
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
/// The full piano roll is rendered to this buffer
pub buffer: BigBuffer,
@ -493,7 +492,6 @@ pub struct Sequencer<E: Engine> {
/// Highlight keys on piano roll.
pub notes_out: [bool;128],
}
impl<E: Engine> Sequencer<E> {
pub fn new (name: &str) -> Self {
Self {
@ -516,7 +514,6 @@ impl<E: Engine> Sequencer<E> {
mode: false,
keys_in: [false;128],
keys_out: [false;128],
phrase: None,
now: 0,
ppq: 96,
transport: None,
@ -620,7 +617,6 @@ impl<E: Engine> Sequencer<E> {
write_midi_output(&mut out.writer(scope), &self.midi_out_buf, frames);
}
}
#[inline]
fn process_monitor_event (&mut self, frame: usize, event: &LiveEvent, bytes: &[u8]) {
match event {
@ -631,11 +627,9 @@ impl<E: Engine> Sequencer<E> {
_ => {}
}
}
#[inline] fn write_to_output_buffer (&mut self, frame: usize, bytes: &[u8]) {
self.midi_out_buf[frame].push(bytes.to_vec());
}
#[inline]
fn process_monitor_message (&mut self, message: &MidiMessage) {
match message {

View file

@ -93,10 +93,12 @@ impl Handle<Tui> for Arranger<Tui> {
// increment: use next clip here
key!(KeyCode::Char('.')) => {
self.phrase_next();
self.show_phrase();
},
// decrement: use previous next clip here
key!(KeyCode::Char(',')) => {
self.phrase_prev();
self.show_phrase();
},
// decrement: use previous clip here
key!(KeyCode::Enter) => {
@ -164,13 +166,16 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
Layers::new(move |add|{
let rows: &[(usize, usize)] = rows.as_ref();
let cols: &[(usize, usize)] = cols.as_ref();
let track_titles = row!((track, (w, _)) in tracks.iter().zip(cols) =>
(&track.name.read().unwrap().as_str() as &dyn Widget<Engine = Tui>)
.min_xy(*w as u16, 2).push_x(offset));
let scene_name = |scene, playing: bool, height|row!(
if playing { "" } else { " " },
(scene as &Scene).name.read().unwrap().as_str(),
).fixed_xy(offset.saturating_sub(1), height);
let scene_clip = |scene, track: usize, w: u16, h: u16|Layers::new(move |add|{
let mut color = Color::Rgb(40, 50, 30);
match (tracks.get(track), (scene as &Scene).clips.get(track)) {
@ -179,7 +184,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
let name = &(phrase as &Arc<RwLock<Phrase>>).read().unwrap().name;
let name = name.read().unwrap();
let name = format!("{clip:02} {}", name);
add(&name.as_str().push_x(1))?;
add(&name.as_str().push_x(1).fixed_x(w))?;
if (track as &Sequencer<_>).playing_phrase == Some(*clip) {
color = COLOR_PLAYING
} else {
@ -192,6 +197,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
};
add(&Background(color))
}).fixed_xy(w, h);
let tracks_clips = col!((scene, (pulses, _)) in scenes.iter().zip(rows) => {
let height = 1.max((pulses / 96) as u16);
let playing = scene.is_playing(tracks);
@ -203,6 +209,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
Ok(())
}).fixed_y(height)
});
add(&VerticalArrangerGrid(offset, &rows, &cols))?;
add(&VerticalArrangerCursor(state.focused, state.selected, offset, &cols, &rows))?;
add(&col!(track_titles, tracks_clips))?;
@ -644,21 +651,21 @@ impl Handle<Tui> for ArrangerRenameModal<Tui> {
impl Sequencer<Tui> {
const H_KEYS_OFFSET: usize = 5;
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) -> Usually<()> {
self.phrase = phrase.map(Clone::clone);
if let Some(ref phrase) = self.phrase {
pub fn show (&mut self, index: Option<usize>) {
self.viewing_phrase = index;
if let Some(phrase) = index.map(|index|self.phrases.get(index)).flatten() {
let width = usize::MAX.min(phrase.read().unwrap().length);
let mut buffer = BigBuffer::new(width, 64);
let phrase = phrase.read().unwrap();
Self::fill_seq_bg(&mut buffer, phrase.length, self.ppq)?;
Self::fill_seq_fg(&mut buffer, &phrase)?;
Self::fill_seq_bg(&mut buffer, phrase.length, self.ppq);
Self::fill_seq_fg(&mut buffer, &phrase);
self.buffer = buffer;
} else {
self.viewing_phrase = None;
self.buffer = Default::default();
}
Ok(())
}
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) -> Usually<()> {
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) {
for x in 0..buf.width {
if x as usize >= length {
break
@ -684,9 +691,8 @@ impl Sequencer<Tui> {
});
}
}
Ok(())
}
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) -> Usually<()> {
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) {
let mut notes_on = [false;128];
for x in 0..buf.width {
if x as usize >= phrase.length {
@ -731,7 +737,6 @@ impl Sequencer<Tui> {
}
}
}
Ok(())
}
pub(crate) fn style_focus (&self) -> Option<Style> {
Some(if self.focused {
@ -765,14 +770,16 @@ impl Content for Sequencer<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let toolbar = Stack::down(move|add|{
add(&col! {"Name", self.name.read().unwrap().as_str(),})?;
if let Some(phrase) = self.phrase.as_ref() {
panic!();
add(&col!("Name:", self.name.read().unwrap().as_str()))?;
if let Some(phrase) = self.viewing_phrase
.map(|index|self.phrases.get(index))
.flatten()
{
let phrase = phrase.read().unwrap();
let length = phrase.length;
let looped = phrase.looped;
add(&"")?;
add(&col!("Length: ", format!("{length}").as_str()))?;
add(&col!("Length: ", format!("{}q{}p", length / 96, length % 96).as_str()))?;
add(&"")?;
add(&col!("Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0"))?;
add(&"")?;
@ -794,7 +801,10 @@ impl Content for Sequencer<Tui> {
}).fill_y(),
// playhead
CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{
if let Some(phrase) = self.phrase.as_ref() {
if let Some(phrase) = self.viewing_phrase
.map(|index|self.phrases.get(index))
.flatten()
{
let time_0 = self.time_axis.start;
let time_z = self.time_axis.scale;
let now = self.now % phrase.read().unwrap().length;