mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
extract Sequencer model
This commit is contained in:
parent
f347ca838b
commit
aa478099d9
14 changed files with 211 additions and 348 deletions
|
|
@ -78,15 +78,15 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
|||
Ok(true)
|
||||
}],
|
||||
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
|
||||
app.seq_buf.time_zoom = prev_note_length(app.seq_buf.time_zoom);
|
||||
app.sequencer.time_zoom = prev_note_length(app.sequencer.time_zoom);
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
|
||||
app.seq_buf.time_zoom = next_note_length(app.seq_buf.time_zoom);
|
||||
app.sequencer.time_zoom = next_note_length(app.sequencer.time_zoom);
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
||||
app.arranger.phrase_mut().map(|phrase|{
|
||||
app.arranger.phrase().map(|x|x.write().unwrap()).map(|mut phrase|{
|
||||
let mut notes = phrase.notes.clone();
|
||||
notes.extend_from_slice(&mut phrase.notes);
|
||||
phrase.notes = notes;
|
||||
|
|
|
|||
|
|
@ -6,36 +6,46 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
|||
app.arranger.mode = !app.arranger.mode;
|
||||
Ok(true)
|
||||
}],
|
||||
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| Ok(
|
||||
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.scene_prev();true},
|
||||
true => {app.arranger.track_prev();true},
|
||||
}
|
||||
)],
|
||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok(
|
||||
false => app.arranger.scene_prev(),
|
||||
true => app.arranger.track_prev(),
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.scene_next();true},
|
||||
true => {app.arranger.track_next();true},
|
||||
}
|
||||
)],
|
||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok(
|
||||
false => app.arranger.scene_next(),
|
||||
true => app.arranger.track_next(),
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.track_prev();true},
|
||||
true => {app.arranger.scene_prev();true},
|
||||
}
|
||||
)],
|
||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok(
|
||||
false => app.arranger.track_prev(),
|
||||
true => app.arranger.scene_prev(),
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| {
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.track_next();true},
|
||||
true => {app.arranger.scene_next();true}
|
||||
}
|
||||
)],
|
||||
false => app.arranger.track_next(),
|
||||
true => app.arranger.scene_next()
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
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| {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub const KEYMAP_CHAIN: &'static [KeyBinding<App>] = keymap!(App {
|
|||
Ok(false)
|
||||
}],
|
||||
[Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.chain_mode = !app.seq_mode;
|
||||
app.chain_mode = !app.chain_mode;
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
|||
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
|
||||
app.entered = false;
|
||||
app.transport.entered = app.entered;
|
||||
app.arranger.entered = app.entered;
|
||||
app.arranger.entered = app.entered;
|
||||
app.sequencer.entered = app.entered;
|
||||
Ok(true)
|
||||
}],
|
||||
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
||||
app.entered = true;
|
||||
app.transport.entered = app.entered;
|
||||
app.arranger.entered = app.entered;
|
||||
app.arranger.entered = app.entered;
|
||||
app.sequencer.entered = app.entered;
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
|
@ -26,8 +28,10 @@ pub fn focus_next (app: &mut App) -> Usually<bool> {
|
|||
app.section.next();
|
||||
app.transport.focused = app.section == AppFocus::Transport;
|
||||
app.transport.entered = app.entered;
|
||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||
app.arranger.entered = app.entered;
|
||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||
app.arranger.entered = app.entered;
|
||||
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||
app.sequencer.entered = app.entered;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +39,9 @@ pub fn focus_prev (app: &mut App) -> Usually<bool> {
|
|||
app.section.prev();
|
||||
app.transport.focused = app.section == AppFocus::Transport;
|
||||
app.transport.entered = app.entered;
|
||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||
app.arranger.entered = app.entered;
|
||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||
app.arranger.entered = app.entered;
|
||||
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||
app.sequencer.entered = app.entered;
|
||||
Ok(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,31 +3,31 @@ 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| {
|
||||
app.note_cursor = app.note_cursor.saturating_sub(1);
|
||||
app.sequencer.note_cursor = app.sequencer.note_cursor.saturating_sub(1);
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "seq_cursor_down", "move cursor up", |app: &mut App| {
|
||||
app.note_cursor = app.note_cursor + 1;
|
||||
app.sequencer.note_cursor = app.sequencer.note_cursor + 1;
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
|
||||
if app.entered {
|
||||
app.time_cursor = app.time_cursor.saturating_sub(1);
|
||||
if app.sequencer.entered {
|
||||
app.sequencer.time_cursor = app.sequencer.time_cursor.saturating_sub(1);
|
||||
} else {
|
||||
app.seq_buf.time_start = app.seq_buf.time_start.saturating_sub(1);
|
||||
app.sequencer.time_start = app.sequencer.time_start.saturating_sub(1);
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
|
||||
if app.entered {
|
||||
app.time_cursor = app.time_cursor + 1;
|
||||
if app.sequencer.entered {
|
||||
app.sequencer.time_cursor = app.sequencer.time_cursor + 1;
|
||||
} else {
|
||||
app.seq_buf.time_start = app.seq_buf.time_start + 1;
|
||||
app.sequencer.time_start = app.sequencer.time_start + 1;
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.seq_mode = !app.seq_mode;
|
||||
app.sequencer.mode = !app.sequencer.mode;
|
||||
Ok(true)
|
||||
}],
|
||||
// [Char('a'), NONE, "note_add", "Add note", note_add],
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ impl Track {
|
|||
_ => {}
|
||||
});
|
||||
let track = app.arranger.track_add(name)?;
|
||||
for phrase in phrases { track.phrases.push(phrase); }
|
||||
for phrase in phrases { track.phrases.push(Arc::new(RwLock::new(phrase))); }
|
||||
for device in devices { track.add_device(device)?; }
|
||||
Ok(track)
|
||||
}
|
||||
|
|
|
|||
26
src/model.rs
26
src/model.rs
|
|
@ -1,8 +1,8 @@
|
|||
//! Application state.
|
||||
|
||||
submod! { arranger looper mixer phrase plugin sampler scene track transport }
|
||||
submod! { arranger looper mixer phrase plugin sampler sequencer scene track transport }
|
||||
|
||||
use crate::{core::*, view::*};
|
||||
use crate::core::*;
|
||||
|
||||
/// Root of application state.
|
||||
pub struct App {
|
||||
|
|
@ -14,8 +14,10 @@ pub struct App {
|
|||
pub section: AppFocus,
|
||||
/// Transport model and view.
|
||||
pub transport: TransportToolbar,
|
||||
/// Arraneger model and view.
|
||||
/// Arranger model and view.
|
||||
pub arranger: Arranger,
|
||||
/// Phrase editor
|
||||
pub sequencer: Sequencer,
|
||||
/// Main JACK client.
|
||||
pub jack: Option<JackClient>,
|
||||
/// Map of external MIDI outs in the jack graph
|
||||
|
|
@ -31,17 +33,6 @@ pub struct App {
|
|||
pub audio_outs: Vec<Arc<Port<Unowned>>>,
|
||||
/// Number of frames requested by process callback
|
||||
chunk_size: usize,
|
||||
|
||||
/// Display mode of sequencer seciton
|
||||
pub seq_mode: bool,
|
||||
/// Display buffer for sequencer
|
||||
pub seq_buf: BufferedSequencerView,
|
||||
/// Display position of cursor within note range
|
||||
pub note_cursor: usize,
|
||||
/// Range of notes to display
|
||||
pub note_start: usize,
|
||||
/// Display position of cursor within time range
|
||||
pub time_cursor: usize,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -58,6 +49,7 @@ impl App {
|
|||
section: AppFocus::default(),
|
||||
transport: TransportToolbar::new(Some(jack.transport())),
|
||||
arranger: Arranger::new(),
|
||||
sequencer: Sequencer::new(),
|
||||
jack: Some(jack),
|
||||
audio_outs: vec![],
|
||||
chain_mode: false,
|
||||
|
|
@ -65,12 +57,6 @@ impl App {
|
|||
midi_in: None,
|
||||
midi_ins: vec![],
|
||||
xdg: Some(xdg),
|
||||
|
||||
seq_mode: false,
|
||||
seq_buf: BufferedSequencerView::new(96, 16384),
|
||||
note_cursor: 0,
|
||||
note_start: 2,
|
||||
time_cursor: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ impl Arranger {
|
|||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||
self.tracks.push(match name {
|
||||
Some(name) => Track::new(name, None, None)?,
|
||||
None => Track::new(&self.track_default_name(), None, None)?
|
||||
});
|
||||
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])
|
||||
}
|
||||
|
|
@ -106,15 +106,15 @@ impl Arranger {
|
|||
|
||||
/// Phrase management methods
|
||||
impl Arranger {
|
||||
pub fn phrase (&self) -> Option<&Phrase> {
|
||||
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_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() {
|
||||
|
|
|
|||
46
src/model/sequencer.rs
Normal file
46
src/model/sequencer.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use crate::{core::*, model::Phrase};
|
||||
|
||||
pub struct Sequencer {
|
||||
pub phrase: Option<Arc<RwLock<Phrase>>>,
|
||||
pub mode: bool,
|
||||
pub buffer: Buffer,
|
||||
pub now: usize,
|
||||
pub ppq: usize,
|
||||
pub note_cursor: usize,
|
||||
pub note_start: usize,
|
||||
pub time_cursor: usize,
|
||||
pub time_start: usize,
|
||||
pub time_zoom: usize,
|
||||
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
|
||||
/// Highlight input keys
|
||||
pub notes_in: [bool; 128],
|
||||
/// Highlight output keys
|
||||
pub notes_out: [bool; 128],
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
buffer: Buffer::empty(Rect::default()),
|
||||
entered: false,
|
||||
focused: false,
|
||||
mode: false,
|
||||
note_cursor: 0,
|
||||
note_start: 0,
|
||||
notes_in: [false;128],
|
||||
notes_out: [false;128],
|
||||
phrase: None,
|
||||
time_cursor: 0,
|
||||
time_start: 0,
|
||||
time_zoom: 12,
|
||||
now: 0,
|
||||
ppq: 96
|
||||
}
|
||||
}
|
||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||
self.phrase = phrase.map(Clone::clone);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ pub struct Track {
|
|||
/// Overdub input to sequence.
|
||||
pub overdub: bool,
|
||||
/// Map: tick -> MIDI events at tick
|
||||
pub phrases: Vec<Phrase>,
|
||||
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||
/// Phrase selector
|
||||
pub sequence: Option<usize>,
|
||||
/// Output from current sequence.
|
||||
|
|
@ -30,11 +30,7 @@ pub struct Track {
|
|||
}
|
||||
|
||||
impl Track {
|
||||
pub fn new (
|
||||
name: &str,
|
||||
phrases: Option<Vec<Phrase>>,
|
||||
devices: Option<Vec<JackDevice>>,
|
||||
) -> Usually<Self> {
|
||||
pub fn new (name: &str) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
midi_out: None,
|
||||
|
|
@ -45,8 +41,8 @@ impl Track {
|
|||
recording: false,
|
||||
overdub: true,
|
||||
sequence: None,
|
||||
phrases: phrases.unwrap_or_else(||Vec::with_capacity(16)),
|
||||
devices: devices.unwrap_or_else(||Vec::with_capacity(16)),
|
||||
phrases: vec![],
|
||||
devices: vec![],
|
||||
device: 0,
|
||||
reset: true,
|
||||
})
|
||||
|
|
@ -118,14 +114,18 @@ impl Track {
|
|||
) = (
|
||||
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id))
|
||||
) {
|
||||
if self.midi_out.is_some() {
|
||||
phrase.process_out(
|
||||
&mut self.midi_out_buf,
|
||||
&mut self.notes_out,
|
||||
timebase,
|
||||
(frame0.saturating_sub(start_frame), frames, period)
|
||||
);
|
||||
}
|
||||
phrase.read().map(|phrase|{
|
||||
if self.midi_out.is_some() {
|
||||
phrase.process_out(
|
||||
&mut self.midi_out_buf,
|
||||
&mut self.notes_out,
|
||||
timebase,
|
||||
(frame0.saturating_sub(start_frame), frames, period)
|
||||
);
|
||||
}
|
||||
});
|
||||
let mut phrase = phrase.write().unwrap();
|
||||
let length = phrase.length;
|
||||
// Monitor and record input
|
||||
if input.is_some() && (self.recording || self.monitoring) {
|
||||
// For highlighting keys and note repeat
|
||||
|
|
@ -143,7 +143,7 @@ impl Track {
|
|||
let quantized = (
|
||||
pulse / quant as f64
|
||||
).round() as usize * quant;
|
||||
let looped = quantized % phrase.length;
|
||||
let looped = quantized % length;
|
||||
looped
|
||||
}, message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ render!(App |self, buf, area| {
|
|||
&self.arranger,
|
||||
&If(self.arranger.selected.is_clip(), &Split::right([
|
||||
&ChainView::vertical(&self),
|
||||
&SequencerView::new(&self),
|
||||
&self.sequencer,
|
||||
]))
|
||||
]).render(buf, area)?;
|
||||
if let Some(ref modal) = self.modal {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ impl Arranger {
|
|||
let label = match scene.clips.get(track_index) {
|
||||
Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) {
|
||||
let icon = if track.sequence == Some(*clip) { "" } else { "┊" };
|
||||
format!("{icon} {}", phrase.name)
|
||||
format!("{icon} {}", phrase.read().unwrap().name)
|
||||
} else {
|
||||
format!(" ??? ")
|
||||
},
|
||||
|
|
@ -233,7 +233,7 @@ impl Arranger {
|
|||
&|buf: &mut Buffer, area: Rect|{
|
||||
let mut x2 = 0;
|
||||
let Rect { x, y, height, .. } = area;
|
||||
for (i, scene) in self.scenes.iter().enumerate() {
|
||||
for (_scene_index, scene) in self.scenes.iter().enumerate() {
|
||||
let active_scene = false;//self.selected == ArrangerFocus::Scene(i) || cursor.1 > 0 && self.cursor.1 - 1 == i;
|
||||
let sep = Some(if active_scene {
|
||||
Style::default().yellow().not_dim()
|
||||
|
|
@ -252,11 +252,10 @@ impl Arranger {
|
|||
let active_track = false;//self.cursor.0 > 0 && self.cursor.0 - 1 == i;
|
||||
if let Some(clip) = clip {
|
||||
let y2 = y + 1 + i as u16 * 2;
|
||||
let label = format!("{}", if let Some(phrase) = self.tracks[i].phrases.get(*clip) {
|
||||
&phrase.name
|
||||
} else {
|
||||
"...."
|
||||
});
|
||||
let label = match self.tracks[i].phrases.get(*clip) {
|
||||
Some(phrase) => &format!("{}", phrase.read().unwrap().name),
|
||||
None => "...."
|
||||
};
|
||||
label.blit(buf, x + x2, y2, Some(if active_track && active_scene {
|
||||
Style::default().not_dim().yellow().bold()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
pub struct LineBuffer {
|
||||
width: usize,
|
||||
cells: Vec<Cell>,
|
||||
style: Option<Style>,
|
||||
bg: Cell,
|
||||
}
|
||||
|
||||
impl LineBuffer {
|
||||
pub fn new (bg: Cell, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
style: None,
|
||||
width: width,
|
||||
cells: vec![bg.clone();width*height],
|
||||
bg,
|
||||
}
|
||||
}
|
||||
pub fn height (&self) -> usize {
|
||||
self.cells.len() / self.width
|
||||
}
|
||||
pub fn style (&mut self, style: Style) -> &mut Self {
|
||||
self.style = Some(style);
|
||||
self
|
||||
}
|
||||
pub fn no_style (&mut self) -> &mut Self {
|
||||
self.style = None;
|
||||
self
|
||||
}
|
||||
pub fn put (&mut self, data: &str, x: usize, y: usize) -> &mut Self {
|
||||
if x < self.width {
|
||||
for (i, c) in data.chars().enumerate() {
|
||||
if x + i >= self.width {
|
||||
break;
|
||||
}
|
||||
let index = y * self.width + x + i;
|
||||
while index >= self.cells.len() {
|
||||
self.cells.extend_from_slice(&vec![self.bg.clone();self.width]);
|
||||
}
|
||||
self.cells[index].set_char(c);
|
||||
if let Some(s) = self.style {
|
||||
self.cells[index].set_style(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn show (&self, buf: &mut Buffer, area: Rect, offset: isize) -> Usually<Rect> {
|
||||
let Rect { x, mut y, width, height } = area;
|
||||
for row in offset..self.height() as isize {
|
||||
let length = self.cells.len();
|
||||
let start = ((row.max(0) as usize)*self.width).min(length);
|
||||
let end = (((row + 1).max(0) as usize)*self.width).min(length);
|
||||
for (column, cell) in self.cells[start..end].iter().enumerate() {
|
||||
if column >= width as usize {
|
||||
break
|
||||
}
|
||||
*buf.get_mut(x + column as u16, y + row as u16) = cell.clone();
|
||||
}
|
||||
y = y + 1;
|
||||
if y > height {
|
||||
break
|
||||
}
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,140 +1,15 @@
|
|||
use crate::{core::*,model::*,view::*};
|
||||
|
||||
pub struct BufferedSequencerView {
|
||||
pub ppq: usize,
|
||||
pub length: usize,
|
||||
pub character: [Vec<char>;64],
|
||||
pub fg: [Vec<Color>;64],
|
||||
pub bg: [Vec<Color>;64],
|
||||
pub notes: [bool;128],
|
||||
/// 1st time step to displayRange of time steps to display
|
||||
pub time_start: usize,
|
||||
/// PPQ per display unit
|
||||
pub time_zoom: usize,
|
||||
}
|
||||
render!(BufferedSequencerView |self, buf, area| {
|
||||
let mut area = area;
|
||||
area.height = area.height.min(64);
|
||||
for y in 0..area.height {
|
||||
for x in 0..area.width {
|
||||
let cell = buf.get_mut(area.x + x, area.y + y);
|
||||
let time_index = (self.time_start + x as usize) * self.time_zoom;
|
||||
let note_index = 63 - y as usize;
|
||||
cell.set_char(self.character[note_index][time_index]);
|
||||
}
|
||||
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 BufferedSequencerView {
|
||||
pub fn new (ppq: usize, length: usize) -> Self {
|
||||
let dots: Vec<char> = vec!['·';length]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i,x)|if i % ppq == 0 { '|' } else { *x })
|
||||
.collect();
|
||||
Self {
|
||||
ppq,
|
||||
length,
|
||||
character: core::array::from_fn(|_|dots.clone()),
|
||||
fg: core::array::from_fn(|_|vec![Color::Reset;length]),
|
||||
bg: core::array::from_fn(|_|vec![Color::Reset;length]),
|
||||
notes: [false;128],
|
||||
time_start: 0,
|
||||
time_zoom: 12,
|
||||
}
|
||||
}
|
||||
pub fn update (&mut self, phrase: Option<&Phrase>) {
|
||||
if phrase.is_none() {
|
||||
return
|
||||
}
|
||||
let phrase = phrase.unwrap();
|
||||
if phrase.length != self.length {
|
||||
*self = Self::new(self.ppq, phrase.length);
|
||||
}
|
||||
self.notes = [false;128];
|
||||
for (x, messages) in phrase.notes.iter().enumerate() {
|
||||
for message in messages.iter() {
|
||||
match message {
|
||||
MidiMessage::NoteOn { key, .. } => {
|
||||
self.notes[key.as_int() as usize] = true;
|
||||
},
|
||||
MidiMessage::NoteOff { key, .. } => {
|
||||
self.notes[key.as_int() as usize] = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for y in 0..64 {
|
||||
let note1 = self.notes[y * 2];
|
||||
let note2 = self.notes[y * 2 + 1];
|
||||
if let Some(block) = half_block(note1, note2) {
|
||||
self.character[63 - y][x] = block;
|
||||
} else if x % self.ppq == 0 {
|
||||
self.character[63 - y][x] = '|';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SequencerView<'a> {
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
/// Displayed phrase
|
||||
phrase: Option<&'a Phrase>,
|
||||
/// Resolution of MIDI sequence
|
||||
ppq: usize,
|
||||
/// Range of notes to display
|
||||
note_start: usize,
|
||||
/// Position of cursor within note range
|
||||
note_cursor: usize,
|
||||
/// PPQ per display unit
|
||||
time_zoom: usize,
|
||||
/// Range of time steps to display
|
||||
time_start: usize,
|
||||
/// Position of cursor within time range
|
||||
time_cursor: usize,
|
||||
/// Current time
|
||||
now: usize,
|
||||
|
||||
/// Highlight input keys
|
||||
notes_in: &'a [bool; 128],
|
||||
/// Highlight output keys
|
||||
notes_out: &'a [bool; 128],
|
||||
}
|
||||
|
||||
impl<'a> SequencerView<'a> {
|
||||
pub fn new (app: &'a App) -> Self {
|
||||
let track = app.arranger.track();
|
||||
Self {
|
||||
phrase: app.arranger.phrase(),
|
||||
focused: app.section == AppFocus::Sequencer,
|
||||
entered: app.entered,
|
||||
ppq: app.transport.ppq(),
|
||||
now: app.transport.pulse(),
|
||||
time_cursor: app.time_cursor,
|
||||
time_start: app.seq_buf.time_start,
|
||||
time_zoom: app.seq_buf.time_zoom,
|
||||
note_cursor: app.note_cursor,
|
||||
note_start: app.note_start,
|
||||
notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] },
|
||||
notes_out: if let Some(track) = track { &track.notes_out } else { &[false;128] },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Render for SequencerView<'a> {
|
||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
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<'a> SequencerView<'a> {
|
||||
impl Sequencer {
|
||||
fn style_focus (&self) -> Option<Style> {
|
||||
Some(if self.focused {
|
||||
Style::default().green().not_dim()
|
||||
|
|
@ -162,15 +37,16 @@ impl<'a> SequencerView<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> SequencerView<'a> {
|
||||
/// 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)?;
|
||||
self.horizontal_quant(buf, area)?;
|
||||
self.horizontal_timer(buf, area, self.phrase)?;
|
||||
self.horizontal_lanes(buf, area, self.phrase)?;
|
||||
self.horizontal_timer(buf, area, &self.phrase)?;
|
||||
self.horizontal_lanes(buf, area, &self.phrase)?;
|
||||
self.horizontal_cursor(buf, area)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -190,8 +66,11 @@ impl<'a> SequencerView<'a> {
|
|||
c.blit(buf, x, y, self.style_focus())
|
||||
}
|
||||
|
||||
fn horizontal_timer (&self, buf: &mut Buffer, area: Rect, phrase: Option<&Phrase>) -> Usually<Rect> {
|
||||
fn horizontal_timer (
|
||||
&self, buf: &mut Buffer, area: Rect, phrase: &Option<Arc<RwLock<Phrase>>>
|
||||
) -> Usually<Rect> {
|
||||
if let Some(phrase) = phrase {
|
||||
let phrase = phrase.read().unwrap();
|
||||
let (time0, time_z, now) = (self.time_start, self.time_zoom, self.now % phrase.length);
|
||||
let Rect { x, width, .. } = area;
|
||||
for x in x+Self::H_KEYS_OFFSET..x+width {
|
||||
|
|
@ -252,68 +131,71 @@ impl<'a> SequencerView<'a> {
|
|||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_lanes (&self, buf: &mut Buffer, area: Rect, phrase: Option<&Phrase>) -> Usually<Rect> {
|
||||
if phrase.is_none() {
|
||||
return Ok(Rect { x: area.x, y: area.x, width: 0, height: 0 })
|
||||
}
|
||||
let Rect { x, y, width, height } = area;
|
||||
let phrase = phrase.unwrap();
|
||||
let now = self.now % phrase.length;
|
||||
let ppq = self.ppq;
|
||||
let time_z = self.time_zoom;
|
||||
let time0 = self.time_start;
|
||||
let note0 = self.note_start;
|
||||
let dim = Style::default().dim();
|
||||
let offset = Self::H_KEYS_OFFSET;
|
||||
let phrase_area = Rect { x: x + offset, y, width: width - offset, height: height - 2 };
|
||||
let mut steps = Vec::with_capacity(phrase_area.width as usize);
|
||||
for x in phrase_area.x .. phrase_area.x + phrase_area.width {
|
||||
let x0 = x.saturating_sub(phrase_area.x) as usize;
|
||||
let step = (0 + time0 + x0) * time_z;
|
||||
let next = (1 + time0 + x0) * time_z;
|
||||
if step >= phrase.length {
|
||||
break
|
||||
}
|
||||
let style = Self::style_timer_step(now, step, next);
|
||||
let cell = buf.get_mut(x, area.y);
|
||||
cell.set_char('-');
|
||||
cell.set_style(style);
|
||||
steps.push((x, step, next));
|
||||
for y in phrase_area.y .. phrase_area.y + phrase_area.height {
|
||||
if y == phrase_area.y {
|
||||
if step % (4 * ppq) == 0 {
|
||||
format!("{}", 1 + step / (4 * ppq)).blit(buf, x, y, None)?;
|
||||
} else if step % ppq == 0 {
|
||||
fn horizontal_lanes (
|
||||
&self, buf: &mut Buffer, area: Rect, phrase: &Option<Arc<RwLock<Phrase>>>
|
||||
) -> Usually<Rect> {
|
||||
if let Some(phrase) = phrase {
|
||||
let Rect { x, y, width, height } = area;
|
||||
let phrase = phrase.read().unwrap();
|
||||
let now = self.now % phrase.length;
|
||||
let ppq = self.ppq;
|
||||
let time_z = self.time_zoom;
|
||||
let time0 = self.time_start;
|
||||
let note0 = self.note_start;
|
||||
let dim = Style::default().dim();
|
||||
let offset = Self::H_KEYS_OFFSET;
|
||||
let phrase_area = Rect { x: x + offset, y, width: width - offset, height: height - 2 };
|
||||
let mut steps = Vec::with_capacity(phrase_area.width as usize);
|
||||
for x in phrase_area.x .. phrase_area.x + phrase_area.width {
|
||||
let x0 = x.saturating_sub(phrase_area.x) as usize;
|
||||
let step = (0 + time0 + x0) * time_z;
|
||||
let next = (1 + time0 + x0) * time_z;
|
||||
if step >= phrase.length {
|
||||
break
|
||||
}
|
||||
let style = Self::style_timer_step(now, step, next);
|
||||
let cell = buf.get_mut(x, area.y);
|
||||
cell.set_char('-');
|
||||
cell.set_style(style);
|
||||
steps.push((x, step, next));
|
||||
for y in phrase_area.y .. phrase_area.y + phrase_area.height {
|
||||
if y == phrase_area.y {
|
||||
if step % (4 * ppq) == 0 {
|
||||
format!("{}", 1 + step / (4 * ppq)).blit(buf, x, y, None)?;
|
||||
} else if step % ppq == 0 {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
|
||||
cell.set_style(dim);
|
||||
}
|
||||
} else {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
|
||||
cell.set_style(dim);
|
||||
}
|
||||
} else {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
|
||||
cell.set_style(dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let wh = Style::default().white().not_dim();
|
||||
for index in 0..height-2 {
|
||||
let note_a = note0 + index as usize * 2;
|
||||
let note_b = note0 + index as usize * 2 + 1;
|
||||
for (x, step, next_step) in steps.iter() {
|
||||
let (a, b) = (
|
||||
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), *step, *next_step),
|
||||
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), *step, *next_step),
|
||||
);
|
||||
if let Some(block) = half_block(a, b) {
|
||||
let y = y + height.saturating_sub(index+2) as u16;
|
||||
let cell = buf.get_mut(*x, y);
|
||||
cell.set_char(block);
|
||||
cell.set_style(wh);
|
||||
let wh = Style::default().white().not_dim();
|
||||
for index in 0..height-2 {
|
||||
let note_a = note0 + index as usize * 2;
|
||||
let note_b = note0 + index as usize * 2 + 1;
|
||||
for (x, step, next_step) in steps.iter() {
|
||||
let (a, b) = (
|
||||
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), *step, *next_step),
|
||||
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), *step, *next_step),
|
||||
);
|
||||
if let Some(block) = half_block(a, b) {
|
||||
let y = y + height.saturating_sub(index+2) as u16;
|
||||
let cell = buf.get_mut(*x, y);
|
||||
cell.set_char(block);
|
||||
cell.set_style(wh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(area)
|
||||
} else {
|
||||
return Ok(Rect { x: area.x, y: area.x, width: 0, height: 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue