render chain with SplitFocus

This commit is contained in:
🪞👃🪞 2024-07-11 20:53:20 +03:00
parent a39e694a3e
commit c3040cef1c
12 changed files with 247 additions and 147 deletions

View file

@ -96,11 +96,11 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
}],
[Char('='), NONE, "zoom_in", "Zoom in", |app: &mut App| {
app.time_zoom = prev_note_length(app.time_zoom);
app.seq_buf.time_zoom = prev_note_length(app.seq_buf.time_zoom);
Ok(true)
}],
[Char('-'), NONE, "zoom_out", "Zoom out", |app: &mut App| {
app.time_zoom = next_note_length(app.time_zoom);
app.seq_buf.time_zoom = next_note_length(app.seq_buf.time_zoom);
Ok(true)
}],

View file

@ -1,28 +1,32 @@
use crate::{core::*, model::App};
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| {
app.arranger_mode = !app.seq_mode;
Ok(true)
}],
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| Ok(
match app.arranger_mode {
false => {app.scene_cursor = app.scene_cursor.saturating_sub(1); true},
true => {app.track_cursor = app.track_cursor.saturating_sub(1); true},
false => {app.prev_scene();true},
true => {app.prev_track();true},
}
)],
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok(
match app.arranger_mode {
false => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); true},
true => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); true},
false => {app.next_scene();true},
true => {app.next_track();true},
}
)],
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok(
match app.arranger_mode {
false => {app.track_cursor = app.track_cursor.saturating_sub(1); true},
true => {app.scene_cursor = app.scene_cursor.saturating_sub(1); true},
false => {app.prev_track();true},
true => {app.prev_scene();true},
}
)],
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok(
match app.arranger_mode {
false => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); true},
true => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); true},
false => {app.next_track();true},
true => {app.next_scene();true}
}
)],
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| Ok(
@ -44,39 +48,11 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
}
)],
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| {
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
//let scene_id = state.cursor.1 - 1;
//let clip_id = state.cursor.0 - 1;
//let scene = &mut state.scenes[scene_id];
//scene.clips[clip_id] = match scene.clips[clip_id] {
//None => Some(0),
//Some(i) => if i >= state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1) {
//None
//} else {
//Some(i + 1)
//}
//};
//}
app.next_phrase();
Ok(true)
}],
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| {
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
//let scene_id = state.cursor.1 - 1;
//let clip_id = state.cursor.0 - 1;
//let scene = &mut state.scenes[scene_id];
//scene.clips[clip_id] = match scene.clips[clip_id] {
//None => Some(state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1)),
//Some(i) => if i == 0 {
//None
//} else {
//Some(i - 1)
//}
//};
//}
Ok(true)
}],
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| {
app.arranger_mode = !app.seq_mode;
app.prev_phrase();
Ok(true)
}],
});

View file

@ -13,7 +13,7 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
if app.entered {
app.time_cursor = app.time_cursor.saturating_sub(1);
} else {
app.time_start = app.time_start.saturating_sub(1);
app.seq_buf.time_start = app.seq_buf.time_start.saturating_sub(1);
}
Ok(true)
}],
@ -21,7 +21,7 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
if app.entered {
app.time_cursor = app.time_cursor + 1;
} else {
app.time_start = app.time_start + 1;
app.seq_buf.time_start = app.seq_buf.time_start + 1;
}
Ok(true)
}],

View file

@ -13,7 +13,7 @@ pub use self::sampler::{Sampler, Sample, read_sample_data};
pub use self::mixer::Mixer;
pub use self::plugin::{Plugin, PluginKind, lv2::LV2Plugin};
use crate::core::*;
use crate::{core::*, view::*};
pub struct App {
/// Paths to user directories
@ -43,6 +43,8 @@ pub struct App {
pub chain_mode: bool,
/// Display mode of sequencer seciton
pub seq_mode: bool,
/// Display buffer for sequencer
pub seq_buf: BufferedSequencerView,
/// Optional modal dialog
pub modal: Option<Box<dyn Component>>,
/// Currently focused section
@ -57,10 +59,6 @@ pub struct App {
pub note_start: usize,
/// Display position of cursor within time range
pub time_cursor: usize,
/// PPQ per display unit
pub time_zoom: usize,
/// Range of time steps to display
pub time_start: usize,
/// Focused scene+1, 0 is track list
pub scene_cursor: usize,
/// Collection of scenes
@ -105,9 +103,8 @@ impl App {
scenes: vec![],
section: AppSection::default(),
seq_mode: false,
seq_buf: BufferedSequencerView::new(96, 16384),
time_cursor: 0,
time_start: 0,
time_zoom: 12,
timebase: Arc::new(Timebase::default()),
track_cursor: 1,
tracks: vec![],

View file

@ -1,5 +1,56 @@
use crate::{core::*, model::App};
impl App {
pub fn next_phrase (&mut self) {
if let Some((track_index, track)) = self.track_mut() {
let phrases = track.phrases.len();
if let Some((_, scene)) = self.scene_mut() {
if let Some(phrase_index) = scene.clips[track_index] {
if phrase_index >= phrases - 1 {
scene.clips[track_index] = None;
} else {
scene.clips[track_index] = Some(phrase_index + 1);
}
} else if phrases > 0 {
scene.clips[track_index] = Some(0);
}
}
}
}
pub fn prev_phrase (&mut self) {
if let Some((track_index, track)) = self.track_mut() {
let phrases = track.phrases.len();
if let Some((_, scene)) = self.scene_mut() {
if let Some(phrase_index) = scene.clips[track_index] {
scene.clips[track_index] = if phrase_index == 0 {
None
} else {
Some(phrase_index - 1)
};
} else if phrases > 0 {
scene.clips[track_index] = Some(phrases - 1);
}
}
}
}
pub fn phrase (&self) -> Option<&Phrase> {
let (track_id, track) = self.track()?;
let (_, scene) = self.scene()?;
track.phrases.get((*scene.clips.get(track_id)?)?)
}
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
let clip = (*scene.clips.get(track_id)?)?;
self.track_mut()?.1.phrases.get_mut(clip)
}
fn phrase_id (&self) -> Option<usize> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
*scene.clips.get(track_id)?
}
}
/// Define a MIDI phrase.
#[macro_export] macro_rules! phrase {
($($t:expr => $msg:expr),* $(,)?) => {{
@ -87,22 +138,3 @@ impl Phrase {
}
}
}
impl App {
pub fn phrase (&self) -> Option<&Phrase> {
let (track_id, track) = self.track()?;
let (_, scene) = self.scene()?;
track.phrases.get((*scene.clips.get(track_id)?)?)
}
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
let clip = (*scene.clips.get(track_id)?)?;
self.track_mut()?.1.phrases.get_mut(clip)
}
pub fn phrase_id (&self) -> Option<usize> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
*scene.clips.get(track_id)?
}
}

View file

@ -1,21 +1,13 @@
use crate::{core::*, model::App};
pub struct Scene {
pub name: String,
pub clips: Vec<Option<usize>>,
}
impl Scene {
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
Self {
name: name.as_ref().into(),
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
}
}
}
impl App {
pub fn new_scene_name (&self) -> String {
pub fn next_scene (&mut self) {
self.scene_cursor = self.scenes.len().min(self.scene_cursor + 1);
}
pub fn prev_scene (&mut self) {
self.scene_cursor = self.scene_cursor.saturating_sub(1);
}
fn new_scene_name (&self) -> String {
format!("Scene {}", self.scenes.len() + 1)
}
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
@ -47,3 +39,17 @@ impl App {
} }
}
}
pub struct Scene {
pub name: String,
pub clips: Vec<Option<usize>>,
}
impl Scene {
fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
Self {
name: name.as_ref().into(),
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
}
}
}

View file

@ -1,6 +1,52 @@
use crate::core::*;
use crate::model::*;
impl App {
pub fn next_track (&mut self) {
self.track_cursor = self.tracks.len().min(self.track_cursor + 1);
}
pub fn prev_track (&mut self) {
self.track_cursor = self.track_cursor.saturating_sub(1);
}
pub fn new_track_name (&self) -> String {
format!("Track {}", self.tracks.len() + 1)
}
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
let name = name.ok_or_else(||self.new_track_name())?;
self.tracks.push(Track::new(&name, None, None)?);
self.track_cursor = self.tracks.len();
Ok(&mut self.tracks[self.track_cursor - 1])
}
fn add_track_with_cb (
&mut self, name: Option<&str>, init: impl FnOnce(&Client, &mut Track)->Usually<()>,
) -> Usually<&mut Track> {
let name = name.ok_or_else(||self.new_track_name())?;
let mut track = Track::new(&name, None, None)?;
init(self.client(), &mut track)?;
self.tracks.push(track);
self.track_cursor = self.tracks.len();
Ok(&mut self.tracks[self.track_cursor - 1])
}
pub fn track (&self) -> Option<(usize, &Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get(id).map(|t|(id, t))
} }
}
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get_mut(id).map(|t|(id, t))
} }
}
}
pub struct Track {
pub name: String,
/// Play input through output.
@ -29,7 +75,7 @@ pub struct Track {
pub notes_out: [bool;128],
}
impl Track {
pub fn new (
fn new (
name: &str,
phrases: Option<Vec<Phrase>>,
devices: Option<Vec<JackDevice>>,
@ -50,19 +96,19 @@ impl Track {
reset: true,
})
}
pub fn get_device (&self, i: usize) -> Option<RwLockReadGuard<Box<dyn Device>>> {
fn get_device (&self, i: usize) -> Option<RwLockReadGuard<Box<dyn Device>>> {
self.devices.get(i).map(|d|d.state.read().unwrap())
}
pub fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn Device>>> {
fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn Device>>> {
self.devices.get(i).map(|d|d.state.write().unwrap())
}
pub fn device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
fn device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
self.get_device(self.device)
}
pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn Device>>> {
self.get_device_mut(self.device)
}
pub fn first_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
fn first_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
self.get_device(0)
}
pub fn connect_first_device (&self) -> Usually<()> {
@ -71,7 +117,7 @@ impl Track {
}
Ok(())
}
pub fn last_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
fn last_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
self.get_device(self.devices.len().saturating_sub(1))
}
pub fn connect_last_device (&self, app: &App) -> Usually<()> {
@ -89,7 +135,7 @@ impl Track {
let index = self.devices.len() - 1;
Ok(&mut self.devices[index])
}
pub fn add_device_with_cb (
fn add_device_with_cb (
&mut self,
mut device: JackDevice,
init: impl Fn(&Self, &mut JackDevice)->Usually<()>
@ -99,21 +145,21 @@ impl Track {
let index = self.devices.len() - 1;
Ok(&mut self.devices[index])
}
pub fn phrase (&self) -> Option<&Phrase> {
fn phrase (&self) -> Option<&Phrase> {
if let Some(phrase) = self.sequence {
return self.phrases.get(phrase)
} else {
None
}
}
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
fn phrase_mut (&mut self) -> Option<&mut Phrase> {
if let Some(phrase) = self.sequence {
return self.phrases.get_mut(phrase)
} else {
None
}
}
pub fn add_phrase (
fn add_phrase (
&mut self, name: &str, length: usize, data: Option<PhraseData>
) -> &mut Phrase {
self.phrases.push(Phrase::new(name, length, data));
@ -243,43 +289,3 @@ impl Track {
}
}
}
impl App {
pub fn new_track_name (&self) -> String {
format!("Track {}", self.tracks.len() + 1)
}
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
let name = name.ok_or_else(||self.new_track_name())?;
self.tracks.push(Track::new(&name, None, None)?);
self.track_cursor = self.tracks.len();
Ok(&mut self.tracks[self.track_cursor - 1])
}
pub fn add_track_with_cb (
&mut self, name: Option<&str>, init: impl FnOnce(&Client, &mut Track)->Usually<()>,
) -> Usually<&mut Track> {
let name = name.ok_or_else(||self.new_track_name())?;
let mut track = Track::new(&name, None, None)?;
init(self.client(), &mut track)?;
self.tracks.push(track);
self.track_cursor = self.tracks.len();
Ok(&mut self.tracks[self.track_cursor - 1])
}
pub fn track (&self) -> Option<(usize, &Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get(id).map(|t|(id, t))
} }
}
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get_mut(id).map(|t|(id, t))
} }
}
}

View file

@ -12,7 +12,7 @@ pub use self::layout::*;
pub use self::transport::TransportView;
pub use self::arranger::*;
pub use self::chain::ChainView;
pub use self::sequencer::SequencerView;
pub use self::sequencer::{SequencerView, BufferedSequencerView};
use crate::{render, App, core::*};
@ -22,7 +22,7 @@ render!(App |self, buf, area| {
&ArrangerView::new(&self, !self.arranger_mode),
&If(self.track_cursor > 0, &Split::right([
&ChainView::vertical(&self),
&SequencerView::new(&self),
&self.seq_buf,
]))
]).render(buf, area)?;
if let Some(ref modal) = self.modal {

View file

@ -24,7 +24,7 @@ pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
Some(Style::default().green())
} else {
None
});
})?;
} else {
break
}
@ -39,10 +39,10 @@ pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
let style = Style::default().gray();
let label1 = format!(" {}", state.name);
label1.blit(buf, x + 1, y, Some(style.white().bold()));
label1.blit(buf, x + 1, y, Some(style.white().bold()))?;
if let Some(ref path) = state.path {
let label2 = format!("{}", &path[..((w as usize - 10).min(path.len()))]);
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()))?;
}
Ok(Rect { x, y, width: w, height: 1 })
}

View file

@ -6,7 +6,7 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rec
{
let style = Style::default().gray();
let title = format!(" {} ({})", state.name, state.voices.len());
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()));
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()))?;
let mut width = title.len() + 2;
for (i, (note, sample)) in state.samples.iter().enumerate() {
let style = if i == state.cursor.0 {
@ -20,15 +20,15 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rec
break
}
if i as usize == state.cursor.0 {
"".blit(buf, x+1, y1, Some(style.bold()));
"".blit(buf, x+1, y1, Some(style.bold()))?;
}
let label1 = format!("{note:3} {:8}", sample.name);
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
label1.blit(buf, x+2, y1, Some(style.bold()));
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style));
label1.blit(buf, x+2, y1, Some(style.bold()))?;
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style))?;
width = width.max(label1.len() + label2.len() + 4);
}
let height = ((1 + state.samples.len()) as u16).min(height);
let height = ((2 + state.samples.len()) as u16).min(height);
Ok(Rect { x, y, width: width as u16, height })
}

View file

@ -1,5 +1,82 @@
use crate::{core::*,model::*};
pub struct BufferedSequencerView {
pub ppq: usize,
pub length: usize,
pub character: [Vec<char>;64],
pub fg: [Vec<Color>;64],
pub bg: [Vec<Color>;64],
pub notes: [bool;128],
/// 1st time step to displayRange of time steps to display
pub time_start: usize,
/// PPQ per display unit
pub time_zoom: usize,
}
render!(BufferedSequencerView |self, buf, area| {
let mut area = area;
area.height = area.height.min(64);
for y in 0..area.height {
for x in 0..area.width {
let cell = buf.get_mut(area.x + x, area.y + y);
let time_index = (self.time_start + x as usize) * self.time_zoom;
let note_index = 63 - y as usize;
cell.set_char(self.character[note_index][time_index]);
}
}
Ok(area)
});
impl BufferedSequencerView {
pub fn new (ppq: usize, length: usize) -> Self {
let dots: Vec<char> = vec!['·';length]
.iter()
.enumerate()
.map(|(i,x)|if i % ppq == 0 { '|' } else { *x })
.collect();
Self {
ppq,
length,
character: core::array::from_fn(|_|dots.clone()),
fg: core::array::from_fn(|_|vec![Color::Reset;length]),
bg: core::array::from_fn(|_|vec![Color::Reset;length]),
notes: [false;128],
time_start: 0,
time_zoom: 12,
}
}
pub fn update (&mut self, phrase: Option<&Phrase>) {
if phrase.is_none() {
return
}
let phrase = phrase.unwrap();
if phrase.length != self.length {
*self = Self::new(self.ppq, phrase.length);
}
self.notes = [false;128];
for (x, messages) in phrase.notes.iter().enumerate() {
for message in messages.iter() {
match message {
MidiMessage::NoteOn { key, .. } => {
self.notes[key.as_int() as usize] = true;
},
MidiMessage::NoteOff { key, .. } => {
self.notes[key.as_int() as usize] = false;
},
_ => {}
}
}
for y in 0..64 {
let note1 = self.notes[y * 2];
let note2 = self.notes[y * 2 + 1];
if let Some(block) = half_block(note1, note2) {
self.character[63 - y][x] = block;
} else if x % self.ppq == 0 {
self.character[63 - y][x] = '|';
}
}
}
}
}
pub struct SequencerView<'a> {
focused: bool,
/// Displayed phrase
@ -37,8 +114,8 @@ impl<'a> SequencerView<'a> {
ppq: app.timebase.ppq() as usize,
now: app.timebase.frame_to_pulse(app.playhead as f64) as usize,
time_cursor: app.time_cursor,
time_start: app.time_start,
time_zoom: app.time_zoom,
time_start: app.seq_buf.time_start,
time_zoom: app.seq_buf.time_zoom,
note_cursor: app.note_cursor,
note_start: app.note_start,
notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] },