mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
make transport focusable
This commit is contained in:
parent
a7a798b484
commit
449615eea8
14 changed files with 220 additions and 237 deletions
|
|
@ -24,6 +24,8 @@ handle!{
|
||||||
|
|
||||||
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
||||||
match state.section {
|
match state.section {
|
||||||
|
AppSection::Transport =>
|
||||||
|
Ok(false),
|
||||||
AppSection::Arranger =>
|
AppSection::Arranger =>
|
||||||
handle_keymap(state, e, crate::control::arranger::KEYMAP_ARRANGER),
|
handle_keymap(state, e, crate::control::arranger::KEYMAP_ARRANGER),
|
||||||
AppSection::Sequencer =>
|
AppSection::Sequencer =>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
//#![feature(fn_traits)]
|
//#![feature(fn_traits)]
|
||||||
//#![feature(unboxed_closures)]
|
//#![feature(unboxed_closures)]
|
||||||
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
||||||
|
#![allow(ambiguous_glob_reexports)]
|
||||||
|
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate jack as _jack;
|
extern crate jack as _jack;
|
||||||
|
|
|
||||||
|
|
@ -217,21 +217,23 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy)]
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
pub enum AppSection { Arranger, Sequencer, Chain, }
|
pub enum AppSection { Transport, Arranger, Sequencer, Chain, }
|
||||||
impl Default for AppSection { fn default () -> Self { Self::Arranger } }
|
impl Default for AppSection { fn default () -> Self { Self::Arranger } }
|
||||||
impl AppSection {
|
impl AppSection {
|
||||||
pub fn prev (&mut self) {
|
pub fn prev (&mut self) {
|
||||||
*self = match self {
|
*self = match self {
|
||||||
Self::Arranger => Self::Chain,
|
Self::Transport => Self::Chain,
|
||||||
|
Self::Arranger => Self::Transport,
|
||||||
Self::Sequencer => Self::Arranger,
|
Self::Sequencer => Self::Arranger,
|
||||||
Self::Chain => Self::Sequencer,
|
Self::Chain => Self::Sequencer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn next (&mut self) {
|
pub fn next (&mut self) {
|
||||||
*self = match self {
|
*self = match self {
|
||||||
|
Self::Transport => Self::Arranger,
|
||||||
Self::Arranger => Self::Sequencer,
|
Self::Arranger => Self::Sequencer,
|
||||||
Self::Sequencer => Self::Chain,
|
Self::Sequencer => Self::Chain,
|
||||||
Self::Chain => Self::Arranger,
|
Self::Chain => Self::Transport,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,36 @@ pub struct Sampler {
|
||||||
pub buffer: Vec<Vec<f32>>,
|
pub buffer: Vec<Vec<f32>>,
|
||||||
pub output_gain: f32
|
pub output_gain: f32
|
||||||
}
|
}
|
||||||
|
render!(Sampler |self, buf, area| {
|
||||||
|
let Rect { x, y, height, .. } = area;
|
||||||
|
let style = Style::default().gray();
|
||||||
|
let title = format!(" {} ({})", self.name, self.voices.len());
|
||||||
|
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()))?;
|
||||||
|
let mut width = title.len() + 2;
|
||||||
|
for (i, (note, sample)) in self.samples.iter().enumerate() {
|
||||||
|
let style = if i == self.cursor.0 {
|
||||||
|
Style::default().green()
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
};
|
||||||
|
let i = i as u16;
|
||||||
|
let y1 = y+1+i;
|
||||||
|
if y1 >= y + height {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i as usize == self.cursor.0 {
|
||||||
|
"⯈".blit(buf, x+1, y1, Some(style.bold()))?;
|
||||||
|
}
|
||||||
|
let label1 = format!("{note:3} {:12}", sample.name);
|
||||||
|
let label2 = format!("{:>6} {:>6} +0.0", 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))?;
|
||||||
|
width = width.max(label1.len() + label2.len() + 4);
|
||||||
|
}
|
||||||
|
let height = ((2 + self.samples.len()) as u16).min(height);
|
||||||
|
Ok(Rect { x, y, width: (width as u16).min(area.width), height })
|
||||||
|
});
|
||||||
|
|
||||||
render!(Sampler = crate::view::sampler::render);
|
|
||||||
handle!(Sampler = crate::control::sampler::handle);
|
handle!(Sampler = crate::control::sampler::handle);
|
||||||
//jack!(Sampler {
|
//jack!(Sampler {
|
||||||
//process = Sampler::process,
|
//process = Sampler::process,
|
||||||
|
|
@ -184,3 +212,48 @@ impl Iterator for Voice {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//fn render_table (
|
||||||
|
//self: &mut Sampler,
|
||||||
|
//stdout: &mut Stdout,
|
||||||
|
//offset: (u16, u16),
|
||||||
|
//) -> Result<(), Box<dyn Error>> {
|
||||||
|
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||||
|
//stdout.queue(move_to(0, 3))?.queue(
|
||||||
|
//Print(" Name Rate Trigger Route")
|
||||||
|
//)?;
|
||||||
|
//for (i, sample) in self.samples.lock().unwrap().iter().enumerate() {
|
||||||
|
//let row = 4 + i as u16;
|
||||||
|
//for (j, (column, field)) in [
|
||||||
|
//(0, format!(" {:7} ", sample.name)),
|
||||||
|
//(9, format!(" {:.1}Hz ", sample.rate)),
|
||||||
|
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
||||||
|
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
||||||
|
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
||||||
|
//].into_iter().enumerate() {
|
||||||
|
//stdout.queue(move_to(column, row))?;
|
||||||
|
//if self.selected_sample == i && self.selected_column == j {
|
||||||
|
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
||||||
|
//} else {
|
||||||
|
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
|
||||||
|
//fn render_meters (
|
||||||
|
//self: &mut Sampler,
|
||||||
|
//stdout: &mut Stdout,
|
||||||
|
//offset: (u16, u16),
|
||||||
|
//) -> Result<(), Box<dyn Error>> {
|
||||||
|
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||||
|
//for (i, sample) in self.samples.lock().iter().enumerate() {
|
||||||
|
//let row = 4 + i as u16;
|
||||||
|
//stdout.queue(move_to(32, row))?.queue(
|
||||||
|
//PrintStyledContent("▁".green())
|
||||||
|
//)?;
|
||||||
|
//}
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,6 @@ impl App {
|
||||||
Ok(&mut self.tracks[self.track_cursor - 1])
|
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)> {
|
pub fn track (&self) -> Option<(usize, &Track)> {
|
||||||
match self.track_cursor { 0 => None, _ => {
|
match self.track_cursor { 0 => None, _ => {
|
||||||
let id = self.track_cursor as usize - 1;
|
let id = self.track_cursor as usize - 1;
|
||||||
|
|
@ -61,6 +50,7 @@ pub struct Track {
|
||||||
pub sequence: Option<usize>,
|
pub sequence: Option<usize>,
|
||||||
/// Output from current sequence.
|
/// Output from current sequence.
|
||||||
pub midi_out: Option<Port<MidiOut>>,
|
pub midi_out: Option<Port<MidiOut>>,
|
||||||
|
/// MIDI output buffer
|
||||||
midi_out_buf: Vec<Vec<Vec<u8>>>,
|
midi_out_buf: Vec<Vec<Vec<u8>>>,
|
||||||
/// Device chain
|
/// Device chain
|
||||||
pub devices: Vec<JackDevice>,
|
pub devices: Vec<JackDevice>,
|
||||||
|
|
@ -74,6 +64,7 @@ pub struct Track {
|
||||||
/// Highlight keys on piano roll.
|
/// Highlight keys on piano roll.
|
||||||
pub notes_out: [bool;128],
|
pub notes_out: [bool;128],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Track {
|
impl Track {
|
||||||
fn new (
|
fn new (
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
pub mod chain;
|
pub mod chain;
|
||||||
pub mod arranger;
|
pub mod arranger;
|
||||||
pub mod sampler;
|
|
||||||
pub mod sequencer;
|
pub mod sequencer;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
pub mod plugin;
|
pub mod plugin;
|
||||||
pub mod border;
|
pub mod border;
|
||||||
|
pub mod theme;
|
||||||
|
|
||||||
pub use self::border::*;
|
pub use self::border::*;
|
||||||
|
pub use self::theme::*;
|
||||||
pub use self::transport::TransportView;
|
pub use self::transport::TransportView;
|
||||||
pub use self::arranger::*;
|
pub use self::arranger::*;
|
||||||
pub use self::chain::ChainView;
|
pub use self::chain::ChainView;
|
||||||
|
|
|
||||||
|
|
@ -24,37 +24,6 @@ impl<'a> ArrangerView<'a> {
|
||||||
vertical
|
vertical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn track_names (&self) -> Vec<&'a str> {
|
|
||||||
let mut track_names = vec!["Mix"];
|
|
||||||
for track in self.tracks.iter() {
|
|
||||||
track_names.push(track.name.as_str());
|
|
||||||
}
|
|
||||||
track_names
|
|
||||||
}
|
|
||||||
fn track_widths (&self) -> Vec<u16> {
|
|
||||||
self.track_names()
|
|
||||||
.iter()
|
|
||||||
.map(|name|(name.len() as u16).max(10) + 3)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
fn bg_color_lo (&self) -> Color {
|
|
||||||
if self.focused && self.entered {
|
|
||||||
Color::Rgb(25, 60, 15)
|
|
||||||
} else if self.focused {
|
|
||||||
Color::Rgb(20, 45, 5)
|
|
||||||
} else {
|
|
||||||
Color::Reset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn bg_color_hi (&self) -> Color {
|
|
||||||
if self.focused && self.entered {
|
|
||||||
Color::Rgb(30, 90, 25)
|
|
||||||
} else if self.focused {
|
|
||||||
Color::Rgb(25, 60, 15)
|
|
||||||
} else {
|
|
||||||
Color::Reset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render for ArrangerView<'a> {
|
impl<'a> Render for ArrangerView<'a> {
|
||||||
|
|
@ -64,7 +33,7 @@ impl<'a> Render for ArrangerView<'a> {
|
||||||
} else {
|
} else {
|
||||||
self.tracks.len() as u16 * 2
|
self.tracks.len() as u16 * 2
|
||||||
});
|
});
|
||||||
fill_bg(buf, area, self.bg_color_lo());
|
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||||
area = if self.vertical {
|
area = if self.vertical {
|
||||||
self.draw_vertical(buf, area)
|
self.draw_vertical(buf, area)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -73,7 +42,7 @@ impl<'a> Render for ArrangerView<'a> {
|
||||||
if self.focused {
|
if self.focused {
|
||||||
//HELP.blit(buf, area.x + 2, area.y + area.height - 1, Some(Style::default().dim()))?;
|
//HELP.blit(buf, area.x + 2, area.y + area.height - 1, Some(Style::default().dim()))?;
|
||||||
if self.entered {
|
if self.entered {
|
||||||
QuarterV(Style::default().green().dim()).draw(buf, area)?;
|
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
|
|
@ -82,45 +51,6 @@ impl<'a> Render for ArrangerView<'a> {
|
||||||
|
|
||||||
impl<'a> ArrangerView<'a> {
|
impl<'a> ArrangerView<'a> {
|
||||||
|
|
||||||
fn vertical_clips (&self, buf: &mut Buffer, x: u16, y: u16, height: u16, track: usize) -> Usually<u16> {
|
|
||||||
let mut index = 0;
|
|
||||||
let mut width = 10;
|
|
||||||
loop {
|
|
||||||
if index >= self.scenes.len() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if y + index as u16 >= height {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
width = 10.max(if let Some(scene) = self.scenes.get(index) {
|
|
||||||
let hi = (track + 1 == self.cursor.0) &&
|
|
||||||
(index + 1 == self.cursor.1);
|
|
||||||
let style = Some(highlight(self.focused, hi));
|
|
||||||
let clip = scene.clips.get(track);
|
|
||||||
let index = index as u16;
|
|
||||||
let label = if let Some(Some(clip)) = clip {
|
|
||||||
if let Some(phrase) = self.tracks[track].phrases.get(*clip) {
|
|
||||||
format!("{} {}", if self.tracks[track].sequence == Some(*clip) {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
" "
|
|
||||||
}, phrase.name)
|
|
||||||
} else {
|
|
||||||
format!(" ??? ")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
format!(" ·········")
|
|
||||||
};
|
|
||||||
label.blit(buf, x, y + index, style)?;
|
|
||||||
label.len() as u16
|
|
||||||
} else {
|
|
||||||
0u16
|
|
||||||
});
|
|
||||||
index = index + 1;
|
|
||||||
}
|
|
||||||
Ok(width)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
|
||||||
return Split::right([
|
return Split::right([
|
||||||
|
|
@ -180,20 +110,13 @@ impl<'a> ArrangerView<'a> {
|
||||||
//width = width.max(2label.len() as u16 + 3);
|
//width = width.max(2label.len() as u16 + 3);
|
||||||
}
|
}
|
||||||
if track_index + 1 == self.cursor.0 {
|
if track_index + 1 == self.cursor.0 {
|
||||||
|
let bg = Nord::bg_hi(self.focused, self.entered);
|
||||||
if self.cursor.1 == 0 {
|
if self.cursor.1 == 0 {
|
||||||
let y = area.y;
|
let y = area.y;
|
||||||
fill_bg(buf, Rect { x, y, width, height: 1 }, if self.focused {
|
fill_bg(buf, Rect { x, y, width, height: 1 }, bg)
|
||||||
Color::Rgb(30, 90, 25)
|
|
||||||
} else {
|
|
||||||
Color::Rgb(25, 60, 15)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
let y = 1 + area.y + 2 * (self.cursor.1 as u16 - 1);
|
let y = 1 + area.y + 2 * (self.cursor.1 as u16 - 1);
|
||||||
fill_bg(buf, Rect { x, y, width, height: 2 }, if self.focused {
|
fill_bg(buf, Rect { x, y, width, height: 2 }, bg)
|
||||||
Color::Rgb(30, 90, 25)
|
|
||||||
} else {
|
|
||||||
Color::Rgb(25, 60, 15)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
x = x + width as u16;
|
x = x + width as u16;
|
||||||
|
|
@ -202,9 +125,7 @@ impl<'a> ArrangerView<'a> {
|
||||||
}
|
}
|
||||||
]).render(buf, area);
|
]).render(buf, area);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ArrangerView<'a> {
|
|
||||||
fn draw_horizontal (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn draw_horizontal (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
let style1 = Some(Style::default().bold().not_dim());
|
let style1 = Some(Style::default().bold().not_dim());
|
||||||
let style2 = Some(Style::default().not_dim());
|
let style2 = Some(Style::default().not_dim());
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ pub struct LozengeDotted(pub Style);
|
||||||
pub struct Quarter(pub Style);
|
pub struct Quarter(pub Style);
|
||||||
pub struct QuarterV(pub Style);
|
pub struct QuarterV(pub Style);
|
||||||
pub struct Chamfer(pub Style);
|
pub struct Chamfer(pub Style);
|
||||||
|
pub struct Corners(pub Style);
|
||||||
|
|
||||||
border! {
|
border! {
|
||||||
Lozenge {
|
Lozenge {
|
||||||
|
|
@ -148,5 +149,13 @@ border! {
|
||||||
fn style (&self) -> Option<Style> {
|
fn style (&self) -> Option<Style> {
|
||||||
Some(self.0)
|
Some(self.0)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Corners {
|
||||||
|
"🬆" "" "🬊"
|
||||||
|
"" ""
|
||||||
|
"🬱" "" "🬵"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub struct ChainView<'a> {
|
||||||
pub track: Option<&'a Track>,
|
pub track: Option<&'a Track>,
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
|
pub entered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ChainView<'a> {
|
impl<'a> ChainView<'a> {
|
||||||
|
|
@ -18,6 +19,7 @@ impl<'a> ChainView<'a> {
|
||||||
pub fn new (app: &'a App, direction: Direction) -> Self {
|
pub fn new (app: &'a App, direction: Direction) -> Self {
|
||||||
Self {
|
Self {
|
||||||
direction,
|
direction,
|
||||||
|
entered: app.entered,
|
||||||
focused: app.section == AppSection::Chain,
|
focused: app.section == AppSection::Chain,
|
||||||
track: match app.track_cursor {
|
track: match app.track_cursor {
|
||||||
0 => None,
|
0 => None,
|
||||||
|
|
@ -34,14 +36,18 @@ impl<'a> Render for ChainView<'a> {
|
||||||
Direction::Down => area.width = area.width.min(40),
|
Direction::Down => area.width = area.width.min(40),
|
||||||
Direction::Right => area.width = area.width.min(10),
|
Direction::Right => area.width = area.width.min(10),
|
||||||
}
|
}
|
||||||
fill_bg(buf, area, if self.focused { Color::Rgb(20, 45, 5) } else { Color::Reset });
|
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||||
self.direction
|
let area = self.direction
|
||||||
.split_focus(0, track.devices.as_slice(), if self.focused {
|
.split_focus(0, track.devices.as_slice(), if self.focused {
|
||||||
Style::default().green().dim()
|
Style::default().green().dim()
|
||||||
} else {
|
} else {
|
||||||
Style::default().dim()
|
Style::default().dim()
|
||||||
})
|
})
|
||||||
.render(buf, area)
|
.render(buf, area)?;
|
||||||
|
if self.focused && self.entered {
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
} else {
|
} else {
|
||||||
let Rect { x, y, width, height } = area;
|
let Rect { x, y, width, height } = area;
|
||||||
let label = "No track selected";
|
let label = "No track selected";
|
||||||
|
|
@ -86,7 +92,7 @@ impl<'a> Render for SplitFocus<'a> {
|
||||||
}
|
}
|
||||||
results
|
results
|
||||||
.get(self.1)
|
.get(self.1)
|
||||||
.map(|focused|Lozenge(self.3).draw(buf, *focused))
|
.map(|focused|Lozenge(self.3).draw(buf, Rect { width, ..*focused }))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use crate::model::*;
|
|
||||||
|
|
||||||
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
|
|
||||||
-> Usually<Rect>
|
|
||||||
{
|
|
||||||
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()))?;
|
|
||||||
let mut width = title.len() + 2;
|
|
||||||
for (i, (note, sample)) in state.samples.iter().enumerate() {
|
|
||||||
let style = if i == state.cursor.0 {
|
|
||||||
Style::default().green()
|
|
||||||
} else {
|
|
||||||
Style::default()
|
|
||||||
};
|
|
||||||
let i = i as u16;
|
|
||||||
let y1 = y+1+i;
|
|
||||||
if y1 >= y + height {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i as usize == state.cursor.0 {
|
|
||||||
"⯈".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))?;
|
|
||||||
width = width.max(label1.len() + label2.len() + 4);
|
|
||||||
}
|
|
||||||
let height = ((2 + state.samples.len()) as u16).min(height);
|
|
||||||
Ok(Rect { x, y, width: width as u16, height })
|
|
||||||
}
|
|
||||||
|
|
||||||
//fn render_table (
|
|
||||||
//state: &mut Sampler,
|
|
||||||
//stdout: &mut Stdout,
|
|
||||||
//offset: (u16, u16),
|
|
||||||
//) -> Result<(), Box<dyn Error>> {
|
|
||||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
|
||||||
//stdout.queue(move_to(0, 3))?.queue(
|
|
||||||
//Print(" Name Rate Trigger Route")
|
|
||||||
//)?;
|
|
||||||
//for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
|
|
||||||
//let row = 4 + i as u16;
|
|
||||||
//for (j, (column, field)) in [
|
|
||||||
//(0, format!(" {:7} ", sample.name)),
|
|
||||||
//(9, format!(" {:.1}Hz ", sample.rate)),
|
|
||||||
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
|
||||||
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
|
||||||
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
|
||||||
//].into_iter().enumerate() {
|
|
||||||
//stdout.queue(move_to(column, row))?;
|
|
||||||
//if state.selected_sample == i && state.selected_column == j {
|
|
||||||
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
|
||||||
//} else {
|
|
||||||
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
||||||
//fn render_meters (
|
|
||||||
//state: &mut Sampler,
|
|
||||||
//stdout: &mut Stdout,
|
|
||||||
//offset: (u16, u16),
|
|
||||||
//) -> Result<(), Box<dyn Error>> {
|
|
||||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
|
||||||
//for (i, sample) in state.samples.lock().iter().enumerate() {
|
|
||||||
//let row = 4 + i as u16;
|
|
||||||
//stdout.queue(move_to(32, row))?.queue(
|
|
||||||
//PrintStyledContent("▁".green())
|
|
||||||
//)?;
|
|
||||||
//}
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{core::*,model::*};
|
use crate::{core::*,model::*,view::*};
|
||||||
|
|
||||||
pub struct BufferedSequencerView {
|
pub struct BufferedSequencerView {
|
||||||
pub ppq: usize,
|
pub ppq: usize,
|
||||||
|
|
@ -79,6 +79,7 @@ impl BufferedSequencerView {
|
||||||
|
|
||||||
pub struct SequencerView<'a> {
|
pub struct SequencerView<'a> {
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
entered: bool,
|
||||||
/// Displayed phrase
|
/// Displayed phrase
|
||||||
phrase: Option<&'a Phrase>,
|
phrase: Option<&'a Phrase>,
|
||||||
/// Resolution of MIDI sequence
|
/// Resolution of MIDI sequence
|
||||||
|
|
@ -111,6 +112,7 @@ impl<'a> SequencerView<'a> {
|
||||||
Self {
|
Self {
|
||||||
phrase: app.phrase(),
|
phrase: app.phrase(),
|
||||||
focused: app.section == AppSection::Sequencer,
|
focused: app.section == AppSection::Sequencer,
|
||||||
|
entered: app.entered,
|
||||||
ppq: app.timebase.ppq() as usize,
|
ppq: app.timebase.ppq() as usize,
|
||||||
now: app.timebase.frame_to_pulse(app.playhead as f64) as usize,
|
now: app.timebase.frame_to_pulse(app.playhead as f64) as usize,
|
||||||
time_cursor: app.time_cursor,
|
time_cursor: app.time_cursor,
|
||||||
|
|
@ -126,9 +128,11 @@ impl<'a> SequencerView<'a> {
|
||||||
|
|
||||||
impl<'a> Render for SequencerView<'a> {
|
impl<'a> Render for SequencerView<'a> {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
let bg = if self.focused { Color::Rgb(20, 45, 5) } else { Color::Reset };
|
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||||
fill_bg(buf, area, bg);
|
|
||||||
self.horizontal_draw(buf, area)?;
|
self.horizontal_draw(buf, area)?;
|
||||||
|
if self.focused && self.entered {
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||||
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
0
src/view/split.rs
Normal file
0
src/view/split.rs
Normal file
45
src/view/theme.rs
Normal file
45
src/view/theme.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub trait Theme {
|
||||||
|
const BG0: Color;
|
||||||
|
const BG1: Color;
|
||||||
|
const BG2: Color;
|
||||||
|
const BG3: Color;
|
||||||
|
const BG4: Color;
|
||||||
|
const RED: Color;
|
||||||
|
const YELLOW: Color;
|
||||||
|
const GREEN: Color;
|
||||||
|
|
||||||
|
fn bg_hi (focused: bool, entered: bool) -> Color {
|
||||||
|
if focused && entered {
|
||||||
|
Self::BG2
|
||||||
|
} else if focused {
|
||||||
|
Self::BG1
|
||||||
|
} else {
|
||||||
|
Self::BG0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bg_lo (focused: bool, entered: bool) -> Color {
|
||||||
|
if focused && entered {
|
||||||
|
Self::BG1
|
||||||
|
} else if focused {
|
||||||
|
Self::BG0
|
||||||
|
} else {
|
||||||
|
Color::Reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Nord;
|
||||||
|
|
||||||
|
impl Theme for Nord {
|
||||||
|
const BG0: Color = Color::Rgb(41, 46, 57);
|
||||||
|
const BG1: Color = Color::Rgb(46, 52, 64);
|
||||||
|
const BG2: Color = Color::Rgb(59, 66, 82);
|
||||||
|
const BG3: Color = Color::Rgb(67, 76, 94);
|
||||||
|
const BG4: Color = Color::Rgb(76, 86, 106);
|
||||||
|
const RED: Color = Color::Rgb(191, 97, 106);
|
||||||
|
const YELLOW: Color = Color::Rgb(235, 203, 139);
|
||||||
|
const GREEN: Color = Color::Rgb(163, 190, 140);
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
use crate::{core::*, view::Split, model::App};
|
use crate::{core::*,view::*,model::{App, AppSection}};
|
||||||
pub struct TransportView {
|
pub struct TransportView {
|
||||||
pub playing: TransportState,
|
focused: bool,
|
||||||
pub record: bool,
|
entered: bool,
|
||||||
pub overdub: bool,
|
playing: TransportState,
|
||||||
pub monitor: bool,
|
quant: usize,
|
||||||
pub quant: usize,
|
ppq: usize,
|
||||||
pub ppq: usize,
|
bpm: usize,
|
||||||
pub bpm: usize,
|
pulse: usize,
|
||||||
pub pulse: usize,
|
usecs: usize,
|
||||||
pub usecs: usize,
|
|
||||||
}
|
}
|
||||||
impl TransportView {
|
impl TransportView {
|
||||||
pub fn new (app: &App) -> Self {
|
pub fn new (app: &App) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
focused: app.section == AppSection::Transport,
|
||||||
|
entered: app.entered,
|
||||||
playing: *app.playing.as_ref().unwrap_or(&TransportState::Stopped),
|
playing: *app.playing.as_ref().unwrap_or(&TransportState::Stopped),
|
||||||
monitor: app.track().map(|t|t.1.monitoring).unwrap_or(false),
|
|
||||||
record: app.track().map(|t|t.1.recording).unwrap_or(false),
|
|
||||||
overdub: app.track().map(|t|t.1.overdub).unwrap_or(false),
|
|
||||||
quant: app.quant,
|
quant: app.quant,
|
||||||
ppq: app.timebase.ppq() as usize,
|
ppq: app.timebase.ppq() as usize,
|
||||||
bpm: app.timebase.bpm() as usize,
|
bpm: app.timebase.bpm() as usize,
|
||||||
|
|
@ -27,66 +25,53 @@ impl TransportView {
|
||||||
}
|
}
|
||||||
render!(TransportView |self, buf, area| {
|
render!(TransportView |self, buf, area| {
|
||||||
let mut area = area;
|
let mut area = area;
|
||||||
area.height = if area.width > 100 { 1 } else { 2 };
|
area.height = 2;
|
||||||
let Self { ppq, bpm, quant, pulse, usecs, .. } = self;
|
let Self { ppq, bpm, quant, pulse, usecs, .. } = self;
|
||||||
fill_bg(buf, area, Color::Rgb(20, 45, 5));
|
|
||||||
let dim = Style::default().dim();
|
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||||
|
|
||||||
|
let gray = Style::default().gray();
|
||||||
let not_dim = Style::default().not_dim();
|
let not_dim = Style::default().not_dim();
|
||||||
let red = not_dim.red();
|
let not_dim_bold = not_dim.bold();
|
||||||
let green = not_dim.green();
|
|
||||||
let yellow = not_dim.yellow();
|
let area = Split::right([
|
||||||
Split::right([
|
|
||||||
|
|
||||||
// Play/Pause button
|
// Play/Pause button
|
||||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||||
let style = Style::default().gray();
|
|
||||||
let style = Some(match &self.playing {
|
let style = Some(match &self.playing {
|
||||||
TransportState::Stopped => style.dim().bold(),
|
TransportState::Stopped => gray.dim().bold(),
|
||||||
TransportState::Starting => style.not_dim().bold(),
|
TransportState::Starting => gray.not_dim().bold(),
|
||||||
TransportState::Rolling => style.not_dim().white().bold()
|
TransportState::Rolling => gray.not_dim().white().bold()
|
||||||
});
|
});
|
||||||
let label = match &self.playing {
|
let label = match &self.playing {
|
||||||
TransportState::Rolling => "▶ PLAYING",
|
TransportState::Rolling => "▶ PLAYING",
|
||||||
TransportState::Starting => "READY ...",
|
TransportState::Starting => "READY ...",
|
||||||
TransportState::Stopped => "⏹ STOPPED",
|
TransportState::Stopped => "⏹ STOPPED",
|
||||||
};
|
};
|
||||||
label.blit(buf, x, y, style)
|
let mut result = label.blit(buf, x + 1, y, style)?;
|
||||||
},
|
result.width = result.width + 1;
|
||||||
|
Ok(result)
|
||||||
// Record button/indicator
|
|
||||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
|
||||||
"⏺ REC".blit(buf, x, y, Some(if self.record { red } else { dim }))
|
|
||||||
},
|
|
||||||
|
|
||||||
// Overdub button/indicator
|
|
||||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
|
||||||
"⏺ DUB".blit(buf, x, y, Some(if self.overdub { yellow } else { dim }))
|
|
||||||
},
|
|
||||||
|
|
||||||
// Monitor button/indicator
|
|
||||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
|
||||||
"⏺ MON".blit(buf, x, y, Some(if self.monitor { green } else { dim }))
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Beats per minute
|
// Beats per minute
|
||||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||||
"BPM".blit(buf, x, y, Some(not_dim))?;
|
"BPM".blit(buf, x, y, Some(not_dim))?;
|
||||||
format!("{}.{:03}", bpm, bpm % 1).blit(buf, x + 4, y, Some(not_dim.bold()))?;
|
let width = format!("{}.{:03}", bpm, bpm % 1).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||||
Ok(Rect { x, y, width: 13, height: 1 })
|
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
|
||||||
},
|
},
|
||||||
|
|
||||||
// Quantization
|
// Quantization
|
||||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||||
"QUANT".blit(buf, x, y, Some(not_dim))?;
|
"QUANT".blit(buf, x, y, Some(not_dim))?;
|
||||||
ppq_to_name(*quant).blit(buf, x + 6, y, Some(not_dim.bold()))?;
|
let width = ppq_to_name(*quant).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||||
Ok(Rect { x, y, width: 12, height: 1 })
|
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
|
||||||
},
|
},
|
||||||
|
|
||||||
// Clip launch sync
|
// Clip launch sync
|
||||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||||
"SYNC".blit(buf, x, y, Some(not_dim))?;
|
"SYNC".blit(buf, x, y, Some(not_dim))?;
|
||||||
"4/4".blit(buf, x + 5, y, Some(not_dim.bold()))?;
|
let width = "4/4".blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||||
Ok(Rect { x, y, width: 12, height: 1 })
|
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
|
||||||
},
|
},
|
||||||
|
|
||||||
// Clock
|
// Clock
|
||||||
|
|
@ -96,8 +81,29 @@ render!(TransportView |self, buf, area| {
|
||||||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
||||||
let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}");
|
let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}");
|
||||||
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(Style::default().not_dim()))
|
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim))
|
||||||
}
|
}
|
||||||
|
|
||||||
]).render(buf, area)
|
]).render(buf, area)?;
|
||||||
|
|
||||||
|
Ok(if self.focused && self.entered {
|
||||||
|
Corners(Style::default().green().not_dim()).draw(buf, area)?
|
||||||
|
} else {
|
||||||
|
area
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Record button/indicator
|
||||||
|
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||||
|
//"⏺ REC".blit(buf, x, y, Some(if self.record { red } else { dim }))
|
||||||
|
//},
|
||||||
|
|
||||||
|
//// Overdub button/indicator
|
||||||
|
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||||
|
//"⏺ DUB".blit(buf, x, y, Some(if self.overdub { yellow } else { dim }))
|
||||||
|
//},
|
||||||
|
|
||||||
|
//// Monitor button/indicator
|
||||||
|
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||||
|
//"⏺ MON".blit(buf, x, y, Some(if self.monitor { green } else { dim }))
|
||||||
|
//},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue