mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
recombine arranger, sequencer modules
This commit is contained in:
parent
7ef97bcf3a
commit
190aca8d3b
12 changed files with 658 additions and 660 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{core::*, handle, App, AppFocus};
|
||||
|
||||
submod!{ arranger chain focus mixer plugin sampler sequencer transport }
|
||||
submod!{ chain focus mixer plugin sampler transport }
|
||||
|
||||
handle!{
|
||||
App |self, e| {
|
||||
|
|
@ -31,9 +31,9 @@ fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
|||
AppFocus::Transport =>
|
||||
handle_keymap(state, e, crate::control::transport::KEYMAP_TRANSPORT),
|
||||
AppFocus::Arranger =>
|
||||
handle_keymap(state, e, crate::control::arranger::KEYMAP_ARRANGER),
|
||||
handle_keymap(state, e, crate::devices::arranger::KEYMAP_ARRANGER),
|
||||
AppFocus::Sequencer =>
|
||||
handle_keymap(state, e, crate::control::sequencer::KEYMAP_SEQUENCER),
|
||||
handle_keymap(state, e, crate::devices::sequencer::KEYMAP_SEQUENCER),
|
||||
AppFocus::Chain => Ok(if state.entered {
|
||||
handle_device(state, e)? ||
|
||||
handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)?
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
use crate::{core::*, model::App};
|
||||
|
||||
/// Key bindings for arranger section.
|
||||
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.arranger.mode = !app.arranger.mode;
|
||||
Ok(true)
|
||||
}],
|
||||
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.scene_prev(),
|
||||
true => app.arranger.track_prev(),
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.scene_next(),
|
||||
true => app.arranger.track_next(),
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.track_prev(),
|
||||
true => app.arranger.scene_prev(),
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.track_next(),
|
||||
true => app.arranger.scene_next()
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
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| {
|
||||
app.arranger.activate();
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
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| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.note_axis.point_dec(); },
|
||||
false => { app.sequencer.note_axis.start_dec(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "seq_cursor_down", "move cursor down", |app: &mut App| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.note_axis.point_inc(); },
|
||||
false => { app.sequencer.note_axis.start_inc(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.time_axis.point_dec(); },
|
||||
false => { app.sequencer.time_axis.start_dec(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.time_axis.point_inc(); },
|
||||
false => { app.sequencer.time_axis.start_inc(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.sequencer.mode = !app.sequencer.mode;
|
||||
Ok(true)
|
||||
}],
|
||||
// [Char('a'), NONE, "note_add", "Add note", note_add],
|
||||
// [Char('z'), NONE, "note_del", "Delete note", note_del],
|
||||
// [CapsLock, NONE, "advance", "Toggle auto advance", nop],
|
||||
// [Char('w'), NONE, "rest", "Advance by note duration", nop],
|
||||
});
|
||||
1
src/devices.rs
Normal file
1
src/devices.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
crate::core::pubmod! { sequencer arranger }
|
||||
|
|
@ -1,5 +1,76 @@
|
|||
/// Arrangement editor.
|
||||
|
||||
use crate::{core::*, model::*, view::*};
|
||||
|
||||
/// Key bindings for arranger section.
|
||||
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.arranger.mode = !app.arranger.mode;
|
||||
Ok(true)
|
||||
}],
|
||||
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.scene_prev(),
|
||||
true => app.arranger.track_prev(),
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.scene_next(),
|
||||
true => app.arranger.track_next(),
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.track_prev(),
|
||||
true => app.arranger.scene_prev(),
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => app.arranger.track_next(),
|
||||
true => app.arranger.scene_next()
|
||||
};
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
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| {
|
||||
app.arranger.activate();
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
||||
/// Represents the tracks and scenes of the composition.
|
||||
pub struct Arranger {
|
||||
/// Display mode of arranger
|
||||
pub mode: bool,
|
||||
/// Currently selected element.
|
||||
pub selected: ArrangerFocus,
|
||||
/// Collection of tracks.
|
||||
pub tracks: Vec<Track>,
|
||||
/// Collection of scenes.
|
||||
pub scenes: Vec<Scene>,
|
||||
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
}
|
||||
|
||||
render!(Arranger |self, buf, area| {
|
||||
let mut area = area;
|
||||
area.height = area.height.min(1 + if self.mode {
|
||||
|
|
@ -21,6 +92,33 @@ render!(Arranger |self, buf, area| {
|
|||
|
||||
impl Arranger {
|
||||
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
mode: false,
|
||||
selected: ArrangerFocus::Clip(0, 0),
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
entered: true,
|
||||
focused: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate (&mut self) {
|
||||
match self.selected {
|
||||
ArrangerFocus::Scene(s) => {
|
||||
for (track_index, track) in self.tracks.iter_mut().enumerate() {
|
||||
track.sequence = self.scenes[s].clips[track_index];
|
||||
track.reset = true;
|
||||
}
|
||||
},
|
||||
ArrangerFocus::Clip(t, s) => {
|
||||
self.tracks[t].sequence = self.scenes[s].clips[t];
|
||||
self.tracks[t].reset = true;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
|
||||
return Split::right([
|
||||
|
|
@ -270,4 +368,195 @@ impl Arranger {
|
|||
},
|
||||
]).render(buf, area)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Track management methods
|
||||
impl Arranger {
|
||||
pub fn track (&self) -> Option<&Track> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
self.selected.track_next(self.tracks.len())
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||
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])
|
||||
}
|
||||
pub fn track_del (&mut self) {
|
||||
unimplemented!("Arranger::track_del");
|
||||
}
|
||||
pub fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene management methods
|
||||
impl Arranger {
|
||||
pub fn scene (&self) -> Option<&Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
|
||||
}
|
||||
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
|
||||
}
|
||||
pub fn scene_next (&mut self) {
|
||||
self.selected.scene_next(self.scenes.len())
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
self.selected.scene_prev()
|
||||
}
|
||||
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||
let clips = vec![None;self.tracks.len()];
|
||||
self.scenes.push(match name {
|
||||
Some(name) => Scene::new(name, clips),
|
||||
None => Scene::new(&self.track_default_name(), clips),
|
||||
});
|
||||
let index = self.scenes.len() - 1;
|
||||
Ok(&mut self.scenes[index])
|
||||
}
|
||||
pub fn scene_del (&mut self) {
|
||||
unimplemented!("Arranger::scene_del");
|
||||
}
|
||||
pub fn scene_default_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Phrase management methods
|
||||
impl Arranger {
|
||||
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_next (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//if phrase_index >= phrases - 1 {
|
||||
//scene.clips[track_index] = None;
|
||||
//} else {
|
||||
//scene.clips[track_index] = Some(phrase_index + 1);
|
||||
//}
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(0);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
pub fn phrase_prev (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//scene.clips[track_index] = if phrase_index == 0 {
|
||||
//None
|
||||
//} else {
|
||||
//Some(phrase_index - 1)
|
||||
//};
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(phrases - 1);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangerFocus {
|
||||
/// The whole mix is selected
|
||||
Mix,
|
||||
/// A track is selected.
|
||||
Track(usize),
|
||||
/// A scene is selected.
|
||||
Scene(usize),
|
||||
/// A clip (track × scene) is selected.
|
||||
Clip(usize, usize),
|
||||
}
|
||||
|
||||
/// Focus identification methods
|
||||
impl ArrangerFocus {
|
||||
pub fn is_track (&self) -> bool {
|
||||
match self { Self::Track(_) => true, _ => false }
|
||||
}
|
||||
pub fn is_scene (&self) -> bool {
|
||||
match self { Self::Scene(_) => true, _ => false }
|
||||
}
|
||||
pub fn is_clip (&self) -> bool {
|
||||
match self { Self::Clip(_, _) => true, _ => false }
|
||||
}
|
||||
}
|
||||
|
||||
/// Track focus methods
|
||||
impl ArrangerFocus {
|
||||
pub fn track (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(t, _) => Some(*t),
|
||||
Self::Track(t) => Some(*t),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn track_next (&mut self, last_track: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Track(0),
|
||||
Self::Track(t) => Self::Track(last_track.min(*t + 1)),
|
||||
Self::Scene(s) => Self::Clip(0, *s),
|
||||
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s),
|
||||
}
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Scene(s) => Self::Scene(*s),
|
||||
Self::Track(0) => Self::Mix,
|
||||
Self::Track(t) => Self::Track(*t - 1),
|
||||
Self::Clip(t, s) => Self::Clip(t.saturating_sub(1), *s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene focus methods
|
||||
impl ArrangerFocus {
|
||||
pub fn scene (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(_, s) => Some(*s),
|
||||
Self::Scene(s) => Some(*s),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn scene_next (&mut self, last_scene: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Scene(0),
|
||||
Self::Track(t) => Self::Scene(*t),
|
||||
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)),
|
||||
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)),
|
||||
}
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Track(t) => Self::Track(*t),
|
||||
Self::Scene(0) => Self::Mix,
|
||||
Self::Scene(s) => Self::Scene(*s - 1),
|
||||
Self::Clip(t, s) => Self::Clip(*t, s.saturating_sub(1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
361
src/devices/sequencer.rs
Normal file
361
src/devices/sequencer.rs
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
/// Phrase editor.
|
||||
|
||||
use crate::{core::*, model::*, view::*};
|
||||
|
||||
/// 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| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.note_axis.point_dec(); },
|
||||
false => { app.sequencer.note_axis.start_dec(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "seq_cursor_down", "move cursor down", |app: &mut App| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.note_axis.point_inc(); },
|
||||
false => { app.sequencer.note_axis.start_inc(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.time_axis.point_dec(); },
|
||||
false => { app.sequencer.time_axis.start_dec(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.time_axis.point_inc(); },
|
||||
false => { app.sequencer.time_axis.start_inc(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.sequencer.mode = !app.sequencer.mode;
|
||||
Ok(true)
|
||||
}],
|
||||
/*
|
||||
[Char('a'), NONE, "note_add", "Add note", note_add],
|
||||
[Char('z'), NONE, "note_del", "Delete note", note_del],
|
||||
[CapsLock, NONE, "advance", "Toggle auto advance", nop],
|
||||
[Char('w'), NONE, "rest", "Advance by note duration", nop],
|
||||
*/
|
||||
});
|
||||
|
||||
/// Phrase editor.
|
||||
pub struct Sequencer {
|
||||
pub mode: bool,
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
|
||||
pub phrase: Option<Arc<RwLock<Phrase>>>,
|
||||
pub buffer: Buffer,
|
||||
pub keys: Buffer,
|
||||
/// Highlight input keys
|
||||
pub keys_in: [bool; 128],
|
||||
/// Highlight output keys
|
||||
pub keys_out: [bool; 128],
|
||||
|
||||
pub now: usize,
|
||||
pub ppq: usize,
|
||||
pub note_axis: FixedAxis<u16>,
|
||||
pub time_axis: ScaledAxis<u16>,
|
||||
|
||||
}
|
||||
|
||||
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 Sequencer {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
buffer: Buffer::empty(Rect::default()),
|
||||
keys: keys_vert(),
|
||||
entered: false,
|
||||
focused: false,
|
||||
mode: false,
|
||||
keys_in: [false;128],
|
||||
keys_out: [false;128],
|
||||
phrase: None,
|
||||
now: 0,
|
||||
ppq: 96,
|
||||
note_axis: FixedAxis {
|
||||
start: 12,
|
||||
point: Some(36)
|
||||
},
|
||||
time_axis: ScaledAxis {
|
||||
start: 0,
|
||||
scale: 24,
|
||||
point: Some(0)
|
||||
},
|
||||
}
|
||||
}
|
||||
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
||||
/// FIXME: Support phrases longer that 65536 ticks
|
||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) -> Usually<()> {
|
||||
self.phrase = phrase.map(Clone::clone);
|
||||
if let Some(ref phrase) = self.phrase {
|
||||
let width = u16::MAX.min(phrase.read().unwrap().length as u16);
|
||||
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height: 64 });
|
||||
let phrase = phrase.read().unwrap();
|
||||
fill_seq_bg(&mut buffer, phrase.length, self.ppq)?;
|
||||
fill_seq_fg(&mut buffer, &phrase)?;
|
||||
self.buffer = buffer;
|
||||
} else {
|
||||
self.buffer = Buffer::empty(Rect::default())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn style_focus (&self) -> Option<Style> {
|
||||
Some(if self.focused {
|
||||
Style::default().green().not_dim()
|
||||
} else {
|
||||
Style::default().green().dim()
|
||||
})
|
||||
}
|
||||
|
||||
fn style_timer_step (now: usize, step: usize, next_step: usize) -> Style {
|
||||
if step <= now && now < next_step {
|
||||
Style::default().yellow().bold().not_dim()
|
||||
} else {
|
||||
Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn index_to_color (&self, index: u16, default: Color) -> Color {
|
||||
let index = index as usize;
|
||||
if self.keys_in[index] && self.keys_out[index] {
|
||||
Color::Yellow
|
||||
} else if self.keys_in[index] {
|
||||
Color::Red
|
||||
} else if self.keys_out[index] {
|
||||
Color::Green
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
const H_KEYS_OFFSET: u16 = 5;
|
||||
|
||||
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
|
||||
self.horizontal_keys(buf, area)?;
|
||||
if let Some(ref phrase) = self.phrase {
|
||||
self.horizontal_timer(buf, area, phrase)?;
|
||||
}
|
||||
self.horizontal_notes(buf, area)?;
|
||||
self.horizontal_cursor(buf, area)?;
|
||||
self.horizontal_quant(buf, area)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn horizontal_notes (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let area = Rect {
|
||||
x: area.x + Self::H_KEYS_OFFSET,
|
||||
y: area.y + 1,
|
||||
width: area.width - Self::H_KEYS_OFFSET,
|
||||
height: area.height - 2
|
||||
};
|
||||
buffer_update(buf, area, &|cell, x, y|{
|
||||
let src_x = (x + self.time_axis.start) * self.time_axis.scale;
|
||||
let src_y = y + self.note_axis.start;
|
||||
if src_x < self.buffer.area.width && src_y < self.buffer.area.height - 1 {
|
||||
let src = self.buffer.get(src_x, self.buffer.area.height - src_y);
|
||||
cell.set_symbol(src.symbol());
|
||||
cell.set_fg(src.fg);
|
||||
}
|
||||
});
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let area = Rect {
|
||||
x: area.x,
|
||||
y: area.y + 1,
|
||||
width: 5,
|
||||
height: area.height - 2
|
||||
};
|
||||
buffer_update(buf, area, &|cell, x, y|{
|
||||
let y = y + self.note_axis.start;
|
||||
if x < self.keys.area.width && y < self.keys.area.height {
|
||||
*cell = self.keys.get(x, y).clone()
|
||||
}
|
||||
});
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_quant (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let quant = ppq_to_name(self.time_axis.scale);
|
||||
let quant_x = area.x + area.width - 1 - quant.len() as u16;
|
||||
let quant_y = area.y + area.height - 2;
|
||||
quant.blit(buf, quant_x, quant_y, self.style_focus())
|
||||
}
|
||||
|
||||
fn horizontal_cursor (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
|
||||
let x = area.x + Self::H_KEYS_OFFSET + time as u16;
|
||||
let y = area.y + 1 + note as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
c.blit(buf, x, y, self.style_focus())
|
||||
} else {
|
||||
Ok(Rect::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn horizontal_timer (
|
||||
&self, buf: &mut Buffer, area: Rect, phrase: &RwLock<Phrase>
|
||||
) -> Usually<Rect> {
|
||||
let phrase = phrase.read().unwrap();
|
||||
let (time0, time_z, now) = (self.time_axis.start, self.time_axis.scale, self.now % phrase.length);
|
||||
let Rect { x, width, .. } = area;
|
||||
for x in x+Self::H_KEYS_OFFSET..x+width {
|
||||
let step = (time0 + (x-Self::H_KEYS_OFFSET)) * time_z;
|
||||
let next_step = (time0 + (x-Self::H_KEYS_OFFSET) + 1) * time_z;
|
||||
let style = Self::style_timer_step(now, step as usize, next_step as usize);
|
||||
"-".blit(buf, x, area.y, Some(style))?;
|
||||
}
|
||||
return Ok(Rect { x: area.x, y: area.y, width: area.width, height: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
fn keys_vert () -> Buffer {
|
||||
let area = Rect { x: 0, y: 0, width: 5, height: 64 };
|
||||
let mut buffer = Buffer::empty(area);
|
||||
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);
|
||||
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));
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
buffer
|
||||
}
|
||||
|
||||
fn nth_octave (index: u16) -> &'static str {
|
||||
match index {
|
||||
0 => "-1",
|
||||
1 => "0",
|
||||
2 => "1",
|
||||
3 => "2",
|
||||
4 => "3",
|
||||
5 => "4",
|
||||
6 => "5",
|
||||
7 => "6",
|
||||
8 => "7",
|
||||
9 => "8",
|
||||
10 => "9",
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn key_colors (index: u16) -> (Color, Color) {
|
||||
match index % 6 {
|
||||
0 => (Color::White, Color::Black),
|
||||
1 => (Color::White, Color::Black),
|
||||
2 => (Color::White, Color::White),
|
||||
3 => (Color::Black, Color::White),
|
||||
4 => (Color::Black, Color::White),
|
||||
5 => (Color::Black, Color::White),
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_seq_bg (buf: &mut Buffer, length: usize, ppq: usize) -> Usually<()> {
|
||||
for x in 0 .. buf.area.width - buf.area.x {
|
||||
if x as usize >= length {
|
||||
break
|
||||
}
|
||||
let style = Style::default();
|
||||
let cell = buf.get_mut(x, buf.area.y);
|
||||
cell.set_char('-');
|
||||
cell.set_style(style);
|
||||
for y in 0 .. buf.area.height - buf.area.y {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(char_seq_bg(ppq, x));
|
||||
cell.set_fg(Color::Gray);
|
||||
cell.modifier = Modifier::DIM;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn char_seq_bg (ppq: usize, x: u16) -> char {
|
||||
if ppq == 0 {
|
||||
'·'
|
||||
} else if x % (4 * ppq as u16) == 0 {
|
||||
'│'
|
||||
} else if x % ppq as u16 == 0 {
|
||||
'╎'
|
||||
} else {
|
||||
'·'
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_seq_fg (buf: &mut Buffer, phrase: &Phrase) -> Usually<()> {
|
||||
let mut notes_on = [false;128];
|
||||
for x in 0 .. buf.area.width - buf.area.x {
|
||||
if x as usize >= phrase.length {
|
||||
break
|
||||
}
|
||||
if let Some(notes) = phrase.notes.get(x as usize) {
|
||||
for note in notes {
|
||||
if phrase.percussive {
|
||||
match note {
|
||||
MidiMessage::NoteOn { key, .. } =>
|
||||
notes_on[key.as_int() as usize] = true,
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
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.area.height - buf.area.y) / 2 {
|
||||
if y >= 64 {
|
||||
break
|
||||
}
|
||||
if let Some(block) = half_block(
|
||||
notes_on[y as usize * 2],
|
||||
notes_on[y as usize * 2 + 1],
|
||||
) {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(block);
|
||||
cell.set_fg(Color::White);
|
||||
}
|
||||
}
|
||||
if phrase.percussive {
|
||||
notes_on.fill(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ extern crate clap;
|
|||
extern crate jack as _jack;
|
||||
extern crate crossterm;
|
||||
|
||||
mod core; crate::core::pubmod! { cli config control model view jack edn }
|
||||
mod core; crate::core::pubmod! { cli config control devices model view jack edn }
|
||||
|
||||
use crate::{core::*, model::*};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//! Application state.
|
||||
|
||||
submod! { axis arranger looper mixer phrase plugin sampler sequencer scene track transport }
|
||||
use crate::{core::*, devices::{arranger::*, sequencer::*}};
|
||||
|
||||
use crate::core::*;
|
||||
submod! { axis looper mixer phrase plugin sampler scene track transport }
|
||||
|
||||
/// Root of application state.
|
||||
pub struct App {
|
||||
|
|
|
|||
|
|
@ -1,234 +0,0 @@
|
|||
use crate::{core::*, model::*};
|
||||
|
||||
/// Represents the tracks and scenes of the composition.
|
||||
pub struct Arranger {
|
||||
/// Display mode of arranger
|
||||
pub mode: bool,
|
||||
/// Currently selected element.
|
||||
pub selected: ArrangerFocus,
|
||||
/// Collection of tracks.
|
||||
pub tracks: Vec<Track>,
|
||||
/// Collection of scenes.
|
||||
pub scenes: Vec<Scene>,
|
||||
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
}
|
||||
|
||||
impl Arranger {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
mode: false,
|
||||
selected: ArrangerFocus::Clip(0, 0),
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
entered: true,
|
||||
focused: true,
|
||||
}
|
||||
}
|
||||
pub fn activate (&mut self) {
|
||||
match self.selected {
|
||||
ArrangerFocus::Scene(s) => {
|
||||
for (track_index, track) in self.tracks.iter_mut().enumerate() {
|
||||
track.sequence = self.scenes[s].clips[track_index];
|
||||
track.reset = true;
|
||||
}
|
||||
},
|
||||
ArrangerFocus::Clip(t, s) => {
|
||||
self.tracks[t].sequence = self.scenes[s].clips[t];
|
||||
self.tracks[t].reset = true;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Track management methods
|
||||
impl Arranger {
|
||||
pub fn track (&self) -> Option<&Track> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
self.selected.track_next(self.tracks.len())
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||
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])
|
||||
}
|
||||
pub fn track_del (&mut self) {
|
||||
unimplemented!("Arranger::track_del");
|
||||
}
|
||||
pub fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene management methods
|
||||
impl Arranger {
|
||||
pub fn scene (&self) -> Option<&Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
|
||||
}
|
||||
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
|
||||
}
|
||||
pub fn scene_next (&mut self) {
|
||||
self.selected.scene_next(self.scenes.len())
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
self.selected.scene_prev()
|
||||
}
|
||||
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||
let clips = vec![None;self.tracks.len()];
|
||||
self.scenes.push(match name {
|
||||
Some(name) => Scene::new(name, clips),
|
||||
None => Scene::new(&self.track_default_name(), clips),
|
||||
});
|
||||
let index = self.scenes.len() - 1;
|
||||
Ok(&mut self.scenes[index])
|
||||
}
|
||||
pub fn scene_del (&mut self) {
|
||||
unimplemented!("Arranger::scene_del");
|
||||
}
|
||||
pub fn scene_default_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Phrase management methods
|
||||
impl Arranger {
|
||||
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_next (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//if phrase_index >= phrases - 1 {
|
||||
//scene.clips[track_index] = None;
|
||||
//} else {
|
||||
//scene.clips[track_index] = Some(phrase_index + 1);
|
||||
//}
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(0);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
pub fn phrase_prev (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//scene.clips[track_index] = if phrase_index == 0 {
|
||||
//None
|
||||
//} else {
|
||||
//Some(phrase_index - 1)
|
||||
//};
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(phrases - 1);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangerFocus {
|
||||
/// The whole mix is selected
|
||||
Mix,
|
||||
/// A track is selected.
|
||||
Track(usize),
|
||||
/// A scene is selected.
|
||||
Scene(usize),
|
||||
/// A clip (track × scene) is selected.
|
||||
Clip(usize, usize),
|
||||
}
|
||||
|
||||
/// Identification methods
|
||||
impl ArrangerFocus {
|
||||
pub fn is_track (&self) -> bool {
|
||||
match self { Self::Track(_) => true, _ => false }
|
||||
}
|
||||
pub fn is_scene (&self) -> bool {
|
||||
match self { Self::Scene(_) => true, _ => false }
|
||||
}
|
||||
pub fn is_clip (&self) -> bool {
|
||||
match self { Self::Clip(_, _) => true, _ => false }
|
||||
}
|
||||
}
|
||||
|
||||
/// Track methods
|
||||
impl ArrangerFocus {
|
||||
pub fn track (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(t, _) => Some(*t),
|
||||
Self::Track(t) => Some(*t),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn track_next (&mut self, last_track: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Track(0),
|
||||
Self::Track(t) => Self::Track(last_track.min(*t + 1)),
|
||||
Self::Scene(s) => Self::Clip(0, *s),
|
||||
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s),
|
||||
}
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Scene(s) => Self::Scene(*s),
|
||||
Self::Track(0) => Self::Mix,
|
||||
Self::Track(t) => Self::Track(*t - 1),
|
||||
Self::Clip(t, s) => Self::Clip(t.saturating_sub(1), *s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene methods
|
||||
impl ArrangerFocus {
|
||||
pub fn scene (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(_, s) => Some(*s),
|
||||
Self::Scene(s) => Some(*s),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn scene_next (&mut self, last_scene: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Scene(0),
|
||||
Self::Track(t) => Self::Scene(*t),
|
||||
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)),
|
||||
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)),
|
||||
}
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Track(t) => Self::Track(*t),
|
||||
Self::Scene(0) => Self::Mix,
|
||||
Self::Scene(s) => Self::Scene(*s - 1),
|
||||
Self::Clip(t, s) => Self::Clip(*t, s.saturating_sub(1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
use crate::{core::*, model::{FixedAxis, ScaledAxis, Phrase}};
|
||||
|
||||
pub struct Sequencer {
|
||||
pub mode: bool,
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
|
||||
pub phrase: Option<Arc<RwLock<Phrase>>>,
|
||||
pub buffer: Buffer,
|
||||
pub keys: Buffer,
|
||||
/// Highlight input keys
|
||||
pub keys_in: [bool; 128],
|
||||
/// Highlight output keys
|
||||
pub keys_out: [bool; 128],
|
||||
|
||||
pub now: usize,
|
||||
pub ppq: usize,
|
||||
pub note_axis: FixedAxis<u16>,
|
||||
pub time_axis: ScaledAxis<u16>,
|
||||
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
buffer: Buffer::empty(Rect::default()),
|
||||
keys: keys_vert(),
|
||||
entered: false,
|
||||
focused: false,
|
||||
mode: false,
|
||||
keys_in: [false;128],
|
||||
keys_out: [false;128],
|
||||
phrase: None,
|
||||
now: 0,
|
||||
ppq: 96,
|
||||
note_axis: FixedAxis {
|
||||
start: 12,
|
||||
point: Some(36)
|
||||
},
|
||||
time_axis: ScaledAxis {
|
||||
start: 0,
|
||||
scale: 24,
|
||||
point: Some(0)
|
||||
},
|
||||
}
|
||||
}
|
||||
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
||||
/// FIXME: Support phrases longer that 65536 ticks
|
||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) -> Usually<()> {
|
||||
self.phrase = phrase.map(Clone::clone);
|
||||
if let Some(ref phrase) = self.phrase {
|
||||
let width = u16::MAX.min(phrase.read().unwrap().length as u16);
|
||||
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height: 64 });
|
||||
let phrase = phrase.read().unwrap();
|
||||
fill_seq_bg(&mut buffer, phrase.length, self.ppq)?;
|
||||
fill_seq_fg(&mut buffer, &phrase)?;
|
||||
self.buffer = buffer;
|
||||
} else {
|
||||
self.buffer = Buffer::empty(Rect::default())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn keys_vert () -> Buffer {
|
||||
let area = Rect { x: 0, y: 0, width: 5, height: 64 };
|
||||
let mut buffer = Buffer::empty(area);
|
||||
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);
|
||||
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));
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
buffer
|
||||
}
|
||||
|
||||
fn nth_octave (index: u16) -> &'static str {
|
||||
match index {
|
||||
0 => "-1",
|
||||
1 => "0",
|
||||
2 => "1",
|
||||
3 => "2",
|
||||
4 => "3",
|
||||
5 => "4",
|
||||
6 => "5",
|
||||
7 => "6",
|
||||
8 => "7",
|
||||
9 => "8",
|
||||
10 => "9",
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn key_colors (index: u16) -> (Color, Color) {
|
||||
match index % 6 {
|
||||
0 => (Color::White, Color::Black),
|
||||
1 => (Color::White, Color::Black),
|
||||
2 => (Color::White, Color::White),
|
||||
3 => (Color::Black, Color::White),
|
||||
4 => (Color::Black, Color::White),
|
||||
5 => (Color::Black, Color::White),
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_seq_bg (buf: &mut Buffer, length: usize, ppq: usize) -> Usually<()> {
|
||||
for x in 0 .. buf.area.width - buf.area.x {
|
||||
if x as usize >= length {
|
||||
break
|
||||
}
|
||||
let style = Style::default();
|
||||
let cell = buf.get_mut(x, buf.area.y);
|
||||
cell.set_char('-');
|
||||
cell.set_style(style);
|
||||
for y in 0 .. buf.area.height - buf.area.y {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(char_seq_bg(ppq, x));
|
||||
cell.set_fg(Color::Gray);
|
||||
cell.modifier = Modifier::DIM;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn char_seq_bg (ppq: usize, x: u16) -> char {
|
||||
if ppq == 0 {
|
||||
'·'
|
||||
} else if x % (4 * ppq as u16) == 0 {
|
||||
'│'
|
||||
} else if x % ppq as u16 == 0 {
|
||||
'╎'
|
||||
} else {
|
||||
'·'
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_seq_fg (buf: &mut Buffer, phrase: &Phrase) -> Usually<()> {
|
||||
let mut notes_on = [false;128];
|
||||
for x in 0 .. buf.area.width - buf.area.x {
|
||||
if x as usize >= phrase.length {
|
||||
break
|
||||
}
|
||||
if let Some(notes) = phrase.notes.get(x as usize) {
|
||||
for note in notes {
|
||||
if phrase.percussive {
|
||||
match note {
|
||||
MidiMessage::NoteOn { key, .. } =>
|
||||
notes_on[key.as_int() as usize] = true,
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
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.area.height - buf.area.y) / 2 {
|
||||
if y >= 64 {
|
||||
break
|
||||
}
|
||||
if let Some(block) = half_block(
|
||||
notes_on[y as usize * 2],
|
||||
notes_on[y as usize * 2 + 1],
|
||||
) {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(block);
|
||||
cell.set_fg(Color::White);
|
||||
}
|
||||
}
|
||||
if phrase.percussive {
|
||||
notes_on.fill(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
use crate::{render, App, core::*};
|
||||
|
||||
submod! {
|
||||
arranger border chain help plugin sequencer split theme transport
|
||||
}
|
||||
submod! { border chain help plugin split theme transport }
|
||||
|
||||
render!(App |self, buf, area| {
|
||||
Split::down([
|
||||
|
|
|
|||
|
|
@ -1,124 +0,0 @@
|
|||
use crate::{core::*,model::*,view::*};
|
||||
|
||||
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 Sequencer {
|
||||
fn style_focus (&self) -> Option<Style> {
|
||||
Some(if self.focused {
|
||||
Style::default().green().not_dim()
|
||||
} else {
|
||||
Style::default().green().dim()
|
||||
})
|
||||
}
|
||||
fn style_timer_step (now: usize, step: usize, next_step: usize) -> Style {
|
||||
if step <= now && now < next_step {
|
||||
Style::default().yellow().bold().not_dim()
|
||||
} else {
|
||||
Style::default()
|
||||
}
|
||||
}
|
||||
fn index_to_color (&self, index: u16, default: Color) -> Color {
|
||||
let index = index as usize;
|
||||
if self.keys_in[index] && self.keys_out[index] {
|
||||
Color::Yellow
|
||||
} else if self.keys_in[index] {
|
||||
Color::Red
|
||||
} else if self.keys_out[index] {
|
||||
Color::Green
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
if let Some(ref phrase) = self.phrase {
|
||||
self.horizontal_timer(buf, area, phrase)?;
|
||||
}
|
||||
self.horizontal_notes(buf, area)?;
|
||||
self.horizontal_cursor(buf, area)?;
|
||||
self.horizontal_quant(buf, area)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn horizontal_notes (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let area = Rect {
|
||||
x: area.x + Self::H_KEYS_OFFSET,
|
||||
y: area.y + 1,
|
||||
width: area.width - Self::H_KEYS_OFFSET,
|
||||
height: area.height - 2
|
||||
};
|
||||
buffer_update(buf, area, &|cell, x, y|{
|
||||
let src_x = (x + self.time_axis.start) * self.time_axis.scale;
|
||||
let src_y = y + self.note_axis.start;
|
||||
if src_x < self.buffer.area.width && src_y < self.buffer.area.height - 1 {
|
||||
let src = self.buffer.get(src_x, self.buffer.area.height - src_y);
|
||||
cell.set_symbol(src.symbol());
|
||||
cell.set_fg(src.fg);
|
||||
}
|
||||
});
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let area = Rect {
|
||||
x: area.x,
|
||||
y: area.y + 1,
|
||||
width: 5,
|
||||
height: area.height - 2
|
||||
};
|
||||
buffer_update(buf, area, &|cell, x, y|{
|
||||
let y = y + self.note_axis.start;
|
||||
if x < self.keys.area.width && y < self.keys.area.height {
|
||||
*cell = self.keys.get(x, y).clone()
|
||||
}
|
||||
});
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_quant (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let quant = ppq_to_name(self.time_axis.scale);
|
||||
let quant_x = area.x + area.width - 1 - quant.len() as u16;
|
||||
let quant_y = area.y + area.height - 2;
|
||||
quant.blit(buf, quant_x, quant_y, self.style_focus())
|
||||
}
|
||||
|
||||
fn horizontal_cursor (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
|
||||
let x = area.x + Self::H_KEYS_OFFSET + time as u16;
|
||||
let y = area.y + 1 + note as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
c.blit(buf, x, y, self.style_focus())
|
||||
} else {
|
||||
Ok(Rect::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn horizontal_timer (
|
||||
&self, buf: &mut Buffer, area: Rect, phrase: &RwLock<Phrase>
|
||||
) -> Usually<Rect> {
|
||||
let phrase = phrase.read().unwrap();
|
||||
let (time0, time_z, now) = (self.time_axis.start, self.time_axis.scale, self.now % phrase.length);
|
||||
let Rect { x, width, .. } = area;
|
||||
for x in x+Self::H_KEYS_OFFSET..x+width {
|
||||
let step = (time0 + (x-Self::H_KEYS_OFFSET)) * time_z;
|
||||
let next_step = (time0 + (x-Self::H_KEYS_OFFSET) + 1) * time_z;
|
||||
let style = Self::style_timer_step(now, step as usize, next_step as usize);
|
||||
"-".blit(buf, x, area.y, Some(style))?;
|
||||
}
|
||||
return Ok(Rect { x: area.x, y: area.y, width: area.width, height: 1 })
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue