extract Sequencer model

This commit is contained in:
🪞👃🪞 2024-07-13 17:11:28 +03:00
parent f347ca838b
commit aa478099d9
14 changed files with 211 additions and 348 deletions

View file

@ -78,15 +78,15 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
Ok(true)
}],
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
app.seq_buf.time_zoom = prev_note_length(app.seq_buf.time_zoom);
app.sequencer.time_zoom = prev_note_length(app.sequencer.time_zoom);
Ok(true)
}],
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
app.seq_buf.time_zoom = next_note_length(app.seq_buf.time_zoom);
app.sequencer.time_zoom = next_note_length(app.sequencer.time_zoom);
Ok(true)
}],
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
app.arranger.phrase_mut().map(|phrase|{
app.arranger.phrase().map(|x|x.write().unwrap()).map(|mut phrase|{
let mut notes = phrase.notes.clone();
notes.extend_from_slice(&mut phrase.notes);
phrase.notes = notes;

View file

@ -6,36 +6,46 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
app.arranger.mode = !app.arranger.mode;
Ok(true)
}],
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| Ok(
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| {
match app.arranger.mode {
false => {app.arranger.scene_prev();true},
true => {app.arranger.track_prev();true},
}
)],
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok(
false => app.arranger.scene_prev(),
true => app.arranger.track_prev(),
};
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| {
match app.arranger.mode {
false => {app.arranger.scene_next();true},
true => {app.arranger.track_next();true},
}
)],
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok(
false => app.arranger.scene_next(),
true => app.arranger.track_next(),
};
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| {
match app.arranger.mode {
false => {app.arranger.track_prev();true},
true => {app.arranger.scene_prev();true},
}
)],
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok(
false => app.arranger.track_prev(),
true => app.arranger.scene_prev(),
};
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| {
match app.arranger.mode {
false => {app.arranger.track_next();true},
true => {app.arranger.scene_next();true}
}
)],
false => app.arranger.track_next(),
true => app.arranger.scene_next()
};
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| {
app.arranger.phrase_next();
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| {
app.arranger.phrase_prev();
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| {

View file

@ -23,7 +23,7 @@ pub const KEYMAP_CHAIN: &'static [KeyBinding<App>] = keymap!(App {
Ok(false)
}],
[Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| {
app.chain_mode = !app.seq_mode;
app.chain_mode = !app.chain_mode;
Ok(true)
}],
});

View file

@ -11,13 +11,15 @@ pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
app.entered = false;
app.transport.entered = app.entered;
app.arranger.entered = app.entered;
app.arranger.entered = app.entered;
app.sequencer.entered = app.entered;
Ok(true)
}],
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
app.entered = true;
app.transport.entered = app.entered;
app.arranger.entered = app.entered;
app.arranger.entered = app.entered;
app.sequencer.entered = app.entered;
Ok(true)
}],
});
@ -26,8 +28,10 @@ pub fn focus_next (app: &mut App) -> Usually<bool> {
app.section.next();
app.transport.focused = app.section == AppFocus::Transport;
app.transport.entered = app.entered;
app.arranger.focused = app.section == AppFocus::Arranger;
app.arranger.entered = app.entered;
app.arranger.focused = app.section == AppFocus::Arranger;
app.arranger.entered = app.entered;
app.sequencer.focused = app.section == AppFocus::Sequencer;
app.sequencer.entered = app.entered;
Ok(true)
}
@ -35,7 +39,9 @@ pub fn focus_prev (app: &mut App) -> Usually<bool> {
app.section.prev();
app.transport.focused = app.section == AppFocus::Transport;
app.transport.entered = app.entered;
app.arranger.focused = app.section == AppFocus::Arranger;
app.arranger.entered = app.entered;
app.arranger.focused = app.section == AppFocus::Arranger;
app.arranger.entered = app.entered;
app.sequencer.focused = app.section == AppFocus::Sequencer;
app.sequencer.entered = app.entered;
Ok(true)
}

View file

@ -3,31 +3,31 @@ use crate::{core::*, model::App};
/// Key bindings for phrase editor.
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
[Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| {
app.note_cursor = app.note_cursor.saturating_sub(1);
app.sequencer.note_cursor = app.sequencer.note_cursor.saturating_sub(1);
Ok(true)
}],
[Down, NONE, "seq_cursor_down", "move cursor up", |app: &mut App| {
app.note_cursor = app.note_cursor + 1;
app.sequencer.note_cursor = app.sequencer.note_cursor + 1;
Ok(true)
}],
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
if app.entered {
app.time_cursor = app.time_cursor.saturating_sub(1);
if app.sequencer.entered {
app.sequencer.time_cursor = app.sequencer.time_cursor.saturating_sub(1);
} else {
app.seq_buf.time_start = app.seq_buf.time_start.saturating_sub(1);
app.sequencer.time_start = app.sequencer.time_start.saturating_sub(1);
}
Ok(true)
}],
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
if app.entered {
app.time_cursor = app.time_cursor + 1;
if app.sequencer.entered {
app.sequencer.time_cursor = app.sequencer.time_cursor + 1;
} else {
app.seq_buf.time_start = app.seq_buf.time_start + 1;
app.sequencer.time_start = app.sequencer.time_start + 1;
}
Ok(true)
}],
[Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| {
app.seq_mode = !app.seq_mode;
app.sequencer.mode = !app.sequencer.mode;
Ok(true)
}],
// [Char('a'), NONE, "note_add", "Add note", note_add],

View file

@ -172,7 +172,7 @@ impl Track {
_ => {}
});
let track = app.arranger.track_add(name)?;
for phrase in phrases { track.phrases.push(phrase); }
for phrase in phrases { track.phrases.push(Arc::new(RwLock::new(phrase))); }
for device in devices { track.add_device(device)?; }
Ok(track)
}

View file

@ -1,8 +1,8 @@
//! Application state.
submod! { arranger looper mixer phrase plugin sampler scene track transport }
submod! { arranger looper mixer phrase plugin sampler sequencer scene track transport }
use crate::{core::*, view::*};
use crate::core::*;
/// Root of application state.
pub struct App {
@ -14,8 +14,10 @@ pub struct App {
pub section: AppFocus,
/// Transport model and view.
pub transport: TransportToolbar,
/// Arraneger model and view.
/// Arranger model and view.
pub arranger: Arranger,
/// Phrase editor
pub sequencer: Sequencer,
/// Main JACK client.
pub jack: Option<JackClient>,
/// Map of external MIDI outs in the jack graph
@ -31,17 +33,6 @@ pub struct App {
pub audio_outs: Vec<Arc<Port<Unowned>>>,
/// Number of frames requested by process callback
chunk_size: usize,
/// Display mode of sequencer seciton
pub seq_mode: bool,
/// Display buffer for sequencer
pub seq_buf: BufferedSequencerView,
/// Display position of cursor within note range
pub note_cursor: usize,
/// Range of notes to display
pub note_start: usize,
/// Display position of cursor within time range
pub time_cursor: usize,
}
impl App {
@ -58,6 +49,7 @@ impl App {
section: AppFocus::default(),
transport: TransportToolbar::new(Some(jack.transport())),
arranger: Arranger::new(),
sequencer: Sequencer::new(),
jack: Some(jack),
audio_outs: vec![],
chain_mode: false,
@ -65,12 +57,6 @@ impl App {
midi_in: None,
midi_ins: vec![],
xdg: Some(xdg),
seq_mode: false,
seq_buf: BufferedSequencerView::new(96, 16384),
note_cursor: 0,
note_start: 2,
time_cursor: 0,
})
}
}

View file

@ -58,10 +58,10 @@ impl Arranger {
self.selected.track_prev()
}
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
self.tracks.push(match name {
Some(name) => Track::new(name, None, None)?,
None => Track::new(&self.track_default_name(), None, None)?
});
self.tracks.push(name.map_or_else(
|| Track::new(&self.track_default_name()),
|name| Track::new(name),
)?);
let index = self.tracks.len() - 1;
Ok(&mut self.tracks[index])
}
@ -106,15 +106,15 @@ impl Arranger {
/// Phrase management methods
impl Arranger {
pub fn phrase (&self) -> Option<&Phrase> {
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
let track_id = self.selected.track()?;
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
}
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
let track_id = self.selected.track()?;
let clip = *self.scene()?.clips.get(track_id)?;
self.tracks.get_mut(track_id)?.phrases.get_mut(clip?)
}
//pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
//let track_id = self.selected.track()?;
//let clip = *self.scene()?.clips.get(track_id)?;
//self.tracks.get_mut(track_id)?.phrases.get_mut(clip?)
//}
pub fn phrase_next (&mut self) {
unimplemented!();
//if let Some((track_index, track)) = self.track_mut() {

46
src/model/sequencer.rs Normal file
View file

@ -0,0 +1,46 @@
use crate::{core::*, model::Phrase};
pub struct Sequencer {
pub phrase: Option<Arc<RwLock<Phrase>>>,
pub mode: bool,
pub buffer: Buffer,
pub now: usize,
pub ppq: usize,
pub note_cursor: usize,
pub note_start: usize,
pub time_cursor: usize,
pub time_start: usize,
pub time_zoom: usize,
pub focused: bool,
pub entered: bool,
/// Highlight input keys
pub notes_in: [bool; 128],
/// Highlight output keys
pub notes_out: [bool; 128],
}
impl Sequencer {
pub fn new () -> Self {
Self {
buffer: Buffer::empty(Rect::default()),
entered: false,
focused: false,
mode: false,
note_cursor: 0,
note_start: 0,
notes_in: [false;128],
notes_out: [false;128],
phrase: None,
time_cursor: 0,
time_start: 0,
time_zoom: 12,
now: 0,
ppq: 96
}
}
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
self.phrase = phrase.map(Clone::clone);
}
}

View file

@ -10,7 +10,7 @@ pub struct Track {
/// Overdub input to sequence.
pub overdub: bool,
/// Map: tick -> MIDI events at tick
pub phrases: Vec<Phrase>,
pub phrases: Vec<Arc<RwLock<Phrase>>>,
/// Phrase selector
pub sequence: Option<usize>,
/// Output from current sequence.
@ -30,11 +30,7 @@ pub struct Track {
}
impl Track {
pub fn new (
name: &str,
phrases: Option<Vec<Phrase>>,
devices: Option<Vec<JackDevice>>,
) -> Usually<Self> {
pub fn new (name: &str) -> Usually<Self> {
Ok(Self {
name: name.to_string(),
midi_out: None,
@ -45,8 +41,8 @@ impl Track {
recording: false,
overdub: true,
sequence: None,
phrases: phrases.unwrap_or_else(||Vec::with_capacity(16)),
devices: devices.unwrap_or_else(||Vec::with_capacity(16)),
phrases: vec![],
devices: vec![],
device: 0,
reset: true,
})
@ -118,14 +114,18 @@ impl Track {
) = (
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id))
) {
if self.midi_out.is_some() {
phrase.process_out(
&mut self.midi_out_buf,
&mut self.notes_out,
timebase,
(frame0.saturating_sub(start_frame), frames, period)
);
}
phrase.read().map(|phrase|{
if self.midi_out.is_some() {
phrase.process_out(
&mut self.midi_out_buf,
&mut self.notes_out,
timebase,
(frame0.saturating_sub(start_frame), frames, period)
);
}
});
let mut phrase = phrase.write().unwrap();
let length = phrase.length;
// Monitor and record input
if input.is_some() && (self.recording || self.monitoring) {
// For highlighting keys and note repeat
@ -143,7 +143,7 @@ impl Track {
let quantized = (
pulse / quant as f64
).round() as usize * quant;
let looped = quantized % phrase.length;
let looped = quantized % length;
looped
}, message);
}

View file

@ -12,7 +12,7 @@ render!(App |self, buf, area| {
&self.arranger,
&If(self.arranger.selected.is_clip(), &Split::right([
&ChainView::vertical(&self),
&SequencerView::new(&self),
&self.sequencer,
]))
]).render(buf, area)?;
if let Some(ref modal) = self.modal {

View file

@ -65,7 +65,7 @@ impl Arranger {
let label = match scene.clips.get(track_index) {
Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) {
let icon = if track.sequence == Some(*clip) { "" } else { "" };
format!("{icon} {}", phrase.name)
format!("{icon} {}", phrase.read().unwrap().name)
} else {
format!(" ??? ")
},
@ -233,7 +233,7 @@ impl Arranger {
&|buf: &mut Buffer, area: Rect|{
let mut x2 = 0;
let Rect { x, y, height, .. } = area;
for (i, scene) in self.scenes.iter().enumerate() {
for (_scene_index, scene) in self.scenes.iter().enumerate() {
let active_scene = false;//self.selected == ArrangerFocus::Scene(i) || cursor.1 > 0 && self.cursor.1 - 1 == i;
let sep = Some(if active_scene {
Style::default().yellow().not_dim()
@ -252,11 +252,10 @@ impl Arranger {
let active_track = false;//self.cursor.0 > 0 && self.cursor.0 - 1 == i;
if let Some(clip) = clip {
let y2 = y + 1 + i as u16 * 2;
let label = format!("{}", if let Some(phrase) = self.tracks[i].phrases.get(*clip) {
&phrase.name
} else {
"...."
});
let label = match self.tracks[i].phrases.get(*clip) {
Some(phrase) => &format!("{}", phrase.read().unwrap().name),
None => "...."
};
label.blit(buf, x + x2, y2, Some(if active_track && active_scene {
Style::default().not_dim().yellow().bold()
} else {

View file

@ -1,66 +0,0 @@
pub struct LineBuffer {
width: usize,
cells: Vec<Cell>,
style: Option<Style>,
bg: Cell,
}
impl LineBuffer {
pub fn new (bg: Cell, width: usize, height: usize) -> Self {
Self {
style: None,
width: width,
cells: vec![bg.clone();width*height],
bg,
}
}
pub fn height (&self) -> usize {
self.cells.len() / self.width
}
pub fn style (&mut self, style: Style) -> &mut Self {
self.style = Some(style);
self
}
pub fn no_style (&mut self) -> &mut Self {
self.style = None;
self
}
pub fn put (&mut self, data: &str, x: usize, y: usize) -> &mut Self {
if x < self.width {
for (i, c) in data.chars().enumerate() {
if x + i >= self.width {
break;
}
let index = y * self.width + x + i;
while index >= self.cells.len() {
self.cells.extend_from_slice(&vec![self.bg.clone();self.width]);
}
self.cells[index].set_char(c);
if let Some(s) = self.style {
self.cells[index].set_style(s);
}
}
}
self
}
pub fn show (&self, buf: &mut Buffer, area: Rect, offset: isize) -> Usually<Rect> {
let Rect { x, mut y, width, height } = area;
for row in offset..self.height() as isize {
let length = self.cells.len();
let start = ((row.max(0) as usize)*self.width).min(length);
let end = (((row + 1).max(0) as usize)*self.width).min(length);
for (column, cell) in self.cells[start..end].iter().enumerate() {
if column >= width as usize {
break
}
*buf.get_mut(x + column as u16, y + row as u16) = cell.clone();
}
y = y + 1;
if y > height {
break
}
}
Ok(area)
}
}

View file

@ -1,140 +1,15 @@
use crate::{core::*,model::*,view::*};
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]);
}
render!(Sequencer |self, buf, area| {
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
self.horizontal_draw(buf, area)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
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,
entered: bool,
/// Displayed phrase
phrase: Option<&'a Phrase>,
/// Resolution of MIDI sequence
ppq: usize,
/// Range of notes to display
note_start: usize,
/// Position of cursor within note range
note_cursor: usize,
/// PPQ per display unit
time_zoom: usize,
/// Range of time steps to display
time_start: usize,
/// Position of cursor within time range
time_cursor: usize,
/// Current time
now: usize,
/// Highlight input keys
notes_in: &'a [bool; 128],
/// Highlight output keys
notes_out: &'a [bool; 128],
}
impl<'a> SequencerView<'a> {
pub fn new (app: &'a App) -> Self {
let track = app.arranger.track();
Self {
phrase: app.arranger.phrase(),
focused: app.section == AppFocus::Sequencer,
entered: app.entered,
ppq: app.transport.ppq(),
now: app.transport.pulse(),
time_cursor: app.time_cursor,
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] },
notes_out: if let Some(track) = track { &track.notes_out } else { &[false;128] },
}
}
}
impl<'a> Render for SequencerView<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
self.horizontal_draw(buf, area)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
Ok(area)
}
}
impl<'a> SequencerView<'a> {
impl Sequencer {
fn style_focus (&self) -> Option<Style> {
Some(if self.focused {
Style::default().green().not_dim()
@ -162,15 +37,16 @@ impl<'a> SequencerView<'a> {
}
}
impl<'a> SequencerView<'a> {
/// Horizontal sequencer
impl Sequencer {
const H_KEYS_OFFSET: u16 = 5;
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
self.horizontal_keys(buf, area)?;
self.horizontal_quant(buf, area)?;
self.horizontal_timer(buf, area, self.phrase)?;
self.horizontal_lanes(buf, area, self.phrase)?;
self.horizontal_timer(buf, area, &self.phrase)?;
self.horizontal_lanes(buf, area, &self.phrase)?;
self.horizontal_cursor(buf, area)?;
Ok(())
}
@ -190,8 +66,11 @@ impl<'a> SequencerView<'a> {
c.blit(buf, x, y, self.style_focus())
}
fn horizontal_timer (&self, buf: &mut Buffer, area: Rect, phrase: Option<&Phrase>) -> Usually<Rect> {
fn horizontal_timer (
&self, buf: &mut Buffer, area: Rect, phrase: &Option<Arc<RwLock<Phrase>>>
) -> Usually<Rect> {
if let Some(phrase) = phrase {
let phrase = phrase.read().unwrap();
let (time0, time_z, now) = (self.time_start, self.time_zoom, self.now % phrase.length);
let Rect { x, width, .. } = area;
for x in x+Self::H_KEYS_OFFSET..x+width {
@ -252,68 +131,71 @@ impl<'a> SequencerView<'a> {
Ok(area)
}
fn horizontal_lanes (&self, buf: &mut Buffer, area: Rect, phrase: Option<&Phrase>) -> Usually<Rect> {
if phrase.is_none() {
return Ok(Rect { x: area.x, y: area.x, width: 0, height: 0 })
}
let Rect { x, y, width, height } = area;
let phrase = phrase.unwrap();
let now = self.now % phrase.length;
let ppq = self.ppq;
let time_z = self.time_zoom;
let time0 = self.time_start;
let note0 = self.note_start;
let dim = Style::default().dim();
let offset = Self::H_KEYS_OFFSET;
let phrase_area = Rect { x: x + offset, y, width: width - offset, height: height - 2 };
let mut steps = Vec::with_capacity(phrase_area.width as usize);
for x in phrase_area.x .. phrase_area.x + phrase_area.width {
let x0 = x.saturating_sub(phrase_area.x) as usize;
let step = (0 + time0 + x0) * time_z;
let next = (1 + time0 + x0) * time_z;
if step >= phrase.length {
break
}
let style = Self::style_timer_step(now, step, next);
let cell = buf.get_mut(x, area.y);
cell.set_char('-');
cell.set_style(style);
steps.push((x, step, next));
for y in phrase_area.y .. phrase_area.y + phrase_area.height {
if y == phrase_area.y {
if step % (4 * ppq) == 0 {
format!("{}", 1 + step / (4 * ppq)).blit(buf, x, y, None)?;
} else if step % ppq == 0 {
fn horizontal_lanes (
&self, buf: &mut Buffer, area: Rect, phrase: &Option<Arc<RwLock<Phrase>>>
) -> Usually<Rect> {
if let Some(phrase) = phrase {
let Rect { x, y, width, height } = area;
let phrase = phrase.read().unwrap();
let now = self.now % phrase.length;
let ppq = self.ppq;
let time_z = self.time_zoom;
let time0 = self.time_start;
let note0 = self.note_start;
let dim = Style::default().dim();
let offset = Self::H_KEYS_OFFSET;
let phrase_area = Rect { x: x + offset, y, width: width - offset, height: height - 2 };
let mut steps = Vec::with_capacity(phrase_area.width as usize);
for x in phrase_area.x .. phrase_area.x + phrase_area.width {
let x0 = x.saturating_sub(phrase_area.x) as usize;
let step = (0 + time0 + x0) * time_z;
let next = (1 + time0 + x0) * time_z;
if step >= phrase.length {
break
}
let style = Self::style_timer_step(now, step, next);
let cell = buf.get_mut(x, area.y);
cell.set_char('-');
cell.set_style(style);
steps.push((x, step, next));
for y in phrase_area.y .. phrase_area.y + phrase_area.height {
if y == phrase_area.y {
if step % (4 * ppq) == 0 {
format!("{}", 1 + step / (4 * ppq)).blit(buf, x, y, None)?;
} else if step % ppq == 0 {
let cell = buf.get_mut(x, y);
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
cell.set_style(dim);
}
} else {
let cell = buf.get_mut(x, y);
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
cell.set_style(dim);
}
} else {
let cell = buf.get_mut(x, y);
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
cell.set_style(dim);
}
}
}
let wh = Style::default().white().not_dim();
for index in 0..height-2 {
let note_a = note0 + index as usize * 2;
let note_b = note0 + index as usize * 2 + 1;
for (x, step, next_step) in steps.iter() {
let (a, b) = (
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), *step, *next_step),
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), *step, *next_step),
);
if let Some(block) = half_block(a, b) {
let y = y + height.saturating_sub(index+2) as u16;
let cell = buf.get_mut(*x, y);
cell.set_char(block);
cell.set_style(wh);
let wh = Style::default().white().not_dim();
for index in 0..height-2 {
let note_a = note0 + index as usize * 2;
let note_b = note0 + index as usize * 2 + 1;
for (x, step, next_step) in steps.iter() {
let (a, b) = (
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), *step, *next_step),
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), *step, *next_step),
);
if let Some(block) = half_block(a, b) {
let y = y + height.saturating_sub(index+2) as u16;
let cell = buf.get_mut(*x, y);
cell.set_char(block);
cell.set_style(wh);
}
}
}
}
Ok(area)
} else {
return Ok(Rect { x: area.x, y: area.x, width: 0, height: 0 })
}
}
}