mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 12:46:42 +01:00
wip: separate PhrasePlayer vs PhraseEditor
This commit is contained in:
parent
7668a6f339
commit
25e54eba4e
8 changed files with 491 additions and 636 deletions
|
|
@ -1,5 +1,4 @@
|
|||
use crate::*;
|
||||
|
||||
impl Content for PhrasePool<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
|
|
@ -14,21 +13,154 @@ impl Content for PhrasePool<Tui> {
|
|||
.fg(Color::Rgb(70, 80, 50))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sequencer<Tui> {
|
||||
impl Content for PhraseEditor<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let field_bg = Color::Rgb(28, 35, 25);
|
||||
let toolbar = Stack::down(move|add|{
|
||||
//let name = format!("{:>9}", self.name.read().unwrap().as_str());
|
||||
//add(&col!("Track:", TuiStyle::bg(name.as_str(), field_bg)))?;
|
||||
if let Some(phrase) = self.phrase {
|
||||
let phrase = phrase.read().unwrap();
|
||||
let length = format!("{}q{}p", phrase.length / PPQ, phrase.length % PPQ);
|
||||
let length = format!("{:>9}", &length);
|
||||
let loop_on = format!("{:>9}", if phrase.loop_on { "on" } else { "off" });
|
||||
let loop_start = format!("{:>9}", phrase.loop_start);
|
||||
let loop_end = format!("{:>9}", phrase.loop_length);
|
||||
add(&"")?;
|
||||
add(&col!("Length:", TuiStyle::bg(length.as_str(), field_bg)))?;
|
||||
add(&col!("Loop:", TuiStyle::bg(loop_on.as_str(), field_bg)))?;
|
||||
add(&col!("L. start:", TuiStyle::bg(loop_start.as_str(), field_bg)))?;
|
||||
add(&col!("L. length:", TuiStyle::bg(loop_end.as_str(), field_bg)))?;
|
||||
}
|
||||
Ok(())
|
||||
}).min_x(10);
|
||||
let content = lay!(
|
||||
// keys
|
||||
CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{
|
||||
if to.area().h() < 2 { return Ok(()) }
|
||||
Ok(to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{
|
||||
let y = y + self.note_axis.start as u16;
|
||||
if x < self.keys.area.width && y < self.keys.area.height {
|
||||
*cell = self.keys.get(x, y).clone()
|
||||
}
|
||||
}))
|
||||
}).fill_y(),
|
||||
// playhead
|
||||
CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{
|
||||
if let Some(phrase) = self.phrase {
|
||||
let time_0 = self.time_axis.start;
|
||||
let time_z = self.time_axis.scale;
|
||||
let now = self.now % phrase.read().unwrap().length;
|
||||
let [x, y, width, _] = to.area();
|
||||
let x2 = x as usize + Self::H_KEYS_OFFSET;
|
||||
let x3 = x as usize + width as usize;
|
||||
for x in x2..x3 {
|
||||
let step = (time_0 + x2) * time_z;
|
||||
let next_step = (time_0 + x2 + 1) * time_z;
|
||||
to.blit(&"-", x as u16, y, Some(PhraseEditor::<Tui>::style_timer_step(
|
||||
now, step as usize, next_step as usize
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}).fill_x(),
|
||||
// notes
|
||||
CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{
|
||||
let offset = Self::H_KEYS_OFFSET as u16;
|
||||
if to.area().h() < 2 || to.area().w() < offset { return Ok(()) }
|
||||
let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2);
|
||||
Ok(to.buffer_update(area, &move |cell, x, y|{
|
||||
cell.set_bg(Color::Rgb(20, 20, 20));
|
||||
let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize;
|
||||
let src_y = (y as usize + self.note_axis.start) as usize;
|
||||
if src_x < self.buffer.width && src_y < self.buffer.height - 1 {
|
||||
let src = self.buffer.get(src_x, self.buffer.height - src_y);
|
||||
src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); });
|
||||
}
|
||||
}))
|
||||
}).fill_x(),
|
||||
// note cursor
|
||||
CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{
|
||||
let area = to.area();
|
||||
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
|
||||
let x = area.x() + Self::H_KEYS_OFFSET as u16 + time as u16;
|
||||
let y = area.y() + 1 + note as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
to.blit(&c, x, y, self.style_focus());
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
// zoom
|
||||
CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{
|
||||
let [x, y, w, h] = to.area.xywh();
|
||||
let quant = ppq_to_name(self.time_axis.scale);
|
||||
Ok(to.blit(
|
||||
&quant,
|
||||
x + w - 1 - quant.len() as u16,
|
||||
y + h - 2,
|
||||
self.style_focus()
|
||||
))
|
||||
}),
|
||||
);
|
||||
row!(toolbar, content).fill_x()
|
||||
.bg(Color::Rgb(40, 50, 30))
|
||||
.border(Lozenge(Style::default()
|
||||
.bg(Color::Rgb(40, 50, 30))
|
||||
.fg(Color::Rgb(70, 80, 50))))
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for PhraseEditor<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char('`')) => {
|
||||
self.mode = !self.mode;
|
||||
},
|
||||
key!(KeyCode::Up) => match self.entered {
|
||||
true => { self.note_axis.point_dec(); },
|
||||
false => { self.note_axis.start_dec(); },
|
||||
},
|
||||
key!(KeyCode::Down) => match self.entered {
|
||||
true => { self.note_axis.point_inc(); },
|
||||
false => { self.note_axis.start_inc(); },
|
||||
},
|
||||
key!(KeyCode::Left) => match self.entered {
|
||||
true => { self.time_axis.point_dec(); },
|
||||
false => { self.time_axis.start_dec(); },
|
||||
},
|
||||
key!(KeyCode::Right) => match self.entered {
|
||||
true => { self.time_axis.point_inc(); },
|
||||
false => { self.time_axis.start_inc(); },
|
||||
},
|
||||
_ => {
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
return Ok(Some(true))
|
||||
}
|
||||
}
|
||||
impl Focusable<Tui> for PhraseEditor<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
impl PhraseEditor<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, index: Option<usize>) {
|
||||
self.viewing_phrase = index;
|
||||
if let Some(phrase) = index.map(|index|self.phrases.get(index)).flatten() {
|
||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||
if let Some(phrase) = phrase {
|
||||
self.phrase = Some(phrase.clone());
|
||||
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_bg(&mut buffer, phrase.length, phrase.ppq);
|
||||
Self::fill_seq_fg(&mut buffer, &phrase);
|
||||
self.buffer = buffer;
|
||||
} else {
|
||||
self.viewing_phrase = None;
|
||||
self.phrase = None;
|
||||
self.buffer = Default::default();
|
||||
}
|
||||
}
|
||||
|
|
@ -121,130 +253,18 @@ impl Sequencer<Tui> {
|
|||
}
|
||||
pub fn index_to_color (&self, index: u16, default: Color) -> Color {
|
||||
let index = index as usize;
|
||||
if self.keys_in[index] && self.keys_out[index] {
|
||||
let (notes_in, notes_out) = (self.notes_in.read().unwrap(), self.notes_out.read().unwrap());
|
||||
if notes_in[index] && notes_out[index] {
|
||||
Color::Yellow
|
||||
} else if self.keys_in[index] {
|
||||
} else if notes_in[index] {
|
||||
Color::Red
|
||||
} else if self.keys_out[index] {
|
||||
} else if notes_out[index] {
|
||||
Color::Green
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Content for Sequencer<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let field_bg = Color::Rgb(28, 35, 25);
|
||||
let toolbar = Stack::down(move|add|{
|
||||
let name = format!("{:>9}", self.name.read().unwrap().as_str());
|
||||
add(&col!("Track:", TuiStyle::bg(name.as_str(), field_bg)))?;
|
||||
if let Some(phrase) = self.viewing_phrase
|
||||
.map(|index|self.phrases.get(index))
|
||||
.flatten()
|
||||
{
|
||||
let phrase = phrase.read().unwrap();
|
||||
let length = format!("{}q{}p", phrase.length / PPQ, phrase.length % PPQ);
|
||||
let length = format!("{:>9}", &length);
|
||||
let loop_on = format!("{:>9}", if phrase.loop_on { "on" } else { "off" });
|
||||
let loop_start = format!("{:>9}", phrase.loop_start);
|
||||
let loop_end = format!("{:>9}", phrase.loop_length);
|
||||
add(&"")?;
|
||||
add(&col!("Length:", TuiStyle::bg(length.as_str(), field_bg)))?;
|
||||
add(&col!("Loop:", TuiStyle::bg(loop_on.as_str(), field_bg)))?;
|
||||
add(&col!("L. start:", TuiStyle::bg(loop_start.as_str(), field_bg)))?;
|
||||
add(&col!("L. length:", TuiStyle::bg(loop_end.as_str(), field_bg)))?;
|
||||
}
|
||||
Ok(())
|
||||
}).min_x(10);
|
||||
let content = lay!(
|
||||
// keys
|
||||
CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{
|
||||
if to.area().h() < 2 { return Ok(()) }
|
||||
Ok(to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{
|
||||
let y = y + self.note_axis.start as u16;
|
||||
if x < self.keys.area.width && y < self.keys.area.height {
|
||||
*cell = self.keys.get(x, y).clone()
|
||||
}
|
||||
}))
|
||||
}).fill_y(),
|
||||
// playhead
|
||||
CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{
|
||||
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;
|
||||
let [x, y, width, _] = to.area();
|
||||
let x2 = x as usize + Sequencer::H_KEYS_OFFSET;
|
||||
let x3 = x as usize + width as usize;
|
||||
for x in x2..x3 {
|
||||
let step = (time_0 + x2) * time_z;
|
||||
let next_step = (time_0 + x2 + 1) * time_z;
|
||||
to.blit(&"-", x as u16, y, Some(Sequencer::<Tui>::style_timer_step(
|
||||
now, step as usize, next_step as usize
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}).fill_x(),
|
||||
// notes
|
||||
CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{
|
||||
let offset = Sequencer::H_KEYS_OFFSET as u16;
|
||||
if to.area().h() < 2 || to.area().w() < offset { return Ok(()) }
|
||||
let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2);
|
||||
Ok(to.buffer_update(area, &move |cell, x, y|{
|
||||
cell.set_bg(Color::Rgb(20, 20, 20));
|
||||
let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize;
|
||||
let src_y = (y as usize + self.note_axis.start) as usize;
|
||||
if src_x < self.buffer.width && src_y < self.buffer.height - 1 {
|
||||
let src = self.buffer.get(src_x, self.buffer.height - src_y);
|
||||
src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); });
|
||||
}
|
||||
}))
|
||||
}).fill_x(),
|
||||
// note cursor
|
||||
CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{
|
||||
let area = to.area();
|
||||
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
|
||||
let x = area.x() + Sequencer::H_KEYS_OFFSET as u16 + time as u16;
|
||||
let y = area.y() + 1 + note as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
to.blit(&c, x, y, self.style_focus());
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
// zoom
|
||||
CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{
|
||||
let [x, y, w, h] = to.area.xywh();
|
||||
let quant = ppq_to_name(self.time_axis.scale);
|
||||
Ok(to.blit(
|
||||
&quant,
|
||||
x + w - 1 - quant.len() as u16,
|
||||
y + h - 2,
|
||||
self.style_focus()
|
||||
))
|
||||
}),
|
||||
);
|
||||
row!(toolbar, content).fill_x()
|
||||
.bg(Color::Rgb(40, 50, 30))
|
||||
.border(Lozenge(Style::default()
|
||||
.bg(Color::Rgb(40, 50, 30))
|
||||
.fg(Color::Rgb(70, 80, 50))))
|
||||
}
|
||||
}
|
||||
impl Focusable<Tui> for Sequencer<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
|
||||
/// Colors of piano keys
|
||||
const KEY_COLORS: [(Color, Color);6] = [
|
||||
(Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
|
||||
|
|
@ -254,7 +274,6 @@ const KEY_COLORS: [(Color, Color);6] = [
|
|||
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
|
||||
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
|
||||
];
|
||||
|
||||
pub(crate) fn keys_vert () -> Buffer {
|
||||
let area = [0, 0, 5, 64];
|
||||
let mut buffer = Buffer::empty(Rect {
|
||||
|
|
@ -281,38 +300,7 @@ pub(crate) fn keys_vert () -> Buffer {
|
|||
});
|
||||
buffer
|
||||
}
|
||||
|
||||
/// Octave number (from -1 to 9)
|
||||
const NTH_OCTAVE: [&'static str;11] = [
|
||||
"-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
||||
];
|
||||
|
||||
impl Handle<Tui> for Sequencer<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char('`')) => {
|
||||
self.mode = !self.mode;
|
||||
},
|
||||
key!(KeyCode::Up) => match self.entered {
|
||||
true => { self.note_axis.point_dec(); },
|
||||
false => { self.note_axis.start_dec(); },
|
||||
},
|
||||
key!(KeyCode::Down) => match self.entered {
|
||||
true => { self.note_axis.point_inc(); },
|
||||
false => { self.note_axis.start_inc(); },
|
||||
},
|
||||
key!(KeyCode::Left) => match self.entered {
|
||||
true => { self.time_axis.point_dec(); },
|
||||
false => { self.time_axis.start_dec(); },
|
||||
},
|
||||
key!(KeyCode::Right) => match self.entered {
|
||||
true => { self.time_axis.point_inc(); },
|
||||
false => { self.time_axis.start_inc(); },
|
||||
},
|
||||
_ => {
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
return Ok(Some(true))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue