From 316fe45b2a35d028ff71a4fd1b6cffc1de25fa4e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 3 Jul 2024 18:36:16 +0300 Subject: [PATCH] bye sequencer --- src/control.rs | 25 +- src/control/sequencer.rs | 203 -------------- src/core/jack.rs | 24 ++ src/main.rs | 65 ++++- src/model.rs | 2 - src/model/sequencer.rs | 141 ---------- src/model/track.rs | 70 +++-- src/view.rs | 12 +- src/view/grid.rs | 4 +- src/view/sequencer.rs | 448 +++++++++++++++++++++++++------ src/view/sequencer/horizontal.rs | 177 ------------ src/view/sequencer/vertical.rs | 98 ------- 12 files changed, 510 insertions(+), 759 deletions(-) delete mode 100644 src/control/sequencer.rs delete mode 100644 src/model/sequencer.rs delete mode 100644 src/view/sequencer/horizontal.rs delete mode 100644 src/view/sequencer/vertical.rs diff --git a/src/control.rs b/src/control.rs index ae5c9e12..23276add 100644 --- a/src/control.rs +++ b/src/control.rs @@ -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] = 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] = 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 { app.playing = match app.playing.expect("after jack init") { @@ -280,7 +301,7 @@ fn add_scene (app: &mut App) -> Usually { fn add_track (app: &mut App) -> Usually { 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) } diff --git a/src/control/sequencer.rs b/src/control/sequencer.rs deleted file mode 100644 index d9115f3b..00000000 --- a/src/control/sequencer.rs +++ /dev/null @@ -1,203 +0,0 @@ -use crate::{core::*, model::*}; - -pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Usually { - handle_keymap(state, event, KEYMAP) -} - -pub const KEYMAP: &'static [KeyBinding] = 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 { - Ok(false) -} - -fn note_add (s: &mut Sequencer) -> Usually { - 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 { - Ok(true) -} - -fn time_cursor_inc (s: &mut Sequencer) -> Usually { - s.time_cursor = s.time_cursor + 1; - Ok(true) -} - -fn time_cursor_dec (s: &mut Sequencer) -> Usually { - s.time_cursor = s.time_cursor.saturating_sub(1); - Ok(true) -} - -fn note_cursor_inc (s: &mut Sequencer) -> Usually { - s.note_cursor = s.note_cursor + 1; - Ok(true) -} - -fn note_cursor_dec (s: &mut Sequencer) -> Usually { - s.note_cursor = s.note_cursor.saturating_sub(1); - Ok(true) -} - -fn cursor_up (s: &mut Sequencer) -> Usually { - 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 { - 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 { - 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 { - 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 { - //s.cursor.2 = s.cursor.2 + 1 - Ok(true) -} - -fn cursor_duration_dec (_: &mut Sequencer) -> Usually { - //if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 } - Ok(true) -} - -fn mode_next (s: &mut Sequencer) -> Usually { - 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 { - s.transport.stop()?; - s.transport.locate(0)?; - s.playing = TransportState::Stopped; - Ok(true) -} - -fn toggle_play (s: &mut Sequencer) -> Usually { - 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 { - s.recording = !s.recording; - Ok(true) -} - -fn toggle_overdub (s: &mut Sequencer) -> Usually { - s.overdub = !s.overdub; - Ok(true) -} - -fn toggle_monitor (s: &mut Sequencer) -> Usually { - s.monitoring = !s.monitoring; - Ok(true) -} - -fn quantize_next (s: &mut Sequencer) -> Usually { - if s.time_zoom < 64 { - s.time_zoom = s.time_zoom * 2; - } - Ok(true) -} - -fn quantize_prev (s: &mut Sequencer) -> Usually { - if s.time_zoom > 1 { - s.time_zoom = s.time_zoom / 2; - } - Ok(true) -} - -fn zoom_in (s: &mut Sequencer) -> Usually { - s.time_zoom = s.time_zoom / 2; - Ok(true) -} - -fn zoom_out (s: &mut Sequencer) -> Usually { - s.time_zoom = s.time_zoom * 2; - Ok(true) -} diff --git a/src/core/jack.rs b/src/core/jack.rs index a8604318..f4e2a894 100644 --- a/src/core/jack.rs +++ b/src/core/jack.rs @@ -147,3 +147,27 @@ impl NotificationHandler for Notifications { 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:?}")); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 61e0d5ae..52c135cc 100644 --- a/src/main.rs +++ b/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>, pub audio_outs: Option>>, 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>>> = 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) } diff --git a/src/model.rs b/src/model.rs index 29de73db..32f2ea39 100644 --- a/src/model.rs +++ b/src/model.rs @@ -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; diff --git a/src/model/sequencer.rs b/src/model/sequencer.rs deleted file mode 100644 index 475c2040..00000000 --- a/src/model/sequencer.rs +++ /dev/null @@ -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, - /// JACK MIDI output port that will be created. - pub midi_out: Port, - /// Holds info about tempo - pub timebase: Arc, - /// Phrase selector - pub sequence: Option, - /// Map: tick -> MIDI events at tick - pub phrases: Vec, - /// Red keys on piano roll. - pub notes_on: Vec, - /// 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>>> = 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, - phrases: Option>, - ) -> Usually { - 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> { Ok(vec![self.midi_in.name()?]) } - fn midi_outs (&self) -> Usually> { 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:?}")); - } - } - } -} diff --git a/src/model/track.rs b/src/model/track.rs index 1589aa81..181f2e8b 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -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, + 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 selector + pub sequence: Option, + /// Output from current sequence. + pub midi_out: Port, + /// Red keys on piano roll. + pub notes_on: Vec, + /// Device chain + pub chain: Chain, } impl Track { pub fn new ( - name: &str, - jack: &Client, - timebase: &Arc, - devices: Option>>, - phrases: Option>, + name: &str, + jack: &Client, + devices: Option>>, + phrases: Option>, ) -> Usually { - 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> { - self.sequencer.midi_ins() - } - fn audio_outs (&self) -> Usually> { - self.chain.audio_outs() - } + fn midi_ins (&self) -> Usually> { Ok(vec![]) } + fn midi_outs (&self) -> Usually> { Ok(vec![self.midi_out.name()?]) } } diff --git a/src/view.rs b/src/view.rs index ad9564b2..533c7f74 100644 --- a/src/view.rs +++ b/src/view.rs @@ -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; } diff --git a/src/view/grid.rs b/src/view/grid.rs index 848fd199..69d875b5 100644 --- a/src/view/grid.rs +++ b/src/view/grid.rs @@ -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!("????") diff --git a/src/view/sequencer.rs b/src/view/sequencer.rs index 50eed021..9b2abbe9 100644 --- a/src/view/sequencer.rs +++ b/src/view/sequencer.rs @@ -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 { - 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 { - 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 { - 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