mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
render chain with SplitFocus
This commit is contained in:
parent
a39e694a3e
commit
c3040cef1c
12 changed files with 247 additions and 147 deletions
|
|
@ -20,7 +20,7 @@
|
||||||
(:12 (36 100))
|
(:12 (36 100))
|
||||||
(:14 (36 110)))
|
(:14 (36 110)))
|
||||||
(phrase { :name "D Beat" :beats 4 :steps 16 }
|
(phrase { :name "D Beat" :beats 4 :steps 16 }
|
||||||
(:00 (36 128))
|
(:00 (36 128) (49 110))
|
||||||
(:00 (44 50))
|
(:00 (44 50))
|
||||||
(:02 (44 30))
|
(:02 (44 30))
|
||||||
(:04 (40 100))
|
(:04 (40 100))
|
||||||
|
|
@ -89,10 +89,16 @@
|
||||||
(sample { :midi 34 :name "808" :file "808.wav" })
|
(sample { :midi 34 :name "808" :file "808.wav" })
|
||||||
(sample { :midi 35 :name "KC1" :file "kik.wav" })
|
(sample { :midi 35 :name "KC1" :file "kik.wav" })
|
||||||
(sample { :midi 36 :name "KC2" :file "kik2.wav" })
|
(sample { :midi 36 :name "KC2" :file "kik2.wav" })
|
||||||
|
(sample { :midi 37 :name "RIM" :file "rim.wav" })
|
||||||
(sample { :midi 38 :name "SN1" :file "sna.wav" })
|
(sample { :midi 38 :name "SN1" :file "sna.wav" })
|
||||||
|
(sample { :midi 39 :name "SHK" :file "shk.wav" })
|
||||||
(sample { :midi 40 :name "SN2" :file "sna2.wav" })
|
(sample { :midi 40 :name "SN2" :file "sna2.wav" })
|
||||||
(sample { :midi 42 :name "HH1" :file "chh.wav" })
|
(sample { :midi 42 :name "HH1" :file "chh.wav" })
|
||||||
(sample { :midi 44 :name "HH2" :file "chh2.wav" })))
|
(sample { :midi 45 :name "HH1" :file "ohh.wav" })
|
||||||
|
(sample { :midi 46 :name "HH1" :file "ohh1.wav" })
|
||||||
|
(sample { :midi 47 :name "HH1" :file "ohh2.wav" })
|
||||||
|
(sample { :midi 44 :name "HH2" :file "chh2.wav" })
|
||||||
|
(sample { :midi 49 :name "CRS" :file "crs.wav" })))
|
||||||
|
|
||||||
(track { :name "Bass" :gain +0.0 }
|
(track { :name "Bass" :gain +0.0 }
|
||||||
(phrase { :name "Bass 1" :beats 4 })
|
(phrase { :name "Bass 1" :beats 4 })
|
||||||
|
|
|
||||||
|
|
@ -96,11 +96,11 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
}],
|
}],
|
||||||
|
|
||||||
[Char('='), NONE, "zoom_in", "Zoom in", |app: &mut App| {
|
[Char('='), NONE, "zoom_in", "Zoom in", |app: &mut App| {
|
||||||
app.time_zoom = prev_note_length(app.time_zoom);
|
app.seq_buf.time_zoom = prev_note_length(app.seq_buf.time_zoom);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('-'), NONE, "zoom_out", "Zoom out", |app: &mut App| {
|
[Char('-'), NONE, "zoom_out", "Zoom out", |app: &mut App| {
|
||||||
app.time_zoom = next_note_length(app.time_zoom);
|
app.seq_buf.time_zoom = next_note_length(app.seq_buf.time_zoom);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,32 @@
|
||||||
use crate::{core::*, model::App};
|
use crate::{core::*, model::App};
|
||||||
|
|
||||||
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
|
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| {
|
||||||
|
app.arranger_mode = !app.seq_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| Ok(
|
||||||
match app.arranger_mode {
|
match app.arranger_mode {
|
||||||
false => {app.scene_cursor = app.scene_cursor.saturating_sub(1); true},
|
false => {app.prev_scene();true},
|
||||||
true => {app.track_cursor = app.track_cursor.saturating_sub(1); true},
|
true => {app.prev_track();true},
|
||||||
}
|
}
|
||||||
)],
|
)],
|
||||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok(
|
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok(
|
||||||
match app.arranger_mode {
|
match app.arranger_mode {
|
||||||
false => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); true},
|
false => {app.next_scene();true},
|
||||||
true => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); true},
|
true => {app.next_track();true},
|
||||||
}
|
}
|
||||||
)],
|
)],
|
||||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok(
|
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok(
|
||||||
match app.arranger_mode {
|
match app.arranger_mode {
|
||||||
false => {app.track_cursor = app.track_cursor.saturating_sub(1); true},
|
false => {app.prev_track();true},
|
||||||
true => {app.scene_cursor = app.scene_cursor.saturating_sub(1); true},
|
true => {app.prev_scene();true},
|
||||||
}
|
}
|
||||||
)],
|
)],
|
||||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok(
|
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok(
|
||||||
match app.arranger_mode {
|
match app.arranger_mode {
|
||||||
false => {app.track_cursor = app.tracks.len().min(app.track_cursor + 1); true},
|
false => {app.next_track();true},
|
||||||
true => {app.scene_cursor = app.scenes.len().min(app.scene_cursor + 1); true},
|
true => {app.next_scene();true}
|
||||||
}
|
}
|
||||||
)],
|
)],
|
||||||
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| Ok(
|
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| Ok(
|
||||||
|
|
@ -44,39 +48,11 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
}
|
}
|
||||||
)],
|
)],
|
||||||
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| {
|
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| {
|
||||||
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
app.next_phrase();
|
||||||
//let scene_id = state.cursor.1 - 1;
|
|
||||||
//let clip_id = state.cursor.0 - 1;
|
|
||||||
//let scene = &mut state.scenes[scene_id];
|
|
||||||
//scene.clips[clip_id] = match scene.clips[clip_id] {
|
|
||||||
//None => Some(0),
|
|
||||||
//Some(i) => if i >= state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1) {
|
|
||||||
//None
|
|
||||||
//} else {
|
|
||||||
//Some(i + 1)
|
|
||||||
//}
|
|
||||||
//};
|
|
||||||
//}
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| {
|
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| {
|
||||||
//if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
app.prev_phrase();
|
||||||
//let scene_id = state.cursor.1 - 1;
|
|
||||||
//let clip_id = state.cursor.0 - 1;
|
|
||||||
//let scene = &mut state.scenes[scene_id];
|
|
||||||
//scene.clips[clip_id] = match scene.clips[clip_id] {
|
|
||||||
//None => Some(state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1)),
|
|
||||||
//Some(i) => if i == 0 {
|
|
||||||
//None
|
|
||||||
//} else {
|
|
||||||
//Some(i - 1)
|
|
||||||
//}
|
|
||||||
//};
|
|
||||||
//}
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| {
|
|
||||||
app.arranger_mode = !app.seq_mode;
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
if app.entered {
|
if app.entered {
|
||||||
app.time_cursor = app.time_cursor.saturating_sub(1);
|
app.time_cursor = app.time_cursor.saturating_sub(1);
|
||||||
} else {
|
} else {
|
||||||
app.time_start = app.time_start.saturating_sub(1);
|
app.seq_buf.time_start = app.seq_buf.time_start.saturating_sub(1);
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
|
|
@ -21,7 +21,7 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
if app.entered {
|
if app.entered {
|
||||||
app.time_cursor = app.time_cursor + 1;
|
app.time_cursor = app.time_cursor + 1;
|
||||||
} else {
|
} else {
|
||||||
app.time_start = app.time_start + 1;
|
app.seq_buf.time_start = app.seq_buf.time_start + 1;
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
|
|
|
||||||
11
src/model.rs
11
src/model.rs
|
|
@ -13,7 +13,7 @@ pub use self::sampler::{Sampler, Sample, read_sample_data};
|
||||||
pub use self::mixer::Mixer;
|
pub use self::mixer::Mixer;
|
||||||
pub use self::plugin::{Plugin, PluginKind, lv2::LV2Plugin};
|
pub use self::plugin::{Plugin, PluginKind, lv2::LV2Plugin};
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::{core::*, view::*};
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
/// Paths to user directories
|
/// Paths to user directories
|
||||||
|
|
@ -43,6 +43,8 @@ pub struct App {
|
||||||
pub chain_mode: bool,
|
pub chain_mode: bool,
|
||||||
/// Display mode of sequencer seciton
|
/// Display mode of sequencer seciton
|
||||||
pub seq_mode: bool,
|
pub seq_mode: bool,
|
||||||
|
/// Display buffer for sequencer
|
||||||
|
pub seq_buf: BufferedSequencerView,
|
||||||
/// Optional modal dialog
|
/// Optional modal dialog
|
||||||
pub modal: Option<Box<dyn Component>>,
|
pub modal: Option<Box<dyn Component>>,
|
||||||
/// Currently focused section
|
/// Currently focused section
|
||||||
|
|
@ -57,10 +59,6 @@ pub struct App {
|
||||||
pub note_start: usize,
|
pub note_start: usize,
|
||||||
/// Display position of cursor within time range
|
/// Display position of cursor within time range
|
||||||
pub time_cursor: usize,
|
pub time_cursor: usize,
|
||||||
/// PPQ per display unit
|
|
||||||
pub time_zoom: usize,
|
|
||||||
/// Range of time steps to display
|
|
||||||
pub time_start: usize,
|
|
||||||
/// Focused scene+1, 0 is track list
|
/// Focused scene+1, 0 is track list
|
||||||
pub scene_cursor: usize,
|
pub scene_cursor: usize,
|
||||||
/// Collection of scenes
|
/// Collection of scenes
|
||||||
|
|
@ -105,9 +103,8 @@ impl App {
|
||||||
scenes: vec![],
|
scenes: vec![],
|
||||||
section: AppSection::default(),
|
section: AppSection::default(),
|
||||||
seq_mode: false,
|
seq_mode: false,
|
||||||
|
seq_buf: BufferedSequencerView::new(96, 16384),
|
||||||
time_cursor: 0,
|
time_cursor: 0,
|
||||||
time_start: 0,
|
|
||||||
time_zoom: 12,
|
|
||||||
timebase: Arc::new(Timebase::default()),
|
timebase: Arc::new(Timebase::default()),
|
||||||
track_cursor: 1,
|
track_cursor: 1,
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,56 @@
|
||||||
use crate::{core::*, model::App};
|
use crate::{core::*, model::App};
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn next_phrase (&mut self) {
|
||||||
|
if let Some((track_index, track)) = self.track_mut() {
|
||||||
|
let phrases = track.phrases.len();
|
||||||
|
if let Some((_, scene)) = self.scene_mut() {
|
||||||
|
if let Some(phrase_index) = scene.clips[track_index] {
|
||||||
|
if phrase_index >= phrases - 1 {
|
||||||
|
scene.clips[track_index] = None;
|
||||||
|
} else {
|
||||||
|
scene.clips[track_index] = Some(phrase_index + 1);
|
||||||
|
}
|
||||||
|
} else if phrases > 0 {
|
||||||
|
scene.clips[track_index] = Some(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prev_phrase (&mut self) {
|
||||||
|
if let Some((track_index, track)) = self.track_mut() {
|
||||||
|
let phrases = track.phrases.len();
|
||||||
|
if let Some((_, scene)) = self.scene_mut() {
|
||||||
|
if let Some(phrase_index) = scene.clips[track_index] {
|
||||||
|
scene.clips[track_index] = if phrase_index == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(phrase_index - 1)
|
||||||
|
};
|
||||||
|
} else if phrases > 0 {
|
||||||
|
scene.clips[track_index] = Some(phrases - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn phrase (&self) -> Option<&Phrase> {
|
||||||
|
let (track_id, track) = self.track()?;
|
||||||
|
let (_, scene) = self.scene()?;
|
||||||
|
track.phrases.get((*scene.clips.get(track_id)?)?)
|
||||||
|
}
|
||||||
|
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
||||||
|
let (track_id, _) = self.track()?;
|
||||||
|
let (_, scene) = self.scene()?;
|
||||||
|
let clip = (*scene.clips.get(track_id)?)?;
|
||||||
|
self.track_mut()?.1.phrases.get_mut(clip)
|
||||||
|
}
|
||||||
|
fn phrase_id (&self) -> Option<usize> {
|
||||||
|
let (track_id, _) = self.track()?;
|
||||||
|
let (_, scene) = self.scene()?;
|
||||||
|
*scene.clips.get(track_id)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Define a MIDI phrase.
|
/// Define a MIDI phrase.
|
||||||
#[macro_export] macro_rules! phrase {
|
#[macro_export] macro_rules! phrase {
|
||||||
($($t:expr => $msg:expr),* $(,)?) => {{
|
($($t:expr => $msg:expr),* $(,)?) => {{
|
||||||
|
|
@ -87,22 +138,3 @@ impl Phrase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn phrase (&self) -> Option<&Phrase> {
|
|
||||||
let (track_id, track) = self.track()?;
|
|
||||||
let (_, scene) = self.scene()?;
|
|
||||||
track.phrases.get((*scene.clips.get(track_id)?)?)
|
|
||||||
}
|
|
||||||
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
|
||||||
let (track_id, _) = self.track()?;
|
|
||||||
let (_, scene) = self.scene()?;
|
|
||||||
let clip = (*scene.clips.get(track_id)?)?;
|
|
||||||
self.track_mut()?.1.phrases.get_mut(clip)
|
|
||||||
}
|
|
||||||
pub fn phrase_id (&self) -> Option<usize> {
|
|
||||||
let (track_id, _) = self.track()?;
|
|
||||||
let (_, scene) = self.scene()?;
|
|
||||||
*scene.clips.get(track_id)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,13 @@
|
||||||
use crate::{core::*, model::App};
|
use crate::{core::*, model::App};
|
||||||
|
|
||||||
pub struct Scene {
|
|
||||||
pub name: String,
|
|
||||||
pub clips: Vec<Option<usize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scene {
|
|
||||||
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new_scene_name (&self) -> String {
|
pub fn next_scene (&mut self) {
|
||||||
|
self.scene_cursor = self.scenes.len().min(self.scene_cursor + 1);
|
||||||
|
}
|
||||||
|
pub fn prev_scene (&mut self) {
|
||||||
|
self.scene_cursor = self.scene_cursor.saturating_sub(1);
|
||||||
|
}
|
||||||
|
fn new_scene_name (&self) -> String {
|
||||||
format!("Scene {}", self.scenes.len() + 1)
|
format!("Scene {}", self.scenes.len() + 1)
|
||||||
}
|
}
|
||||||
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||||
|
|
@ -47,3 +39,17 @@ impl App {
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Scene {
|
||||||
|
pub name: String,
|
||||||
|
pub clips: Vec<Option<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.as_ref().into(),
|
||||||
|
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,52 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::model::*;
|
use crate::model::*;
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn next_track (&mut self) {
|
||||||
|
self.track_cursor = self.tracks.len().min(self.track_cursor + 1);
|
||||||
|
}
|
||||||
|
pub fn prev_track (&mut self) {
|
||||||
|
self.track_cursor = self.track_cursor.saturating_sub(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_track_name (&self) -> String {
|
||||||
|
format!("Track {}", self.tracks.len() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||||
|
let name = name.ok_or_else(||self.new_track_name())?;
|
||||||
|
self.tracks.push(Track::new(&name, None, None)?);
|
||||||
|
self.track_cursor = self.tracks.len();
|
||||||
|
Ok(&mut self.tracks[self.track_cursor - 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_track_with_cb (
|
||||||
|
&mut self, name: Option<&str>, init: impl FnOnce(&Client, &mut Track)->Usually<()>,
|
||||||
|
) -> Usually<&mut Track> {
|
||||||
|
let name = name.ok_or_else(||self.new_track_name())?;
|
||||||
|
let mut track = Track::new(&name, None, None)?;
|
||||||
|
init(self.client(), &mut track)?;
|
||||||
|
self.tracks.push(track);
|
||||||
|
self.track_cursor = self.tracks.len();
|
||||||
|
Ok(&mut self.tracks[self.track_cursor - 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track (&self) -> Option<(usize, &Track)> {
|
||||||
|
match self.track_cursor { 0 => None, _ => {
|
||||||
|
let id = self.track_cursor as usize - 1;
|
||||||
|
self.tracks.get(id).map(|t|(id, t))
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
|
||||||
|
match self.track_cursor { 0 => None, _ => {
|
||||||
|
let id = self.track_cursor as usize - 1;
|
||||||
|
self.tracks.get_mut(id).map(|t|(id, t))
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Play input through output.
|
/// Play input through output.
|
||||||
|
|
@ -29,7 +75,7 @@ pub struct Track {
|
||||||
pub notes_out: [bool;128],
|
pub notes_out: [bool;128],
|
||||||
}
|
}
|
||||||
impl Track {
|
impl Track {
|
||||||
pub fn new (
|
fn new (
|
||||||
name: &str,
|
name: &str,
|
||||||
phrases: Option<Vec<Phrase>>,
|
phrases: Option<Vec<Phrase>>,
|
||||||
devices: Option<Vec<JackDevice>>,
|
devices: Option<Vec<JackDevice>>,
|
||||||
|
|
@ -50,19 +96,19 @@ impl Track {
|
||||||
reset: true,
|
reset: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn get_device (&self, i: usize) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
fn get_device (&self, i: usize) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
||||||
self.devices.get(i).map(|d|d.state.read().unwrap())
|
self.devices.get(i).map(|d|d.state.read().unwrap())
|
||||||
}
|
}
|
||||||
pub fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn Device>>> {
|
fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn Device>>> {
|
||||||
self.devices.get(i).map(|d|d.state.write().unwrap())
|
self.devices.get(i).map(|d|d.state.write().unwrap())
|
||||||
}
|
}
|
||||||
pub fn device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
fn device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
||||||
self.get_device(self.device)
|
self.get_device(self.device)
|
||||||
}
|
}
|
||||||
pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn Device>>> {
|
pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn Device>>> {
|
||||||
self.get_device_mut(self.device)
|
self.get_device_mut(self.device)
|
||||||
}
|
}
|
||||||
pub fn first_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
fn first_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
||||||
self.get_device(0)
|
self.get_device(0)
|
||||||
}
|
}
|
||||||
pub fn connect_first_device (&self) -> Usually<()> {
|
pub fn connect_first_device (&self) -> Usually<()> {
|
||||||
|
|
@ -71,7 +117,7 @@ impl Track {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn last_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
fn last_device (&self) -> Option<RwLockReadGuard<Box<dyn Device>>> {
|
||||||
self.get_device(self.devices.len().saturating_sub(1))
|
self.get_device(self.devices.len().saturating_sub(1))
|
||||||
}
|
}
|
||||||
pub fn connect_last_device (&self, app: &App) -> Usually<()> {
|
pub fn connect_last_device (&self, app: &App) -> Usually<()> {
|
||||||
|
|
@ -89,7 +135,7 @@ impl Track {
|
||||||
let index = self.devices.len() - 1;
|
let index = self.devices.len() - 1;
|
||||||
Ok(&mut self.devices[index])
|
Ok(&mut self.devices[index])
|
||||||
}
|
}
|
||||||
pub fn add_device_with_cb (
|
fn add_device_with_cb (
|
||||||
&mut self,
|
&mut self,
|
||||||
mut device: JackDevice,
|
mut device: JackDevice,
|
||||||
init: impl Fn(&Self, &mut JackDevice)->Usually<()>
|
init: impl Fn(&Self, &mut JackDevice)->Usually<()>
|
||||||
|
|
@ -99,21 +145,21 @@ impl Track {
|
||||||
let index = self.devices.len() - 1;
|
let index = self.devices.len() - 1;
|
||||||
Ok(&mut self.devices[index])
|
Ok(&mut self.devices[index])
|
||||||
}
|
}
|
||||||
pub fn phrase (&self) -> Option<&Phrase> {
|
fn phrase (&self) -> Option<&Phrase> {
|
||||||
if let Some(phrase) = self.sequence {
|
if let Some(phrase) = self.sequence {
|
||||||
return self.phrases.get(phrase)
|
return self.phrases.get(phrase)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
||||||
if let Some(phrase) = self.sequence {
|
if let Some(phrase) = self.sequence {
|
||||||
return self.phrases.get_mut(phrase)
|
return self.phrases.get_mut(phrase)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn add_phrase (
|
fn add_phrase (
|
||||||
&mut self, name: &str, length: usize, data: Option<PhraseData>
|
&mut self, name: &str, length: usize, data: Option<PhraseData>
|
||||||
) -> &mut Phrase {
|
) -> &mut Phrase {
|
||||||
self.phrases.push(Phrase::new(name, length, data));
|
self.phrases.push(Phrase::new(name, length, data));
|
||||||
|
|
@ -243,43 +289,3 @@ impl Track {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
|
||||||
|
|
||||||
pub fn new_track_name (&self) -> String {
|
|
||||||
format!("Track {}", self.tracks.len() + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
|
||||||
let name = name.ok_or_else(||self.new_track_name())?;
|
|
||||||
self.tracks.push(Track::new(&name, None, None)?);
|
|
||||||
self.track_cursor = self.tracks.len();
|
|
||||||
Ok(&mut self.tracks[self.track_cursor - 1])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_track_with_cb (
|
|
||||||
&mut self, name: Option<&str>, init: impl FnOnce(&Client, &mut Track)->Usually<()>,
|
|
||||||
) -> Usually<&mut Track> {
|
|
||||||
let name = name.ok_or_else(||self.new_track_name())?;
|
|
||||||
let mut track = Track::new(&name, None, None)?;
|
|
||||||
init(self.client(), &mut track)?;
|
|
||||||
self.tracks.push(track);
|
|
||||||
self.track_cursor = self.tracks.len();
|
|
||||||
Ok(&mut self.tracks[self.track_cursor - 1])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn track (&self) -> Option<(usize, &Track)> {
|
|
||||||
match self.track_cursor { 0 => None, _ => {
|
|
||||||
let id = self.track_cursor as usize - 1;
|
|
||||||
self.tracks.get(id).map(|t|(id, t))
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
|
|
||||||
match self.track_cursor { 0 => None, _ => {
|
|
||||||
let id = self.track_cursor as usize - 1;
|
|
||||||
self.tracks.get_mut(id).map(|t|(id, t))
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub use self::layout::*;
|
||||||
pub use self::transport::TransportView;
|
pub use self::transport::TransportView;
|
||||||
pub use self::arranger::*;
|
pub use self::arranger::*;
|
||||||
pub use self::chain::ChainView;
|
pub use self::chain::ChainView;
|
||||||
pub use self::sequencer::SequencerView;
|
pub use self::sequencer::{SequencerView, BufferedSequencerView};
|
||||||
|
|
||||||
use crate::{render, App, core::*};
|
use crate::{render, App, core::*};
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ render!(App |self, buf, area| {
|
||||||
&ArrangerView::new(&self, !self.arranger_mode),
|
&ArrangerView::new(&self, !self.arranger_mode),
|
||||||
&If(self.track_cursor > 0, &Split::right([
|
&If(self.track_cursor > 0, &Split::right([
|
||||||
&ChainView::vertical(&self),
|
&ChainView::vertical(&self),
|
||||||
&SequencerView::new(&self),
|
&self.seq_buf,
|
||||||
]))
|
]))
|
||||||
]).render(buf, area)?;
|
]).render(buf, area)?;
|
||||||
if let Some(ref modal) = self.modal {
|
if let Some(ref modal) = self.modal {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
|
||||||
Some(Style::default().green())
|
Some(Style::default().green())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
});
|
})?;
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -39,10 +39,10 @@ pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
|
||||||
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
||||||
let style = Style::default().gray();
|
let style = Style::default().gray();
|
||||||
let label1 = format!(" {}", state.name);
|
let label1 = format!(" {}", state.name);
|
||||||
label1.blit(buf, x + 1, y, Some(style.white().bold()));
|
label1.blit(buf, x + 1, y, Some(style.white().bold()))?;
|
||||||
if let Some(ref path) = state.path {
|
if let Some(ref path) = state.path {
|
||||||
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
||||||
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()))?;
|
||||||
}
|
}
|
||||||
Ok(Rect { x, y, width: w, height: 1 })
|
Ok(Rect { x, y, width: w, height: 1 })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rec
|
||||||
{
|
{
|
||||||
let style = Style::default().gray();
|
let style = Style::default().gray();
|
||||||
let title = format!(" {} ({})", state.name, state.voices.len());
|
let title = format!(" {} ({})", state.name, state.voices.len());
|
||||||
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()));
|
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()))?;
|
||||||
let mut width = title.len() + 2;
|
let mut width = title.len() + 2;
|
||||||
for (i, (note, sample)) in state.samples.iter().enumerate() {
|
for (i, (note, sample)) in state.samples.iter().enumerate() {
|
||||||
let style = if i == state.cursor.0 {
|
let style = if i == state.cursor.0 {
|
||||||
|
|
@ -20,15 +20,15 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rec
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if i as usize == state.cursor.0 {
|
if i as usize == state.cursor.0 {
|
||||||
"⯈".blit(buf, x+1, y1, Some(style.bold()));
|
"⯈".blit(buf, x+1, y1, Some(style.bold()))?;
|
||||||
}
|
}
|
||||||
let label1 = format!("{note:3} {:8}", sample.name);
|
let label1 = format!("{note:3} {:8}", sample.name);
|
||||||
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
|
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
|
||||||
label1.blit(buf, x+2, y1, Some(style.bold()));
|
label1.blit(buf, x+2, y1, Some(style.bold()))?;
|
||||||
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style));
|
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style))?;
|
||||||
width = width.max(label1.len() + label2.len() + 4);
|
width = width.max(label1.len() + label2.len() + 4);
|
||||||
}
|
}
|
||||||
let height = ((1 + state.samples.len()) as u16).min(height);
|
let height = ((2 + state.samples.len()) as u16).min(height);
|
||||||
Ok(Rect { x, y, width: width as u16, height })
|
Ok(Rect { x, y, width: width as u16, height })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,82 @@
|
||||||
use crate::{core::*,model::*};
|
use crate::{core::*,model::*};
|
||||||
|
|
||||||
|
pub struct BufferedSequencerView {
|
||||||
|
pub ppq: usize,
|
||||||
|
pub length: usize,
|
||||||
|
pub character: [Vec<char>;64],
|
||||||
|
pub fg: [Vec<Color>;64],
|
||||||
|
pub bg: [Vec<Color>;64],
|
||||||
|
pub notes: [bool;128],
|
||||||
|
/// 1st time step to displayRange of time steps to display
|
||||||
|
pub time_start: usize,
|
||||||
|
/// PPQ per display unit
|
||||||
|
pub time_zoom: usize,
|
||||||
|
}
|
||||||
|
render!(BufferedSequencerView |self, buf, area| {
|
||||||
|
let mut area = area;
|
||||||
|
area.height = area.height.min(64);
|
||||||
|
for y in 0..area.height {
|
||||||
|
for x in 0..area.width {
|
||||||
|
let cell = buf.get_mut(area.x + x, area.y + y);
|
||||||
|
let time_index = (self.time_start + x as usize) * self.time_zoom;
|
||||||
|
let note_index = 63 - y as usize;
|
||||||
|
cell.set_char(self.character[note_index][time_index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
});
|
||||||
|
impl BufferedSequencerView {
|
||||||
|
pub fn new (ppq: usize, length: usize) -> Self {
|
||||||
|
let dots: Vec<char> = vec!['·';length]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i,x)|if i % ppq == 0 { '|' } else { *x })
|
||||||
|
.collect();
|
||||||
|
Self {
|
||||||
|
ppq,
|
||||||
|
length,
|
||||||
|
character: core::array::from_fn(|_|dots.clone()),
|
||||||
|
fg: core::array::from_fn(|_|vec![Color::Reset;length]),
|
||||||
|
bg: core::array::from_fn(|_|vec![Color::Reset;length]),
|
||||||
|
notes: [false;128],
|
||||||
|
time_start: 0,
|
||||||
|
time_zoom: 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update (&mut self, phrase: Option<&Phrase>) {
|
||||||
|
if phrase.is_none() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let phrase = phrase.unwrap();
|
||||||
|
if phrase.length != self.length {
|
||||||
|
*self = Self::new(self.ppq, phrase.length);
|
||||||
|
}
|
||||||
|
self.notes = [false;128];
|
||||||
|
for (x, messages) in phrase.notes.iter().enumerate() {
|
||||||
|
for message in messages.iter() {
|
||||||
|
match message {
|
||||||
|
MidiMessage::NoteOn { key, .. } => {
|
||||||
|
self.notes[key.as_int() as usize] = true;
|
||||||
|
},
|
||||||
|
MidiMessage::NoteOff { key, .. } => {
|
||||||
|
self.notes[key.as_int() as usize] = false;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for y in 0..64 {
|
||||||
|
let note1 = self.notes[y * 2];
|
||||||
|
let note2 = self.notes[y * 2 + 1];
|
||||||
|
if let Some(block) = half_block(note1, note2) {
|
||||||
|
self.character[63 - y][x] = block;
|
||||||
|
} else if x % self.ppq == 0 {
|
||||||
|
self.character[63 - y][x] = '|';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SequencerView<'a> {
|
pub struct SequencerView<'a> {
|
||||||
focused: bool,
|
focused: bool,
|
||||||
/// Displayed phrase
|
/// Displayed phrase
|
||||||
|
|
@ -37,8 +114,8 @@ impl<'a> SequencerView<'a> {
|
||||||
ppq: app.timebase.ppq() as usize,
|
ppq: app.timebase.ppq() as usize,
|
||||||
now: app.timebase.frame_to_pulse(app.playhead as f64) as usize,
|
now: app.timebase.frame_to_pulse(app.playhead as f64) as usize,
|
||||||
time_cursor: app.time_cursor,
|
time_cursor: app.time_cursor,
|
||||||
time_start: app.time_start,
|
time_start: app.seq_buf.time_start,
|
||||||
time_zoom: app.time_zoom,
|
time_zoom: app.seq_buf.time_zoom,
|
||||||
note_cursor: app.note_cursor,
|
note_cursor: app.note_cursor,
|
||||||
note_start: app.note_start,
|
note_start: app.note_start,
|
||||||
notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] },
|
notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue