mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
bye sequencer
This commit is contained in:
parent
2165e5d45d
commit
316fe45b2a
12 changed files with 510 additions and 759 deletions
|
|
@ -3,7 +3,6 @@ pub mod focus;
|
||||||
pub mod mixer;
|
pub mod mixer;
|
||||||
pub mod plugin;
|
pub mod plugin;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
pub mod sequencer;
|
|
||||||
|
|
||||||
pub use self::focus::*;
|
pub use self::focus::*;
|
||||||
|
|
||||||
|
|
@ -42,6 +41,28 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
[Char('a'), CONTROL, "add_scene", "add a new scene", add_scene],
|
[Char('a'), CONTROL, "add_scene", "add a new scene", add_scene],
|
||||||
[Char('`'), NONE, "switch_mode", "switch the display mode", switch_mode],
|
[Char('`'), NONE, "switch_mode", "switch the display mode", switch_mode],
|
||||||
});
|
});
|
||||||
|
//-pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
|
||||||
|
//- [Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
||||||
|
//- [Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
||||||
|
//- [Left, NONE, "cursor_left", "move cursor left", cursor_left],
|
||||||
|
//- [Right, NONE, "cursor_right", "move cursor right", cursor_right],
|
||||||
|
//- [Char('.'), NONE, "cursor_inc", "increase note duration", cursor_duration_inc],
|
||||||
|
//- [Char(','), NONE, "cursor_dec", "decrease note duration", cursor_duration_dec],
|
||||||
|
//- [Char('`'), NONE, "mode_next", "Next view mode", mode_next],
|
||||||
|
//- [Char('='), NONE, "zoom_in", "Zoom in", zoom_in],
|
||||||
|
//- [Char('-'), NONE, "zoom_out", "Zoom out", zoom_out],
|
||||||
|
//- [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],
|
||||||
|
//- [Char(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play],
|
||||||
|
//- [Char('r'), NONE, "toggle_record", "Toggle recording", toggle_record],
|
||||||
|
//- [Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub],
|
||||||
|
//- [Char('m'), NONE, "toggle_monitor", "Toggle input monitoring", toggle_monitor],
|
||||||
|
//- [Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind],
|
||||||
|
//- [Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
|
||||||
|
//- [Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev],
|
||||||
|
//-});
|
||||||
|
|
||||||
fn toggle_play (app: &mut App) -> Usually<bool> {
|
fn toggle_play (app: &mut App) -> Usually<bool> {
|
||||||
app.playing = match app.playing.expect("after jack init") {
|
app.playing = match app.playing.expect("after jack init") {
|
||||||
|
|
@ -280,7 +301,7 @@ fn add_scene (app: &mut App) -> Usually<bool> {
|
||||||
|
|
||||||
fn add_track (app: &mut App) -> Usually<bool> {
|
fn add_track (app: &mut App) -> Usually<bool> {
|
||||||
let name = format!("Track {}", app.tracks.len() + 1);
|
let name = format!("Track {}", app.tracks.len() + 1);
|
||||||
app.tracks.push(Track::new(&name, app.jack.as_ref().unwrap().as_client(), &app.timebase, None, None)?);
|
app.tracks.push(Track::new(&name, app.jack.as_ref().unwrap().as_client(), None, None)?);
|
||||||
app.track_cursor = app.tracks.len();
|
app.track_cursor = app.tracks.len();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
use crate::{core::*, model::*};
|
|
||||||
|
|
||||||
pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Usually<bool> {
|
|
||||||
handle_keymap(state, event, KEYMAP)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
|
|
||||||
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
|
||||||
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
|
||||||
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
|
|
||||||
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
|
|
||||||
[Char('.'), NONE, "cursor_inc", "increase note duration", cursor_duration_inc],
|
|
||||||
[Char(','), NONE, "cursor_dec", "decrease note duration", cursor_duration_dec],
|
|
||||||
[Char('`'), NONE, "mode_next", "Next view mode", mode_next],
|
|
||||||
[Char('='), NONE, "zoom_in", "Zoom in", zoom_in],
|
|
||||||
[Char('-'), NONE, "zoom_out", "Zoom out", zoom_out],
|
|
||||||
[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],
|
|
||||||
[Char(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play],
|
|
||||||
[Char('r'), NONE, "toggle_record", "Toggle recording", toggle_record],
|
|
||||||
[Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub],
|
|
||||||
[Char('m'), NONE, "toggle_monitor", "Toggle input monitoring", toggle_monitor],
|
|
||||||
[Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind],
|
|
||||||
[Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
|
|
||||||
[Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev],
|
|
||||||
});
|
|
||||||
|
|
||||||
fn nop (_: &mut Sequencer) -> Usually<bool> {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn note_add (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
if s.sequence.is_none() {
|
|
||||||
return Ok(false)
|
|
||||||
}
|
|
||||||
let ppq = s.timebase.ppq() as usize;
|
|
||||||
let step = s.time_start + s.time_cursor;
|
|
||||||
let start = step as usize * ppq / s.time_zoom;
|
|
||||||
let end = (step + 1) as usize * ppq / s.time_zoom;
|
|
||||||
let key = u7::from_int_lossy((s.note_cursor + s.note_start) as u8);
|
|
||||||
let note_on = MidiMessage::NoteOn { key, vel: 100.into() };
|
|
||||||
let note_off = MidiMessage::NoteOff { key, vel: 100.into() };
|
|
||||||
let sequence = &mut s.phrases[s.sequence.unwrap()].notes;
|
|
||||||
if sequence.contains_key(&start) {
|
|
||||||
sequence.get_mut(&start).unwrap().push(note_on.clone());
|
|
||||||
} else {
|
|
||||||
sequence.insert(start, vec![note_on]);
|
|
||||||
}
|
|
||||||
if sequence.contains_key(&end) {
|
|
||||||
sequence.get_mut(&end).unwrap().push(note_off.clone());
|
|
||||||
} else {
|
|
||||||
sequence.insert(end, vec![note_off]);
|
|
||||||
};
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn note_del (_: &mut Sequencer) -> Usually<bool> {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn time_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.time_cursor = s.time_cursor + 1;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn time_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.time_cursor = s.time_cursor.saturating_sub(1);
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn note_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.note_cursor = s.note_cursor + 1;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn note_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.note_cursor = s.note_cursor.saturating_sub(1);
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_up (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
match s.view {
|
|
||||||
SequencerMode::Vertical => time_cursor_dec(s),
|
|
||||||
SequencerMode::Horizontal => note_cursor_dec(s),
|
|
||||||
_ => Ok(false)
|
|
||||||
}?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_down (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
match s.view {
|
|
||||||
SequencerMode::Vertical => time_cursor_inc(s),
|
|
||||||
SequencerMode::Horizontal => note_cursor_inc(s),
|
|
||||||
_ => Ok(false)
|
|
||||||
}?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_left (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
match s.view {
|
|
||||||
SequencerMode::Vertical => note_cursor_dec(s),
|
|
||||||
SequencerMode::Horizontal => time_cursor_dec(s),
|
|
||||||
_ => Ok(false)
|
|
||||||
}?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_right (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
match s.view {
|
|
||||||
SequencerMode::Vertical => note_cursor_inc(s),
|
|
||||||
SequencerMode::Horizontal => time_cursor_inc(s),
|
|
||||||
_ => Ok(false)
|
|
||||||
}?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_duration_inc (_: &mut Sequencer) -> Usually<bool> {
|
|
||||||
//s.cursor.2 = s.cursor.2 + 1
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_duration_dec (_: &mut Sequencer) -> Usually<bool> {
|
|
||||||
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mode_next (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.view = s.view.next();
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
impl SequencerMode {
|
|
||||||
fn next (&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Horizontal => Self::Vertical,
|
|
||||||
Self::Vertical => Self::Tiny,
|
|
||||||
Self::Tiny => Self::Horizontal,
|
|
||||||
_ => self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_and_rewind (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.transport.stop()?;
|
|
||||||
s.transport.locate(0)?;
|
|
||||||
s.playing = TransportState::Stopped;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.playing = match s.playing {
|
|
||||||
TransportState::Stopped => {
|
|
||||||
s.transport.start()?;
|
|
||||||
TransportState::Starting
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
s.transport.stop()?;
|
|
||||||
s.transport.locate(0)?;
|
|
||||||
TransportState::Stopped
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_record (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.recording = !s.recording;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.overdub = !s.overdub;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.monitoring = !s.monitoring;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
if s.time_zoom < 64 {
|
|
||||||
s.time_zoom = s.time_zoom * 2;
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
if s.time_zoom > 1 {
|
|
||||||
s.time_zoom = s.time_zoom / 2;
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn zoom_in (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.time_zoom = s.time_zoom / 2;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn zoom_out (s: &mut Sequencer) -> Usually<bool> {
|
|
||||||
s.time_zoom = s.time_zoom * 2;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
@ -147,3 +147,27 @@ impl<T: Fn(AppEvent) + Send> NotificationHandler for Notifications<T> {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add "all notes off" to the start of a buffer.
|
||||||
|
pub fn all_notes_off (output: &mut MIDIChunk) {
|
||||||
|
output[0] = Some(vec![]);
|
||||||
|
if let Some(Some(frame)) = output.get_mut(0) {
|
||||||
|
let mut buf = vec![];
|
||||||
|
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
|
||||||
|
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
|
||||||
|
evt.write(&mut buf).unwrap();
|
||||||
|
frame.push(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write to JACK port from output buffer (containing notes from sequence and/or monitor)
|
||||||
|
pub fn write_output (writer: &mut ::jack::MidiWriter, output: &mut MIDIChunk, frames: usize) {
|
||||||
|
for time in 0..frames {
|
||||||
|
if let Some(Some(frame)) = output.get_mut(time ) {
|
||||||
|
for event in frame.iter() {
|
||||||
|
writer.write(&::jack::RawMidi { time: time as u32, bytes: &event })
|
||||||
|
.expect(&format!("{event:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
65
src/main.rs
65
src/main.rs
|
|
@ -24,26 +24,24 @@ pub fn main () -> Usually<()> {
|
||||||
state.modal = Some(Box::new(crate::config::SetupModal(Some(xdg.clone()))));
|
state.modal = Some(Box::new(crate::config::SetupModal(Some(xdg.clone()))));
|
||||||
}
|
}
|
||||||
state.scenes = vec![
|
state.scenes = vec![
|
||||||
Scene::new("Intro", vec![Some(0), Some(0), None, None]),
|
Scene::new("Intro", vec![None, Some(0), None, None]),
|
||||||
|
Scene::new("Hook", vec![Some(0), Some(0), None, None]),
|
||||||
];
|
];
|
||||||
let jack = jack_run("tek", &app)?;
|
let jack = jack_run("tek", &app)?;
|
||||||
let timebase = &state.timebase;
|
let timebase = &state.timebase;
|
||||||
let ppq = timebase.ppq() as usize;
|
let ppq = timebase.ppq() as usize;
|
||||||
state.tracks = vec![
|
state.tracks = vec![
|
||||||
|
|
||||||
Track::new("Drums", &jack.as_client(), &timebase, Some(vec![
|
Track::new("Drums", &jack.as_client(), Some(vec![
|
||||||
|
|
||||||
Sampler::new("Sampler", Some(BTreeMap::from([
|
Sampler::new("Sampler", Some(BTreeMap::from([
|
||||||
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
|
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
|
||||||
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
|
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
|
||||||
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
|
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
|
||||||
])))?.boxed(),
|
])))?.boxed(),
|
||||||
|
|
||||||
Plugin::lv2(
|
Plugin::lv2(
|
||||||
"Panagement",
|
"Panagement",
|
||||||
"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2"
|
"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2"
|
||||||
)?.boxed(),
|
)?.boxed(),
|
||||||
|
|
||||||
]), Some(vec![
|
]), Some(vec![
|
||||||
Phrase::new("4 kicks", ppq * 4, Some(phrase! {
|
Phrase::new("4 kicks", ppq * 4, Some(phrase! {
|
||||||
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
|
@ -53,7 +51,8 @@ pub fn main () -> Usually<()> {
|
||||||
})),
|
})),
|
||||||
]))?,
|
]))?,
|
||||||
|
|
||||||
Track::new("Bass", &jack.as_client(), &timebase, Some(vec![
|
Track::new("Bass", &jack.as_client(), Some(vec![
|
||||||
|
Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?.boxed(),
|
||||||
]), Some(vec![
|
]), Some(vec![
|
||||||
Phrase::new("Offbeat", ppq * 4, Some(phrase! {
|
Phrase::new("Offbeat", ppq * 4, Some(phrase! {
|
||||||
00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
|
00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
|
||||||
|
|
@ -66,6 +65,7 @@ pub fn main () -> Usually<()> {
|
||||||
14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||||
})),
|
})),
|
||||||
]))?,
|
]))?,
|
||||||
|
|
||||||
];
|
];
|
||||||
state.track_cursor = 1;
|
state.track_cursor = 1;
|
||||||
state.scene_cursor = 1;
|
state.scene_cursor = 1;
|
||||||
|
|
@ -100,14 +100,57 @@ pub struct App {
|
||||||
pub midi_in: Option<Port<MidiIn>>,
|
pub midi_in: Option<Port<MidiIn>>,
|
||||||
pub audio_outs: Option<Vec<Port<AudioOut>>>,
|
pub audio_outs: Option<Vec<Port<AudioOut>>>,
|
||||||
pub metronome: bool,
|
pub metronome: bool,
|
||||||
|
/// Range of notes to display
|
||||||
|
pub note_start: usize,
|
||||||
|
/// Position of cursor within note range
|
||||||
|
pub note_cursor: usize,
|
||||||
|
/// PPQ per display unit
|
||||||
|
pub time_zoom: usize,
|
||||||
|
/// Range of time steps to display
|
||||||
|
pub time_start: usize,
|
||||||
|
/// Position of cursor within time range
|
||||||
|
pub time_cursor: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
process!(App |self, client, scope| {
|
process!(App |self, client, scope| {
|
||||||
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
||||||
self.playing = Some(transport.state);
|
self.playing = Some(transport.state);
|
||||||
self.playhead = transport.pos.frame() as usize;
|
self.playhead = transport.pos.frame() as usize;
|
||||||
for Track { sequencer, .. } in self.tracks.iter_mut() {
|
for Track {
|
||||||
sequencer.process(client, scope);
|
sequence, phrases, midi_out, monitoring, recording, ref mut notes_on, ..
|
||||||
|
} in self.tracks.iter_mut() {
|
||||||
|
if sequence.is_none() { continue }
|
||||||
|
let phrase = phrases.get_mut(sequence.unwrap());
|
||||||
|
if phrase.is_none() { continue }
|
||||||
|
let phrase = phrase.unwrap();
|
||||||
|
let frame = scope.last_frame_time() as usize;
|
||||||
|
let frames = scope.n_frames() as usize;
|
||||||
|
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
|
||||||
|
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
||||||
|
if Some(transport.state) != self.playing {
|
||||||
|
all_notes_off(&mut output);
|
||||||
|
}
|
||||||
|
self.playing = Some(transport.state);
|
||||||
|
// Play from phrase into output buffer
|
||||||
|
if self.playing == Some(TransportState::Rolling) {
|
||||||
|
phrase.process_out(
|
||||||
|
&mut output,
|
||||||
|
notes_on,
|
||||||
|
&self.timebase,
|
||||||
|
frame,
|
||||||
|
frames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Play from input to monitor, and record into phrase.
|
||||||
|
phrase.process_in(
|
||||||
|
self.midi_in.as_ref().unwrap().iter(scope),
|
||||||
|
notes_on,
|
||||||
|
if *monitoring { Some(&mut output) } else { None },
|
||||||
|
*recording && self.playing == Some(TransportState::Rolling),
|
||||||
|
&self.timebase,
|
||||||
|
frame,
|
||||||
|
);
|
||||||
|
write_output(&mut midi_out.writer(scope), &mut output, frames);
|
||||||
}
|
}
|
||||||
Control::Continue
|
Control::Continue
|
||||||
});
|
});
|
||||||
|
|
@ -165,12 +208,6 @@ impl App {
|
||||||
self.scenes.get_mut(id).map(|t|(id, t))
|
self.scenes.get_mut(id).map(|t|(id, t))
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
pub fn sequencer (&self) -> Option<&Sequencer> {
|
|
||||||
Some(&self.track()?.1.sequencer)
|
|
||||||
}
|
|
||||||
pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer> {
|
|
||||||
Some(&mut self.track_mut()?.1.sequencer)
|
|
||||||
}
|
|
||||||
pub fn chain (&self) -> Option<&Chain> {
|
pub fn chain (&self) -> Option<&Chain> {
|
||||||
Some(&self.track()?.1.chain)
|
Some(&self.track()?.1.chain)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,11 @@ pub mod phrase;
|
||||||
pub mod plugin;
|
pub mod plugin;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod sequencer;
|
|
||||||
pub mod track;
|
pub mod track;
|
||||||
|
|
||||||
pub use self::phrase::Phrase;
|
pub use self::phrase::Phrase;
|
||||||
pub use self::scene::Scene;
|
pub use self::scene::Scene;
|
||||||
pub use self::track::Track;
|
pub use self::track::Track;
|
||||||
pub use self::sequencer::{Sequencer, SequencerMode};
|
|
||||||
pub use self::chain::Chain;
|
pub use self::chain::Chain;
|
||||||
pub use self::sampler::{Sampler, Sample};
|
pub use self::sampler::{Sampler, Sample};
|
||||||
pub use self::mixer::Mixer;
|
pub use self::mixer::Mixer;
|
||||||
|
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use crate::model::*;
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum SequencerMode { Tiny, Compact, Horizontal, Vertical, }
|
|
||||||
pub struct Sequencer {
|
|
||||||
pub name: String,
|
|
||||||
/// JACK transport handle.
|
|
||||||
pub transport: ::jack::Transport,
|
|
||||||
/// JACK MIDI input port that will be created.
|
|
||||||
pub midi_in: Port<MidiIn>,
|
|
||||||
/// JACK MIDI output port that will be created.
|
|
||||||
pub midi_out: Port<MidiOut>,
|
|
||||||
/// Holds info about tempo
|
|
||||||
pub timebase: Arc<Timebase>,
|
|
||||||
/// Phrase selector
|
|
||||||
pub sequence: Option<usize>,
|
|
||||||
/// Map: tick -> MIDI events at tick
|
|
||||||
pub phrases: Vec<Phrase>,
|
|
||||||
/// Red keys on piano roll.
|
|
||||||
pub notes_on: Vec<bool>,
|
|
||||||
/// Play sequence to output.
|
|
||||||
pub playing: TransportState,
|
|
||||||
/// Play input through output.
|
|
||||||
pub monitoring: bool,
|
|
||||||
/// Write input to sequence.
|
|
||||||
pub recording: bool,
|
|
||||||
/// Don't delete when recording.
|
|
||||||
pub overdub: bool,
|
|
||||||
/// Display mode
|
|
||||||
pub view: SequencerMode,
|
|
||||||
/// Range of notes to display
|
|
||||||
pub note_start: usize,
|
|
||||||
/// Position of cursor within note range
|
|
||||||
pub note_cursor: usize,
|
|
||||||
/// PPM per display unit
|
|
||||||
pub time_zoom: usize,
|
|
||||||
/// Range of time steps to display
|
|
||||||
pub time_start: usize,
|
|
||||||
/// Position of cursor within time range
|
|
||||||
pub time_cursor: usize,
|
|
||||||
}
|
|
||||||
render!(Sequencer = crate::view::sequencer::render);
|
|
||||||
handle!(Sequencer = crate::control::sequencer::handle);
|
|
||||||
process!(Sequencer |self, _client, scope| {
|
|
||||||
if self.sequence.is_none() { return Control::Continue }
|
|
||||||
let phrase = self.phrases.get_mut(self.sequence.unwrap());
|
|
||||||
if phrase.is_none() { return Control::Continue }
|
|
||||||
let phrase = phrase.unwrap();
|
|
||||||
let frame = scope.last_frame_time() as usize;
|
|
||||||
let frames = scope.n_frames() as usize;
|
|
||||||
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
|
|
||||||
let transport = self.transport.query().unwrap();
|
|
||||||
if transport.state != self.playing {
|
|
||||||
all_notes_off(&mut output);
|
|
||||||
}
|
|
||||||
self.playing = transport.state;
|
|
||||||
// Play from phrase into output buffer
|
|
||||||
if self.playing == TransportState::Rolling {
|
|
||||||
phrase.process_out(
|
|
||||||
&mut output,
|
|
||||||
&mut self.notes_on,
|
|
||||||
&self.timebase,
|
|
||||||
frame,
|
|
||||||
frames
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Play from input to monitor, and record into phrase.
|
|
||||||
phrase.process_in(
|
|
||||||
self.midi_in.iter(scope),
|
|
||||||
&mut self.notes_on,
|
|
||||||
if self.monitoring { Some(&mut output) } else { None },
|
|
||||||
self.recording && self.playing == TransportState::Rolling,
|
|
||||||
&self.timebase,
|
|
||||||
frame,
|
|
||||||
);
|
|
||||||
write_output(&mut self.midi_out.writer(scope), &mut output, frames);
|
|
||||||
Control::Continue
|
|
||||||
});
|
|
||||||
impl Sequencer {
|
|
||||||
pub fn new (
|
|
||||||
name: &str,
|
|
||||||
timebase: &Arc<Timebase>,
|
|
||||||
phrases: Option<Vec<Phrase>>,
|
|
||||||
) -> Usually<Self> {
|
|
||||||
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
|
||||||
let transport = client.transport();
|
|
||||||
Ok(Self {
|
|
||||||
name: name.into(),
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
phrases: phrases.unwrap_or_else(||vec![Phrase::default()]),
|
|
||||||
sequence: Some(0),
|
|
||||||
|
|
||||||
transport,
|
|
||||||
midi_in: client.register_port("in", MidiIn::default())?,
|
|
||||||
monitoring: true,
|
|
||||||
recording: true,
|
|
||||||
midi_out: client.register_port("out", MidiOut::default())?,
|
|
||||||
playing: TransportState::Starting,
|
|
||||||
overdub: true,
|
|
||||||
|
|
||||||
view: SequencerMode::Horizontal,
|
|
||||||
notes_on: vec![false;128],
|
|
||||||
note_start: 12,
|
|
||||||
note_cursor: 0,
|
|
||||||
time_zoom: 24,
|
|
||||||
time_start: 0,
|
|
||||||
time_cursor: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> {
|
|
||||||
self.phrases.get(self.sequence?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PortList for Sequencer {
|
|
||||||
fn midi_ins (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_in.name()?]) }
|
|
||||||
fn midi_outs (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_out.name()?]) }
|
|
||||||
}
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
|
||||||
pub fn all_notes_off (output: &mut MIDIChunk) {
|
|
||||||
output[0] = Some(vec![]);
|
|
||||||
if let Some(Some(frame)) = output.get_mut(0) {
|
|
||||||
let mut buf = vec![];
|
|
||||||
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
|
|
||||||
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
|
|
||||||
evt.write(&mut buf).unwrap();
|
|
||||||
frame.push(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write to JACK port from output buffer (containing notes from sequence and/or monitor)
|
|
||||||
fn write_output (writer: &mut ::jack::MidiWriter, output: &mut MIDIChunk, frames: usize) {
|
|
||||||
for time in 0..frames {
|
|
||||||
if let Some(Some(frame)) = output.get_mut(time ) {
|
|
||||||
for event in frame.iter() {
|
|
||||||
writer.write(&::jack::RawMidi { time: time as u32, bytes: &event })
|
|
||||||
.expect(&format!("{event:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,50 +2,48 @@ use crate::core::*;
|
||||||
use crate::model::*;
|
use crate::model::*;
|
||||||
|
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sequencer: Sequencer,
|
/// Play input through output.
|
||||||
pub chain: Chain,
|
pub monitoring: bool,
|
||||||
pub midi_out: Port<MidiOut>,
|
/// Write input to sequence.
|
||||||
|
pub recording: bool,
|
||||||
|
/// Overdub input to sequence.
|
||||||
|
pub overdub: bool,
|
||||||
|
/// Map: tick -> MIDI events at tick
|
||||||
|
pub phrases: Vec<Phrase>,
|
||||||
|
/// Phrase selector
|
||||||
|
pub sequence: Option<usize>,
|
||||||
|
/// Output from current sequence.
|
||||||
|
pub midi_out: Port<MidiOut>,
|
||||||
|
/// Red keys on piano roll.
|
||||||
|
pub notes_on: Vec<bool>,
|
||||||
|
/// Device chain
|
||||||
|
pub chain: Chain,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Track {
|
impl Track {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
name: &str,
|
name: &str,
|
||||||
jack: &Client,
|
jack: &Client,
|
||||||
timebase: &Arc<Timebase>,
|
devices: Option<Vec<Box<dyn Device>>>,
|
||||||
devices: Option<Vec<Box<dyn Device>>>,
|
phrases: Option<Vec<Phrase>>,
|
||||||
phrases: Option<Vec<Phrase>>,
|
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
let sequencer = Sequencer::new(&name, timebase, phrases)?;
|
Ok(Self {
|
||||||
let chain = Chain::new(&name, devices)?;
|
name: name.to_string(),
|
||||||
let midi_out = jack.register_port(name, MidiOut)?;
|
chain: Chain::new(&name, devices)?,
|
||||||
//let (client, _status) = Client::new("init", ClientOptions::NO_START_SERVER)?;
|
midi_out: jack.register_port(name, MidiOut)?,
|
||||||
//{
|
notes_on: vec![],
|
||||||
//if let (Some(output), Some(input)) = (
|
monitoring: false,
|
||||||
//sequencer.midi_outs()?.get(0).clone(),
|
recording: false,
|
||||||
//if let Some(item) = chain.items.get(0) {
|
overdub: true,
|
||||||
//if let Some(port) = item.midi_ins()?.get(0) {
|
sequence: None,
|
||||||
//Some(port.clone())
|
phrases: phrases.unwrap_or_else(||vec![])
|
||||||
//} else {
|
})
|
||||||
//None
|
|
||||||
//}
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//}
|
|
||||||
//) {
|
|
||||||
//client.connect_ports_by_name(&output, &input)?;
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
Ok(Self { name: name.to_string(), sequencer, chain, midi_out })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortList for Track {
|
impl PortList for Track {
|
||||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
fn midi_ins (&self) -> Usually<Vec<String>> { Ok(vec![]) }
|
||||||
self.sequencer.midi_ins()
|
fn midi_outs (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_out.name()?]) }
|
||||||
}
|
|
||||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
|
||||||
self.chain.audio_outs()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
12
src/view.rs
12
src/view.rs
|
|
@ -46,10 +46,14 @@ render!(App |self, buf, area| {
|
||||||
}.render(buf, Rect { x, y, width, height: height / 3 })?.height;
|
}.render(buf, Rect { x, y, width, height: height / 3 })?.height;
|
||||||
|
|
||||||
y = y + SequencerView {
|
y = y + SequencerView {
|
||||||
focused: self.section == 2,
|
focused: self.section == 2,
|
||||||
ppq: self.timebase.ppq() as usize,
|
ppq: self.timebase.ppq() as usize,
|
||||||
track: track,
|
phrase: track.map(|t|&t.phrases[0]),
|
||||||
phrase: track.map(|t|&t.sequencer.phrases[0]),
|
time_cursor: self.time_cursor,
|
||||||
|
time_start: self.time_start,
|
||||||
|
time_zoom: self.time_zoom,
|
||||||
|
note_cursor: self.note_cursor,
|
||||||
|
note_start: self.note_start,
|
||||||
}.render(buf, Rect { x, y, width, height })?.height;
|
}.render(buf, Rect { x, y, width, height })?.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ impl<'a> SceneGridView<'a> {
|
||||||
label.blit(self.buf, self.area.x + x2, self.area.y, Some(Style::default().bold()));
|
label.blit(self.buf, self.area.x + x2, self.area.y, Some(Style::default().bold()));
|
||||||
for (i, clip) in scene.clips.iter().enumerate() {
|
for (i, clip) in scene.clips.iter().enumerate() {
|
||||||
if let Some(clip) = clip {
|
if let Some(clip) = clip {
|
||||||
if let Some(phrase) = self.tracks[i].sequencer.phrases.get(*clip) {
|
if let Some(phrase) = self.tracks[i].phrases.get(*clip) {
|
||||||
let label = format!("{}", &phrase.name);
|
let label = format!("{}", &phrase.name);
|
||||||
label.blit(self.buf, self.area.x + x2, self.area.y + 1 + i as u16, None);
|
label.blit(self.buf, self.area.x + x2, self.area.y + 1 + i as u16, None);
|
||||||
x3 = x3.max(label.len() as u16)
|
x3 = x3.max(label.len() as u16)
|
||||||
|
|
@ -186,7 +186,7 @@ impl<'a> SceneGridView<'a> {
|
||||||
let clip = scene.clips.get(track);
|
let clip = scene.clips.get(track);
|
||||||
let index = index as u16;
|
let index = index as u16;
|
||||||
let label = if let Some(Some(clip)) = clip {
|
let label = if let Some(Some(clip)) = clip {
|
||||||
if let Some(phrase) = self.tracks[track].sequencer.phrases.get(*clip) {
|
if let Some(phrase) = self.tracks[track].phrases.get(*clip) {
|
||||||
format!("⯈{}", phrase.name)
|
format!("⯈{}", phrase.name)
|
||||||
} else {
|
} else {
|
||||||
format!("????")
|
format!("????")
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
use crate::{core::*,model::*,view::*};
|
use crate::{core::*,model::*,view::*};
|
||||||
|
|
||||||
pub mod horizontal;
|
#[derive(Debug, Clone)]
|
||||||
pub mod vertical;
|
pub enum SequencerMode { Tiny, Compact, Horizontal, Vertical, }
|
||||||
|
|
||||||
pub struct SequencerView<'a> {
|
pub struct SequencerView<'a> {
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
pub phrase: Option<&'a Phrase>,
|
pub phrase: Option<&'a Phrase>,
|
||||||
pub track: Option<&'a Track>,
|
|
||||||
pub ppq: usize,
|
pub ppq: usize,
|
||||||
|
/// Range of notes to display
|
||||||
|
pub note_start: usize,
|
||||||
|
/// Position of cursor within note range
|
||||||
|
pub note_cursor: usize,
|
||||||
|
/// PPQ per display unit
|
||||||
|
pub time_zoom: usize,
|
||||||
|
/// Range of time steps to display
|
||||||
|
pub time_start: usize,
|
||||||
|
/// Position of cursor within time range
|
||||||
|
pub time_cursor: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render for SequencerView<'a> {
|
impl<'a> Render for SequencerView<'a> {
|
||||||
|
|
@ -18,87 +27,366 @@ impl<'a> Render for SequencerView<'a> {
|
||||||
lozenge_left(buf, x, y, height, style);
|
lozenge_left(buf, x, y, height, style);
|
||||||
lozenge_right(buf, x + width - 1, y, height, style);
|
lozenge_right(buf, x + width - 1, y, height, style);
|
||||||
}
|
}
|
||||||
if let Some(ref track) = self.track {
|
self::horizontal::draw(
|
||||||
self::horizontal::draw(
|
buf,
|
||||||
buf,
|
area,
|
||||||
area,
|
self.phrase,
|
||||||
self.phrase,
|
self.ppq,
|
||||||
self.ppq,
|
self.time_cursor,
|
||||||
track.sequencer.time_cursor,
|
self.time_start,
|
||||||
track.sequencer.time_start,
|
self.time_zoom,
|
||||||
track.sequencer.time_zoom,
|
self.note_cursor,
|
||||||
track.sequencer.note_cursor,
|
self.note_start,
|
||||||
track.sequencer.note_start,
|
Some(if self.focused {
|
||||||
Some(if self.focused {
|
Style::default().green().not_dim()
|
||||||
Style::default().green().not_dim()
|
} else {
|
||||||
} else {
|
Style::default().green().dim()
|
||||||
Style::default().green().dim()
|
})
|
||||||
})
|
)?;
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
mod horizontal {
|
||||||
let Rect { x, y, width, height } = area;
|
use crate::core::*;
|
||||||
let header = draw_header(s, buf, area)?;
|
use super::*;
|
||||||
let piano = match s.view {
|
|
||||||
SequencerMode::Tiny => Rect { x, y, width, height: 0 },
|
|
||||||
SequencerMode::Compact => Rect { x, y, width, height: 0 },
|
|
||||||
SequencerMode::Vertical => self::vertical::draw(s, buf, Rect {
|
|
||||||
x, y: y + header.height, width, height,
|
|
||||||
})?,
|
|
||||||
SequencerMode::Horizontal => self::horizontal::draw(
|
|
||||||
buf,
|
|
||||||
Rect { x, y: y + header.height, width, height, },
|
|
||||||
s.phrase(),
|
|
||||||
s.timebase.ppq() as usize,
|
|
||||||
s.time_cursor,
|
|
||||||
s.time_start,
|
|
||||||
s.time_zoom,
|
|
||||||
s.note_cursor,
|
|
||||||
s.note_start,
|
|
||||||
None
|
|
||||||
)?,
|
|
||||||
};
|
|
||||||
Ok(draw_box(buf, Rect {
|
|
||||||
x, y,
|
|
||||||
width: header.width.max(piano.width),
|
|
||||||
height: header.height + piano.height
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
pub fn draw (
|
||||||
let Rect { x, y, width, .. } = area;
|
buf: &mut Buffer,
|
||||||
let style = Style::default().gray();
|
area: Rect,
|
||||||
crate::view::TransportView {
|
phrase: Option<&Phrase>,
|
||||||
timebase: &s.timebase,
|
ppq: usize,
|
||||||
playing: s.playing,
|
time: usize,
|
||||||
record: s.recording,
|
time0: usize,
|
||||||
overdub: s.overdub,
|
time_z: usize,
|
||||||
monitor: s.monitoring,
|
note: usize,
|
||||||
frame: 0
|
note0: usize,
|
||||||
}.render(buf, area)?;
|
style: Option<Style>,
|
||||||
let separator = format!("├{}┤", "-".repeat((width - 2).into()));
|
) -> Usually<Rect> {
|
||||||
separator.blit(buf, x, y + 2, Some(style.dim()));
|
let now = 0;
|
||||||
let _ = draw_clips(s, buf, area)?;
|
let notes = &[];
|
||||||
Ok(Rect { x, y, width, height: 3 })
|
match time_z {
|
||||||
}
|
1 => "1/384",
|
||||||
|
2 => "1/192",
|
||||||
pub fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
3 => "1/128",
|
||||||
let Rect { x, y, .. } = area;
|
4 => "1/96",
|
||||||
let style = Style::default().gray();
|
6 => "1/64",
|
||||||
for (i, sequence) in s.phrases.iter().enumerate() {
|
8 => "1/48",
|
||||||
let label = format!("▶ {}", &sequence.name);
|
12 => "1/32",
|
||||||
label.blit(buf, x + 2, y + 3 + (i as u16)*2, Some(if Some(i) == s.sequence {
|
16 => "1/24",
|
||||||
match s.playing {
|
24 => "1/16",
|
||||||
TransportState::Rolling => style.white().bold(),
|
32 => "1/12",
|
||||||
_ => style.not_dim().bold()
|
48 => "1/8",
|
||||||
}
|
64 => "1/6",
|
||||||
} else {
|
96 => "1/4",
|
||||||
style.dim()
|
128 => "1/3",
|
||||||
}));
|
192 => "1/2",
|
||||||
|
384 => "1/1",
|
||||||
|
_ => ""
|
||||||
|
}.blit(buf, area.x, area.y, Some(Style::default().dim()));
|
||||||
|
keys(buf, area, note0, notes)?;
|
||||||
|
timer(buf, area, time0, now);
|
||||||
|
if let Some(phrase) = phrase {
|
||||||
|
lanes(buf, area, phrase, ppq, time_z, time0, note0);
|
||||||
|
}
|
||||||
|
let style = style.unwrap_or_else(||{Style::default().green().not_dim()});
|
||||||
|
cursor(buf, area, style, time, note);
|
||||||
|
//footer(buf, area, note0, note, time0, time, time_z);
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timer (buf: &mut Buffer, area: Rect, time0: usize, now: usize) {
|
||||||
|
let x = area.x + 5;
|
||||||
|
for step in time0..(time0+area.width as usize).saturating_sub(5) {
|
||||||
|
buf.set_string(x + step as u16, area.y, &"-", if step == now {
|
||||||
|
Style::default().yellow().bold().not_dim()
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keys (buf: &mut Buffer, area: Rect, note0: usize, _notes: &[bool])
|
||||||
|
-> Usually<Rect>
|
||||||
|
{
|
||||||
|
let bw = Style::default().dim();
|
||||||
|
let Rect { x, y, width, height } = area;
|
||||||
|
let h = height.saturating_sub(2);
|
||||||
|
for index in 0..h {
|
||||||
|
let y = y + h - index;
|
||||||
|
let key = KEYS_VERTICAL[(index % 6) as usize];
|
||||||
|
key.blit(buf, x + 1, y, Some(bw));
|
||||||
|
"█".blit(buf, x + 2, y, Some(bw));
|
||||||
|
"|---".repeat(width.saturating_sub(6) as usize).blit(buf, x + 5, y, Some(bw.black()));
|
||||||
|
let note_a = note0 + (index * 2) as usize;
|
||||||
|
if note_a % 12 == 0 {
|
||||||
|
let octave = format!("C{}", (note_a / 12) as i8 - 2);
|
||||||
|
octave.blit(buf, x + 3, y, None);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let note_b = note0 + (index * 2) as usize;
|
||||||
|
if note_b % 12 == 0 {
|
||||||
|
let octave = format!("C{}", (note_b / 12) as i8 - 2);
|
||||||
|
octave.blit(buf, x + 3, y, None);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lanes (
|
||||||
|
buf: &mut Buffer,
|
||||||
|
area: Rect,
|
||||||
|
phrase: &Phrase,
|
||||||
|
ppq: usize,
|
||||||
|
time_z: usize,
|
||||||
|
_time0: usize,
|
||||||
|
note0: usize,
|
||||||
|
) {
|
||||||
|
let Rect { x, y, width, height } = area;
|
||||||
|
//let time0 = time0 / time_z;
|
||||||
|
//let time1 = time0 + width as usize;
|
||||||
|
//let note1 = note0 + height as usize;
|
||||||
|
let bg = Style::default();
|
||||||
|
let (bw, wh) = (bg.dim(), bg.white());
|
||||||
|
let offset = 5;
|
||||||
|
for x in x+offset..x+width-offset {
|
||||||
|
let step = (x-offset) as usize * time_z;
|
||||||
|
if step % ppq == 0 {
|
||||||
|
"|".blit(buf, x as u16, y, Some(Style::default().dim()));
|
||||||
|
}
|
||||||
|
let bar = 4 * ppq;
|
||||||
|
if step % bar == 0 {
|
||||||
|
format!("{}", (step/bar)+1)
|
||||||
|
.blit(buf, x as u16, y, Some(Style::default().bold().not_dim()));
|
||||||
|
}
|
||||||
|
let (a, b) = (step, step + time_z);
|
||||||
|
for index in 0..height-2 {
|
||||||
|
let note_a = note0 + index as usize * 2;
|
||||||
|
let note_b = note0 + index as usize * 2 + 1;
|
||||||
|
let (character, style) = match (
|
||||||
|
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), a, b),
|
||||||
|
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), a, b),
|
||||||
|
) {
|
||||||
|
(true, true) => ("█", wh),
|
||||||
|
(false, true) => ("▀", wh),
|
||||||
|
(true, false) => ("▄", wh),
|
||||||
|
(false, false) => ("·", bw),
|
||||||
|
};
|
||||||
|
let y = y + height.saturating_sub(index+2) as u16;
|
||||||
|
character.blit(buf, x, y, Some(style));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor (
|
||||||
|
buf: &mut Buffer,
|
||||||
|
area: Rect,
|
||||||
|
style: Style,
|
||||||
|
time: usize,
|
||||||
|
note: usize
|
||||||
|
) {
|
||||||
|
let x = area.x + 5 + time as u16;
|
||||||
|
let y = area.y + 1 + note as u16 / 2;
|
||||||
|
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||||
|
c.blit(buf, x, y, Some(style));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn footer (
|
||||||
|
buf: &mut Buffer,
|
||||||
|
area: Rect,
|
||||||
|
note0: usize,
|
||||||
|
note: usize,
|
||||||
|
time0: usize,
|
||||||
|
time: usize,
|
||||||
|
time_z: usize,
|
||||||
|
) {
|
||||||
|
let Rect { mut x, y, width, height } = area;
|
||||||
|
buf.set_string(x, y + height, format!("├{}┤", "-".repeat((width - 2).into())),
|
||||||
|
Style::default().dim());
|
||||||
|
buf.set_string(x, y + height + 2, format!("├{}┤", "-".repeat((width - 2).into())),
|
||||||
|
Style::default().dim());
|
||||||
|
x = x + 2;
|
||||||
|
{
|
||||||
|
for (_, [letter, title, value]) in [
|
||||||
|
["S", &format!("ync"), &format!("<4/4>")],
|
||||||
|
["Q", &format!("uant"), &format!("<1/{}>", 4 * time_z)],
|
||||||
|
["N", &format!("ote"), &format!("{} ({}-{})", note0 + note, note0, "X")],
|
||||||
|
["T", &format!("ime"), &format!("{} ({}-{})", time0 + time, time0 + 1, "X")],
|
||||||
|
].iter().enumerate() {
|
||||||
|
buf.set_string(x, y + height + 1, letter, Style::default().bold().yellow().dim());
|
||||||
|
x = x + 1;
|
||||||
|
buf.set_string(x, y + height + 1, &title, Style::default().bold().dim());
|
||||||
|
x = x + title.len() as u16 + 1;
|
||||||
|
buf.set_string(x, y + height + 1, &value, Style::default().not_dim());
|
||||||
|
x = x + value.len() as u16;
|
||||||
|
buf.set_string(x, y + height + 1, " ", Style::default().dim());
|
||||||
|
x = x + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(Rect { x, y, width: 14, height: 14 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//mod vertical {
|
||||||
|
//use super::*;
|
||||||
|
|
||||||
|
//pub fn draw (
|
||||||
|
//s: &Sequencer,
|
||||||
|
//buf: &mut Buffer,
|
||||||
|
//mut area: Rect,
|
||||||
|
//) -> Usually<Rect> {
|
||||||
|
//area.x = area.x + 13;
|
||||||
|
//keys(s, buf, area, 0);
|
||||||
|
//steps(s, buf, area, 0);
|
||||||
|
//playhead(s, buf, area.x, area.y);
|
||||||
|
//Ok(area)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
||||||
|
//if s.sequence.is_none() {
|
||||||
|
//return
|
||||||
|
//}
|
||||||
|
//let ppq = s.timebase.ppq() as usize;
|
||||||
|
//let bg = Style::default();
|
||||||
|
//let bw = bg.dim();
|
||||||
|
//let wh = bg.white();
|
||||||
|
//let Rect { x, y, .. } = area;
|
||||||
|
//for step in s.time_start..s.time_start+area.height as usize {
|
||||||
|
//let y = y - (s.time_start + step / 2) as u16;
|
||||||
|
//let step = step as usize;
|
||||||
|
////buf.set_string(x + 5, y, &" ".repeat(32.max(note1-s.note_start)as usize), bg);
|
||||||
|
//if step % s.time_zoom == 0 {
|
||||||
|
//buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
|
||||||
|
//}
|
||||||
|
//for k in s.note_start..s.note_start+area.width as usize {
|
||||||
|
//let key = ::midly::num::u7::from_int_lossy(k as u8);
|
||||||
|
//if step % 2 == 0 {
|
||||||
|
//let (a, b, c) = (
|
||||||
|
//(step + 0) as usize * ppq / s.time_zoom as usize,
|
||||||
|
//(step + 1) as usize * ppq / s.time_zoom as usize,
|
||||||
|
//(step + 2) as usize * ppq / s.time_zoom as usize,
|
||||||
|
//);
|
||||||
|
//let phrase = &s.phrases[s.sequence.unwrap()];
|
||||||
|
//let (character, style) = match (
|
||||||
|
//phrase.contains_note_on(key, a, b),
|
||||||
|
//phrase.contains_note_on(key, b, c),
|
||||||
|
//) {
|
||||||
|
//(true, true) => ("█", wh),
|
||||||
|
//(true, false) => ("▀", wh),
|
||||||
|
//(false, true) => ("▄", wh),
|
||||||
|
//(false, false) => ("·", bw),
|
||||||
|
//};
|
||||||
|
//character.blit(buf, x + (5 + k - s.note_start) as u16, y, Some(style));
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//if beat == step as usize {
|
||||||
|
//buf.set_string(x + 4, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow());
|
||||||
|
//for key in s.note_start..s.note_start+area.width as usize {
|
||||||
|
//let _color = if s.notes_on[key as usize] {
|
||||||
|
//Style::default().red()
|
||||||
|
//} else {
|
||||||
|
//KEY_STYLE[key as usize % 12]
|
||||||
|
//};
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//pub fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
||||||
|
//let x = x + 5 + s.note_cursor as u16;
|
||||||
|
//let y = y + s.time_cursor as u16 / 2;
|
||||||
|
//let c = if s.time_cursor % 2 == 0 { "▀" } else { "▄" };
|
||||||
|
//buf.set_string(x, y, c, Style::default());
|
||||||
|
//}
|
||||||
|
|
||||||
|
//pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
||||||
|
//let ppq = s.timebase.ppq() as usize;
|
||||||
|
//let Rect { x, y, .. } = area;
|
||||||
|
//for key in s.note_start..s.note_start+area.width as usize {
|
||||||
|
//let x = x + (5 + key - s.note_start) as u16;
|
||||||
|
//if key % 12 == 0 {
|
||||||
|
//let octave = format!("C{}", (key / 12) as i8 - 4);
|
||||||
|
//buf.set_string(x, y, &octave, Style::default());
|
||||||
|
//}
|
||||||
|
//let mut color = KEY_STYLE[key as usize % 12];
|
||||||
|
//let mut is_on = s.notes_on[key as usize];
|
||||||
|
//let step = beat;
|
||||||
|
//let (a, b, c) = (
|
||||||
|
//(step + 0) as usize * ppq / s.time_zoom as usize,
|
||||||
|
//(step + 1) as usize * ppq / s.time_zoom as usize,
|
||||||
|
//(step + 2) as usize * ppq / s.time_zoom as usize,
|
||||||
|
//);
|
||||||
|
//let key = ::midly::num::u7::from(key as u8);
|
||||||
|
//let phrase = &s.phrases[s.sequence.unwrap()];
|
||||||
|
//is_on = is_on || phrase.contains_note_on(key, a, b);
|
||||||
|
//is_on = is_on || phrase.contains_note_on(key, b, c);
|
||||||
|
//if is_on {
|
||||||
|
//color = Style::default().red();
|
||||||
|
//}
|
||||||
|
//buf.set_string(x, y - 1, &format!("▄"), color);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
////pub fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
////let Rect { x, y, width, height } = area;
|
||||||
|
////let header = draw_header(s, buf, area)?;
|
||||||
|
////let piano = match s.view {
|
||||||
|
////SequencerMode::Tiny => Rect { x, y, width, height: 0 },
|
||||||
|
////SequencerMode::Compact => Rect { x, y, width, height: 0 },
|
||||||
|
////SequencerMode::Vertical => self::vertical::draw(s, buf, Rect {
|
||||||
|
////x, y: y + header.height, width, height,
|
||||||
|
////})?,
|
||||||
|
////SequencerMode::Horizontal => self::horizontal::draw(
|
||||||
|
////buf,
|
||||||
|
////Rect { x, y: y + header.height, width, height, },
|
||||||
|
////s.phrase(),
|
||||||
|
////s.timebase.ppq() as usize,
|
||||||
|
////s.time_cursor,
|
||||||
|
////s.time_start,
|
||||||
|
////s.time_zoom,
|
||||||
|
////s.note_cursor,
|
||||||
|
////s.note_start,
|
||||||
|
////None
|
||||||
|
////)?,
|
||||||
|
////};
|
||||||
|
////Ok(draw_box(buf, Rect {
|
||||||
|
////x, y,
|
||||||
|
////width: header.width.max(piano.width),
|
||||||
|
////height: header.height + piano.height
|
||||||
|
////}))
|
||||||
|
////}
|
||||||
|
|
||||||
|
////pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
////let Rect { x, y, width, .. } = area;
|
||||||
|
////let style = Style::default().gray();
|
||||||
|
////crate::view::TransportView {
|
||||||
|
////timebase: &s.timebase,
|
||||||
|
////playing: s.playing,
|
||||||
|
////record: s.recording,
|
||||||
|
////overdub: s.overdub,
|
||||||
|
////monitor: s.monitoring,
|
||||||
|
////frame: 0
|
||||||
|
////}.render(buf, area)?;
|
||||||
|
////let separator = format!("├{}┤", "-".repeat((width - 2).into()));
|
||||||
|
////separator.blit(buf, x, y + 2, Some(style.dim()));
|
||||||
|
////let _ = draw_clips(s, buf, area)?;
|
||||||
|
////Ok(Rect { x, y, width, height: 3 })
|
||||||
|
////}
|
||||||
|
|
||||||
|
////pub fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
////let Rect { x, y, .. } = area;
|
||||||
|
////let style = Style::default().gray();
|
||||||
|
////for (i, sequence) in s.phrases.iter().enumerate() {
|
||||||
|
////let label = format!("▶ {}", &sequence.name);
|
||||||
|
////label.blit(buf, x + 2, y + 3 + (i as u16)*2, Some(if Some(i) == s.sequence {
|
||||||
|
////match s.playing {
|
||||||
|
////TransportState::Rolling => style.white().bold(),
|
||||||
|
////_ => style.not_dim().bold()
|
||||||
|
////}
|
||||||
|
////} else {
|
||||||
|
////style.dim()
|
||||||
|
////}));
|
||||||
|
////}
|
||||||
|
////Ok(Rect { x, y, width: 14, height: 14 })
|
||||||
|
////}
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub fn draw (
|
|
||||||
buf: &mut Buffer,
|
|
||||||
area: Rect,
|
|
||||||
phrase: Option<&Phrase>,
|
|
||||||
ppq: usize,
|
|
||||||
time: usize,
|
|
||||||
time0: usize,
|
|
||||||
time_z: usize,
|
|
||||||
note: usize,
|
|
||||||
note0: usize,
|
|
||||||
style: Option<Style>,
|
|
||||||
) -> Usually<Rect> {
|
|
||||||
let now = 0;
|
|
||||||
let notes = &[];
|
|
||||||
match time_z {
|
|
||||||
1 => "1/384",
|
|
||||||
2 => "1/192",
|
|
||||||
3 => "1/128",
|
|
||||||
4 => "1/96",
|
|
||||||
6 => "1/64",
|
|
||||||
8 => "1/48",
|
|
||||||
12 => "1/32",
|
|
||||||
16 => "1/24",
|
|
||||||
24 => "1/16",
|
|
||||||
32 => "1/12",
|
|
||||||
48 => "1/8",
|
|
||||||
64 => "1/6",
|
|
||||||
96 => "1/4",
|
|
||||||
128 => "1/3",
|
|
||||||
192 => "1/2",
|
|
||||||
384 => "1/1",
|
|
||||||
_ => ""
|
|
||||||
}.blit(buf, area.x, area.y, Some(Style::default().dim()));
|
|
||||||
keys(buf, area, note0, notes)?;
|
|
||||||
timer(buf, area, time0, now);
|
|
||||||
if let Some(phrase) = phrase {
|
|
||||||
lanes(buf, area, phrase, ppq, time_z, time0, note0);
|
|
||||||
}
|
|
||||||
let style = style.unwrap_or_else(||{Style::default().green().not_dim()});
|
|
||||||
cursor(buf, area, style, time, note);
|
|
||||||
//footer(buf, area, note0, note, time0, time, time_z);
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn timer (buf: &mut Buffer, area: Rect, time0: usize, now: usize) {
|
|
||||||
let x = area.x + 5;
|
|
||||||
for step in time0..(time0+area.width as usize).saturating_sub(5) {
|
|
||||||
buf.set_string(x + step as u16, area.y, &"-", if step == now {
|
|
||||||
Style::default().yellow().bold().not_dim()
|
|
||||||
} else {
|
|
||||||
Style::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keys (buf: &mut Buffer, area: Rect, note0: usize, _notes: &[bool])
|
|
||||||
-> Usually<Rect>
|
|
||||||
{
|
|
||||||
let bw = Style::default().dim();
|
|
||||||
let Rect { x, y, width, height } = area;
|
|
||||||
let h = height.saturating_sub(2);
|
|
||||||
for index in 0..h {
|
|
||||||
let y = y + h - index;
|
|
||||||
let key = KEYS_VERTICAL[(index % 6) as usize];
|
|
||||||
key.blit(buf, x + 1, y, Some(bw));
|
|
||||||
"█".blit(buf, x + 2, y, Some(bw));
|
|
||||||
"|---".repeat(width.saturating_sub(6) as usize).blit(buf, x + 5, y, Some(bw.black()));
|
|
||||||
let note_a = note0 + (index * 2) as usize;
|
|
||||||
if note_a % 12 == 0 {
|
|
||||||
let octave = format!("C{}", (note_a / 12) as i8 - 2);
|
|
||||||
octave.blit(buf, x + 3, y, None);
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let note_b = note0 + (index * 2) as usize;
|
|
||||||
if note_b % 12 == 0 {
|
|
||||||
let octave = format!("C{}", (note_b / 12) as i8 - 2);
|
|
||||||
octave.blit(buf, x + 3, y, None);
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lanes (
|
|
||||||
buf: &mut Buffer,
|
|
||||||
area: Rect,
|
|
||||||
phrase: &Phrase,
|
|
||||||
ppq: usize,
|
|
||||||
time_z: usize,
|
|
||||||
_time0: usize,
|
|
||||||
note0: usize,
|
|
||||||
) {
|
|
||||||
let Rect { x, y, width, height } = area;
|
|
||||||
//let time0 = time0 / time_z;
|
|
||||||
//let time1 = time0 + width as usize;
|
|
||||||
//let note1 = note0 + height as usize;
|
|
||||||
let bg = Style::default();
|
|
||||||
let (bw, wh) = (bg.dim(), bg.white());
|
|
||||||
let offset = 5;
|
|
||||||
for x in x+offset..x+width-offset {
|
|
||||||
let step = (x-offset) as usize * time_z;
|
|
||||||
if step % ppq == 0 {
|
|
||||||
"|".blit(buf, x as u16, y, Some(Style::default().dim()));
|
|
||||||
}
|
|
||||||
let bar = 4 * ppq;
|
|
||||||
if step % bar == 0 {
|
|
||||||
format!("{}", (step/bar)+1)
|
|
||||||
.blit(buf, x as u16, y, Some(Style::default().bold().not_dim()));
|
|
||||||
}
|
|
||||||
let (a, b) = (step, step + time_z);
|
|
||||||
for index in 0..height-2 {
|
|
||||||
let note_a = note0 + index as usize * 2;
|
|
||||||
let note_b = note0 + index as usize * 2 + 1;
|
|
||||||
let (character, style) = match (
|
|
||||||
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), a, b),
|
|
||||||
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), a, b),
|
|
||||||
) {
|
|
||||||
(true, true) => ("█", wh),
|
|
||||||
(false, true) => ("▀", wh),
|
|
||||||
(true, false) => ("▄", wh),
|
|
||||||
(false, false) => ("·", bw),
|
|
||||||
};
|
|
||||||
let y = y + height.saturating_sub(index+2) as u16;
|
|
||||||
character.blit(buf, x, y, Some(style));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor (
|
|
||||||
buf: &mut Buffer,
|
|
||||||
area: Rect,
|
|
||||||
style: Style,
|
|
||||||
time: usize,
|
|
||||||
note: usize
|
|
||||||
) {
|
|
||||||
let x = area.x + 5 + time as u16;
|
|
||||||
let y = area.y + 1 + note as u16 / 2;
|
|
||||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
|
||||||
c.blit(buf, x, y, Some(style));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn footer (
|
|
||||||
buf: &mut Buffer,
|
|
||||||
area: Rect,
|
|
||||||
note0: usize,
|
|
||||||
note: usize,
|
|
||||||
time0: usize,
|
|
||||||
time: usize,
|
|
||||||
time_z: usize,
|
|
||||||
) {
|
|
||||||
let Rect { mut x, y, width, height } = area;
|
|
||||||
buf.set_string(x, y + height, format!("├{}┤", "-".repeat((width - 2).into())),
|
|
||||||
Style::default().dim());
|
|
||||||
buf.set_string(x, y + height + 2, format!("├{}┤", "-".repeat((width - 2).into())),
|
|
||||||
Style::default().dim());
|
|
||||||
x = x + 2;
|
|
||||||
{
|
|
||||||
for (_, [letter, title, value]) in [
|
|
||||||
["S", &format!("ync"), &format!("<4/4>")],
|
|
||||||
["Q", &format!("uant"), &format!("<1/{}>", 4 * time_z)],
|
|
||||||
["N", &format!("ote"), &format!("{} ({}-{})", note0 + note, note0, "X")],
|
|
||||||
["T", &format!("ime"), &format!("{} ({}-{})", time0 + time, time0 + 1, "X")],
|
|
||||||
].iter().enumerate() {
|
|
||||||
buf.set_string(x, y + height + 1, letter, Style::default().bold().yellow().dim());
|
|
||||||
x = x + 1;
|
|
||||||
buf.set_string(x, y + height + 1, &title, Style::default().bold().dim());
|
|
||||||
x = x + title.len() as u16 + 1;
|
|
||||||
buf.set_string(x, y + height + 1, &value, Style::default().not_dim());
|
|
||||||
x = x + value.len() as u16;
|
|
||||||
buf.set_string(x, y + height + 1, " ", Style::default().dim());
|
|
||||||
x = x + 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub fn draw (
|
|
||||||
s: &Sequencer,
|
|
||||||
buf: &mut Buffer,
|
|
||||||
mut area: Rect,
|
|
||||||
) -> Usually<Rect> {
|
|
||||||
area.x = area.x + 13;
|
|
||||||
keys(s, buf, area, 0);
|
|
||||||
steps(s, buf, area, 0);
|
|
||||||
playhead(s, buf, area.x, area.y);
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
|
||||||
if s.sequence.is_none() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let ppq = s.timebase.ppq() as usize;
|
|
||||||
let bg = Style::default();
|
|
||||||
let bw = bg.dim();
|
|
||||||
let wh = bg.white();
|
|
||||||
let Rect { x, y, .. } = area;
|
|
||||||
for step in s.time_start..s.time_start+area.height as usize {
|
|
||||||
let y = y - (s.time_start + step / 2) as u16;
|
|
||||||
let step = step as usize;
|
|
||||||
//buf.set_string(x + 5, y, &" ".repeat(32.max(note1-s.note_start)as usize), bg);
|
|
||||||
if step % s.time_zoom == 0 {
|
|
||||||
buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
|
|
||||||
}
|
|
||||||
for k in s.note_start..s.note_start+area.width as usize {
|
|
||||||
let key = ::midly::num::u7::from_int_lossy(k as u8);
|
|
||||||
if step % 2 == 0 {
|
|
||||||
let (a, b, c) = (
|
|
||||||
(step + 0) as usize * ppq / s.time_zoom as usize,
|
|
||||||
(step + 1) as usize * ppq / s.time_zoom as usize,
|
|
||||||
(step + 2) as usize * ppq / s.time_zoom as usize,
|
|
||||||
);
|
|
||||||
let phrase = &s.phrases[s.sequence.unwrap()];
|
|
||||||
let (character, style) = match (
|
|
||||||
phrase.contains_note_on(key, a, b),
|
|
||||||
phrase.contains_note_on(key, b, c),
|
|
||||||
) {
|
|
||||||
(true, true) => ("█", wh),
|
|
||||||
(true, false) => ("▀", wh),
|
|
||||||
(false, true) => ("▄", wh),
|
|
||||||
(false, false) => ("·", bw),
|
|
||||||
};
|
|
||||||
character.blit(buf, x + (5 + k - s.note_start) as u16, y, Some(style));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if beat == step as usize {
|
|
||||||
buf.set_string(x + 4, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow());
|
|
||||||
for key in s.note_start..s.note_start+area.width as usize {
|
|
||||||
let _color = if s.notes_on[key as usize] {
|
|
||||||
Style::default().red()
|
|
||||||
} else {
|
|
||||||
KEY_STYLE[key as usize % 12]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
|
||||||
let x = x + 5 + s.note_cursor as u16;
|
|
||||||
let y = y + s.time_cursor as u16 / 2;
|
|
||||||
let c = if s.time_cursor % 2 == 0 { "▀" } else { "▄" };
|
|
||||||
buf.set_string(x, y, c, Style::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
|
||||||
let ppq = s.timebase.ppq() as usize;
|
|
||||||
let Rect { x, y, .. } = area;
|
|
||||||
for key in s.note_start..s.note_start+area.width as usize {
|
|
||||||
let x = x + (5 + key - s.note_start) as u16;
|
|
||||||
if key % 12 == 0 {
|
|
||||||
let octave = format!("C{}", (key / 12) as i8 - 4);
|
|
||||||
buf.set_string(x, y, &octave, Style::default());
|
|
||||||
}
|
|
||||||
let mut color = KEY_STYLE[key as usize % 12];
|
|
||||||
let mut is_on = s.notes_on[key as usize];
|
|
||||||
let step = beat;
|
|
||||||
let (a, b, c) = (
|
|
||||||
(step + 0) as usize * ppq / s.time_zoom as usize,
|
|
||||||
(step + 1) as usize * ppq / s.time_zoom as usize,
|
|
||||||
(step + 2) as usize * ppq / s.time_zoom as usize,
|
|
||||||
);
|
|
||||||
let key = ::midly::num::u7::from(key as u8);
|
|
||||||
let phrase = &s.phrases[s.sequence.unwrap()];
|
|
||||||
is_on = is_on || phrase.contains_note_on(key, a, b);
|
|
||||||
is_on = is_on || phrase.contains_note_on(key, b, c);
|
|
||||||
if is_on {
|
|
||||||
color = Style::default().red();
|
|
||||||
}
|
|
||||||
buf.set_string(x, y - 1, &format!("▄"), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue