diff --git a/src/control.rs b/src/control.rs index 08780477..386be6cc 100644 --- a/src/control.rs +++ b/src/control.rs @@ -78,15 +78,15 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding] = keymap!(App { Ok(true) }], [Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| { - app.seq_buf.time_zoom = prev_note_length(app.seq_buf.time_zoom); + app.sequencer.time_zoom = prev_note_length(app.sequencer.time_zoom); Ok(true) }], [Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| { - app.seq_buf.time_zoom = next_note_length(app.seq_buf.time_zoom); + app.sequencer.time_zoom = next_note_length(app.sequencer.time_zoom); Ok(true) }], [Char('x'), NONE, "extend", "double the current clip", |app: &mut App| { - app.arranger.phrase_mut().map(|phrase|{ + app.arranger.phrase().map(|x|x.write().unwrap()).map(|mut phrase|{ let mut notes = phrase.notes.clone(); notes.extend_from_slice(&mut phrase.notes); phrase.notes = notes; diff --git a/src/control/arranger.rs b/src/control/arranger.rs index 4b0804de..cce4b0e2 100644 --- a/src/control/arranger.rs +++ b/src/control/arranger.rs @@ -6,36 +6,46 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding] = keymap!(App { app.arranger.mode = !app.arranger.mode; Ok(true) }], - [Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| Ok( + [Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| { match app.arranger.mode { - false => {app.arranger.scene_prev();true}, - true => {app.arranger.track_prev();true}, - } - )], - [Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok( + false => app.arranger.scene_prev(), + true => app.arranger.track_prev(), + }; + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + Ok(true) + }], + [Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| { match app.arranger.mode { - false => {app.arranger.scene_next();true}, - true => {app.arranger.track_next();true}, - } - )], - [Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok( + false => app.arranger.scene_next(), + true => app.arranger.track_next(), + }; + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + Ok(true) + }], + [Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| { match app.arranger.mode { - false => {app.arranger.track_prev();true}, - true => {app.arranger.scene_prev();true}, - } - )], - [Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok( + false => app.arranger.track_prev(), + true => app.arranger.scene_prev(), + }; + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + Ok(true) + }], + [Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| { match app.arranger.mode { - false => {app.arranger.track_next();true}, - true => {app.arranger.scene_next();true} - } - )], + false => app.arranger.track_next(), + true => app.arranger.scene_next() + }; + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + Ok(true) + }], [Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| { app.arranger.phrase_next(); + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); Ok(true) }], [Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| { app.arranger.phrase_prev(); + app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); Ok(true) }], [Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| { diff --git a/src/control/chain.rs b/src/control/chain.rs index 9552843a..642b531b 100644 --- a/src/control/chain.rs +++ b/src/control/chain.rs @@ -23,7 +23,7 @@ pub const KEYMAP_CHAIN: &'static [KeyBinding] = keymap!(App { Ok(false) }], [Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| { - app.chain_mode = !app.seq_mode; + app.chain_mode = !app.chain_mode; Ok(true) }], }); diff --git a/src/control/focus.rs b/src/control/focus.rs index 23678fe3..548baca8 100644 --- a/src/control/focus.rs +++ b/src/control/focus.rs @@ -11,13 +11,15 @@ pub const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { [Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{ app.entered = false; app.transport.entered = app.entered; - app.arranger.entered = app.entered; + app.arranger.entered = app.entered; + app.sequencer.entered = app.entered; Ok(true) }], [Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{ app.entered = true; app.transport.entered = app.entered; - app.arranger.entered = app.entered; + app.arranger.entered = app.entered; + app.sequencer.entered = app.entered; Ok(true) }], }); @@ -26,8 +28,10 @@ pub fn focus_next (app: &mut App) -> Usually { app.section.next(); app.transport.focused = app.section == AppFocus::Transport; app.transport.entered = app.entered; - app.arranger.focused = app.section == AppFocus::Arranger; - app.arranger.entered = app.entered; + app.arranger.focused = app.section == AppFocus::Arranger; + app.arranger.entered = app.entered; + app.sequencer.focused = app.section == AppFocus::Sequencer; + app.sequencer.entered = app.entered; Ok(true) } @@ -35,7 +39,9 @@ pub fn focus_prev (app: &mut App) -> Usually { app.section.prev(); app.transport.focused = app.section == AppFocus::Transport; app.transport.entered = app.entered; - app.arranger.focused = app.section == AppFocus::Arranger; - app.arranger.entered = app.entered; + app.arranger.focused = app.section == AppFocus::Arranger; + app.arranger.entered = app.entered; + app.sequencer.focused = app.section == AppFocus::Sequencer; + app.sequencer.entered = app.entered; Ok(true) } diff --git a/src/control/sequencer.rs b/src/control/sequencer.rs index 20d08515..5f8d0e16 100644 --- a/src/control/sequencer.rs +++ b/src/control/sequencer.rs @@ -3,31 +3,31 @@ use crate::{core::*, model::App}; /// Key bindings for phrase editor. pub const KEYMAP_SEQUENCER: &'static [KeyBinding] = keymap!(App { [Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| { - app.note_cursor = app.note_cursor.saturating_sub(1); + app.sequencer.note_cursor = app.sequencer.note_cursor.saturating_sub(1); Ok(true) }], [Down, NONE, "seq_cursor_down", "move cursor up", |app: &mut App| { - app.note_cursor = app.note_cursor + 1; + app.sequencer.note_cursor = app.sequencer.note_cursor + 1; Ok(true) }], [Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| { - if app.entered { - app.time_cursor = app.time_cursor.saturating_sub(1); + if app.sequencer.entered { + app.sequencer.time_cursor = app.sequencer.time_cursor.saturating_sub(1); } else { - app.seq_buf.time_start = app.seq_buf.time_start.saturating_sub(1); + app.sequencer.time_start = app.sequencer.time_start.saturating_sub(1); } Ok(true) }], [Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| { - if app.entered { - app.time_cursor = app.time_cursor + 1; + if app.sequencer.entered { + app.sequencer.time_cursor = app.sequencer.time_cursor + 1; } else { - app.seq_buf.time_start = app.seq_buf.time_start + 1; + app.sequencer.time_start = app.sequencer.time_start + 1; } Ok(true) }], [Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| { - app.seq_mode = !app.seq_mode; + app.sequencer.mode = !app.sequencer.mode; Ok(true) }], // [Char('a'), NONE, "note_add", "Add note", note_add], diff --git a/src/edn.rs b/src/edn.rs index 3e2e9a5f..976cd5d9 100644 --- a/src/edn.rs +++ b/src/edn.rs @@ -172,7 +172,7 @@ impl Track { _ => {} }); let track = app.arranger.track_add(name)?; - for phrase in phrases { track.phrases.push(phrase); } + for phrase in phrases { track.phrases.push(Arc::new(RwLock::new(phrase))); } for device in devices { track.add_device(device)?; } Ok(track) } diff --git a/src/model.rs b/src/model.rs index bf760d9f..193f5bae 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,8 +1,8 @@ //! Application state. -submod! { arranger looper mixer phrase plugin sampler scene track transport } +submod! { arranger looper mixer phrase plugin sampler sequencer scene track transport } -use crate::{core::*, view::*}; +use crate::core::*; /// Root of application state. pub struct App { @@ -14,8 +14,10 @@ pub struct App { pub section: AppFocus, /// Transport model and view. pub transport: TransportToolbar, - /// Arraneger model and view. + /// Arranger model and view. pub arranger: Arranger, + /// Phrase editor + pub sequencer: Sequencer, /// Main JACK client. pub jack: Option, /// Map of external MIDI outs in the jack graph @@ -31,17 +33,6 @@ pub struct App { pub audio_outs: Vec>>, /// Number of frames requested by process callback chunk_size: usize, - - /// Display mode of sequencer seciton - pub seq_mode: bool, - /// Display buffer for sequencer - pub seq_buf: BufferedSequencerView, - /// Display position of cursor within note range - pub note_cursor: usize, - /// Range of notes to display - pub note_start: usize, - /// Display position of cursor within time range - pub time_cursor: usize, } impl App { @@ -58,6 +49,7 @@ impl App { section: AppFocus::default(), transport: TransportToolbar::new(Some(jack.transport())), arranger: Arranger::new(), + sequencer: Sequencer::new(), jack: Some(jack), audio_outs: vec![], chain_mode: false, @@ -65,12 +57,6 @@ impl App { midi_in: None, midi_ins: vec![], xdg: Some(xdg), - - seq_mode: false, - seq_buf: BufferedSequencerView::new(96, 16384), - note_cursor: 0, - note_start: 2, - time_cursor: 0, }) } } diff --git a/src/model/arranger.rs b/src/model/arranger.rs index de1576c8..c99f08df 100644 --- a/src/model/arranger.rs +++ b/src/model/arranger.rs @@ -58,10 +58,10 @@ impl Arranger { self.selected.track_prev() } pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> { - self.tracks.push(match name { - Some(name) => Track::new(name, None, None)?, - None => Track::new(&self.track_default_name(), None, None)? - }); + self.tracks.push(name.map_or_else( + || Track::new(&self.track_default_name()), + |name| Track::new(name), + )?); let index = self.tracks.len() - 1; Ok(&mut self.tracks[index]) } @@ -106,15 +106,15 @@ impl Arranger { /// Phrase management methods impl Arranger { - pub fn phrase (&self) -> Option<&Phrase> { + pub fn phrase (&self) -> Option<&Arc>> { let track_id = self.selected.track()?; self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?) } - pub fn phrase_mut (&mut self) -> Option<&mut Phrase> { - let track_id = self.selected.track()?; - let clip = *self.scene()?.clips.get(track_id)?; - self.tracks.get_mut(track_id)?.phrases.get_mut(clip?) - } + //pub fn phrase_mut (&mut self) -> Option<&mut Phrase> { + //let track_id = self.selected.track()?; + //let clip = *self.scene()?.clips.get(track_id)?; + //self.tracks.get_mut(track_id)?.phrases.get_mut(clip?) + //} pub fn phrase_next (&mut self) { unimplemented!(); //if let Some((track_index, track)) = self.track_mut() { diff --git a/src/model/sequencer.rs b/src/model/sequencer.rs new file mode 100644 index 00000000..bfe18082 --- /dev/null +++ b/src/model/sequencer.rs @@ -0,0 +1,46 @@ +use crate::{core::*, model::Phrase}; + +pub struct Sequencer { + pub phrase: Option>>, + pub mode: bool, + pub buffer: Buffer, + pub now: usize, + pub ppq: usize, + pub note_cursor: usize, + pub note_start: usize, + pub time_cursor: usize, + pub time_start: usize, + pub time_zoom: usize, + + pub focused: bool, + pub entered: bool, + + /// Highlight input keys + pub notes_in: [bool; 128], + /// Highlight output keys + pub notes_out: [bool; 128], +} + +impl Sequencer { + pub fn new () -> Self { + Self { + buffer: Buffer::empty(Rect::default()), + entered: false, + focused: false, + mode: false, + note_cursor: 0, + note_start: 0, + notes_in: [false;128], + notes_out: [false;128], + phrase: None, + time_cursor: 0, + time_start: 0, + time_zoom: 12, + now: 0, + ppq: 96 + } + } + pub fn show (&mut self, phrase: Option<&Arc>>) { + self.phrase = phrase.map(Clone::clone); + } +} diff --git a/src/model/track.rs b/src/model/track.rs index c24d5c49..b0565a02 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -10,7 +10,7 @@ pub struct Track { /// Overdub input to sequence. pub overdub: bool, /// Map: tick -> MIDI events at tick - pub phrases: Vec, + pub phrases: Vec>>, /// Phrase selector pub sequence: Option, /// Output from current sequence. @@ -30,11 +30,7 @@ pub struct Track { } impl Track { - pub fn new ( - name: &str, - phrases: Option>, - devices: Option>, - ) -> Usually { + pub fn new (name: &str) -> Usually { Ok(Self { name: name.to_string(), midi_out: None, @@ -45,8 +41,8 @@ impl Track { recording: false, overdub: true, sequence: None, - phrases: phrases.unwrap_or_else(||Vec::with_capacity(16)), - devices: devices.unwrap_or_else(||Vec::with_capacity(16)), + phrases: vec![], + devices: vec![], device: 0, reset: true, }) @@ -118,14 +114,18 @@ impl Track { ) = ( playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id)) ) { - if self.midi_out.is_some() { - phrase.process_out( - &mut self.midi_out_buf, - &mut self.notes_out, - timebase, - (frame0.saturating_sub(start_frame), frames, period) - ); - } + phrase.read().map(|phrase|{ + if self.midi_out.is_some() { + phrase.process_out( + &mut self.midi_out_buf, + &mut self.notes_out, + timebase, + (frame0.saturating_sub(start_frame), frames, period) + ); + } + }); + let mut phrase = phrase.write().unwrap(); + let length = phrase.length; // Monitor and record input if input.is_some() && (self.recording || self.monitoring) { // For highlighting keys and note repeat @@ -143,7 +143,7 @@ impl Track { let quantized = ( pulse / quant as f64 ).round() as usize * quant; - let looped = quantized % phrase.length; + let looped = quantized % length; looped }, message); } diff --git a/src/view.rs b/src/view.rs index bc8eafc4..db16c80f 100644 --- a/src/view.rs +++ b/src/view.rs @@ -12,7 +12,7 @@ render!(App |self, buf, area| { &self.arranger, &If(self.arranger.selected.is_clip(), &Split::right([ &ChainView::vertical(&self), - &SequencerView::new(&self), + &self.sequencer, ])) ]).render(buf, area)?; if let Some(ref modal) = self.modal { diff --git a/src/view/arranger.rs b/src/view/arranger.rs index cbc0c5bb..80245725 100644 --- a/src/view/arranger.rs +++ b/src/view/arranger.rs @@ -65,7 +65,7 @@ impl Arranger { let label = match scene.clips.get(track_index) { Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) { let icon = if track.sequence == Some(*clip) { "" } else { "┊" }; - format!("{icon} {}", phrase.name) + format!("{icon} {}", phrase.read().unwrap().name) } else { format!(" ??? ") }, @@ -233,7 +233,7 @@ impl Arranger { &|buf: &mut Buffer, area: Rect|{ let mut x2 = 0; let Rect { x, y, height, .. } = area; - for (i, scene) in self.scenes.iter().enumerate() { + for (_scene_index, scene) in self.scenes.iter().enumerate() { let active_scene = false;//self.selected == ArrangerFocus::Scene(i) || cursor.1 > 0 && self.cursor.1 - 1 == i; let sep = Some(if active_scene { Style::default().yellow().not_dim() @@ -252,11 +252,10 @@ impl Arranger { let active_track = false;//self.cursor.0 > 0 && self.cursor.0 - 1 == i; if let Some(clip) = clip { let y2 = y + 1 + i as u16 * 2; - let label = format!("{}", if let Some(phrase) = self.tracks[i].phrases.get(*clip) { - &phrase.name - } else { - "...." - }); + let label = match self.tracks[i].phrases.get(*clip) { + Some(phrase) => &format!("{}", phrase.read().unwrap().name), + None => "...." + }; label.blit(buf, x + x2, y2, Some(if active_track && active_scene { Style::default().not_dim().yellow().bold() } else { diff --git a/src/view/linebuf.rs b/src/view/linebuf.rs deleted file mode 100644 index ec69e085..00000000 --- a/src/view/linebuf.rs +++ /dev/null @@ -1,66 +0,0 @@ -pub struct LineBuffer { - width: usize, - cells: Vec, - style: Option