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 plugin;
|
||||
pub mod sampler;
|
||||
pub mod sequencer;
|
||||
|
||||
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('`'), 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> {
|
||||
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> {
|
||||
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();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.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 timebase = &state.timebase;
|
||||
let ppq = timebase.ppq() as usize;
|
||||
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([
|
||||
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
|
||||
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
|
||||
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
|
||||
])))?.boxed(),
|
||||
|
||||
Plugin::lv2(
|
||||
"Panagement",
|
||||
"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2"
|
||||
)?.boxed(),
|
||||
|
||||
]), Some(vec![
|
||||
Phrase::new("4 kicks", ppq * 4, Some(phrase! {
|
||||
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![
|
||||
Phrase::new("Offbeat", ppq * 4, Some(phrase! {
|
||||
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() },
|
||||
})),
|
||||
]))?,
|
||||
|
||||
];
|
||||
state.track_cursor = 1;
|
||||
state.scene_cursor = 1;
|
||||
|
|
@ -100,14 +100,57 @@ pub struct App {
|
|||
pub midi_in: Option<Port<MidiIn>>,
|
||||
pub audio_outs: Option<Vec<Port<AudioOut>>>,
|
||||
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| {
|
||||
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
||||
self.playing = Some(transport.state);
|
||||
self.playhead = transport.pos.frame() as usize;
|
||||
for Track { sequencer, .. } in self.tracks.iter_mut() {
|
||||
sequencer.process(client, scope);
|
||||
for Track {
|
||||
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
|
||||
});
|
||||
|
|
@ -165,12 +208,6 @@ impl App {
|
|||
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> {
|
||||
Some(&self.track()?.1.chain)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,11 @@ pub mod phrase;
|
|||
pub mod plugin;
|
||||
pub mod sampler;
|
||||
pub mod scene;
|
||||
pub mod sequencer;
|
||||
pub mod track;
|
||||
|
||||
pub use self::phrase::Phrase;
|
||||
pub use self::scene::Scene;
|
||||
pub use self::track::Track;
|
||||
pub use self::sequencer::{Sequencer, SequencerMode};
|
||||
pub use self::chain::Chain;
|
||||
pub use self::sampler::{Sampler, Sample};
|
||||
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:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,49 +3,47 @@ use crate::model::*;
|
|||
|
||||
pub struct Track {
|
||||
pub name: String,
|
||||
pub sequencer: Sequencer,
|
||||
pub chain: Chain,
|
||||
/// Play input through output.
|
||||
pub monitoring: bool,
|
||||
/// 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 {
|
||||
pub fn new (
|
||||
name: &str,
|
||||
jack: &Client,
|
||||
timebase: &Arc<Timebase>,
|
||||
devices: Option<Vec<Box<dyn Device>>>,
|
||||
phrases: Option<Vec<Phrase>>,
|
||||
) -> Usually<Self> {
|
||||
let sequencer = Sequencer::new(&name, timebase, phrases)?;
|
||||
let chain = Chain::new(&name, devices)?;
|
||||
let midi_out = jack.register_port(name, MidiOut)?;
|
||||
//let (client, _status) = Client::new("init", ClientOptions::NO_START_SERVER)?;
|
||||
//{
|
||||
//if let (Some(output), Some(input)) = (
|
||||
//sequencer.midi_outs()?.get(0).clone(),
|
||||
//if let Some(item) = chain.items.get(0) {
|
||||
//if let Some(port) = item.midi_ins()?.get(0) {
|
||||
//Some(port.clone())
|
||||
//} else {
|
||||
//None
|
||||
//}
|
||||
//} else {
|
||||
//None
|
||||
//}
|
||||
//) {
|
||||
//client.connect_ports_by_name(&output, &input)?;
|
||||
//}
|
||||
//}
|
||||
Ok(Self { name: name.to_string(), sequencer, chain, midi_out })
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
chain: Chain::new(&name, devices)?,
|
||||
midi_out: jack.register_port(name, MidiOut)?,
|
||||
notes_on: vec![],
|
||||
monitoring: false,
|
||||
recording: false,
|
||||
overdub: true,
|
||||
sequence: None,
|
||||
phrases: phrases.unwrap_or_else(||vec![])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PortList for Track {
|
||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||
self.sequencer.midi_ins()
|
||||
}
|
||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||
self.chain.audio_outs()
|
||||
}
|
||||
fn midi_ins (&self) -> Usually<Vec<String>> { Ok(vec![]) }
|
||||
fn midi_outs (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_out.name()?]) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,8 +48,12 @@ render!(App |self, buf, area| {
|
|||
y = y + SequencerView {
|
||||
focused: self.section == 2,
|
||||
ppq: self.timebase.ppq() as usize,
|
||||
track: track,
|
||||
phrase: track.map(|t|&t.sequencer.phrases[0]),
|
||||
phrase: track.map(|t|&t.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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ impl<'a> SceneGridView<'a> {
|
|||
label.blit(self.buf, self.area.x + x2, self.area.y, Some(Style::default().bold()));
|
||||
for (i, clip) in scene.clips.iter().enumerate() {
|
||||
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);
|
||||
label.blit(self.buf, self.area.x + x2, self.area.y + 1 + i as u16, None);
|
||||
x3 = x3.max(label.len() as u16)
|
||||
|
|
@ -186,7 +186,7 @@ impl<'a> SceneGridView<'a> {
|
|||
let clip = scene.clips.get(track);
|
||||
let index = index as u16;
|
||||
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)
|
||||
} else {
|
||||
format!("????")
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
use crate::{core::*,model::*,view::*};
|
||||
|
||||
pub mod horizontal;
|
||||
pub mod vertical;
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SequencerMode { Tiny, Compact, Horizontal, Vertical, }
|
||||
|
||||
pub struct SequencerView<'a> {
|
||||
pub focused: bool,
|
||||
pub phrase: Option<&'a Phrase>,
|
||||
pub track: Option<&'a Track>,
|
||||
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> {
|
||||
|
|
@ -18,87 +27,366 @@ impl<'a> Render for SequencerView<'a> {
|
|||
lozenge_left(buf, x, y, height, style);
|
||||
lozenge_right(buf, x + width - 1, y, height, style);
|
||||
}
|
||||
if let Some(ref track) = self.track {
|
||||
self::horizontal::draw(
|
||||
buf,
|
||||
area,
|
||||
self.phrase,
|
||||
self.ppq,
|
||||
track.sequencer.time_cursor,
|
||||
track.sequencer.time_start,
|
||||
track.sequencer.time_zoom,
|
||||
track.sequencer.note_cursor,
|
||||
track.sequencer.note_start,
|
||||
self.time_cursor,
|
||||
self.time_start,
|
||||
self.time_zoom,
|
||||
self.note_cursor,
|
||||
self.note_start,
|
||||
Some(if self.focused {
|
||||
Style::default().green().not_dim()
|
||||
} else {
|
||||
Style::default().green().dim()
|
||||
})
|
||||
)?;
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}))
|
||||
}
|
||||
mod horizontal {
|
||||
use crate::core::*;
|
||||
use super::*;
|
||||
|
||||
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()
|
||||
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.dim()
|
||||
}));
|
||||
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