mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
386 lines
14 KiB
Rust
386 lines
14 KiB
Rust
use crate::{core::*,model::*,view::*};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum SequencerMode { Tiny, Compact, Horizontal, Vertical, }
|
|
|
|
pub struct SequencerView<'a> {
|
|
pub focused: bool,
|
|
pub phrase: Option<&'a Phrase>,
|
|
pub ppq: usize,
|
|
/// Range of notes to display
|
|
pub note_start: usize,
|
|
/// Position of cursor within note range
|
|
pub note_cursor: usize,
|
|
/// PPQ per display unit
|
|
pub time_zoom: usize,
|
|
/// Range of time steps to display
|
|
pub time_start: usize,
|
|
/// Position of cursor within time range
|
|
pub time_cursor: usize,
|
|
|
|
/// Current time
|
|
pub now: usize
|
|
}
|
|
|
|
impl<'a> Render for SequencerView<'a> {
|
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
let Rect { x, y, width, height } = area;
|
|
let style = Some(Style::default().green().dim());
|
|
if self.focused {
|
|
lozenge_left(buf, x, y, height, style);
|
|
lozenge_right(buf, x + width - 1, y, height, style);
|
|
}
|
|
self.draw_horizontal(buf, area)?;
|
|
Ok(area)
|
|
}
|
|
}
|
|
|
|
impl<'a> SequencerView<'a> {
|
|
fn draw_horizontal (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
|
|
let style = Some(if self.focused {
|
|
Style::default().green().not_dim()
|
|
} else {
|
|
Style::default().green().dim()
|
|
});
|
|
let notes = &[];
|
|
pulse_to_note_length(self.time_zoom)
|
|
.blit(buf, area.x, area.y, Some(Style::default().dim()));
|
|
self::horizontal::keys(buf, area, self.note_start, notes)?;
|
|
if let Some(phrase) = self.phrase {
|
|
self::horizontal::timer(buf, area, self.time_start, self.time_zoom, self.now % phrase.length);
|
|
self::horizontal::lanes(buf, area, phrase, self.ppq, self.time_zoom, self.time_start, self.note_start);
|
|
}
|
|
let style = style.unwrap_or_else(||{Style::default().green().not_dim()});
|
|
self::horizontal::cursor(buf, area, style, self.time_cursor, self.note_cursor);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn pulse_to_note_length (time_z: usize) -> &'static str {
|
|
match time_z {
|
|
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",
|
|
384 => "1/1",
|
|
_ => ""
|
|
}
|
|
}
|
|
|
|
mod horizontal {
|
|
use crate::core::*;
|
|
use super::*;
|
|
|
|
pub fn timer (buf: &mut Buffer, area: Rect, time0: usize, time_z: usize, now: usize) {
|
|
let Rect { x, width, .. } = area;
|
|
let offset = 5;
|
|
for x in x+offset..x+width-offset {
|
|
let step = (time0 + (x-offset) as usize) * time_z;
|
|
let next_step = (time0 + (x-offset) as usize + 1) * time_z;
|
|
let style = if step <= now && now < next_step {
|
|
Style::default().yellow().bold().not_dim()
|
|
} else {
|
|
Style::default()
|
|
};
|
|
"-".blit(buf, x, area.y, Some(style))
|
|
}
|
|
}
|
|
|
|
pub fn keys (buf: &mut Buffer, area: Rect, note0: usize, _notes: &[bool])
|
|
-> Usually<Rect>
|
|
{
|
|
let bw = Style::default().dim();
|
|
let Rect { x, y, width, height } = area;
|
|
let h = height.saturating_sub(2);
|
|
for index in 0..h {
|
|
let y = y + h - index;
|
|
let key = KEYS_VERTICAL[(index % 6) as usize];
|
|
key.blit(buf, x + 1, y, Some(bw));
|
|
"█".blit(buf, x + 2, y, Some(bw));
|
|
"░".repeat(width.saturating_sub(6) as usize).blit(buf, x + 5, y, Some(bw.black()));
|
|
let note_a = note0 + (index * 2) as usize;
|
|
if note_a % 12 == 0 {
|
|
let octave = format!("C{}", (note_a / 12) as i8 - 2);
|
|
octave.blit(buf, x + 3, y, None);
|
|
continue
|
|
}
|
|
let note_b = note0 + (index * 2) as usize;
|
|
if note_b % 12 == 0 {
|
|
let octave = format!("C{}", (note_b / 12) as i8 - 2);
|
|
octave.blit(buf, x + 3, y, None);
|
|
continue
|
|
}
|
|
}
|
|
Ok(area)
|
|
}
|
|
|
|
pub fn lanes (
|
|
buf: &mut Buffer,
|
|
area: Rect,
|
|
phrase: &Phrase,
|
|
ppq: usize,
|
|
time_z: usize,
|
|
time0: usize,
|
|
note0: usize,
|
|
) {
|
|
let Rect { x, y, width, height } = area;
|
|
//let time0 = time0 / time_z;
|
|
//let time1 = time0 + width as usize;
|
|
//let note1 = note0 + height as usize;
|
|
let bg = Style::default();
|
|
let (bw, wh) = (bg.dim(), bg.white().not_dim());
|
|
let offset = 5;
|
|
for x in x+offset..x+width-offset {
|
|
let step = (time0 + (x-offset) as usize) * time_z;
|
|
let next_step = (time0 + (x-offset) as usize + 1) * time_z;
|
|
if step % ppq == 0 {
|
|
"|".blit(buf, x as u16, y, Some(Style::default().dim()));
|
|
}
|
|
let bar = 4 * ppq;
|
|
if step % bar == 0 {
|
|
format!("{}", (step/bar)+1)
|
|
.blit(buf, x as u16, y, Some(Style::default().bold().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;
|
|
let (character, mut style) = match (
|
|
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),
|
|
) {
|
|
(true, true) => ("█", wh),
|
|
(false, true) => ("▀", wh),
|
|
(true, false) => ("▄", wh),
|
|
(false, false) => (if step % ppq == 0 { "|" } else { "·" }, bw),
|
|
};
|
|
let y = y + height.saturating_sub(index+2) as u16;
|
|
if step > phrase.length {
|
|
style = Style::default().gray()
|
|
}
|
|
character.blit(buf, x, y, Some(style));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn cursor (
|
|
buf: &mut Buffer,
|
|
area: Rect,
|
|
style: Style,
|
|
time: usize,
|
|
note: usize
|
|
) {
|
|
let x = area.x + 5 + time as u16;
|
|
let y = area.y + 1 + note as u16 / 2;
|
|
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
|
c.blit(buf, x, y, Some(style));
|
|
}
|
|
}
|
|
|
|
//pub fn footer (
|
|
//buf: &mut Buffer,
|
|
//area: Rect,
|
|
//note0: usize,
|
|
//note: usize,
|
|
//time0: usize,
|
|
//time: usize,
|
|
//time_z: usize,
|
|
//) {
|
|
//let Rect { mut x, y, width, height } = area;
|
|
//buf.set_string(x, y + height, format!("├{}┤", "-".repeat((width - 2).into())),
|
|
//Style::default().dim());
|
|
//buf.set_string(x, y + height + 2, format!("├{}┤", "-".repeat((width - 2).into())),
|
|
//Style::default().dim());
|
|
//x = x + 2;
|
|
//{
|
|
//for (_, [letter, title, value]) in [
|
|
//["S", &format!("ync"), &format!("<4/4>")],
|
|
//["Q", &format!("uant"), &format!("<1/{}>", 4 * time_z)],
|
|
//["N", &format!("ote"), &format!("{} ({}-{})", note0 + note, note0, "X")],
|
|
//["T", &format!("ime"), &format!("{} ({}-{})", time0 + time, time0 + 1, "X")],
|
|
//].iter().enumerate() {
|
|
//buf.set_string(x, y + height + 1, letter, Style::default().bold().yellow().dim());
|
|
//x = x + 1;
|
|
//buf.set_string(x, y + height + 1, &title, Style::default().bold().dim());
|
|
//x = x + title.len() as u16 + 1;
|
|
//buf.set_string(x, y + height + 1, &value, Style::default().not_dim());
|
|
//x = x + value.len() as u16;
|
|
//buf.set_string(x, y + height + 1, " ", Style::default().dim());
|
|
//x = x + 2;
|
|
//}
|
|
//}
|
|
//}
|
|
|
|
//mod vertical {
|
|
//use super::*;
|
|
|
|
//pub fn draw (
|
|
//s: &Sequencer,
|
|
//buf: &mut Buffer,
|
|
//mut area: Rect,
|
|
//) -> Usually<Rect> {
|
|
//area.x = area.x + 13;
|
|
//keys(s, buf, area, 0);
|
|
//steps(s, buf, area, 0);
|
|
//playhead(s, buf, area.x, area.y);
|
|
//Ok(area)
|
|
//}
|
|
|
|
//pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
|
//if s.sequence.is_none() {
|
|
//return
|
|
//}
|
|
//let ppq = s.timebase.ppq() as usize;
|
|
//let bg = Style::default();
|
|
//let bw = bg.dim();
|
|
//let wh = bg.white();
|
|
//let Rect { x, y, .. } = area;
|
|
//for step in s.time_start..s.time_start+area.height as usize {
|
|
//let y = y - (s.time_start + step / 2) as u16;
|
|
//let step = step as usize;
|
|
////buf.set_string(x + 5, y, &" ".repeat(32.max(note1-s.note_start)as usize), bg);
|
|
//if step % s.time_zoom == 0 {
|
|
//buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
|
|
//}
|
|
//for k in s.note_start..s.note_start+area.width as usize {
|
|
//let key = ::midly::num::u7::from_int_lossy(k as u8);
|
|
//if step % 2 == 0 {
|
|
//let (a, b, c) = (
|
|
//(step + 0) as usize * ppq / s.time_zoom as usize,
|
|
//(step + 1) as usize * ppq / s.time_zoom as usize,
|
|
//(step + 2) as usize * ppq / s.time_zoom as usize,
|
|
//);
|
|
//let phrase = &s.phrases[s.sequence.unwrap()];
|
|
//let (character, style) = match (
|
|
//phrase.contains_note_on(key, a, b),
|
|
//phrase.contains_note_on(key, b, c),
|
|
//) {
|
|
//(true, true) => ("█", wh),
|
|
//(true, false) => ("▀", wh),
|
|
//(false, true) => ("▄", wh),
|
|
//(false, false) => ("·", bw),
|
|
//};
|
|
//character.blit(buf, x + (5 + k - s.note_start) as u16, y, Some(style));
|
|
//}
|
|
//}
|
|
//if beat == step as usize {
|
|
//buf.set_string(x + 4, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow());
|
|
//for key in s.note_start..s.note_start+area.width as usize {
|
|
//let _color = if s.notes_on[key as usize] {
|
|
//Style::default().red()
|
|
//} else {
|
|
//KEY_STYLE[key as usize % 12]
|
|
//};
|
|
//}
|
|
//}
|
|
//}
|
|
//}
|
|
|
|
//pub fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
|
//let x = x + 5 + s.note_cursor as u16;
|
|
//let y = y + s.time_cursor as u16 / 2;
|
|
//let c = if s.time_cursor % 2 == 0 { "▀" } else { "▄" };
|
|
//buf.set_string(x, y, c, Style::default());
|
|
//}
|
|
|
|
//pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
|
//let ppq = s.timebase.ppq() as usize;
|
|
//let Rect { x, y, .. } = area;
|
|
//for key in s.note_start..s.note_start+area.width as usize {
|
|
//let x = x + (5 + key - s.note_start) as u16;
|
|
//if key % 12 == 0 {
|
|
//let octave = format!("C{}", (key / 12) as i8 - 4);
|
|
//buf.set_string(x, y, &octave, Style::default());
|
|
//}
|
|
//let mut color = KEY_STYLE[key as usize % 12];
|
|
//let mut is_on = s.notes_on[key as usize];
|
|
//let step = beat;
|
|
//let (a, b, c) = (
|
|
//(step + 0) as usize * ppq / s.time_zoom as usize,
|
|
//(step + 1) as usize * ppq / s.time_zoom as usize,
|
|
//(step + 2) as usize * ppq / s.time_zoom as usize,
|
|
//);
|
|
//let key = ::midly::num::u7::from(key as u8);
|
|
//let phrase = &s.phrases[s.sequence.unwrap()];
|
|
//is_on = is_on || phrase.contains_note_on(key, a, b);
|
|
//is_on = is_on || phrase.contains_note_on(key, b, c);
|
|
//if is_on {
|
|
//color = Style::default().red();
|
|
//}
|
|
//buf.set_string(x, y - 1, &format!("▄"), color);
|
|
//}
|
|
//}
|
|
|
|
////pub fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
////let Rect { x, y, width, height } = area;
|
|
////let header = draw_header(s, buf, area)?;
|
|
////let piano = match s.view {
|
|
////SequencerMode::Tiny => Rect { x, y, width, height: 0 },
|
|
////SequencerMode::Compact => Rect { x, y, width, height: 0 },
|
|
////SequencerMode::Vertical => self::vertical::draw(s, buf, Rect {
|
|
////x, y: y + header.height, width, height,
|
|
////})?,
|
|
////SequencerMode::Horizontal => self::horizontal::draw(
|
|
////buf,
|
|
////Rect { x, y: y + header.height, width, height, },
|
|
////s.phrase(),
|
|
////s.timebase.ppq() as usize,
|
|
////s.time_cursor,
|
|
////s.time_start,
|
|
////s.time_zoom,
|
|
////s.note_cursor,
|
|
////s.note_start,
|
|
////None
|
|
////)?,
|
|
////};
|
|
////Ok(draw_box(buf, Rect {
|
|
////x, y,
|
|
////width: header.width.max(piano.width),
|
|
////height: header.height + piano.height
|
|
////}))
|
|
////}
|
|
|
|
////pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
////let Rect { x, y, width, .. } = area;
|
|
////let style = Style::default().gray();
|
|
////crate::view::TransportView {
|
|
////timebase: &s.timebase,
|
|
////playing: s.playing,
|
|
////record: s.recording,
|
|
////overdub: s.overdub,
|
|
////monitor: s.monitoring,
|
|
////frame: 0
|
|
////}.render(buf, area)?;
|
|
////let separator = format!("├{}┤", "-".repeat((width - 2).into()));
|
|
////separator.blit(buf, x, y + 2, Some(style.dim()));
|
|
////let _ = draw_clips(s, buf, area)?;
|
|
////Ok(Rect { x, y, width, height: 3 })
|
|
////}
|
|
|
|
////pub fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
////let Rect { x, y, .. } = area;
|
|
////let style = Style::default().gray();
|
|
////for (i, sequence) in s.phrases.iter().enumerate() {
|
|
////let label = format!("▶ {}", &sequence.name);
|
|
////label.blit(buf, x + 2, y + 3 + (i as u16)*2, Some(if Some(i) == s.sequence {
|
|
////match s.playing {
|
|
////TransportState::Rolling => style.white().bold(),
|
|
////_ => style.not_dim().bold()
|
|
////}
|
|
////} else {
|
|
////style.dim()
|
|
////}));
|
|
////}
|
|
////Ok(Rect { x, y, width: 14, height: 14 })
|
|
////}
|
|
//}
|