wip: refactor pt.6, fixed tek_api

This commit is contained in:
🪞👃🪞 2024-11-10 14:35:20 +01:00
parent 5df08409e5
commit 869d92110d
29 changed files with 1678 additions and 1679 deletions

View file

@ -1,13 +1,8 @@
use crate::*;
use std::cmp::PartialEq;
/// MIDI message structural
pub type PhraseData = Vec<Vec<MidiMessage>>;
/// MIDI message serialized
pub type PhraseMessage = Vec<u8>;
/// Collection of serialized MIDI messages
pub type PhraseChunk = [Vec<PhraseMessage>];
/// Root level object for standalone `tek_sequencer`
pub struct SequencerView<E: Engine> {
pub struct SequencerApp<E: Engine> {
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Arc<RwLock<JackClient>>,
/// Controls the JACK transport.
@ -25,394 +20,8 @@ pub struct SequencerView<E: Engine> {
/// Whether the currently focused item is entered
pub entered: bool,
}
pub struct PhrasePoolView<E: Engine> {
_engine: PhantomData<E>,
state: PhrasePool,
/// Scroll offset
pub scroll: usize,
/// Mode switch
pub mode: Option<PhrasePoolMode>,
/// Whether this widget is focused
pub focused: bool,
/// Whether this widget is entered
pub entered: bool,
}
/// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq)] pub enum SequencerFocus {
/// The transport (toolbar) is focused
Transport,
/// The phrase list (pool) is focused
PhrasePool,
/// The phrase editor (sequencer) is focused
PhraseEditor,
}
/// Status bar for sequencer app
pub enum SequencerStatusBar {
Transport,
PhrasePool,
PhraseEditor,
}
/// Modes for phrase pool
pub enum PhrasePoolMode {
/// Renaming a pattern
Rename(usize, String),
/// Editing the length of a pattern
Length(usize, usize, PhraseLengthFocus),
}
/// Contains state for viewing and editing a phrase
pub struct PhraseEditor<E: Engine> {
_engine: PhantomData<E>,
/// Phrase being played
pub phrase: Option<Arc<RwLock<Phrase>>>,
/// Length of note that will be inserted, in pulses
pub note_len: usize,
/// The full piano keys are rendered to this buffer
pub keys: Buffer,
/// The full piano roll is rendered to this buffer
pub buffer: BigBuffer,
/// Cursor/scroll/zoom in pitch axis
pub note_axis: RwLock<FixedAxis<usize>>,
/// Cursor/scroll/zoom in time axis
pub time_axis: RwLock<ScaledAxis<usize>>,
/// Whether this widget is focused
pub focused: bool,
/// Whether note enter mode is enabled
pub entered: bool,
/// Display mode
pub mode: bool,
/// Notes currently held at input
pub notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
/// Current position of global playhead
pub now: Arc<Pulse>,
/// Width of notes area at last render
pub width: AtomicUsize,
/// Height of notes area at last render
pub height: AtomicUsize,
}
/// Phrase player.
pub struct PhrasePlayer {
/// Global timebase
pub clock: Arc<TransportTime>,
/// Start time and phrase being played
pub phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Start time and next phrase
pub next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Play input through output.
pub monitoring: bool,
/// Write input to sequence.
pub recording: bool,
/// Overdub input to sequence.
pub overdub: bool,
/// Send all notes off
pub reset: bool, // TODO?: after Some(nframes)
/// Record from MIDI ports to current sequence.
pub midi_inputs: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
pub midi_outputs: Vec<Port<MidiOut>>,
/// MIDI output buffer
pub midi_note: Vec<u8>,
/// MIDI output buffer
pub midi_chunk: Vec<Vec<Vec<u8>>>,
/// Notes currently held at input
pub notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
}
/// Displays and edits phrase length.
pub struct PhraseLength<E: Engine> {
_engine: PhantomData<E>,
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of phrase in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<PhraseLengthFocus>,
}
/// Focused field of `PhraseLength`
#[derive(Copy, Clone)] pub enum PhraseLengthFocus {
/// Editing the number of bars
Bar,
/// Editing the number of beats
Beat,
/// Editing the number of ticks
Tick,
}
/// Focus layout of sequencer app
impl<E: Engine> FocusGrid for Sequencer<E> {
type Item = SequencerFocus;
fn cursor (&self) -> (usize, usize) {
self.focus_cursor
}
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.focus_cursor
}
fn layout (&self) -> &[&[SequencerFocus]] { &[
&[SequencerFocus::Transport],
&[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor],
] }
fn focus_enter (&mut self) {
self.entered = true
}
fn focus_exit (&mut self) {
self.entered = false
}
fn entered (&self) -> Option<Self::Item> {
if self.entered { Some(self.focused()) } else { None }
}
fn update_focus (&mut self) {
let focused = self.focused();
if let Some(transport) = self.transport.as_ref() {
transport.write().unwrap().focused = focused == SequencerFocus::Transport
}
self.phrases.write().unwrap().focused = focused == SequencerFocus::PhrasePool;
self.editor.focused = focused == SequencerFocus::PhraseEditor;
}
}
impl<E: Engine> PhrasePool<E> {
pub fn new () -> Self {
Self {
_engine: Default::default(),
scroll: 0,
phrase: 0,
phrases: vec![Arc::new(RwLock::new(Phrase::default()))],
mode: None,
focused: false,
entered: false,
}
}
pub fn len (&self) -> usize { self.phrases.len() }
pub fn phrase (&self) -> &Arc<RwLock<Phrase>> { &self.phrases[self.phrase] }
pub fn select_prev (&mut self) { self.phrase = self.index_before(self.phrase) }
pub fn select_next (&mut self) { self.phrase = self.index_after(self.phrase) }
pub fn index_before (&self, index: usize) -> usize {
index.overflowing_sub(1).0.min(self.len() - 1)
}
pub fn index_after (&self, index: usize) -> usize {
(index + 1) % self.len()
}
pub fn index_of (&self, phrase: &Phrase) -> Option<usize> {
for i in 0..self.phrases.len() {
if *self.phrases[i].read().unwrap() == *phrase { return Some(i) }
}
return None
}
fn new_phrase (name: Option<&str>, color: Option<ItemColorTriplet>) -> Arc<RwLock<Phrase>> {
Arc::new(RwLock::new(Phrase::new(
String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color
)))
}
pub fn delete_selected (&mut self) {
if self.phrase > 0 {
self.phrases.remove(self.phrase);
self.phrase = self.phrase.min(self.phrases.len().saturating_sub(1));
}
}
pub fn append_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
self.phrases.push(Self::new_phrase(name, color));
self.phrase = self.phrases.len() - 1;
}
pub fn insert_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
self.phrases.insert(self.phrase + 1, Self::new_phrase(name, color));
self.phrase += 1;
}
pub fn insert_dup (&mut self) {
let mut phrase = self.phrases[self.phrase].read().unwrap().duplicate();
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);
self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase)));
self.phrase += 1;
}
pub fn randomize_color (&mut self) {
let mut phrase = self.phrases[self.phrase].write().unwrap();
phrase.color = ItemColorTriplet::random();
}
pub fn begin_rename (&mut self) {
self.mode = Some(PhrasePoolMode::Rename(
self.phrase,
self.phrases[self.phrase].read().unwrap().name.clone()
));
}
pub fn begin_length (&mut self) {
self.mode = Some(PhrasePoolMode::Length(
self.phrase,
self.phrases[self.phrase].read().unwrap().length,
PhraseLengthFocus::Bar
));
}
pub fn move_up (&mut self) {
if self.phrase > 1 {
self.phrases.swap(self.phrase - 1, self.phrase);
self.phrase -= 1;
}
}
pub fn move_down (&mut self) {
if self.phrase < self.phrases.len().saturating_sub(1) {
self.phrases.swap(self.phrase + 1, self.phrase);
self.phrase += 1;
}
}
}
impl<E: Engine> PhraseEditor<E> {
pub fn new () -> Self {
Self {
_engine: Default::default(),
phrase: None,
note_len: 24,
notes_in: Arc::new(RwLock::new([false;128])),
notes_out: Arc::new(RwLock::new([false;128])),
keys: keys_vert(),
buffer: Default::default(),
focused: false,
entered: false,
mode: false,
now: Arc::new(0.into()),
width: 0.into(),
height: 0.into(),
note_axis: RwLock::new(FixedAxis {
start: 12,
point: Some(36),
clamp: Some(127)
}),
time_axis: RwLock::new(ScaledAxis {
start: 00,
point: Some(00),
clamp: Some(000),
scale: 24
}),
}
}
pub fn note_cursor_inc (&self) {
let mut axis = self.note_axis.write().unwrap();
axis.point_dec(1);
if let Some(point) = axis.point { if point < axis.start { axis.start = (point / 2) * 2; } }
}
pub fn note_cursor_dec (&self) {
let mut axis = self.note_axis.write().unwrap();
axis.point_inc(1);
if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } }
}
pub fn note_page_up (&self) {
let mut axis = self.note_axis.write().unwrap();
axis.start_dec(3);
axis.point_dec(3);
}
pub fn note_page_down (&self) {
let mut axis = self.note_axis.write().unwrap();
axis.start_inc(3);
axis.point_inc(3);
}
pub fn note_scroll_inc (&self) { self.note_axis.write().unwrap().start_dec(1); }
pub fn note_scroll_dec (&self) { self.note_axis.write().unwrap().start_inc(1); }
pub fn note_length_inc (&mut self) { self.note_len = next_note_length(self.note_len) }
pub fn note_length_dec (&mut self) { self.note_len = prev_note_length(self.note_len) }
pub fn time_cursor_advance (&self) {
let point = self.time_axis.read().unwrap().point;
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
let forward = |time|(time + self.note_len) % length;
self.time_axis.write().unwrap().point = point.map(forward);
}
pub fn time_cursor_inc (&self) {
let scale = self.time_axis.read().unwrap().scale;
self.time_axis.write().unwrap().point_inc(scale);
}
pub fn time_cursor_dec (&self) {
let scale = self.time_axis.read().unwrap().scale;
self.time_axis.write().unwrap().point_dec(scale);
}
pub fn time_scroll_inc (&self) {
let scale = self.time_axis.read().unwrap().scale;
self.time_axis.write().unwrap().start_inc(scale);
}
pub fn time_scroll_dec (&self) {
let scale = self.time_axis.read().unwrap().scale;
self.time_axis.write().unwrap().start_dec(scale);
}
pub fn time_zoom_in (&self) {
let scale = self.time_axis.read().unwrap().scale;
self.time_axis.write().unwrap().scale = prev_note_length(scale)
}
pub fn time_zoom_out (&self) {
let scale = self.time_axis.read().unwrap().scale;
self.time_axis.write().unwrap().scale = next_note_length(scale)
}
}
impl PhrasePlayer {
pub fn new (
jack: &Arc<RwLock<JackClient>>,
clock: &Arc<TransportTime>,
name: &str
) -> Usually<Self> {
let jack = jack.read().unwrap();
Ok(Self {
clock: clock.clone(),
phrase: None,
next_phrase: None,
notes_in: Arc::new(RwLock::new([false;128])),
notes_out: Arc::new(RwLock::new([false;128])),
monitoring: false,
recording: false,
overdub: true,
reset: true,
midi_note: Vec::with_capacity(8),
midi_chunk: vec![Vec::with_capacity(16);16384],
midi_outputs: vec![
jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())?
],
midi_inputs: vec![
jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())?
],
})
}
pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; }
pub fn toggle_record (&mut self) { self.recording = !self.recording; }
pub fn toggle_overdub (&mut self) { self.overdub = !self.overdub; }
pub fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
let start = self.clock.next_launch_pulse();
self.next_phrase = Some((
Instant::from_pulse(&self.clock.timebase(), start as f64),
phrase.map(|p|p.clone())
));
self.reset = true;
}
pub fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.phrase.as_ref() {
Some(self.clock.current.pulse.get() - started.pulse.get())
} else {
None
}
}
}
impl<E: Engine> PhraseLength<E> {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus }
}
pub fn bars (&self) -> usize { self.pulses / (self.bpb * self.ppq) }
pub fn beats (&self) -> usize { (self.pulses % (self.bpb * self.ppq)) / self.ppq }
pub fn ticks (&self) -> usize { self.pulses % self.ppq }
pub fn bars_string (&self) -> String { format!("{}", self.bars()) }
pub fn beats_string (&self) -> String { format!("{}", self.beats()) }
pub fn ticks_string (&self) -> String { format!("{:>02}", self.ticks()) }
}
impl PhraseLengthFocus {
pub fn next (&mut self) {
*self = match self {
Self::Bar => Self::Beat,
Self::Beat => Self::Tick,
Self::Tick => Self::Bar,
}
}
pub fn prev (&mut self) {
*self = match self {
Self::Bar => Self::Tick,
Self::Beat => Self::Bar,
Self::Tick => Self::Beat,
}
}
}
impl Content for Sequencer<Tui> {
impl Content for SequencerApp<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Stack::down(move|add|{
@ -423,452 +32,14 @@ impl Content for Sequencer<Tui> {
})
}
}
impl Handle<Tui> for Sequencer<Tui> {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
if let Some(entered) = self.entered() {
use SequencerFocus::*;
if let Some(true) = match entered {
Transport => self.transport.as_mut().map(|t|t.handle(i)).transpose()?.flatten(),
PhrasePool => self.phrases.write().unwrap().handle(i)?,
PhraseEditor => self.editor.handle(i)?,
} {
return Ok(Some(true))
}
/// JACK process callback for sequencer app
impl Audio for SequencerApp {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if let Some(ref transport) = self.transport {
transport.write().unwrap().process(client, scope);
}
if let Some(command) = SequencerCommand::input_to_command(self, i) {
let _undo = command.execute(self)?;
return Ok(Some(true))
}
Ok(None)
self.player.process(client, scope);
Control::Continue
}
}
impl InputToCommand<Tui, Sequencer<Tui>> for SequencerCommand {
fn input_to_command (state: &Sequencer<Tui>, input: &TuiInput) -> Option<Self> {
use SequencerCommand::*;
use FocusCommand::*;
match input.event() {
key!(KeyCode::Tab) => Some(Focus(Next)),
key!(Shift-KeyCode::Tab) => Some(Focus(Prev)),
key!(KeyCode::BackTab) => Some(Focus(Prev)),
key!(Shift-KeyCode::BackTab) => Some(Focus(Prev)),
key!(KeyCode::Up) => Some(Focus(Up)),
key!(KeyCode::Down) => Some(Focus(Down)),
key!(KeyCode::Left) => Some(Focus(Left)),
key!(KeyCode::Right) => Some(Focus(Right)),
key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)),
_ => match state.focused() {
SequencerFocus::Transport => if let Some(t) = state.transport.as_ref() {
TransportCommand::input_to_command(&*t.read().unwrap(), input).map(Transport)
} else {
None
},
SequencerFocus::PhrasePool =>
PhrasePoolCommand::input_to_command(&*state.phrases.read().unwrap(), input)
.map(Phrases),
SequencerFocus::PhraseEditor =>
PhraseEditorCommand::input_to_command(&state.editor, input)
.map(Editor),
}
}
}
}
// TODO: Display phrases always in order of appearance
impl Content for PhrasePool<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { focused, phrases, mode, .. } = self;
let content = col!(
(i, phrase) in phrases.iter().enumerate() => Layers::new(|add|{
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let mut length = PhraseLength::new(length, None);
if let Some(PhrasePoolMode::Length(phrase, new_length, focus)) = mode {
if *focused && i == *phrase {
length.pulses = *new_length;
length.focus = Some(*focus);
}
}
let length = length.align_e().fill_x();
let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x();
let mut row2 = format!(" {name}");
if let Some(PhrasePoolMode::Rename(phrase, _)) = mode {
if *focused && i == *phrase { row2 = format!("{row2}"); }
};
let row2 = TuiStyle::bold(row2, true);
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?;
Ok(if *focused && i == self.phrase { add(&CORNERS)?; })
})
);
let border_color = if *focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)};
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border);
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
let upper_left = format!("[{}] Phrases", if self.entered {""} else {" "});
let upper_right = format!("({})", phrases.len());
lay!(
content,
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
)
}
}
impl Handle<Tui> for PhrasePool<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = PhrasePoolCommand::input_to_command(self, from) {
let _undo = command.execute(self)?;
return Ok(Some(true))
}
Ok(None)
}
}
impl InputToCommand<Tui, PhrasePool<Tui>> for PhrasePoolCommand {
fn input_to_command (state: &PhrasePool<Tui>, input: &TuiInput) -> Option<Self> {
match input.event() {
key!(KeyCode::Up) => Some(Self::Prev),
key!(KeyCode::Down) => Some(Self::Next),
key!(KeyCode::Char(',')) => Some(Self::MoveUp),
key!(KeyCode::Char('.')) => Some(Self::MoveDown),
key!(KeyCode::Delete) => Some(Self::Delete),
key!(KeyCode::Char('a')) => Some(Self::Append),
key!(KeyCode::Char('i')) => Some(Self::Insert),
key!(KeyCode::Char('d')) => Some(Self::Duplicate),
key!(KeyCode::Char('c')) => Some(Self::RandomColor),
key!(KeyCode::Char('n')) => Some(Self::Rename(PhraseRenameCommand::Begin)),
key!(KeyCode::Char('t')) => Some(Self::Length(PhraseLengthCommand::Begin)),
_ => match state.mode {
Some(PhrasePoolMode::Rename(..)) => PhraseRenameCommand::input_to_command(state, input)
.map(Self::Rename),
Some(PhrasePoolMode::Length(..)) => PhraseLengthCommand::input_to_command(state, input)
.map(Self::Length),
_ => None
}
}
}
}
impl InputToCommand<Tui, PhrasePool<Tui>> for PhraseRenameCommand {
fn input_to_command (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
match from.event() {
key!(KeyCode::Backspace) => Some(Self::Backspace),
key!(KeyCode::Enter) => Some(Self::Confirm),
key!(KeyCode::Esc) => Some(Self::Cancel),
key!(KeyCode::Char(c)) => Some(Self::Append(*c)),
_ => None
}
}
}
impl InputToCommand<Tui, PhrasePool<Tui>> for PhraseLengthCommand {
fn input_to_command (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
match from.event() {
key!(KeyCode::Up) => Some(Self::Inc),
key!(KeyCode::Down) => Some(Self::Dec),
key!(KeyCode::Right) => Some(Self::Next),
key!(KeyCode::Left) => Some(Self::Prev),
key!(KeyCode::Enter) => Some(Self::Confirm),
key!(KeyCode::Esc) => Some(Self::Cancel),
_ => None
}
}
}
impl Content for PhraseLength<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Layers::new(move|add|{
match self.focus {
None => add(&row!(
" ", self.bars_string(),
".", self.beats_string(),
".", self.ticks_string(),
" "
)),
Some(PhraseLengthFocus::Bar) => add(&row!(
"[", self.bars_string(),
"]", self.beats_string(),
".", self.ticks_string(),
" "
)),
Some(PhraseLengthFocus::Beat) => add(&row!(
" ", self.bars_string(),
"[", self.beats_string(),
"]", self.ticks_string(),
" "
)),
Some(PhraseLengthFocus::Tick) => add(&row!(
" ", self.bars_string(),
".", self.beats_string(),
"[", self.ticks_string(),
"]"
)),
}
})
}
}
impl Content for PhraseEditor<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { focused, entered, keys, phrase, buffer, note_len, .. } = self;
let FixedAxis {
start: note_start, point: note_point, clamp: note_clamp
} = *self.note_axis.read().unwrap();
let ScaledAxis {
start: time_start, point: time_point, clamp: time_clamp, scale: time_scale
} = *self.time_axis.read().unwrap();
//let color = Color::Rgb(0,255,0);
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
Ok(if to.area().h() >= 2 {
to.buffer_update(to.area().set_w(5), &|cell, x, y|{
let y = y + (note_start / 2) as u16;
if x < keys.area.width && y < keys.area.height {
*cell = keys.get(x, y).clone()
}
});
})
}).fill_y();
let notes_bg_null = Color::Rgb(28, 35, 25);
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
let area = to.area();
let h = area.h() as usize;
self.height.store(h, Ordering::Relaxed);
self.width.store(area.w() as usize, Ordering::Relaxed);
let mut axis = self.note_axis.write().unwrap();
if let Some(point) = axis.point {
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
axis.start += 2;
}
}
Ok(if to.area().h() >= 2 {
let area = to.area();
to.buffer_update(area, &move |cell, x, y|{
cell.set_bg(notes_bg_null);
let src_x = (x as usize + time_start) * time_scale;
let src_y = y as usize + note_start / 2;
if src_x < buffer.width && src_y < buffer.height - 1 {
buffer.get(src_x, buffer.height - src_y - 2).map(|src|{
cell.set_symbol(src.symbol());
cell.set_fg(src.fg);
cell.set_bg(src.bg);
});
}
});
})
}).fill_x();
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
Ok(if *focused && *entered {
let area = to.area();
if let (Some(time), Some(note)) = (time_point, note_point) {
let x1 = area.x() + (time / time_scale) as u16;
let x2 = x1 + (self.note_len / time_scale) as u16;
let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
let c = if note % 2 == 0 { "" } else { "" };
for x in x1..x2 {
to.blit(&c, x, y, Some(Style::default().fg(Color::Rgb(0,255,0))));
}
}
})
});
let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30));
let playhead_active = playhead_inactive.clone().yellow().bold().not_dim();
let playhead = CustomWidget::new(
|to:[u16;2]|Ok(Some(to.clip_h(1))),
move|to: &mut TuiOutput|{
if let Some(_) = phrase {
let now = self.now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
let time_clamp = time_clamp
.expect("time_axis of sequencer expected to be clamped");
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
let this_step = time_start + (x + 0) * time_scale;
let next_step = time_start + (x + 1) * time_scale;
let x = to.area().x() + x as u16;
let active = this_step <= now && now < next_step;
let character = if active { "|" } else { "·" };
let style = if active { playhead_active } else { playhead_inactive };
to.blit(&character, x, to.area.y(), Some(style));
}
}
Ok(())
}
).push_x(6).align_sw();
let border_color = if *focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)};
let title_color = if *focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
let note_area = lay!(notes, cursor).fill_x();
let piano_roll = row!(keys, note_area).fill_x();
let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border);
let content = lay!(content, playhead);
let mut upper_left = format!("[{}] Sequencer", if *entered {""} else {" "});
if let Some(phrase) = phrase {
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
}
let mut lower_right = format!(
"┤{}x{}├",
self.width.load(Ordering::Relaxed),
self.height.load(Ordering::Relaxed),
);
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
//pulses_to_name(time_scale),
//time_start, time_point.unwrap_or(0),
//time_scale, time_clamp.unwrap_or(0),
//);
if *focused && *entered {
lower_right = format!("┤Note: {} {}├─{lower_right}",
self.note_axis.read().unwrap().point.unwrap(),
pulses_to_name(*note_len));
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
//pulses_to_name(*note_len),
//note_start,
//note_point.unwrap_or(0),
//note_clamp.unwrap_or(0),
//);
}
let upper_right = if let Some(phrase) = phrase {
format!("┤Length: {}", phrase.read().unwrap().length)
} else {
String::new()
};
lay!(
content,
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
TuiStyle::fg(lower_right.to_string(), title_color).pull_x(1).align_se().fill_xy(),
)
}
}
impl<E: Engine> PhraseEditor<E> {
pub fn put (&mut self) {
if let (Some(phrase), Some(time), Some(note)) = (
&self.phrase,
self.time_axis.read().unwrap().point,
self.note_axis.read().unwrap().point,
) {
let mut phrase = phrase.write().unwrap();
let key: u7 = u7::from((127 - note) as u8);
let vel: u7 = 100.into();
let start = time;
let end = (start + self.note_len) % phrase.length;
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
self.buffer = Self::redraw(&phrase);
}
}
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
if let Some(phrase) = phrase {
self.phrase = Some(phrase.clone());
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
self.buffer = Self::redraw(&*phrase.read().unwrap());
} else {
self.phrase = None;
self.time_axis.write().unwrap().clamp = Some(0);
self.buffer = Default::default();
}
}
fn redraw (phrase: &Phrase) -> BigBuffer {
let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65);
Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq);
Self::fill_seq_fg(&mut buf, &phrase);
buf
}
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) {
for x in 0..buf.width {
// Only fill as far as phrase length
if x as usize >= length { break }
// Fill each row with background characters
for y in 0 .. buf.height {
buf.get_mut(x, y).map(|cell|{
cell.set_char(if ppq == 0 {
'·'
} else if x % (4 * ppq) == 0 {
'│'
} else if x % ppq == 0 {
'╎'
} else {
'·'
});
cell.set_fg(Color::Rgb(48, 64, 56));
cell.modifier = Modifier::DIM;
});
}
}
}
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) {
let mut notes_on = [false;128];
for x in 0..buf.width {
if x as usize >= phrase.length {
break
}
if let Some(notes) = phrase.notes.get(x as usize) {
if phrase.percussive {
for note in notes {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
_ => {}
}
}
} else {
for note in notes {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } =>
notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
for y in 0..buf.height {
if y >= 64 {
break
}
if let Some(block) = half_block(
notes_on[y as usize * 2],
notes_on[y as usize * 2 + 1],
) {
buf.get_mut(x, y).map(|cell|{
cell.set_char(block);
cell.set_fg(Color::White);
});
}
}
if phrase.percussive {
notes_on.fill(false);
}
}
}
}
}
/// Colors of piano keys
const KEY_COLORS: [(Color, Color);6] = [
(Color::Rgb(255, 255, 255), Color::Rgb(255, 255, 255)),
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
(Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
(Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
(Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
];
pub(crate) fn keys_vert () -> Buffer {
let area = [0, 0, 5, 64];
let mut buffer = Buffer::empty(Rect {
x: area.x(), y: area.y(), width: area.w(), height: area.h()
});
buffer_update(&mut buffer, area, &|cell, x, y| {
let y = 63 - y;
match x {
0 => {
cell.set_char('▀');
let (fg, bg) = KEY_COLORS[((6 - y % 6) % 6) as usize];
cell.set_fg(fg);
cell.set_bg(bg);
},
1 => {
cell.set_char('▀');
cell.set_fg(Color::White);
cell.set_bg(Color::White);
},
2 => if y % 6 == 0 { cell.set_char('C'); },
3 => if y % 6 == 0 { cell.set_symbol(NTH_OCTAVE[(y / 6) as usize]); },
_ => {}
}
});
buffer
}