bye sequencer

This commit is contained in:
🪞👃🪞 2024-07-03 18:36:16 +03:00
parent 2165e5d45d
commit 316fe45b2a
12 changed files with 510 additions and 759 deletions

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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:?}"));
}
}
}
}

View file

@ -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)
}

View file

@ -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;

View file

@ -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:?}"));
}
}
}
}

View file

@ -2,50 +2,48 @@ use crate::core::*;
use crate::model::*;
pub struct Track {
pub name: String,
pub sequencer: Sequencer,
pub chain: Chain,
pub midi_out: Port<MidiOut>,
pub name: String,
/// 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>>,
name: &str,
jack: &Client,
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()?]) }
}

View file

@ -46,10 +46,14 @@ render!(App |self, buf, area| {
}.render(buf, Rect { x, y, width, height: height / 3 })?.height;
y = y + SequencerView {
focused: self.section == 2,
ppq: self.timebase.ppq() as usize,
track: track,
phrase: track.map(|t|&t.sequencer.phrases[0]),
focused: self.section == 2,
ppq: self.timebase.ppq() as usize,
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;
}

View file

@ -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!("????")

View file

@ -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,
Some(if self.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
})
)?;
}
self::horizontal::draw(
buf,
area,
self.phrase,
self.ppq,
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()
}
} else {
style.dim()
}));
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;
}
}
}
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 })
////}
//}

View file

@ -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;
}
}
}

View file

@ -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);
}
}