mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
|
[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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
|
[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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
[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();
|
let mut notes = phrase.notes.clone();
|
||||||
notes.extend_from_slice(&mut phrase.notes);
|
notes.extend_from_slice(&mut phrase.notes);
|
||||||
phrase.notes = notes;
|
phrase.notes = notes;
|
||||||
|
|
|
||||||
|
|
@ -6,36 +6,46 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
app.arranger.mode = !app.arranger.mode;
|
app.arranger.mode = !app.arranger.mode;
|
||||||
Ok(true)
|
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 {
|
match app.arranger.mode {
|
||||||
false => {app.arranger.scene_prev();true},
|
false => app.arranger.scene_prev(),
|
||||||
true => {app.arranger.track_prev();true},
|
true => app.arranger.track_prev(),
|
||||||
}
|
};
|
||||||
)],
|
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok(
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| {
|
||||||
match app.arranger.mode {
|
match app.arranger.mode {
|
||||||
false => {app.arranger.scene_next();true},
|
false => app.arranger.scene_next(),
|
||||||
true => {app.arranger.track_next();true},
|
true => app.arranger.track_next(),
|
||||||
}
|
};
|
||||||
)],
|
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok(
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| {
|
||||||
match app.arranger.mode {
|
match app.arranger.mode {
|
||||||
false => {app.arranger.track_prev();true},
|
false => app.arranger.track_prev(),
|
||||||
true => {app.arranger.scene_prev();true},
|
true => app.arranger.scene_prev(),
|
||||||
}
|
};
|
||||||
)],
|
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok(
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| {
|
||||||
match app.arranger.mode {
|
match app.arranger.mode {
|
||||||
false => {app.arranger.track_next();true},
|
false => app.arranger.track_next(),
|
||||||
true => {app.arranger.scene_next();true}
|
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| {
|
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| {
|
||||||
app.arranger.phrase_next();
|
app.arranger.phrase_next();
|
||||||
|
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| {
|
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| {
|
||||||
app.arranger.phrase_prev();
|
app.arranger.phrase_prev();
|
||||||
|
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| {
|
[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)
|
Ok(false)
|
||||||
}],
|
}],
|
||||||
[Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| {
|
[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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@ pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
app.entered = false;
|
app.entered = false;
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
||||||
app.entered = true;
|
app.entered = true;
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
@ -28,6 +30,8 @@ pub fn focus_next (app: &mut App) -> Usually<bool> {
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,5 +41,7 @@ pub fn focus_prev (app: &mut App) -> Usually<bool> {
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,31 @@ use crate::{core::*, model::App};
|
||||||
/// Key bindings for phrase editor.
|
/// Key bindings for phrase editor.
|
||||||
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
|
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
[Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut 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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Down, NONE, "seq_cursor_down", "move cursor up", |app: &mut App| {
|
[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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
|
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
|
||||||
if app.entered {
|
if app.sequencer.entered {
|
||||||
app.time_cursor = app.time_cursor.saturating_sub(1);
|
app.sequencer.time_cursor = app.sequencer.time_cursor.saturating_sub(1);
|
||||||
} else {
|
} 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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
|
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
|
||||||
if app.entered {
|
if app.sequencer.entered {
|
||||||
app.time_cursor = app.time_cursor + 1;
|
app.sequencer.time_cursor = app.sequencer.time_cursor + 1;
|
||||||
} else {
|
} else {
|
||||||
app.seq_buf.time_start = app.seq_buf.time_start + 1;
|
app.sequencer.time_start = app.sequencer.time_start + 1;
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| {
|
[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)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
// [Char('a'), NONE, "note_add", "Add note", note_add],
|
// [Char('a'), NONE, "note_add", "Add note", note_add],
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ impl Track {
|
||||||
_ => {}
|
_ => {}
|
||||||
});
|
});
|
||||||
let track = app.arranger.track_add(name)?;
|
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)?; }
|
for device in devices { track.add_device(device)?; }
|
||||||
Ok(track)
|
Ok(track)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
src/model.rs
26
src/model.rs
|
|
@ -1,8 +1,8 @@
|
||||||
//! Application state.
|
//! 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.
|
/// Root of application state.
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|
@ -14,8 +14,10 @@ pub struct App {
|
||||||
pub section: AppFocus,
|
pub section: AppFocus,
|
||||||
/// Transport model and view.
|
/// Transport model and view.
|
||||||
pub transport: TransportToolbar,
|
pub transport: TransportToolbar,
|
||||||
/// Arraneger model and view.
|
/// Arranger model and view.
|
||||||
pub arranger: Arranger,
|
pub arranger: Arranger,
|
||||||
|
/// Phrase editor
|
||||||
|
pub sequencer: Sequencer,
|
||||||
/// Main JACK client.
|
/// Main JACK client.
|
||||||
pub jack: Option<JackClient>,
|
pub jack: Option<JackClient>,
|
||||||
/// Map of external MIDI outs in the jack graph
|
/// Map of external MIDI outs in the jack graph
|
||||||
|
|
@ -31,17 +33,6 @@ pub struct App {
|
||||||
pub audio_outs: Vec<Arc<Port<Unowned>>>,
|
pub audio_outs: Vec<Arc<Port<Unowned>>>,
|
||||||
/// Number of frames requested by process callback
|
/// Number of frames requested by process callback
|
||||||
chunk_size: usize,
|
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 {
|
impl App {
|
||||||
|
|
@ -58,6 +49,7 @@ impl App {
|
||||||
section: AppFocus::default(),
|
section: AppFocus::default(),
|
||||||
transport: TransportToolbar::new(Some(jack.transport())),
|
transport: TransportToolbar::new(Some(jack.transport())),
|
||||||
arranger: Arranger::new(),
|
arranger: Arranger::new(),
|
||||||
|
sequencer: Sequencer::new(),
|
||||||
jack: Some(jack),
|
jack: Some(jack),
|
||||||
audio_outs: vec![],
|
audio_outs: vec![],
|
||||||
chain_mode: false,
|
chain_mode: false,
|
||||||
|
|
@ -65,12 +57,6 @@ impl App {
|
||||||
midi_in: None,
|
midi_in: None,
|
||||||
midi_ins: vec![],
|
midi_ins: vec![],
|
||||||
xdg: Some(xdg),
|
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()
|
self.selected.track_prev()
|
||||||
}
|
}
|
||||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||||
self.tracks.push(match name {
|
self.tracks.push(name.map_or_else(
|
||||||
Some(name) => Track::new(name, None, None)?,
|
|| Track::new(&self.track_default_name()),
|
||||||
None => Track::new(&self.track_default_name(), None, None)?
|
|name| Track::new(name),
|
||||||
});
|
)?);
|
||||||
let index = self.tracks.len() - 1;
|
let index = self.tracks.len() - 1;
|
||||||
Ok(&mut self.tracks[index])
|
Ok(&mut self.tracks[index])
|
||||||
}
|
}
|
||||||
|
|
@ -106,15 +106,15 @@ impl Arranger {
|
||||||
|
|
||||||
/// Phrase management methods
|
/// Phrase management methods
|
||||||
impl Arranger {
|
impl Arranger {
|
||||||
pub fn phrase (&self) -> Option<&Phrase> {
|
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
|
||||||
let track_id = self.selected.track()?;
|
let track_id = self.selected.track()?;
|
||||||
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
|
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
|
||||||
}
|
}
|
||||||
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
//pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
||||||
let track_id = self.selected.track()?;
|
//let track_id = self.selected.track()?;
|
||||||
let clip = *self.scene()?.clips.get(track_id)?;
|
//let clip = *self.scene()?.clips.get(track_id)?;
|
||||||
self.tracks.get_mut(track_id)?.phrases.get_mut(clip?)
|
//self.tracks.get_mut(track_id)?.phrases.get_mut(clip?)
|
||||||
}
|
//}
|
||||||
pub fn phrase_next (&mut self) {
|
pub fn phrase_next (&mut self) {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
//if let Some((track_index, track)) = self.track_mut() {
|
//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.
|
/// Overdub input to sequence.
|
||||||
pub overdub: bool,
|
pub overdub: bool,
|
||||||
/// Map: tick -> MIDI events at tick
|
/// Map: tick -> MIDI events at tick
|
||||||
pub phrases: Vec<Phrase>,
|
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
/// Phrase selector
|
/// Phrase selector
|
||||||
pub sequence: Option<usize>,
|
pub sequence: Option<usize>,
|
||||||
/// Output from current sequence.
|
/// Output from current sequence.
|
||||||
|
|
@ -30,11 +30,7 @@ pub struct Track {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Track {
|
impl Track {
|
||||||
pub fn new (
|
pub fn new (name: &str) -> Usually<Self> {
|
||||||
name: &str,
|
|
||||||
phrases: Option<Vec<Phrase>>,
|
|
||||||
devices: Option<Vec<JackDevice>>,
|
|
||||||
) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
midi_out: None,
|
midi_out: None,
|
||||||
|
|
@ -45,8 +41,8 @@ impl Track {
|
||||||
recording: false,
|
recording: false,
|
||||||
overdub: true,
|
overdub: true,
|
||||||
sequence: None,
|
sequence: None,
|
||||||
phrases: phrases.unwrap_or_else(||Vec::with_capacity(16)),
|
phrases: vec![],
|
||||||
devices: devices.unwrap_or_else(||Vec::with_capacity(16)),
|
devices: vec![],
|
||||||
device: 0,
|
device: 0,
|
||||||
reset: true,
|
reset: true,
|
||||||
})
|
})
|
||||||
|
|
@ -118,6 +114,7 @@ impl Track {
|
||||||
) = (
|
) = (
|
||||||
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id))
|
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id))
|
||||||
) {
|
) {
|
||||||
|
phrase.read().map(|phrase|{
|
||||||
if self.midi_out.is_some() {
|
if self.midi_out.is_some() {
|
||||||
phrase.process_out(
|
phrase.process_out(
|
||||||
&mut self.midi_out_buf,
|
&mut self.midi_out_buf,
|
||||||
|
|
@ -126,6 +123,9 @@ impl Track {
|
||||||
(frame0.saturating_sub(start_frame), frames, period)
|
(frame0.saturating_sub(start_frame), frames, period)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
let mut phrase = phrase.write().unwrap();
|
||||||
|
let length = phrase.length;
|
||||||
// Monitor and record input
|
// Monitor and record input
|
||||||
if input.is_some() && (self.recording || self.monitoring) {
|
if input.is_some() && (self.recording || self.monitoring) {
|
||||||
// For highlighting keys and note repeat
|
// For highlighting keys and note repeat
|
||||||
|
|
@ -143,7 +143,7 @@ impl Track {
|
||||||
let quantized = (
|
let quantized = (
|
||||||
pulse / quant as f64
|
pulse / quant as f64
|
||||||
).round() as usize * quant;
|
).round() as usize * quant;
|
||||||
let looped = quantized % phrase.length;
|
let looped = quantized % length;
|
||||||
looped
|
looped
|
||||||
}, message);
|
}, message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ render!(App |self, buf, area| {
|
||||||
&self.arranger,
|
&self.arranger,
|
||||||
&If(self.arranger.selected.is_clip(), &Split::right([
|
&If(self.arranger.selected.is_clip(), &Split::right([
|
||||||
&ChainView::vertical(&self),
|
&ChainView::vertical(&self),
|
||||||
&SequencerView::new(&self),
|
&self.sequencer,
|
||||||
]))
|
]))
|
||||||
]).render(buf, area)?;
|
]).render(buf, area)?;
|
||||||
if let Some(ref modal) = self.modal {
|
if let Some(ref modal) = self.modal {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ impl Arranger {
|
||||||
let label = match scene.clips.get(track_index) {
|
let label = match scene.clips.get(track_index) {
|
||||||
Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) {
|
Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) {
|
||||||
let icon = if track.sequence == Some(*clip) { "" } else { "┊" };
|
let icon = if track.sequence == Some(*clip) { "" } else { "┊" };
|
||||||
format!("{icon} {}", phrase.name)
|
format!("{icon} {}", phrase.read().unwrap().name)
|
||||||
} else {
|
} else {
|
||||||
format!(" ??? ")
|
format!(" ??? ")
|
||||||
},
|
},
|
||||||
|
|
@ -233,7 +233,7 @@ impl Arranger {
|
||||||
&|buf: &mut Buffer, area: Rect|{
|
&|buf: &mut Buffer, area: Rect|{
|
||||||
let mut x2 = 0;
|
let mut x2 = 0;
|
||||||
let Rect { x, y, height, .. } = area;
|
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 active_scene = false;//self.selected == ArrangerFocus::Scene(i) || cursor.1 > 0 && self.cursor.1 - 1 == i;
|
||||||
let sep = Some(if active_scene {
|
let sep = Some(if active_scene {
|
||||||
Style::default().yellow().not_dim()
|
Style::default().yellow().not_dim()
|
||||||
|
|
@ -252,11 +252,10 @@ impl Arranger {
|
||||||
let active_track = false;//self.cursor.0 > 0 && self.cursor.0 - 1 == i;
|
let active_track = false;//self.cursor.0 > 0 && self.cursor.0 - 1 == i;
|
||||||
if let Some(clip) = clip {
|
if let Some(clip) = clip {
|
||||||
let y2 = y + 1 + i as u16 * 2;
|
let y2 = y + 1 + i as u16 * 2;
|
||||||
let label = format!("{}", if let Some(phrase) = self.tracks[i].phrases.get(*clip) {
|
let label = match self.tracks[i].phrases.get(*clip) {
|
||||||
&phrase.name
|
Some(phrase) => &format!("{}", phrase.read().unwrap().name),
|
||||||
} else {
|
None => "...."
|
||||||
"...."
|
};
|
||||||
});
|
|
||||||
label.blit(buf, x + x2, y2, Some(if active_track && active_scene {
|
label.blit(buf, x + x2, y2, Some(if active_track && active_scene {
|
||||||
Style::default().not_dim().yellow().bold()
|
Style::default().not_dim().yellow().bold()
|
||||||
} else {
|
} 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::*};
|
use crate::{core::*,model::*,view::*};
|
||||||
|
|
||||||
pub struct BufferedSequencerView {
|
render!(Sequencer |self, buf, area| {
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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));
|
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||||
self.horizontal_draw(buf, area)?;
|
self.horizontal_draw(buf, area)?;
|
||||||
if self.focused && self.entered {
|
if self.focused && self.entered {
|
||||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SequencerView<'a> {
|
impl Sequencer {
|
||||||
fn style_focus (&self) -> Option<Style> {
|
fn style_focus (&self) -> Option<Style> {
|
||||||
Some(if self.focused {
|
Some(if self.focused {
|
||||||
Style::default().green().not_dim()
|
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;
|
const H_KEYS_OFFSET: u16 = 5;
|
||||||
|
|
||||||
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
|
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
|
||||||
self.horizontal_keys(buf, area)?;
|
self.horizontal_keys(buf, area)?;
|
||||||
self.horizontal_quant(buf, area)?;
|
self.horizontal_quant(buf, area)?;
|
||||||
self.horizontal_timer(buf, area, self.phrase)?;
|
self.horizontal_timer(buf, area, &self.phrase)?;
|
||||||
self.horizontal_lanes(buf, area, self.phrase)?;
|
self.horizontal_lanes(buf, area, &self.phrase)?;
|
||||||
self.horizontal_cursor(buf, area)?;
|
self.horizontal_cursor(buf, area)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -190,8 +66,11 @@ impl<'a> SequencerView<'a> {
|
||||||
c.blit(buf, x, y, self.style_focus())
|
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 {
|
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 (time0, time_z, now) = (self.time_start, self.time_zoom, self.now % phrase.length);
|
||||||
let Rect { x, width, .. } = area;
|
let Rect { x, width, .. } = area;
|
||||||
for x in x+Self::H_KEYS_OFFSET..x+width {
|
for x in x+Self::H_KEYS_OFFSET..x+width {
|
||||||
|
|
@ -252,12 +131,12 @@ impl<'a> SequencerView<'a> {
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn horizontal_lanes (&self, buf: &mut Buffer, area: Rect, phrase: Option<&Phrase>) -> Usually<Rect> {
|
fn horizontal_lanes (
|
||||||
if phrase.is_none() {
|
&self, buf: &mut Buffer, area: Rect, phrase: &Option<Arc<RwLock<Phrase>>>
|
||||||
return Ok(Rect { x: area.x, y: area.x, width: 0, height: 0 })
|
) -> Usually<Rect> {
|
||||||
}
|
if let Some(phrase) = phrase {
|
||||||
let Rect { x, y, width, height } = area;
|
let Rect { x, y, width, height } = area;
|
||||||
let phrase = phrase.unwrap();
|
let phrase = phrase.read().unwrap();
|
||||||
let now = self.now % phrase.length;
|
let now = self.now % phrase.length;
|
||||||
let ppq = self.ppq;
|
let ppq = self.ppq;
|
||||||
let time_z = self.time_zoom;
|
let time_z = self.time_zoom;
|
||||||
|
|
@ -314,6 +193,9 @@ impl<'a> SequencerView<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(area)
|
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